Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

toolwarden

CI

A guard hook for Claude Code that blocks dangerous tool calls before they run — rm -rf /, DROP TABLE, curl | bash, force-pushes to main, writes to .env, and more.

AI coding agents can autonomously execute shell commands, file operations, and database queries. Without guardrails, a misinterpreted instruction can cause irreversible damage. toolwarden hooks into Claude Code's PreToolUse event and scans every Bash, Write, and Edit call against a curated set of threat patterns:

Category Examples
Destructive file ops rm -rf /, deleting .env or .git
Destructive git ops git push --force to main/master, git reset --hard, git clean -f
Database destruction DROP TABLE, TRUNCATE, DELETE FROM without WHERE
Permission abuse chmod 777, recursive world-writable permissions
Network exfiltration curl | bash, uploading files via curl -d @ / -T / -F
System danger sudo, npm publish
Data loss docker compose down -v, docker volume rm, rm on SQLite files
Protected paths writes to .env, .git/ internals, key/credential/token files

Rules are tiered:

  • deny — irreversible or exfiltration patterns are blocked outright. The denial reason, including a safer alternative, is fed back to the agent so it can self-correct.
  • ask — risky-but-sometimes-legitimate patterns (git reset --hard, docker system prune, …) escalate to a permission prompt for per-case approval.

Zero dependencies: a single Python 3 stdlib script plus a JSON rules file. No network calls.

Install

Clone the repo, then run the installer against any repository you want to protect:

git clone https://github.com/pazsitz/toolwarden.git
python3 toolwarden/install.py /path/to/your/repo   # defaults to the current directory

There is deliberately no curl | bash one-liner — toolwarden blocks that pattern for a reason. Clone or download, read install.py (it's short), then run it.

The installer:

  1. copies toolwarden.py and toolwarden-patterns.json into <repo>/.claude/hooks/,
  2. registers the hook in <repo>/.claude/settings.json (created if missing; existing settings are preserved; re-running never duplicates the entry).

Restart your Claude Code session afterwards — hooks are loaded at session start. Since the files live inside your repo, committing them means every teammate gets the same protection automatically.

Add the log directory to your repo's .gitignore:

.claude/logs/

Update

Pull the latest toolwarden and re-run the installer. It overwrites the hook and core patterns, and never touches your local rules file.

Uninstall

python3 toolwarden/install.py /path/to/your/repo --uninstall

Removes the hook files and the settings entry; local rules and logs are left in place.

Configuration

Env var Values Effect
TOOLWARDEN_MODE enforce (default) / warn warn never blocks — logs the match and surfaces a warning
TOOLWARDEN_ALLOWLIST comma-separated rule IDs Skips those rules, e.g. TOOLWARDEN_ALLOWLIST=sudo,git-reset-hard

The hook inherits its environment from the Claude Code session, so set these either in the shell you launch Claude Code from, or per project via the env key in .claude/settings.json:

{ "env": { "TOOLWARDEN_ALLOWLIST": "sudo" } }

Project-specific rules

Add your own rules in .claude/hooks/toolwarden-patterns.local.json (next to the installed hook). Local rules are evaluated before core rules, and a local rule with the same id replaces the core rule — so you can also retier or reword a built-in. Updates never overwrite this file.

Each rule:

{
  "id": "rm-user-data",
  "category": "project-data",
  "tier": "deny",
  "tools": ["Bash"],
  "regex": "\\brm\\s[^|;&]*\\bdata/users(/|\\b)",
  "message": "rm targeting data/users/ — the primary user data store of this project.",
  "alternative": "Move to a backup location instead: mv data/users/<x> /tmp/backup-<x>"
}

See examples/toolwarden-patterns.local.json for a complete example. Within each file, rules are evaluated in order and the first match wins — keep deny rules before ask rules.

Rules reference

ID Tier Catches
rm-rf-root deny recursive rm on /, ~, or $HOME
rm-env-git deny deleting .env or .git
sql-drop deny DROP TABLE/DATABASE/SCHEMA
sql-truncate deny SQL TRUNCATE (file truncate -s is fine)
sql-delete-no-where deny DELETE FROM without WHERE
curl-pipe-shell deny curl/wget piped into a shell
curl-upload deny curl uploading local files (-d @, -T, -F …=@)
chmod-777-recursive deny recursive world-writable permissions
git-force-push-main deny force-push to main/master (--force-with-lease allowed)
sudo deny any sudo command
npm-publish deny publishing to the npm registry
docker-down-volumes deny docker compose down -v (deletes volumes)
docker-volume-rm deny docker volume rm / prune
rm-database-files deny rm on .db / .sqlite files
write-env deny Write/Edit to .env* (except .env.example)
write-git-internals deny Write/Edit inside .git/
write-secrets deny Write/Edit to .pem/.key/credentials/token files
git-reset-hard ask git reset --hard
git-clean-force ask git clean -f
chmod-777-single ask non-recursive chmod 777
rm-rf-subpath ask recursive rm outside /tmp
docker-system-prune ask docker system prune

Logging

Matched events and internal errors append JSON Lines to .claude/logs/toolwarden.jsonl:

{"ts": "2026-07-16T10:00:00+00:00", "tool": "Bash", "rule_id": "sudo", "category": "system-danger", "tier": "deny", "decision": "deny", "mode": "enforce", "target_snippet": "sudo apt install foo"}

Clean (non-matching) calls are not logged.

Failure mode & limitations

  • Fail-open: if the hook itself errors (malformed patterns file, unexpected exception), the tool call proceeds and a visible systemMessage warns that the guard is inactive; the error is logged. A broken guard should never brick your agent.
  • Regex scanning is defense-in-depth, not a sandbox — deliberately obfuscated commands can evade it. Claude Code's permission prompts remain the primary gate; toolwarden catches the accidental and the obvious.

Development

pip install pytest
pytest -v

This repo dogfoods its own hook: .claude/settings.json points at src/toolwarden.py.

New rules are welcome — a rule PR should include the rule itself plus matching and near-miss test cases in tests/test_patterns.py.

License

MIT

About

A guard hook for AI that blocks dangerous calls

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages