Skip to content

feat: MCP collector — handshake supply-chain defenses (phase 11a) - #17

Merged
carbon-evolution merged 12 commits into
mainfrom
feat/agent-firewall-11-mcp
Jul 30, 2026
Merged

carbon-evolution merged 12 commits into
mainfrom
feat/agent-firewall-11-mcp

Conversation

@carbon-evolution

Copy link
Copy Markdown
Owner

Phase 11a — the MCP supply-chain collector

MCP servers hand the agent a list of tools at startup. Three attacks live at that boundary and none are visible to the hook collector: rug-pull (a trusted server quietly changes a tool later), tool-name shadowing (two servers, or a server and a builtin, claim the same name), and description poisoning (injection text in a tool description). This adds a transparent agentfw mcp stdio proxy and a /mcp daemon endpoint that catch all three at handshake, deterministically — no model required.

How it works

agentfw mcp --id <name> -- <real server cmd> wraps the server in the client's .mcp.json. The proxy relays JSON-RPC byte-for-byte and tees only the handshake to /mcp. The daemon pins each server's tool manifest (stable SHA-256 over sorted names + descriptions + input schemas, at ~/.agentfw/manifests/) and checks:

  • Driftask (ask-manifest-drift) when the hash differs from the pin
  • Shadowingask (ask-tool-shadow) against a cross-server + builtin registry
  • Poisoning → the existing ask-injection-in-tool-description rule over each description

Safety posture

  • Ships in shadow mode — pins and audits, withholds nothing until enforce: true.
  • Fails closed on enforce: an MCP handshake has no interactive ask, so a non-allow verdict returns a JSON-RPC error in place of the manifest (the server starts with no tools that session).
  • Fails open on its own errors: daemon unreachable or line unparsable → passes through untouched. A broken firewall must never break MCP.

Architecture

The agent crate stays I/O-free: inspect_mcp_handshake(server, tools, manifest_changed, tool_shadow) runs the injection detector over descriptions and evaluates policy. The daemon owns all I/O — a persistent ManifestStore and an in-memory ToolRegistry in AppState. Reuses the existing EventKind::ManifestSeen (defined in phase 08) rather than a new variant.

Tests

405 workspace tests (from 384), 0 failing; clippy -D warnings clean; fmt clean. New: manifest hash/diff (5), pin store + shadowing (3), JSON-RPC manifest recognition (2), relay enforcement (1+1), inspect_mcp_handshake + policy signals (agent), and 4 /mcp endpoint tests (first-sight pins, drift→ask, poisoned→ask, builtin-shadow→ask).

Documented v1 deferrals

Per-call argument/result inspection and HTTP/SSE MCP transport are out of scope (stdio only). The drift audit reason is a summary, not a per-tool diff, because the pin stores the hash, not the old manifest. Cross-server shadowing seeds names lazily per daemon run (clients re-handshake every server at startup).

Design: docs/superpowers/specs/2026-07-30-agent-firewall-11-mcp-collector-design.md. Plan: docs/superpowers/plans/2026-07-30-agent-firewall-11-mcp-collector.md.

🤖 Generated with Claude Code

carbon-evolution and others added 12 commits July 30, 2026 20:47
Transparent stdio proxy (agentfw mcp -- <server>) that tees the MCP handshake to
the daemon, which pins each server's tool manifest and flags three supply-chain
attacks at handshake, deterministically: rug-pull (manifest drift), tool-name
shadowing (cross-server/builtin collisions), and description poisoning (reuses the
existing injection-over-tool_description rule).

Daemon-connected (central cross-server view), server identity via --id, ships in
shadow mode (enforce opt-in; non-interactive ask fails closed), fail-open on any
proxy/daemon error. v1 defers per-call inspection and HTTP/SSE transport.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
13 TDD tasks: ToolDecl schema + McpHandshake event, description-facet
projection, drift/shadow policy signals, inspect_mcp_handshake, JSON-RPC
manifest recognition, stable manifest hash + diff, persistent pin store +
tool registry, shipped drift/shadow rules, the /mcp endpoint, the stdio
relay, the agentfw mcp subcommand, end-to-end tests, and README + PR.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Phase 08 already defined ManifestSeen as the MCP-handshake event and projects
its tool descriptions onto ToolDescription; the plan's McpHandshake was a
duplicate. Task 2 (facet projection) is therefore already satisfied.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Runs the injection detector over tool descriptions (reusing the shipped
ask-injection-in-tool-description rule) and evaluates policy with the daemon-
computed manifest_changed and tool_shadow facts. Pure; no manifest state in the
agent crate.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… proxy

- jsonrpc: tolerant recognition of the tools/list manifest over stdio
- manifest: stable SHA-256 pin (reorder-invariant, content-sensitive) + diff
- store: persistent per-server pins (0600) + cross-server ToolRegistry
- proxy: transparent bidirectional stdio relay + handshake tee + fail-open enforce
Adds sha2 dep and pub mod mcp.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Computes manifest drift (vs the persistent pin) and tool-name shadowing (vs the
cross-server registry), runs detection + policy via inspect_mcp_handshake, pins
the new manifest, audits an mcp_handshake line, and returns the verdict. Fails
open on unparsable input. AppState gains manifests + tools; wired into main and
the two existing test harnesses. 4 endpoint tests: first-sight allow, drift ask,
poisoned-description ask, builtin-shadow ask.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Wraps a real MCP server (agentfw mcp --id <name> -- <cmd> <args>), resolves the
daemon /mcp URL from config, loads the bearer token, and runs the stdio relay.
Server id defaults to a 12-char hash of the command when --id is omitted.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Adds an 'MCP supply-chain defenses' subsection (agentfw mcp wrapping, the three
handshake checks, shadow-default + fail-closed-on-enforce + fail-open-on-error),
updates the test badge (336->405) and per-crate/module tables, and records phase
11a in the project history and roadmap.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@carbon-evolution
carbon-evolution merged commit de3ab6a into main Jul 30, 2026
2 checks passed
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.

1 participant