Skip to content
Technical report & reference implementation · commit cefec0d · 25 Oct 2026 Apache-2.0 · loopback 127.0.0.1
AgentGate

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.

Status
Reference implementation; lab-bound
Evaluated tree
cefec0d · 25 Oct 2026
Correspondence
alec@room23.net
Artifacts
report · slides · json · source

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.

§1

Empirical summary

All figures from python -m agent_gateway evaluate-collusion at commit cefec0d, one machine, except where a row says otherwise.

MeasureValueNote
Decision CPU time, p500.126 msThread 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, p950.892 ms
Decision CPU time, p990.999 ms
Slowest single decision3.84 ms
Benign false denials0 / 7505 workflows × 50 runs. A smoke check on same-author workflows, not a false-positive rate.
Hash-chained decision log750 / 750SHA-256 chain verified; audit manifest HMAC-signed. An edited or dropped line in a copy is detected.
Collusion carrier checks57 / 57A side-channel 8/8 · B delegation 8/8 · C de-obfuscation & canary 41/41.
Stateful Hypothesis machines5Including the delegation DAG machine, 120 examples × 24 steps.
ATLAS falsification suite7 / 7Mappings approximate.
Tests passing, workspace784262 agent-gateway · 512 agent-watch (514 collected, 2 skipped) · 10 agentwatch-analyzer.
Real-model loopback observation2 of 13Separate measurement. qwen2.5:3b against the localhost honeypot, non-control runs, one machine. Never pooled with the rows above.
Figure 1. Per-decision CPU time, 750 benign decisions, linear scale. The shaded band is the 2 ms reference. The single slowest decision lies outside it and is shown rather than trimmed.

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 treeValueNote
Gateway tests passing1239The 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 guards124 / 124Six test files; reported separately from A / B / C.
Stateful Hypothesis machines239 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.py5122 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 codes15JSON-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, workspace17611,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.
§2

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.

ModuleMechanismVerdict on a signalTests
side_channel_guard.pyBounds 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 min8
delegation_guard.pyHand-off is a lattice meet; deny caveats travel as chained HMACs on the recipient’s agw1. token.confused_deputy_delegation_blocked8
deobfuscate.pySalted 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 min21
canary_watermark.pyZero-width HMAC watermark bound to (session, step, tool), plus credential digests.canary_watermark_exfil with origin step20
egress_uri_guard.pyCanonicalizes every URI or host argument; no DNS lookup.ssrf_boundary_violation / hold44
arg_ast_guard.pyLexical argument firewall: traversal, command and header injection, read-only queries.arg_firewall_violation44
schema_integrity_guard.pySHA-256 of canonical tool schemas pinned per (server, tool) across sessions; normalized names compared across servers by edit distance.schema_drift_denied / tool_shadowing_denied17
gui_action_guard.pyPointer coordinates against reserved rectangles and the viewport; modifier-normalized shortcut deny-list and raw control characters.gui_region_denied / gui_keychord_denied63
memory_provenance_guard.pyHMAC-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 taint4
temporal_accumulator_guard.pyBounded per-(session, destination) window over salted digests of registered tokens (no plaintext held) and zero-width watermark counts; U+200D not counted.sharded_exfil_denied9
claude_code_hook.pyClaude Code PreToolUse adapter: credential paths, shell command policy, link-local metadata and file:// targets; idempotent --install.CREDENTIAL_PATH_DENIED / SHELL_POLICY_DENIED / METADATA_URI_DENIED48
mcp_blueprint.pyClient 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_REQUIRED20
interpreter_exec_guard.pyv12.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_PRIMITIVE75
mcp_capability_manifest_guard.pyv12.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_FLOOD44
cross_agent_collusion_guard.pyv12.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_AMPLIFICATION6
mcp_rugpull_guard.pyv12: 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_POISONING25
workspace_config_guard.pyv12: 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_DIRECTIVE65
control_flow_integrity_guard.pyv12: 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_TARGET5
stego_exfil_guard.pyv12: 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_EXFIL24
php_defense_guard.pyPHP 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_EVAL49
audit_chain.pyseq / prev_hash / entry_hash per line; manifest HMAC with the root key.tamper evidence, not prevention15

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.

