GitHub - vandith1/agent-guard: A pre-execution guard that stops your coding agent running destructive commands. One shell file, no dependencies. · GitHub
/" data-turbo-transient="true" />
Skip to content
Search/
Sign in<br>Sign upAppearance settings
You signed in with another tab or window. Reload to refresh your session.<br>You signed out in another tab or window. Reload to refresh your session.<br>You switched accounts on another tab or window. Reload to refresh your session.
Dismiss alert
{{ message }}
vandith1
agent-guard
Public
Notifications<br>You must be signed in to change notification settings
Fork
Star
main
BranchesTags
Go to file
CodeOpen more actions menu
Folders and files<br>NameNameLast commit message<br>Last commit date<br>Latest commit
History<br>6 Commits<br>6 Commits
docs
docs
LICENSE
LICENSE
README.md
README.md
guard-command.sh
guard-command.sh
install.sh
install.sh
test-guard.sh
test-guard.sh
View all files
Repository files navigation
agent-guard
Your coding agent will eventually run git reset --hard, force-push to main, or<br>drop a table. Not because it's careless — because it's confident, and the loop<br>drifted three steps ago.
Try it in your browser → — paste any command, see whether it would be blocked and by which rule. Nothing is sent anywhere; the rules run locally.
This is a ~60-line shell hook that stops the commands you can't undo. It runs<br>before every shell command the agent proposes, exits 2 to deny, and writes the<br>reason to stderr — which agent CLIs feed back into the loop, so the agent stops<br>retrying and surfaces the command to you instead.
BLOCKED by guard [discard all local changes (git reset --hard)]<br>command: git reset --hard HEAD~3<br>This one is gated to the human. Do not retry, do not reword.<br>Surface the exact command and let the human run it.
Install
bash install.sh # merges a PreToolUse hook into .claude/settings.json<br>bash test-guard.sh # 6 assertions, should be 6 passed / 0 failed
Works with Claude Code's PreToolUse hook, and with any agent CLI that runs a<br>shell hook before a tool call and respects its exit code. Requires bash,<br>grep -E, and python3.
What it blocks
git reset --hard · rm -rf · git clean -f/-x · git checkout . ·<br>git filter-branch · force-push to main/master/production · DROP TABLE ·<br>TRUNCATE · FLUSHALL · aws delete-*/terminate-* · kubectl delete ·<br>terraform destroy · docker … prune · chmod 777 · curl … | sh ·<br>raw writes to /dev/sd*.
Rules live in one array at the top of the script as label@@regex pairs. Edit<br>them. Add a test case when you do — including a benign command the new pattern<br>must not catch. That's how you find an over-greedy regex before it finds you.
What this does and does not stop
It stops: a confident agent proposing an irreversible command in plain text, which is the<br>failure mode I actually hit — the loop drifted three steps ago and the next command discards a<br>day's work.
It does not stop: anything deliberately evading it. g""it reset --hard, a base64 payload<br>through eval, a shell script, an alias. The matcher is a regex over the proposed command string,<br>and a regex loses to an adversary every time.
That distinction is the whole threat model, and it is worth being blunt about: if your agent is<br>obfuscating commands to get around a hook you installed, a pattern list is not your problem. This<br>assumes a well-intentioned model having a bad moment, not a hostile one.
It also does not replace the things that are strictly more reliable because they sit outside your<br>machine: server-side branch protection, least-privilege credentials, and backups. Nothing here is a<br>substitute for any of those. Use them, and use this for the irreversible operations they do not<br>cover — a hard reset on uncommitted work, a recursive delete, a dropped table, a destroyed stack.
A seatbelt, not a rollcage.
Two things worth knowing
It matches the whole command string, arguments included. So a commit message<br>that quotes a gated phrase gets blocked. That's deliberate: the alternative is<br>parsing shell quoting, expansion and eval, and every parser is a new way to<br>slip a command past the guard. Fail-safe beats clever. The habit that fixes it:<br>git commit -F .commit-msg instead of -m "…", never a heredoc.
It fails closed. If it can't read the command out of the hook payload, it<br>blocks. A guard that silently degrades to "allow everything" is worse than no<br>guard, because you'd stop watching.
The idea behind it: two tiers
Destructive commands and external-effect commands are different problems.
Tier 1 — rm -rf, reset --hard, DROP TABLE. Unrecoverable. Never<br>negotiable, no unlock, not even for you-in-a-hurry. This repo is tier 1.
Tier 2 — git push, npm publish, deploys, migrations. Real effects, but<br>reviewable and reversible. These shouldn't be banned — they should be gated,<br>unlockable by a human for a short window that auto-relocks.
Tier 2 is where the design gets interesting: the unlock has to be something the<br>agent can't touch, including refreshing its...