Agent tool-boundary enforcement · multi-agent collusion carriers
AgentGate: Deterministic Guardrails Against Multi-Agent Collusion Carriers
A Policy Enforcement Point (pep.py) bound to 127.0.0.1 sits in the tool loop of a registered agent or MCP client and decides every tools/call before it runs. It addresses three ways agents can coordinate past a content filter—post-taint side channels, delegation laundering, and split or encoded smuggling—with arithmetic, hashing, and HMAC on the call path. No model is consulted to reach a decision.
Abstract
Agents that share tools can leak through which argument they pick and when they call, can launder a restricted capability by handing work to a clean peer, and can move a protected value in small or re-encoded pieces. AgentGate bounds the first with a post-taint side-channel guard, defeats the second with monotonic chained-HMAC capability attenuation across delegation, and catches the third with salted n-gram fingerprints over decoded forms plus zero-width canary watermarks. In the recorded run the repository’s carrier tests passed 57 of 57, 750 benign decisions produced no denials, and per-decision CPU time had a p50 of 0.126 ms and a p99 of 0.999 ms against a 2 ms reference; the slowest single decision took 3.84 ms. These are same-author lab checks, stated with their limits in §5.
Empirical summary
All figures from python -m agent_gateway evaluate-collusion at commit cefec0d, one machine, except where a row says otherwise.
| Measure | Value | Note |
|---|---|---|
| Decision CPU time, p50 | 0.126 ms | Thread CPU time per in-process evaluate_call, 750 benign decisions, decision-log I/O included. 2.000 ms is a reference, not a guarantee. |
| Decision CPU time, p95 | 0.892 ms | |
| Decision CPU time, p99 | 0.999 ms | |
| Slowest single decision | 3.84 ms | |
| Benign false denials | 0 / 750 | 5 workflows × 50 runs. A smoke check on same-author workflows, not a false-positive rate. |
| Hash-chained decision log | 750 / 750 | SHA-256 chain verified; audit manifest HMAC-signed. An edited or dropped line in a copy is detected. |
| Collusion carrier checks | 57 / 57 | A side-channel 8/8 · B delegation 8/8 · C de-obfuscation & canary 41/41. |
| Stateful Hypothesis machines | 5 | Including the delegation DAG machine, 120 examples × 24 steps. |
| ATLAS falsification suite | 7 / 7 | Mappings approximate. |
| Tests passing, workspace | 784 | 262 agent-gateway · 512 agent-watch (514 collected, 2 skipped) · 10 agentwatch-analyzer. |
| Real-model loopback observation | 2 of 13 | Separate measurement. qwen2.5:3b against the localhost honeypot, non-control runs, one machine. Never pooled with the rows above. |
Post-sprint: the v14 tree
The rows above, the sample artifacts [S1]–[S3], and sprint_submission/ are the frozen 25 October Apart Research sprint snapshot at cefec0d. They are not re-run or changed. Later work added four v11 guards (a separate Group D, never added to 57/57), the v11.1 to v12.1 guards, and the fourteen v12.2 to v14 reference validators. That v14 tree is on main via PR #36. The table below keeps the 31 October record of that tree. Re-measured 1 November 2026 on Python 3.12.3 with no php: main at 2dbca57 is 1,159 passed and 6 skipped (1,165 collected), and this branch (PR #34 on top of that) is 1,172 passed and 6 skipped (1,178 collected).
| Measure, v14 tree | Value | Note |
|---|---|---|
| Gateway tests passing | 1239 | The 31 Oct v14 record was 1,165 collected (262 at the snapshot, plus 127 v11, 68 v11.1, 49 PHP Defense Guard, 146 v12, 144 v12.1, 78 v12.2, 20 v12.3, 18 v12.4, 96 v12.5, 19 v12.6, 37 v12.7, 18 v12.8, 40 round-2 and 43 v14). This branch adds 13 tests (cross-session labels, canonical MCP ids, rebase alias across a new process). 1 Nov, no php: 1,172 passed, 6 skipped (1,178 collected). PR #42 adds 67 tests (shell and staged-collusion guards, URI / path / homoglyph bypass strings). 2 Nov, no php: 1,239 passed, 6 skipped (1,245 collected); 1,245 passed with php. The six skips are PHP parity. |
| Group D, v11 guards | 124 / 124 | Six test files; reported separately from A / B / C. |
| Stateful Hypothesis machines | 23 | 9 after v12.1 (memory provenance, temporal accumulator, control-flow integrity, cross-agent collusion), then 4 with v12.2, one each for v12.3 to v12.8, and 4 with v14. |
Modules behind pep.py | 51 | 22 at the snapshot; 26 after v11; 29 with v11.1 and the PHP Defense Guard; 33 after v12; 36 after v12.1; 41 after v12.2; one more for each of v12.3 to v12.8; 51 with v14. |
| Reference-validator rule codes | 15 | JSON-RPC -32066 through -32080 for R, W, F, I, P, T, S, V, D, Z, C, E, B and A (R has two). See the validator layer. |
| Tests passing, workspace | 1761 | 1,239 agent-gateway passed (6 skipped, no php) · 512 agent-watch passed (514 collected, 2 skipped with requirements-dev.txt; 510 passed and 4 skipped on the 31 October machine, which did not have gunicorn) · 10 agentwatch-analyzer passed, re-run 2 Nov. |
Where the gate sits
Every routed tool call passes through one loopback process. Calls that are not routed through it are not seen; see §5.
Agent / MCP client
Registered key in config/agents.json; agw1. capability token.
Loopback PEP
pep.py · mcp_proxy.py
127.0.0.1 · taint & attenuate
Post-taint side channel
side_channel_guard.py
Chained-HMAC delegation DAG
delegation_guard.py
Salted n-gram de-obfuscator & zero-width canary
deobfuscate.py · canary_watermark.py
SSRF & argument guards
egress_uri_guard.py · arg_ast_guard.py
Evidence
SHA-256 hash-chained decision log · HMAC-signed audit_manifest.json · verify
| Module | Mechanism | Verdict on a signal | Tests |
|---|---|---|---|
| side_channel_guard.py | Bounds distinct argument variants, call count, and timing regularity once a session is tainted and has read sensitive data. | covert_side_channel_suspected; session held, registered root key frozen 15 min | 8 |
| delegation_guard.py | Hand-off is a lattice meet; deny caveats travel as chained HMACs on the recipient’s agw1. token. | confused_deputy_delegation_blocked | 8 |
| deobfuscate.py | Salted SHA-256 digests of 8-char n-grams of raw, Base64, hex, URL and whitespace-stripped forms over a per-session window. | deobfuscated_exfil_detected; key frozen 15 min | 21 |
| canary_watermark.py | Zero-width HMAC watermark bound to (session, step, tool), plus credential digests. | canary_watermark_exfil with origin step | 20 |
| egress_uri_guard.py | Canonicalizes every URI or host argument; no DNS lookup. | ssrf_boundary_violation / hold | 44 |
| arg_ast_guard.py | Lexical argument firewall: traversal, command and header injection, read-only queries. | arg_firewall_violation | 44 |
| schema_integrity_guard.py | SHA-256 of canonical tool schemas pinned per (server, tool) across sessions; normalized names compared across servers by edit distance. | schema_drift_denied / tool_shadowing_denied | 17 |
| gui_action_guard.py | Pointer coordinates against reserved rectangles and the viewport; modifier-normalized shortcut deny-list and raw control characters. | gui_region_denied / gui_keychord_denied | 63 |
| memory_provenance_guard.py | HMAC-signed cross-session resource lineage; readers inherit the highest writer taint and the intersected capability mask. | provenance_tamper_denied on an edited record; readers inherit writer taint | 4 |
| temporal_accumulator_guard.py | Bounded per-(session, destination) window over salted digests of registered tokens (no plaintext held) and zero-width watermark counts; U+200D not counted. | sharded_exfil_denied | 9 |
| claude_code_hook.py | Claude Code PreToolUse adapter: credential paths, shell command policy, link-local metadata and file:// targets; idempotent --install. | CREDENTIAL_PATH_DENIED / SHELL_POLICY_DENIED / METADATA_URI_DENIED | 48 |
| mcp_blueprint.py | Client MCP delivery harness behind the PEP: read-only parameterized queries capped at 100 rows, field redaction, confirmation tokens on writes. | MUTATION_VERB_DENIED / ROW_LIMIT_EXCEEDED / CONFIRMATION_REQUIRED | 20 |
| interpreter_exec_guard.py | v12.1: inline python -c (parsed with ast), node -e, ruby -e, perl -e, php -r, sh -c, and heredoc payloads; protected-path writes, decoded payloads piped into a shell, process / network primitives. | INTERPRETER_PROTECTED_WRITE / INTERPRETER_OBFUSCATED_PIPE / INTERPRETER_DANGEROUS_PRIMITIVE | 75 |
| mcp_capability_manifest_guard.py | v12.1: every MCP listing, the first included, against a server capability profile; credential and execution parameters, schemas that contradict a tool’s name, namespace floods. | MCP_FIRST_SEEN_OVERPRIVILEGED / MCP_SCHEMA_PURPOSE_MISMATCH / MCP_TOOL_NAMESPACE_FLOOD | 44 |
| cross_agent_collusion_guard.py | v12.1: provenance graph of agent sessions and shared notes, files, and messages with monotone taint and toxic-chain stages; Hypothesis state machine over arbitrary hand-off sequences. | COLLUSION_CROSS_AGENT_LAUNDERING / COLLUSION_SPLIT_TOXIC_CHAIN / COLLUSION_CYCLIC_AMPLIFICATION | 6 |
| mcp_rugpull_guard.py | v12: SHA-256 fingerprint per (server, tool) of normalized description and canonical schema; HMAC re-approval for drift; tool-name ownership against other servers and core built-ins; hidden instruction markers in descriptions and parameter schemas. | MCP_DEFINITION_DRIFT / MCP_TOOL_SHADOWING / MCP_DESCRIPTION_POISONING | 25 |
| workspace_config_guard.py | v12: file tools and shell commands (>, tee, cp, mv, ln -s, chmod, sed -i, git config) against agent instruction, MCP / IDE config, and execution-hook paths; hidden directives in instruction files. | WORKSPACE_CONFIG_WRITE / WORKSPACE_HOOK_TAMPER / WORKSPACE_SYMLINK_ESCAPE / WORKSPACE_HIDDEN_DIRECTIVE | 65 |
| control_flow_integrity_guard.py | v12: per-session integrity label; after an untrusted read, privileged sinks need a grant and a matching target in a pre-taint UserCapabilityManifest. | CFI_TAINTED_PRIVILEGED_SINK / CFI_UNENDORSED_TARGET | 5 |
| stego_exfil_guard.py | v12: salted 6-gram shingles of secrets and their encodings against branch, tag, package, DNS, and URL identifiers; dense subsequence alignment by bit-parallel LCS. | STEGO_SECRET_SHINGLE_LEAK / STEGO_SUBSEQUENCE_EXFIL | 24 |
| php_defense_guard.py | PHP Defense: drop-in runtime input checks for Laravel, WordPress, and plain PHP 7.4+ (php_defense_guard.php) with a Python mirror; keys and values of $_GET / $_POST / $_COOKIE, as received and URL-decoded; 403 plus a hash-chained log entry, or log-only; excluded keys for search boxes; SARIF rules with CWE tags. Lexical checks, not a WAF. | PHP_SQLI_UNION / PHP_SQLI_TAUTOLOGY / PHP_PATH_TRAVERSAL / PHP_DESERIALIZE_DENIED / PHP_RCE_EVAL | 49 |
| audit_chain.py | seq / prev_hash / entry_hash per line; manifest HMAC with the root key. | tamper evidence, not prevention | 15 |
51 modules sit behind pep.py in agent-gateway in the v14 tree (22 at the sprint snapshot, 26 after v11, 29 with v11.1 and the PHP Defense Guard, 33 after v12, 36 after v12.1, 41 after v12.2), including the fourteen reference validators R, W, F, I, P, T, S, V, D, Z, C, E, B and A and their shared common.py (described with measured p99s on the main page; traces 09 and 10 below replay D and Z), the three v12.1 guards (19 further tests cover their PEP, MCP-proxy, hook, SARIF, benign-benchmark, and latency checks), the four v12 evasion guards (27 further tests cover their PEP, MCP-proxy, Claude Code hook, SARIF, and latency checks), the four v11 guards (post-sprint, Group D; 31 further tests cover their PEP, MCP-proxy, and SARIF wiring), the v11.1 Claude Code hook and MCP delivery blueprint, the PHP Defense Guard (Laravel / WordPress / plain PHP runtime input checks), the guards above, the MCP proxy, the toxic-flow analyzer, the SARIF exporter, and the CLI.
Interactive policy engine trace
pep.py in-process recordings. Verdicts, CPU medians, and chain state were recorded against the real modules by examples/record_site_traces.py and are replayed here; nothing on this page calls a server.
- Carrier class
- Active guard module
- Input payload / tool args
- Engine verdict
- Measured CPU time
- SHA-256 chain state
| Path | Hops | Status |
|---|
| # | Call | Arguments | Verdict | CPU median |
|---|
Carrier A (post-taint side channel) is covered by the eight tests in test_side_channel_guard.py, including a Hypothesis state machine; it has no browser trace here.
Reproduce in about 30 seconds
The evaluation itself runs in about 5 s after install. It binds no port, makes no network call, and writes the JSON, Markdown, PDF report, and slide deck to the folder you name. Latency will differ by machine.
git clone https://github.com/freeling10-prog/rogue-ai-detection cd rogue-ai-detection/agent-gateway python -m pip install -e '.[test]' export AGENT_GATEWAY_HMAC_KEY="$(python -c 'import secrets; print(secrets.token_hex(32))')" python -m agent_gateway evaluate-collusion --report ./audit_out
To re-record the §3 traces: python ../examples/record_site_traces.py from the same directory with the same key exported.
Claude Code PreToolUse hook, one command
python -m agent_gateway.claude_code_hook --install
Adds one PreToolUse command hook to ~/.claude/settings.json (running it again adds nothing). Before each tool call it denies credential paths (.env but not .env.example, ~/.ssh, ~/.aws, .npmrc, .pypirc), recursive home or root deletion, force pushes to main, download-to-shell pipes, and link-local metadata or file:// targets, and appends a SHA-256 hash-chained entry to ~/.claude/agentgate_audit.jsonl. --self-test runs four validation checks and prints CPU time per decision. Lexical configuration checks, not a sandbox.
Scope, threat model & non-goals
What the numbers above do not say.
- Same-author lab checks
- The carrier scenarios and the benign workflows were written by the person who wrote the guards. 57/57 shows the guards behave as their tests specify; it is not recall against an unknown adversary. 0/750 is a smoke check, not a false-positive rate for a deployment.
- 2 of 13 stays separate
- The only real-model figure is a loopback archive:
qwen2.5:3bagainst the localhost honeypot, 2 of 13 non-control runs, one machine. It is a different measurement and is never pooled with the lab checks. Not a benchmark. - Loopback only
- Default bind is
127.0.0.1. A non-loopback bind needs an explicit flag and an isolated lab network. No public honeypot is deployed; honeypot output is leads for a person. - 15-minute lab key freeze
- Freeze applies only to keys in the lab registry, for 900 seconds. An unregistered key is never frozen.
- Approximate ATLAS
- MITRE ATLAS ids are the project’s approximate mappings. Not a certification, and not a statement that anything passes ATLAS, OWASP, NIST, SOC 2, or ISO.
- Routed calls only
- Calls that bypass the gateway are not seen. An untrusted source that is never labeled never taints a session. Answer-only injections that trigger no gated call are out of scope.
- Not a sandbox
- No OS sandboxing or network isolation. Anyone holding
AGENT_GATEWAY_HMAC_KEYcan mint capabilities; key rotation is not implemented. Tool backends in the lab are simulated echoes. - v12.1 guard limits
- Inline interpreter writes (
python -c,node -e,ruby -e,perl -e,php -r, heredocs, decoded pipes) and MCP tools over-privileged from their first listing are now enforced byinterpreter_exec_guard.pyandmcp_capability_manifest_guard.py. Both are static: code fetched or decrypted at run time, a path computed from runtime input with no protected literal in sight, or a script file run by name (python x.py) is not analysed, and a tool with a clean schema can still misbehave on its server. An encrypted or paraphrased payload is still not caught.cross_agent_collusion_guard.pysees only routed notes, files, bus messages, and delegations; its labels never decay, so a long-lived shared artifact stays tainted. - v12 guard limits
- Lexical and deterministic.
mcp_rugpull_guard.pypins what it first sees; the v12.1 capability profile now checks that first listing, but a description can still mislead in plain words.workspace_config_guard.pyreads paths and command words; inline interpreter writes are now covered byinterpreter_exec_guard.py.control_flow_integrity_guard.pyclassifies tools by name and argument keys; in the engine a manifest only narrows.stego_exfil_guard.pymisses a paraphrased or encrypted secret and one leaked a character per identifier. - v11 guard limits
schema_integrity_guard.pycompares digests and normalized names, not what a description means.gui_action_guard.pychecks coordinates and key names against configured rectangles and a deny-list; it does not look at the screen.temporal_accumulator_guard.pymatches registered tokens and watermark characters within a bounded sliding window per (session, destination); it does not catch a paraphrased value or pieces spread across destinations.memory_provenance_guard.pytracks only resources written through routed tool calls, and its ledger lives in memory unless exported withexport_provenance_ledger().- Not a pen test
- Not a penetration test, not a red team of anyone’s users, and not legal advice.
Engineering & audit engagements
Schedule of deliverables. Fixed fees; the clock starts when a usable tool list arrives. No host deployment: delivered as reports, SARIF bundles, and mergeable PRs and configs. The code stays Apache-2.0 either way.
| Tier | Engagement | Artifacts delivered (producing module) | Clock | Fee |
|---|---|---|---|---|
| B | 48-Hour Hardened MCP Server BuildFixed-Scope Implementation |
|
48 h | $750$350 kickoff · $400 on passing acceptance testsbook build |
| T1 | MCP & Multi-Agent Exposure DiagnosticTool Inventory Review |
|
24 h | $1,800book T1 |
| T2 | Inline PEP Hardening & CI Gate SprintFixed Tool-Boundary Note |
|
48 h | $4,500book T2 |
| T3 | Continuous Red-Team, Canary & Deception RetainerNote + Lab Instrumentation |
|
48 h, then quarterly | $6,500+ $2,500 / quarterbook T3 |
| Add-on | Integration Blueprint (code or config sketches for wiring the top held calls into your harness) · Reviewer Briefing Call (30 min) | +$2,500 · +$750 | ||
Full terms and the out-of-scope list: audit-offer.md. Free alternative: the Apache-2.0 GitHub Action runs the offline analyzer on your own logs.
Sample artifacts
Unedited output of our own collusion-sprint run. Inspect the format before you write.
- [S1]AgentGate: Deterministic Guardrails Against Multi-Agent Collusion Carriers. Sample executive audit report, PDF, 4 pp., 10.7 kB.
sha256 5b0e0bbbcde0b124f5a6d9e6fe01e2abde5b37cb0720c97a4faab4376deef146 - [S2]Collusion sprint briefing deck. Sample briefing deck, PDF, 6 slides, 19.0 kB.
sha256 94f3564a37f01ceacbf41ff41265252902d0313447005eae2a60a4de3dd515cd - [S3]collusion_benchmark_results.json. Sample benchmark artifact, schema
agent-gateway.collusion-eval/1, per-test node ids and latency percentiles, 16.8 kB.
sha256 12759864e7c09d53c85d2b680cd06d35cfb29777102aff0374848def59ead407
Start the clock
Copy the inventory table, fill it in (no secrets, no customer payloads), and send it in the booking thread.