Solutions
Below the permission list, where it can't be stepped around.
A deny rule sitting next to a wildcard interpreter grant stops an honest agent, not a redirected one. The guard reads the command string whichever interpreter would have run it, and that placement is the entire point.
It inspects what a call does
The command, the paths in the payload, and executable content — not the bytes a document happens to carry.
It asks before it refuses
Block, allow once, or allow for a set time. A block that only ever says no gets worked around.
Every block names its caller
The tool, the session id, the working directory, and the process tree above the hook — which program is actually running.
What it blocks
| Pattern | |
|---|---|
| Originals | the screen-capture store, Messages, Mail, SSH keys, Keychains, the Photos library, executed documents |
| Destructive verbs | recursive remove, DELETE FROM, DROP TABLE, --force, the privacy-reset command |
Three surfaces, and one deliberate omission
| Surface | Drawn from | Checked against |
|---|---|---|
| Command | the command, cmd, script or code field of the call | originals and destructive verbs |
| Paths | file_path, path, notebook_path, pattern, glob, any *_path or *_file | originals |
| Executable content | content or new_string, only when the target looks executable — a script extension, a path under bin/ or hooks/, or no extension at all | originals and destructive verbs |
File contents are otherwise not inspected. A document that names a path is not an access to it, and matching prose bought nothing: text about a destructive verb deletes nothing, and a script that carries one is refused twice over — when it is written to an executable path, and again when it is run.
Unrecognised payloads degrade safely
If the JSON parses but no field is recognisable, every byte is still checked for a path to an original — one appearing in any argument means something is being pointed at one — while destructive verbs are only checked when the tool's name suggests it executes things. That way a search query or a message body can say the words. If the payload is not JSON at all, the whole thing is checked against both patterns.
Answerable, so it survives contact with work
A hook has no terminal and no window, so it cannot ask anything itself. It writes a request to disk, polls for a verdict, and the app raises the prompt. The request id is a fingerprint of the layer plus the inspected surface: that call, not that pattern.
Allow for N minutes writes an override the guard consults before blocking, so an identical call goes through until it expires. Overrides are listed in Settings with a Revoke button, and they expire on their own.
Pause offers fifteen minutes, an hour, four hours, or until you resume it. The guard re-reads its state on every call, so a pause takes effect now rather than at the next restart — and it keeps logging throughout, so you can see what went through while it was off. A banner runs across every screen and the menu bar icon goes hollow.
Fourteen cases, re-run on every scan
The behaviour matrix is tested against a sandboxed home directory, and the interesting rows are the allowances: prose naming an original and a destructive verb is allowed; a search query mentioning a destructive verb is allowed; the same verb inside a shell script is blocked. Four of those rows run again as rules on every scan.