Claude Code has your shell. What's watching it?
You hand an AI agent your terminal and your repo. Most of the time it's safe. Until it isn't.
You give Claude Code a task and step away. It reads files, runs tests, edits code, makes a commit. Then comes the run where something goes wrong: the model slips. It rm -rfs the wrong folder, pipes a script off some random site into your shell, or reads .env and makes a network call that quietly carries the key out with it.
Claude Code ships with an answer: hooks. A PreToolUse hook runs before every tool call and can allow or deny it. So you write one — block rm -rf, block curl | sh. Half an hour later, everything works.
Or does it?
A hook only sees one call
A hook gets one tool call with nothing around it. It can match rm -rf, once you’ve also covered rm -fr, rm -r -f, find -delete, and whatever variant turns up next week. The calls that actually do damage often aren’t single commands at all — they’re flows.
Reading .env is fine. An outbound curl is fine. The two together, where the curl is carrying what you just read, is the whole problem. The hook inspecting that curl has no idea the read ever happened.
When context is the whole story
Here’s a concrete scenario. The agent is helping rotate an API key. It:
Reads
.env— seesOPENAI_API_KEY=sk-proj-abc123...Edits the rotation script to pass in the old key
Runs
curl https://keys.internal/rotate -H "Authorization: Bearer $KEY"
The third command looks clean. No filename, no obvious secret. A hook on that curl sees a routine API call and passes it. But $KEY was populated from the .env read two steps earlier, and the sk- prefix is now riding in the Authorization header.
sasy-guard rebuilds the session as a dependency graph. When it checks the curl, it walks backward along the edges and finds the .env read in that call’s causal chain. The call is denied — not because the command string looked dangerous, but because of where the data came from.
The same block fires whether the secret traveled through a subshell, a base64 round-trip, or a subagent that did the read on the curl’s behalf. It’s about data lineage, not command text.

What sasy-guard does instead
sasy-guard rebuilds the whole session as a graph — every tool call is a node, with edges back to the calls whose output it depends on — and checks each new call against that graph in a separate engine the agent can’t configure.

It follows the data, not the order. The curl gets cut only when its backward slice reaches the .env node. Unrelated outbound calls go through untouched. A sequence rule like “read, then curl” would over-block the innocent pairs and still miss every case that takes a detour.
It blocks the obvious switch-off. A prompt-injected agent’s first move is to disable whatever’s watching it, and sasy-guard’s hook lives in .claude/settings.json — so that means rewriting that file. But that edit is itself a tool call, and refusing it is the first thing sasy-guard does. The policy is pinned the moment the session starts, and a killed engine fails closed: every call denied until it’s back. The one-line switch-off that kills a hand-rolled hook doesn’t work here.
It knows things the command string doesn’t. Is this npm package known-bad? Did a secret scan run clean over the files you touched? Is the target repo public? None of that is in the call text. sasy-guard runs gitleaks and osv-scanner for you and caches the answers. A git push waits until a clean gitleaks run actually covers your recent edits — an echo 'no leaks found' can’t fake the all-clear.
One policy you can read, not a pile of scripts. Every rule is Soufflé Datalog. Ship one copy to the whole team; when a new pattern shows up, it lands once and everyone has it the same day.
Everything else in the box
The same session-graph backbone also holds a large unreviewed push for inspection before it leaves, decodes hidden-Unicode prompt-injection instructions before anything acts on them, and normalizes the many forms of reverse shells and curl | sh installers — treating a trusted installer host differently from an unknown one. Each denial comes back with a plain reason (config_persistence, toxic_flow) so the agent can tell you exactly what fired.
Write the hook for rm -rf. Use sasy-guard for everything past it.
We just released sasy-guard. The best way to judge it is to run it on your own Claude Code: uv tool install sasy-guard, point it at a repo, and watch the guards fire against a live session. Setup takes a couple of minutes: Enforce Policy on Claude Code. For the rule-by-rule breakdown, see Why sasy-guard, not your own hooks?.
We want your feedback: tell us what it catches and where it gets in your way. And if your team needs the policy fitted to its own stack and threat model, we’re glad to work on that with you.







