Claude Code has your shell. What's watching it?
sasy-guard rebuilds a Claude Code session as a dependency graph and checks every tool call against a policy the agent can't switch off.
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.

A hook sees the curl alone. sasy-guard traces the curl back along the
data and lands on your .env.
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.

Same calls, two views. The session is an ordered list; the graph links each
call to the data it used, so the slice from the curl lands on .env and
skips the edit that ran right before it.
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.

Every tool call detours through the engine before it runs. The engine sees the whole session, not just the one call in front of it.
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.