§3

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

#CallArgumentsVerdictCPU 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.

§4

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.

§5

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:3b against 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_KEY can 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 by interpreter_exec_guard.py and mcp_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.py sees 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.py pins 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.py reads paths and command words; inline interpreter writes are now covered by interpreter_exec_guard.py. control_flow_integrity_guard.py classifies tools by name and argument keys; in the engine a manifest only narrows. stego_exfil_guard.py misses a paraphrased or encrypted secret and one leaked a character per identifier.
v11 guard limits
schema_integrity_guard.py compares digests and normalized names, not what a description means. gui_action_guard.py checks coordinates and key names against configured rectangles and a deny-list; it does not look at the screen. temporal_accumulator_guard.py matches 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.py tracks only resources written through routed tool calls, and its ledger lives in memory unless exported with export_provenance_ledger().
Not a pen test
Not a penetration test, not a red team of anyone’s users, and not legal advice.
§6

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.

TierEngagementArtifacts delivered (producing module)ClockFee
B 48-Hour Hardened MCP Server BuildFixed-Scope Implementation
  • Custom MCP server for one system (Postgres, Stripe, HubSpot, Slack, or an internal REST API) on mcp_blueprint.py
  • Wrapped in pep.py allow / deny rules: read-only allowlist, 100-row cap, confirmation tokens on writes, unapproved mutation verbs denied
  • Sensitive-field redaction and a SHA-256 hash-chained audit log (audit_chain.py)
  • A pytest acceptance suite; delivered as a mergeable PR. Nothing is deployed on your hosts by us.
48 h $750$350 kickoff · $400 on passing acceptance testsbook build
T1 MCP & Multi-Agent Exposure DiagnosticTool Inventory Review
  • Executive collusion & posture report, PDF (agent_gateway audit)
  • SARIF 2.1.0 bundle for GitHub code scanning, approximate ATLAS ids (sarif_export.py)
  • Sigma rules + STIX 2.1 bundle for your log format (agentwatch-analyzer)
24 h $1,800book T1
T2 Inline PEP Hardening & CI Gate SprintFixed Tool-Boundary Note
  • T1, plus the full written tool-boundary note
  • Loopback pep.py / mcp_proxy.py policy set for your tool list (delegation_guard.py, toxic_combinations.py, arg_ast_guard.py, egress_uri_guard.py)
  • 9 stateful Hypothesis regression machines wired into a CI workflow
  • Per-run tamper evidence (audit_chain.py): hash-chained log, HMAC-signed manifest, verify
48 h $4,500book T2
T3 Continuous Red-Team, Canary & Deception RetainerNote + Lab Instrumentation
  • T2, plus a lab gateway pass on 127.0.0.1 and an offline analyzer pass on an authorized log
  • Localhost tripwire config (agent-watch; human-review leads only)
  • Canary watermark tracking (canary_watermark.py), de-obfuscation and side-channel guards (deobfuscate.py, side_channel_guard.py)
  • Quarterly evaluate-collusion latency & posture re-run (scripted lab scenarios, not a red team of your users)
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.

  1. [S1]AgentGate: Deterministic Guardrails Against Multi-Agent Collusion Carriers. Sample executive audit report, PDF, 4 pp., 10.7 kB.
    sha256 5b0e0bbbcde0b124f5a6d9e6fe01e2abde5b37cb0720c97a4faab4376deef146
  2. [S2]Collusion sprint briefing deck. Sample briefing deck, PDF, 6 slides, 19.0 kB.
    sha256 94f3564a37f01ceacbf41ff41265252902d0313447005eae2a60a4de3dd515cd
  3. [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.

Tool Name | Argument Shapes | Where Untrusted Text Enters | Tier: T1 $1,800, T2 $4,500 or T3 $6,500 --------- | --------------- | --------------------------- | ------------------------------------- example.fetch | {url: string} | the fetched page body | T1 example.send_email | {to: string, body: string} | — | T1