Skip to content

hemlock attack engagement

A directory-local engagement context (./.hemlock-engagement.toml) scopes attack-mode runs. When an engagement context is present, every attack subcommand:

  • Skips the "running unscoped" warning
  • Records the engagement campaign ID and chain SHA-256 in any output JSONL header
  • Cross-checks targets against scope.allowed_targets (run) and target.framework (optimize)

The engagement file is a chained, content-addressable TOML — the previous engagement's chain.this_sha256 is recorded in the next one's chain.prev_sha256, producing a tamper-evident sequence.

Synopsis

hemlock attack engagement init <campaign-id> [flags]
hemlock attack engagement show

init

Create a directory-local engagement file at ./.hemlock-engagement.toml. Refuses to overwrite an existing file.

Flag Type Description
--operator string Operator's real name (real-name field, not anonymized). Defaults to ~/.hemlock/config.toml operator.name if set.
--operator-contact string Email for the operator
--organization string Operator's organization (optional)
--auth-source string Source of authorization (e.g. MSA, statement-of-work)
--auth-contact string Authorizing contact (optional)
--auth-expires string RFC3339 expiration for the authorization (optional)
--scope string Free-text scope statement
--target []string Repeatable: each --target adds an entry to scope.allowed_targets (host, host:port, or full URL)
--notes string Free-form notes appended to the engagement file (optional)
--force bool Overwrite an existing ./.hemlock-engagement.toml
--no-prompt bool Fail on missing required fields instead of prompting interactively

show

Print and verify the current engagement context. Reports validation errors, the chain hash, and a one-line summary.

Engagement file shape

schema_version = 1

[engagement]
campaign_id = "EX-2026-001"
created_at = "2026-05-06T12:00:00Z"

[operator]
name = "Nathan Keys"
contact = "nathan.keys@pm.me"

[authorization]
source = "engagement letter ACME-2026-04-rev3"

[scope]
statement = "Authorized red-team measurement against ACME staging RAG endpoints."
allowed_targets = ["http://staging-rag.acme.example:8100", "staging-rag.acme.example"]

[chain]
prev_sha256 = "abc123..."  # previous engagement's this_sha256, if any
this_sha256 = "..."        # SHA-256 of this file with [chain] zeroed

chain.this_sha256 is the SHA-256 of the file's content with the [chain] block zeroed, computed and stamped at write time. The previous engagement's this_sha256 flows into the next engagement's prev_sha256, producing a tamper-evident sequence; the per-user history file ($XDG_CONFIG_HOME/hemlock/engagement-history.jsonl or ~/.hemlock/engagement-history.jsonl) records each engagement's hash for cross-engagement audit.

Examples

hemlock attack engagement init EX-2026-001 \
  --operator "Nathan Keys" \
  --operator-contact "nathan.keys@pm.me" \
  --auth-source "ACME engagement letter 2026-04-rev3" \
  --auth-expires "2026-08-31T23:59:59Z" \
  --scope "Authorized red-team measurement against ACME staging RAG endpoints" \
  --target "http://staging-rag.acme.example:8100" \
  --target "staging-rag.acme.example"

hemlock attack engagement show
  • hemlock attack optimize — cross-checks target.framework against scope.allowed_targets
  • hemlock run — cross-checks --target host:port against scope.allowed_targets
  • hemlock attack report — surfaces the engagement ID and chain SHA-256 in the deliverable's provenance footer