Skip to content

Add the flight recorder: hash-chained session recordings with replay and verify - #7

Merged
botzrDev merged 1 commit into
mainfrom
claude/nifty-wozniak-y7k50f
Oct 22, 2026
Merged

botzrDev merged 1 commit into
mainfrom
claude/nifty-wozniak-y7k50f

Conversation

@botzrDev

Copy link
Copy Markdown
Owner

Summary

Third roadmap item after local mode (botzrDev/mcp-guard#5) and argument-level policy (botzrDev/mcp-guard#6): evidence. The audit log says a tool was called. The flight recorder keeps what was asked, what came back, and what the guard blocked, in order, in a form that can be proven unedited.

What it records

Every mcp-guard serve session becomes one JSONL file: each client request, each response the guard returns, upstream notifications, and every guard decision (policy_deny, pin_deny, policy_would_deny, tool_hidden_policy, tool_hidden_pin). session_start carries the upstream command, config path, policy mode and guard version; session_end says why the session ended.

Tamper evidence

Each record carries prev (the previous record's hash) and hash (over its own contents prefixed by prev). Editing, deleting, inserting or reordering a record breaks the chain at a known point. With [recorder] signing_key_env set, hashes are HMAC-SHA256 under a shared key, so a forger who can edit the file but lacks the key cannot rechain it. The docs are explicit about what plain hashes do and do not protect against.

Redaction

Payloads are redacted before hashing: built-in patterns for private key blocks, AWS, GitHub, Slack, OpenAI, Anthropic, Google, Stripe and mcp-guard keys, JWTs, bearer tokens and user:password@ URLs, plus the user's [audit] redaction_rules. Oversized payloads are truncated to a preview. payload_sha256 is the hash of the original, so a payload you still hold can be matched to a record without the recording containing the secret.

New commands

  • mcp-guard replay [--list] [--session latest|<id>|<file>] [--denied] [--tool X] [--json] renders a session as a timeline and reports chain status.
  • mcp-guard verify [--session latest|all|...] [--json] recomputes the chain, names the first bad record, exits 1 on tampering, and checks HMAC signatures when the key is present (marks OK* when it is not).

mcp-guard wrap enables recording for every wrapped server under <config dir>/recordings/<client>-<server>/. doctor reports RECORDER_DISABLED. Recorder failures are logged and never block the agent; nothing is ever written to stdout.

Docs

New docs/flight-recorder.md (format, field table, redaction, the tamper-evidence model and its limits, operations). Updated docs/local-mode.md, docs/configuration.md, docs/cli.md, the config template, CHANGELOG, README and CLAUDE.md.

Verification

Check Result
cargo fmt --all -- --check pass
cargo clippy --all-features -- -D warnings (CI command) pass
RUSTDOCFLAGS=-D warnings cargo doc --no-deps --all-features pass
cargo test --all-features all suites pass
cargo test --workspace all suites pass

New tests:

  • Unit: chain construction and verification; tamper detection for edit, removal, reorder and a garbage line; signed chains with the right key, wrong key and no key; redaction of built-in and user patterns with the original hash preserved; truncation; include_results = false keeping errors; session listing and selection.
  • Proxy: the exact record sequence for an allowed call and a denied call.
  • End to end: a live serve session recorded, then replay --list, replay, replay --denied, verify --session all, tampering the file and seeing verify fail with the record number and replay report CHAIN BROKEN; a signed session verified with, without and with the wrong key; clear messages when there are no recordings.

The e2e helper gained a graceful close() that drops stdin and waits, matching how real clients exit, so session_end is exercised.

Next

Recording in gateway mode keyed by identity, retention, and a self-contained HTML session report, then the 2026-07-28 protocol shim.


Generated by Claude Code

…and verify

Every serve-mode session is now recorded to a JSONL file: each client
request, each response the guard returns, upstream notifications, and every
guard decision (policy denial, pin denial, observe-mode would-deny, hidden
tools). Each record carries the hash of the previous one and its own hash,
so edits, deletions, insertions and reordering break the chain at a known
record. With [recorder] signing_key_env set, hashes are HMAC-SHA256 under a
key the writer and verifier share, so a forger without the key cannot
recompute the chain.

Payloads are redacted before hashing with built-in patterns (private keys,
AWS, GitHub, Slack, OpenAI, Anthropic, Google, Stripe and mcp-guard keys,
JWTs, bearer tokens, credentials in URLs) plus the user's [audit]
redaction_rules, and truncated above max_payload_bytes with the hash of the
full original kept, so a recording can be shared without leaking secrets.

New commands:
- mcp-guard replay: timeline of a session with arguments, outcomes and
  decisions; --list, --denied, --tool, --json; reports chain status.
- mcp-guard verify: recompute the chain for one or all sessions, name the
  first bad record, exit 1 on tampering; checks signatures when the key is
  present and says so when it is not.

wrap enables the recorder for every wrapped server under
<config dir>/recordings/<client>-<server>/; doctor reports RECORDER_DISABLED.
A recorder failure is logged and never blocks the agent.

Docs: new docs/flight-recorder.md (format, redaction, tamper-evidence model
and its limits, operations), plus local-mode, configuration, CLI reference,
template, CHANGELOG, README and CLAUDE.md.

Tests: unit tests for chaining, tamper detection (edit, removal, reorder,
garbage line), signed chains, redaction, truncation, include_results, session
listing; a proxy test asserting the record sequence for a call and a denial;
e2e tests running a live serve session then replay and verify, tampering
the file, and signed recordings with, without and with the wrong key.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JKKYdbt8Gd5B9Q4C3wim3f
@botzrDev
botzrDev marked this pull request as ready for review October 22, 2026 18:07
@botzrDev
botzrDev merged commit 9bea401 into main Oct 22, 2026
9 of 13 checks passed
@botzrDev
botzrDev deleted the claude/nifty-wozniak-y7k50f branch October 22, 2026 18:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants