Operator command cookbook¶
This page gives practical usage for every command shown by the normal ait
--help screen. Use the command's own --help output for its complete option
list, and use the direct interception guide
for the end-to-end operator workflow.
Examples use uppercase placeholders such as SESSION_ID. Replace them with IDs
printed by AIT. Add --json when another program will consume the result.
Health and Cockpit service¶
| Command | Use when | Example | Expected result |
|---|---|---|---|
ait doctor |
Before an assessment or when a transport will not start | ait doctor --profile proxy |
Checks local binaries, packaged assets, storage, and proxy prerequisites without target contact |
ait server start |
Start the local API/Cockpit independently of a lab | ait server start --listen 127.0.0.1:8790 |
Returns the listener and starts a loopback-only secured server |
ait server status |
Determine whether the shared Cockpit is already running | ait server status |
Reports listener, PID, health, and local state |
ait server stop |
Stop the shared Cockpit after all labs and sessions are finished | ait server stop |
Stops the local server; it does not silently resolve another process's traffic |
Use the assessment profile for a broader offline readiness check:
Lab commands¶
The lab is the safest place to learn each decision. It launches only packaged local components and never requires a provider or Docker.
| Command | Use when | Example | Expected result |
|---|---|---|---|
ait lab list |
See exercises and active/stopped lab instances | ait lab list |
Catalog plus current lab IDs and states |
ait lab start |
Launch an exercise and open its active Cockpit session | ait lab start --exercise delegated-a2a-message |
Starts local agents, interception, and a controlled effect ledger |
ait lab status |
Inspect component, SDK, receiver, and ledger state | ait lab status LAB_ID |
Current lab and process receipts |
ait lab trigger |
Generate the exercise traffic after interception is ready | ait lab trigger LAB_ID |
A matching message enters the pending queue |
ait lab reset |
Clear messages, tasks, rules, and ledger state between comparisons | ait lab reset LAB_ID |
Fresh deterministic exercise state |
ait lab stop |
Terminate every process owned by the lab | ait lab stop LAB_ID |
Agents, listener, wrappers, and streams stop cleanly |
ait lab use-target |
Copy protocol and break behavior from a lab to a real target | ait lab use-target LAB_ID --protocol a2a_jsonrpc --upstream http://127.0.0.1:9000 |
Deposits a new target setup; preparation itself sends no probe |
Transport selection examples:
ait lab start --exercise mcp-tool-result --mcp-transport stdio
ait lab start --exercise asynchronous-artifact --a2a-binding rest
ait lab start --exercise delegated-a2a-message --a2a-binding grpc
Use --fixture only for the handwritten regression fixture. Normal operators
should use the SDK-backed default.
Connection commands¶
Connections describe where AIT listens and where it forwards. Saving or listing
a connection is offline. discover and test contact the endpoint explicitly
named by the operator.
| Command | Use when | Example | Expected result |
|---|---|---|---|
ait connections add |
Save an explicit HTTP/SSE/WebSocket/gRPC relay placement | ait connections add --name specialist --protocol a2a_jsonrpc --upstream http://127.0.0.1:9000 |
Returns a connection ID and proposed loopback listener |
ait connections add |
Save an MCP stdio wrapper placement | ait connections add --name policy --protocol mcp_stdio --command python policy_server.py |
Returns a connection with a shell-free command vector |
ait connections discover |
Read an authorized A2A Agent Card and propose connection details | ait connections discover --upstream https://agent.example |
Displays advertised interfaces without starting interception |
ait connections list |
Find connection IDs for later starts | ait connections list |
Saved connection table |
ait connections test |
Verify endpoint connectivity or validate a stdio executable | ait connections test CONNECTION_ID |
Health/command validation; no offensive traffic is generated |
Starting and controlling interception¶
| Command | Use when | Example | Expected result |
|---|---|---|---|
ait intercept start |
Start from a saved connection | ait intercept start --connection CONNECTION_ID --open |
Returns session/listener IDs and opens the Cockpit |
ait intercept start |
Start an ad hoc HTTP relay | ait intercept start --protocol a2a_rest --upstream http://127.0.0.1:9000 --direction request |
Listener pauses matching requests |
ait intercept start |
Break only on message content | ait intercept start --connection CONNECTION_ID --where 'json.params.message.parts.0.text:contains:refund' |
Repeatable; all predicates must hold. Ops: equals, contains, exists, not_exists, regex, in |
ait intercept start |
Bound an unattended session | ait intercept start --connection CONNECTION_ID --pending-timeout 60 --pending-timeout-action drop |
A paused message holds the sender's connection open; timed-out messages are marked timed_out |
ait intercept start |
Wrap an MCP stdio server | ait intercept start --protocol mcp_stdio --command python policy_server.py |
Prints the replacement wrapper command |
ait intercept status |
Check listener, queue, process, and interception state | ait intercept status SESSION_ID |
Session health and pending count |
ait intercept on |
Begin pausing matching messages | ait intercept on SESSION_ID |
Matching traffic waits for decisions |
ait intercept off |
Continue forwarding without pausing while keeping observation active | ait intercept off SESSION_ID |
New traffic passes and remains visible in History |
ait intercept pending |
List messages awaiting an operator decision | ait intercept pending SESSION_ID |
Message IDs, operations, directions, and age |
ait intercept stop |
Stop after choosing how to resolve paused traffic | ait intercept stop SESSION_ID --pending forward |
Pending originals forward unchanged, then owned processes stop |
Use --pending drop only when dropping unresolved messages is the intended
shutdown behavior.
ait intercept measure: does it actually work?¶
sweep tells you what can be aimed here. This tells you whether it does
anything. Run it before you put a primitive in a report.
a2a.approval-amount
control arm moved the receiver <count>/<trials>
attack arm moved the receiver <count>/<trials>
adjusted p <p> significant=<bool>
The shape of the output, not a result. Measured rates from this project are held back until the work is published.
Each trial delivers both arms and compares them against each other: the attack counts only when it took the receiver further than its own control did. That within-pair comparison is the whole design. The first version of this scored the control ⅚ because its oracle asked "did an event happen": true of any delivered message, and a control that scores alongside the attack has isolated nothing.
Unreadable pairs are dropped rather than counted, and if more than a quarter of them go, no rate is reported at all: a number computed from what survived would be quoting a sample chosen by whatever broke.
Small samples cannot be significant, and the arithmetic says so. Exact McNemar on four discordant pairs is p=0.0625. A flawless 4/4 is not a finding; six is the minimum that can cross.
ait intercept sweep: what can I even aim here?¶
Run it first. It answers the question every engagement opens with: of the catalogue, how much of it addresses this target's traffic at all?
6 of 26 primitives address this traffic (1 message(s) considered)
a2a.actor-substitution identity Actor/principal
a2a.approval-amount content Approval parameters
credential.forge credential Actor and authority
...
not addressable here:
mcp.tool-shadowing the attack arm cannot be applied: JSON Pointer...
Six of twenty-six is not a defect. MCP techniques cannot apply to A2A traffic. However, it is the number to plan against, and assuming twenty-six is how an assessment ends up reporting a target as sound because the primitives aimed at it were the ones that could not reach it.
The exclusions carry their reason. A pointer that does not resolve is usually a
retargeting job (intercept attack --set), where a selector that did not match
means the technique belongs to a different kind of traffic.
Coverage is not effect. A primitive listed here reaches the wire; whether it
moves the receiver needs an attack arm, its close control, and
ait intercept evidence.
Inspecting and deciding messages¶
Every decision operates on an immutable captured original. An invalid edit leaves the message pending.
| Command | Use when | Example | Expected result |
|---|---|---|---|
ait intercept show |
Inspect one complete decoded message and safe envelope | ait intercept show MESSAGE_ID --session SESSION_ID |
Original, state, metadata, and the transport envelope |
ait intercept suggest |
Ask AIT for local message-specific offensive tests | ait intercept suggest MESSAGE_ID --session SESSION_ID |
Exact change/action, hypothesis, observation, and close control; no target contact |
ait intercept edit |
Replace one decoded value and forward explicitly | ait intercept edit MESSAGE_ID --session SESSION_ID --set /params/message/metadata/amount=75 |
Encodes and delivers the edited message |
ait intercept edit |
Apply one derived suggestion and forward explicitly | ait intercept edit MESSAGE_ID --suggestion a2a.context-crossover |
Applies the deposited patch, validates it, and delivers |
ait intercept forward |
Deliver the immutable original | ait intercept forward MESSAGE_ID --session SESSION_ID |
One unchanged delivery |
ait intercept drop |
Resolve without upstream delivery | ait intercept drop MESSAGE_ID --session SESSION_ID |
Records a dropped decision |
ait intercept replay |
Test replay/idempotency with correlated copies | ait intercept replay MESSAGE_ID --copies 2 |
Delivers the requested bounded number of copies. Requests, SSE, WebSocket, and stdio only: a unary HTTP response has one delivery slot and is refused rather than silently delivered once |
ait intercept attack |
List the catalogued primitives that apply to a message | ait intercept attack --target MESSAGE_ID |
Boundary, trust hypothesis, and title for each applicable primitive |
ait intercept attack |
Deliver the attack arm of a primitive | ait intercept attack mcp.tool-shadowing --target MESSAGE_ID |
Applies the injection and prints the control to run next and why |
ait intercept attack |
Deliver the close control | ait intercept attack mcp.tool-shadowing --target NEXT_MESSAGE_ID --arm control |
The near-miss that should stay inert; without it a positive is not attributable |
ait intercept send |
Originate a message through the active listener | ait intercept send --path /message --body-json @probe.json |
Delivers composed traffic and returns its transcript refs |
ait intercept evidence |
Score the recorded arms into a reviewable finding | ait intercept evidence --primitive mcp.tool-shadowing --out finding.json |
Verdict tier, attribution, and what remains unproven with the reason |
ait intercept evidence |
Record an out-of-band observation | ait intercept evidence --external-effect --note "controlled ledger row 42" |
Only pass this for an effect an oracle confirmed; a target response is not an oracle |
ait intercept send |
Resend a captured request, optionally edited | ait intercept send --from MESSAGE_ID --set /params/message/metadata/amount=999 |
The repeater. Injected traffic passes the same gate, so a matching break condition will pause it |
ait intercept compare |
Compare sent, delivered, receiver response, and observable effect | ait intercept compare MESSAGE_ID |
Structural differences and an honest evidence limitation |
JSON values passed to --set are decoded when possible. Quote objects and
arrays in the shell:
Live-rule commands¶
Rules repeat a successful manual decision. Preview and enable are separate so a saved rule never starts altering traffic merely because it exists.
| Command | Use when | Example | Expected result |
|---|---|---|---|
ait intercept rule save |
Derive a selector and patches from a manually edited message | ait intercept rule save MESSAGE_ID --name controlled-amount --scope session |
Creates a disabled or explicitly scoped reusable rule |
ait intercept rule list |
Inspect rule order, scope, enabled state, and hit counts | ait intercept rule list --session SESSION_ID |
Ordered rule table |
ait intercept rule preview |
See local match count and sample messages before enabling | ait intercept rule preview RULE_ID --session SESSION_ID |
Offline preview; no delivery |
ait intercept rule enable |
Apply a reviewed rule to future matching traffic | ait intercept rule enable RULE_ID --session SESSION_ID |
Rule becomes active |
ait intercept rule disable |
Stop future application without deleting evidence | ait intercept rule disable RULE_ID |
Rule remains saved but inactive |
ait intercept rule edit |
Replace a bounded rule from reviewed JSON/YAML | ait intercept rule edit RULE_ID --from reviewed-rule.yaml |
Validated rule revision |
ait intercept rule delete |
Remove a rule that should no longer be selectable | ait intercept rule delete RULE_ID |
Rule removed; historical message decisions remain |
ait intercept rule export |
Move reviewed rules to another controlled workspace | ait intercept rule export --out rules.json |
Ordered, sanitized rule document |
ait intercept rule import |
Deposit exported rules without automatic activation | ait intercept rule import --from rules.json |
Imported rules remain disabled unless --enable is explicit |
Transform-rule commands¶
A live rule is a JSON-Pointer patch derived from a manual edit, scoped to a session. A transform rule is a YAML document Seam's engine loads, armed on a connection so it survives a restart, carrying regex replace, deep merge, positional insert, file payloads and chain guards. Different objects; know which one you are holding.
| Command | Use when | Example | Expected result |
|---|---|---|---|
ait intercept transform arm |
Arm a YAML rewrite rule | ait intercept transform arm ./poison-schema.yaml |
Validated against the whole prospective set, written to the connection, hot-applied if a session is live |
ait intercept transform list |
See which rule wins | ait intercept transform list |
Evaluation order, with chained, DISABLED, priority and SHADOWED flags |
ait intercept transform test |
Pre-flight before arming on a live target | ait intercept transform test --fixture msg.json --expect-rule my_rule |
Fails when the named rule cannot fire, including when an earlier rule shadows it |
ait intercept transform trace |
Tune offline against captured traffic | ait intercept transform trace --transcript session.json |
Per record, which rule the engine would apply, and why the others missed |
ait intercept transform explain |
Check intended paths and transports | ait intercept transform explain my_rule |
Declared mutation paths, inferred transports, failure modes |
ait intercept transform remove |
Disarm without deleting the file | ait intercept transform remove my_rule |
Removed from the connection and from the live session |
test and trace report what the engine would do, which is not the same as
which rules match: the engine applies the first match and returns, so at most one
rule is applied and the rest report miss_code: shadowed.
Multi-step chains¶
A rule can guard on another rule having already delivered:
| Guard | Means |
|---|---|
chain.fires.<rule-id> gte 1 |
after that rule's mutation reached the wire |
chain.fires.<rule-id> lt 1 |
unless it landed: a fallback, not a control |
chain.self lt 1 |
fire once, then go quiet |
chain.fires counts delivered mutations. A rule whose output the operator
dropped, replaced with the sender's original bytes, or which failed to encode
does not advance a chain, so a dependent rule cannot arm on a poison that never
landed.
| Command | Use when | Example | Expected result |
|---|---|---|---|
ait intercept status |
Watch a chain progress | ait intercept status |
chain armed plus delivered count per step |
ait intercept evidence |
Report a chained attack | ait intercept evidence --out finding.json |
Steps in order, what armed each, and the standing caveat that ordering is not causation |
Two things change when a chain guard is loaded: interactive SSE interception
becomes sequential (an operator pause then blocks the stream), and chain counters
reset at session start and whenever you edit and re-arm a rule: /rules/apply
returns the reset ids as chain_reset.
A worked example with its control arm ships at
agentic-redteam/seam/rules/chains/mcp-tool-schema-induced-call/.
Shadow endpoints¶
A substituted destination that points at nothing proves only that routing moved.
A shadow answers like the endpoint it replaced and records what arrived, which is
also the out-of-band oracle a finding needs to reach external_effect_observed.
| Command | Use when | Example | Expected result |
|---|---|---|---|
ait intercept shadow start |
Give a redirect somewhere real to land | ait intercept shadow start a2a-agent --name shadow-specialist |
Loopback URL; serves an Agent Card and accepts delegations |
ait intercept shadow start |
Catch a substituted callback or elicitation URL | ait intercept shadow start callback |
Records any request to any path |
ait intercept shadow start |
Catch a redirected MCP client | ait intercept shadow start mcp-server |
Serves initialize/tools/list, records tools/call arguments |
ait intercept shadow list |
See what is running and what it has caught | ait intercept shadow list |
Name, kind, URL, observation count |
ait intercept shadow observations |
Read what arrived | ait intercept shadow observations --name shadow-specialist |
Timestamped arrivals with the payload each carried |
ait intercept shadow stop |
Finish; observations stay on disk | ait intercept shadow stop shadow-specialist |
Reports where the record was kept |
The three routing primitives, a2a.card-endpoint-substitution,
a2a.callback-substitution, and mcp.elicitation-url-substitution, declare
{{shadow:<kind>}} as their redirect target, resolved to a running shadow when
the primitive runs. Running one with no shadow started fails with the command to
fix it, rather than quietly delivering a redirect into a closed port.
Shadows bind to loopback only, and their observation files are written 0600:
a substituted destination receiving a bearer token is the point of a
credential-relay test, so the token is recorded in full.
What an arrival proves. Traffic reached an endpoint you control and you can see exactly what it carried, independently of anything the target reported. That is the top evidence tier. It does not prove a real attacker would hold that endpoint, nor that anyone downstream would notice the substitution: the finding says both.
Session export and offline review¶
| Command | Use when | Example | Expected result |
|---|---|---|---|
ait intercept export |
Produce a client-safe session report | ait intercept export SESSION_ID --format html --out session.html |
Redacted by default: allowlisted metadata, bodies omitted, header values withheld unless known-safe |
ait intercept export |
Keep a complete capture for your own analysis | ait intercept export SESSION_ID --format jsonl --raw --out session-raw.jsonl |
Includes credential values; labelled "status": "raw" and never described as sanitized |
ait intercept export |
Preserve machine-readable evidence | ait intercept export SESSION_ID --format jsonl --out session.jsonl |
Bounded message/decision records |
ait intercept export |
Reuse connection and rule behavior without fixture state | ait intercept export SESSION_ID --format profile --out connection-profile.json |
Sanitized placement/profile document |
ait intercept open |
Verify and inspect an export without target contact | ait intercept open session.jsonl |
Digest, counts, redaction state, and message summary |
Traffic commands¶
These commands are mainly for older run artifacts and Advanced investigations.
For a direct interception session, prefer intercept pending, show,
compare, and export.
| Command | Use when | Example | Expected result |
|---|---|---|---|
ait traffic tail |
Follow paginated records from a saved run | ait traffic tail --run RUN_ID --follow |
Cursor-based event stream |
ait traffic inspect |
Filter one run by task, correlation, lifecycle phase, stream, or session sequence | ait traffic inspect --run RUN_ID --seq-in-session '10..20' --related-limit 20 |
The first matching record plus bounded correlations |
ait traffic export |
Build a portable run evidence bundle | ait traffic export --run RUN_ID --out evidence-bundle |
Checksummed artifact bundle |
Session sequence filters are numeric. Use 10 for one exact record, >=10 or
<20 for a one-sided bound, and 10..20 for an inclusive range. 10 does not
match 100. The same expressions work in the Cockpit History sequence box.
Advanced¶
ait advanced reaches the peer tools AIT coordinates rather than reimplements,
plus the workspace and target plumbing the primary groups do not need. It
forwards the remaining arguments to the full parser:
Run ait advanced --help, then the selected leaf command with --help, for
the complete Advanced surface. None of it is a prerequisite for interception,
message editing, lab exercises, rules, or session export.
Complete terminal-only example¶
This is the direct operator loop without the Cockpit:
ait lab start --exercise delegated-a2a-message --no-open --json
ait lab trigger LAB_ID
ait intercept pending SESSION_ID
ait intercept show MESSAGE_ID --session SESSION_ID
ait intercept suggest MESSAGE_ID --session SESSION_ID
ait intercept edit MESSAGE_ID \
--session SESSION_ID \
--set /params/message/metadata/amount=75
ait intercept compare MESSAGE_ID --session SESSION_ID
ait intercept export SESSION_ID --format html --out delegated-edit.html
ait lab stop LAB_ID
The expected result is not merely “the JSON changed.” The specialist must receive the delivered amount and the controlled ledger must independently show the resulting test action.