Destructive Command Guard - High-performance Rust hook for Claude Code that blocks dangerous commands before execution. SIMD-accelerated, modular pack system, whitelist-first architecture...
A high-performance Claude Code hook that intercepts and blocks destructive commands before they execute. Written in Rust with SIMD-accelerated filtering for sub-millisecond latency.
AI coding agents are powerful but fallible. They can accidentally run destructive commands:
rm -rf ./src (typo)git reset --hard (destroys uncommitted changes)git checkout -- . (discards all modifications)git clean -fd (permanently deletes untracked files)DCG intercepts dangerous commands before execution and blocks them with a clear explanation, giving you a chance to stash your changes first.
Safe patterns are checked before destructive patterns. This ensures explicitly safe commands are never accidentally blocked:
git checkout -b feature β Matches SAFE "checkout-new-branch" β ALLOW
git checkout -- file.txt β No safe match, matches DESTRUCTIVE β DENY
Unrecognized commands are allowed by default. This ensures:
The pattern set prioritizes never allowing dangerous commands over avoiding false positives. A few extra prompts for manual confirmation are acceptable; lost work is not.
| Command | Reason |
|---|---|
git reset --hard |
Destroys uncommitted changes |
git reset --merge |
Destroys uncommitted changes |
git checkout -- <file> |
Discards file modifications |
git restore <file> (without --staged) |
Discards uncommitted changes |
git clean -f |
Permanently deletes untracked files |
| Command | Reason |
|---|---|
git push --force / -f |
Overwrites remote commits |
git branch -d, --delete, -D, -f, -M, -C |
Deletes or force-overwrites a user-owned branch ref |
| Command | Reason |
|---|---|
git stash drop |
Permanently deletes a stash |
git stash clear |
Permanently deletes all stashes |
| Command | Reason |
|---|---|
rm -rf (outside /tmp, /var/tmp, $TMPDIR) |
Recursive deletion is dangerous |
Safe operations pass through silently:
git status, git log, git diff, git add, git commit, git push, git pull, git fetch, read-only branch listings, git stash, git stash pop, git stash list
| Pattern | Why Safe |
|---|---|
git checkout -b <branch> |
Creating new branches |
git checkout --orphan <branch> |
Creating orphan branches |
git restore --staged <file> |
Unstaging only, doesn't touch working tree |
git restore -S <file> |
Short flag for staged |
git clean -n / --dry-run |
Preview mode, no actual deletion |
rm -rf /tmp/* |
Temp directories are ephemeral |
rm -rf $TMPDIR/* |
Shell variable forms |
--force-with-leasegit push --force-with-lease # ALLOWED - refuses if remote has unseen commits
git push --force # BLOCKED - can overwrite others' work
DCG uses a modular "pack" system to organize patterns by category:
| Pack | Description |
|---|---|
core.git |
Destructive git commands |
core.filesystem |
Dangerous rm -rf outside temp |
| Pack | Description |
|---|---|
database.postgresql |
DROP/TRUNCATE in PostgreSQL |
database.mysql |
DROP/TRUNCATE in MySQL/MariaDB |
database.mongodb |
dropDatabase, drop() |
database.redis |
FLUSHALL/FLUSHDB |
database.sqlite |
DROP in SQLite |
| Pack | Description |
|---|---|
containers.docker |
docker system prune, docker rm -f |
containers.compose |
docker-compose down --volumes |
containers.podman |
podman system prune |
| Pack | Description |
|---|---|
kubernetes.kubectl |
kubectl delete namespace |
kubernetes.helm |
helm uninstall |
kubernetes.kustomize |
kustomize delete patterns |
| Pack | Description |
|---|---|
cloud.aws |
Destructive AWS CLI commands |
cloud.gcp |
Destructive gcloud commands |
cloud.azure |
Destructive az commands |
| Pack | Description |
|---|---|
infrastructure.terraform |
terraform destroy |
infrastructure.ansible |
Dangerous ansible patterns |
infrastructure.pulumi |
pulumi destroy |
| Pack | Description |
|---|---|
system.disk |
dd, mkfs, fdisk operations |
system.permissions |
Dangerous chmod/chown patterns |
system.services |
systemctl stop/disable patterns |
| Pack | Description |
|---|---|
strict_git |
Extra paranoid git protections |
package_managers |
npm unpublish, cargo yank |
# ~/.config/dcg/config.toml
[packs]
enabled = [
"database.postgresql",
"containers.docker",
"kubernetes", # Enables all kubernetes sub-packs
]
| Variable | Description |
|---|---|
DCG_PACKS="containers.docker,kubernetes" |
Enable packs (comma-separated) |
DCG_DISABLE="kubernetes.helm" |
Disable packs/sub-packs |
DCG_VERBOSE=1 |
Verbose output |
DCG_COLOR=auto|always|never |
Color mode |
DCG_BYPASS=1 |
Bypass DCG entirely (escape hatch) |
curl -fsSL "https://raw.githubusercontent.com/Dicklesworthstone/destructive_command_guard/main/install.sh?$(date +%s)" | bash
# Easy mode: auto-update PATH
curl -fsSL "https://raw.githubusercontent.com/Dicklesworthstone/destructive_command_guard/main/install.sh?$(date +%s)" | bash -s -- --easy-mode
# System-wide (requires sudo)
curl -fsSL "https://raw.githubusercontent.com/Dicklesworthstone/destructive_command_guard/main/install.sh?$(date +%s)" | sudo bash -s -- --system
cargo +nightly install --git https://github.com/Dicklesworthstone/destructive_command_guard
Available for: Linux x86_64, Linux ARM64, macOS Intel, macOS Apple Silicon, Windows
Add to ~/.claude/settings.json:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash|PowerShell",
"hooks": [
{
"type": "command",
"command": "/absolute/path/to/dcg"
}
]
}
]
}
}
Replace /absolute/path/to/dcg with the exact resolved binary path. Do not use
bare dcg: non-interactive hook shells may not inherit the interactive
PATH. On native Windows, use install.ps1 so the hook receives the
PowerShell-safe absolute command and explicit shell selection.
Important: Restart Claude Code after adding the hook.
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Claude Code β
β Agent executes `rm -rf ./build` β
βββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ PreToolUse hook (stdin: JSON)
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β dcg β
β ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ β
β β Parse βββββΆβ Normalize βββββΆβ Quick Reject β β
β β JSON β β Command β β Filter β β
β ββββββββββββββββ ββββββββββββββββ ββββββββ¬ββββββββ β
β β β
β βββββββββββββββββββββββββββββ β
β βΌ β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β Pattern Matching β β
β β 1. Check SAFE_PATTERNS (whitelist) βββΆ Allow if match β β
β β 2. Check DESTRUCTIVE_PATTERNS βββββββΆ Deny if match β β
β β 3. No match βββββββββββββββββββββββββΆ Allow (default) β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ stdout: JSON (deny) or empty (allow)
PreToolUse format/usr/bin/git status β git status| Code | Meaning |
|---|---|
0 |
Command is safe, proceed |
2 |
Command is blocked, do not execute |
Test commands manually:
# Show version with build metadata
dcg --version
# Test a command
echo '{"tool_name":"Bash","tool_input":{"command":"git reset --hard"}}' | dcg
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
BLOCKED dcg
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Reason: git reset --hard destroys uncommitted changes. Use 'git stash' first.
Command: git reset --hard HEAD~1
Tip: If you need to run this command, execute it manually in a terminal.
Consider using 'git stash' first to save your changes.
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
| Command Type | Suggestion |
|---|---|
git reset, git checkout -- |
"Consider using 'git stash' first" |
git clean |
"Use 'git clean -n' first to preview" |
git push --force |
"Consider using '--force-with-lease'" |
rm -rf |
"Verify the path carefully before running manually" |
/usr/bin/git reset --hard # Blocked
/usr/local/bin/git checkout -- . # Blocked
/bin/rm -rf /home/user # Blocked
rm -rf /path # Combined flags
rm -fr /path # Reversed order
rm -r -f /path # Separate flags
rm --recursive --force /path # Long flags
All variants are handled.
rm -rf $TMPDIR/build # Allowed (temp)
rm -rf ${TMPDIR}/build # Allowed
rm -rf "$TMPDIR/build" # Allowed
rm -rf "${TMPDIR:-/tmp}/build" # Allowed
git restore --staged file.txt # Allowed (unstaging only)
git restore -S file.txt # Allowed (short flag)
git restore file.txt # BLOCKED (discards changes)
git restore --worktree file.txt # BLOCKED (explicit worktree)
git restore -S -W file.txt # BLOCKED (includes worktree)
DCG is designed for zero perceived latency:
| Optimization | Technique |
|---|---|
| Lazy Static | Regex patterns compiled once via LazyLock |
| SIMD Quick Reject | memchr crate for CPU vector instructions |
| Early Exit | Safe match returns immediately |
| Zero-Copy JSON | serde_json operates on input buffer |
| Zero-Allocation | Cow<str> for path normalization |
| Release Profile | opt-level="z", LTO, single codegen unit |
Result: Sub-millisecond execution for typical commands.
| Type | Count |
|---|---|
| Safe patterns (whitelist) | 34 |
| Destructive patterns (blacklist) | 16 |
git checkout -- or git reset --hardgit stash drop/clearrm -rf outside temp directories./deploy.sh contents not inspected)DCG assumes the AI agent is well-intentioned but fallible. It catches honest mistakes, not adversarial attacks.
~/.claude/settings.json has hook configurationecho '{"tool_name":"Bash","tool_input":{"command":"git reset --hard"}}' | dcgDCG_BYPASS=1 or run command manuallyQ: Why block both git branch -d and git branch -D?
Lowercase -d checks merge state, but still removes the branch name, upstream-tracking configuration, and convenient reflog reference. Those are user-owned state, so every delete form requires explicit approval. Review first with git branch -vv, git branch --merged, and git branch --no-merged.
Q: Why is git push --force-with-lease allowed?
Force-with-lease refuses to push if the remote has commits you haven't seen, preventing accidental overwrites.
Q: Why block all rm -rf outside temp directories?
Recursive forced deletion is extremely dangerous. A typo or wrong variable can delete critical files. Temp directories are designed to be ephemeral.
Q: What if I really need to run a blocked command?
DCG instructs the agent to ask for permission. Run the command manually in a separate terminal after making a conscious decision.
| Tool | Integration |
|---|---|
| Claude Code | Native PreToolUse hook |
| Agent Mail | Agents can report blocked commands to coordinator |
| BV | Flag tasks that repeatedly trigger DCG |
| CASS | Search DCG block patterns across sessions |
| RU | DCG protects agent-sweep from destructive commits |