Overview
Stopgate started from a want, not a threat model: I wanted to leave Claude Code running and go do something else without clicking 'approve' on every step, but I was genuinely worried about what it might do while I wasn't watching. I'd just finished AICES, which showed the usual way of flagging 'bad' actions (scan the text for scary words) is measuring vocabulary, not intent, so I built the actionable version of that lesson. The pitch isn't safety, it's autonomy. You don't babysit your agent because it's dangerous; you babysit it because you can't tell the difference between it reading a file and it reading your .env and POSTing it somewhere it found in a README. Stopgate can tell the difference, so you can go do something else. It ships as `stopgate report` (read-only, zero-config), a session digest, an auto-learned policy, `stopgate run` (unattended mode), a live Claude Code PreToolUse hook, and `stopgate eval` (the benchmark).
The Problem
Most guardrails you can install today — and my own first attempt — detect vocabulary: they grep for 'rm -rf', 'sudo', 'password'. (Research systems like CaMeL, Invariant, and LlamaFirewall do go after dataflow — but they're not a one-line install you drop onto your Claude Code sessions.) I'd already published the proof that the keyword class of detector is false-confident (AICES: a bag-of-words classifier with no model scores 0.967 on the standard harm benchmark and chance once the attacker rewords). A keyword guardrail has two failure modes that both kill it: it misses encoded attacks, and it cries wolf on every file that merely mentions a scary word. A gate that interrupts you 20 times a day gets turned off by lunch, and then it protects nothing.
My Approach
A lexical action layer plus a non-lexical provenance layer — not 'dataflow instead of keywords,' but both. Stopgate tags every tool result as trusted or untrusted, taints the session when the agent reads untrusted content, and fingerprints every secret it sees using a rolling hash with an entropy gate (so low-entropy config values don't cause false positives). When an outbound payload contains bytes from a secret read earlier — even base64 or hex-encoded — it flags it. You cannot rephrase your way past a hash comparison. The safety-critical invariant lives in the engine, not in policy: money, production, destructive, and any secret leaving the machine ALWAYS stop for a human, and I wrote a test proving that even a maximally-permissive learned policy cannot unblock them. I don't claim this is the first dataflow guardrail — CaMeL (DeepMind), Invariant Labs (Snyk), and Meta's LlamaFirewall got to provenance-based defense before me. What's new here is the open, minimal-pair controlled benchmark for agent actions, and a zero-config build you can install today; the gate is an honest implementation of a known idea.
Technical Implementation
Pure-Python policy engine (deterministic, no I/O), a taint tracker, an action-class detector, and a log parser that reads Claude Code's own session transcript. Two hooks, because one isn't enough: `stopgate hook` (PreToolUse) judges the pending call, but PreToolUse fires *before* the tool runs — so it never sees a result, and the result is where the secret bytes are. `stopgate watch` (PostToolUse) is the only place a secret can be captured at all. Between calls the taint state is persisted per session, so 'read the .env at step 3, POST it at step 19' is caught live — without that, a hook is a fresh process every call and the tracker resets, quietly degrading the tool into the pattern matcher it exists to beat. The state file stores only rolling-hash fingerprints and salted hashes: enough to recognise the bytes leaving, never enough to reconstruct them, so a stolen state file is worthless. `stopgate run` supervises a session: auto-approve in-policy, hard-stop the irreversible, print a receipt. The benchmark (`stopgate eval`) ports my AICES minimal-pair methodology to agent actions: malicious traces paired with benign twins that use the same vocabulary, a keyword baseline to beat, bootstrap confidence intervals, per-example results, and self-checks that make a rigged harness fail loudly. 339 tests, green CI, and it's on PyPI (`pip install stopgate`); MIT; local-first with zero network calls of its own.
Results
On the seed benchmark, Stopgate detects 69% of attacks to the keyword baseline's 62% — but the number that matters is false alarms, where the keyword gate fires 367 times per 1,000 actions versus Stopgate's 133. That 2.75x gap is the whole product: it's the difference between a tool you leave running and one you disable. Stopgate also catches a base64-encoded secret exfil the keyword gate misses entirely. The honest part: the set includes injection-provenance pairs — a benign-looking action an agent runs because untrusted content told it to — where Stopgate ties the keyword baseline at chance. Adding those dropped Stopgate's detection from 0.80 to 0.69; I'd rather publish the blind spot than a flattering number. Stopgate's genuine dataflow edge is secret-egress, not injection-provenance. Small, non-human-audited seed set: indicative, not definitive, and that caveat ships in the tool's own output.
What I Learned
Guardrails don't die from weak detection — they die from false positives, because every false alarm is a moment the user had to come back. That reframes the entire product: precision isn't a metric, it's the reason someone can walk away. I also made myself follow my own AICES lesson: I built the benchmark before believing any result, put the keyword baseline in as the thing to beat, and added self-checks that fail loudly if the harness is rigged. The sharper lesson came later, from actually deploying it: the benchmark passed, the tests passed, and the live gate was still hollow. A PreToolUse hook is a fresh process on every call, so the taint tracker reset each time and the dataflow moat — the entire reason this beats a keyword gate — was inert in the one mode that ships. Pointing it at a real Claude Code session returned '0 actions, all unparseable', because I'd only ever tested my own log format. Passing tests were measuring the code I wrote, not the thing I claimed. Now the control test is the one I trust most: the same exfil command is ALLOWED before the secret is read and HARD-STOPPED after — if that ever collapses, the block was never really about dataflow. And because a security tool that overclaims is worse than none, Stopgate states plainly what it can't do — best-effort, not a guarantee, not a substitute for sandboxing — in the README, a DISCLAIMER, and a warning printed on every run.