Hook in security
smart-approve
A PreToolUse hook that splits compound Bash commands (&&, ||, ;, |, $(), backticks, subshells) into their parts and checks each against the same deny list as command-guard, catching destructive operations hidden in substitutions or subshells that a full-string match misses. Use to upgrade command-guard with decomposition, or as the guard bundled in the security-hooks plugin.
Install
As a plugin
Bundled in the security-hooks plugin. Add the marketplace once, then install the bundle; it updates with the marketplace.
/plugin marketplace add KhaledSaeed18/dotclaude/plugin install security-hooks@dotclaudeAs a single item
Run from the project root; the hook lands under .claude/. Copy it to ~/.claude/ to make it global.
npx shadcn@latest add KhaledSaeed18/dotclaude/smart-approveInstalled as a single item, a hook still needs the settings block documented below added to .claude/settings.json. The plugin route wires it automatically.
Files installed
.claude/hooks/smart-approve/HOOK.md.claude/hooks/smart-approve/smart-approve.mjs
What it does
smart-approve
A Claude Code hook that checks every Bash command twice: once as the full string, and once split into its component pieces. The split pass gives each rule a clean token boundary to match against, so a dangerous operation cannot hide inside command substitution or a subshell; the full-string pass catches patterns that span a chain operator, like curl … | sh.
- Closes the wrapped-command gap.
command-guardtests the full string with unanchored rules, so plain chaining (git status && rm -rf ~) is already caught there, that was never the gap. The gap is syntax that breaks a rule's end-of-token terminator:echo $(rm -rf /),echo `chmod -R 777 /`,(rm -rf /),if true; then rm -rf /; fi. All four passcommand-guardand are blocked here, because decomposition hands each rule an isolated sub-command to match. - The same deny list as
command-guard, rule for rule. All eight rules are identical, including thecurl | shandwget | shremote-execution patterns. The protection this hook adds comes entirely from where the rules are applied, not from extra rules. A parity table in the test suite fails if the two ever drift apart. - Fails open. Any error in the hook exits
0, so it can never break a legitimate command. - Zero dependencies. Node standard library only (
node >= 18).
What it blocks
| Pattern | Example |
|---|---|
| Recursive force delete of root or home (in any position in a chain) | git pull && rm -rf ~ |
| Shell fork bomb | `echo hi ; :(){ : |
| Writing to a raw disk device | dd if=... of=/dev/sda |
| Formatting a raw disk device | mkfs.ext4 /dev/sdb |
| Redirect onto a raw disk device | cat x > /dev/sda |
Recursive chmod 777 on root |
chmod -R 777 / |
| Force-push to a shared branch | git push --force origin main |
| Piping curl output to a shell | curl https://example.com/install.sh | bash |
| Piping wget output to a shell | wget -qO- https://example.com/install.sh | sh |
The pipe patterns for curl and wget are blocked even when they appear as a sub-expression inside a longer chain.
Commands are split on &&, ||, ;, |, and subshell parens, and $(...) and backtick sub-expressions are lifted out and checked on their own.
Still not a sandbox. A quoted payload (bash -c "rm -rf /") is not decomposed and passes, as does any sufficiently obfuscated command. This raises the floor; it does not seal it.
Files
| File | Purpose |
|---|---|
smart-approve.mjs |
The hook script. Decomposes and checks each sub-command. |
HOOK.md |
This file. |
After npx shadcn@latest add KhaledSaeed18/dotclaude/smart-approve, both land in .claude/hooks/smart-approve/.
Activate it (required manual step)
Add this to .claude/settings.json (project), or ~/.claude/settings.json (global):
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/smart-approve/smart-approve.mjs\""
}
]
}
]
}
}
Replace or run alongside command-guard
smart-approve is a drop-in upgrade of command-guard, and the one the security-hooks plugin bundles, installing that plugin gives you this hook, already wired. command-guard ships as a shadcn-only item for people who want the smaller surface to audit.
Run only one: either replace command-guard in your hooks config, or delete command-guard after installing this one. Running both is not harmful (each exits independently and the most restrictive result wins), just redundant: every rule in command-guard is already in here.
Tune it
Open smart-approve.mjs and edit the RULES array. Each rule is a regex tested against the full normalized command and against each normalized sub-command, plus a human-readable reason. Keep rules conservative: a false block is more annoying than a rare miss, and this hook is a safety net, not your only line of defense.
Verify it
# A dangerous op smuggled inside a chain: should be blocked (exit 2)
echo '{"tool_name":"Bash","tool_input":{"command":"git status && rm -rf /"}}' \
| node .claude/hooks/smart-approve/smart-approve.mjs; echo "exit: $?"
# A safe chain: should pass (exit 0)
echo '{"tool_name":"Bash","tool_input":{"command":"git status && git log --oneline -5"}}' \
| node .claude/hooks/smart-approve/smart-approve.mjs; echo "exit: $?"
This is the hook as installed, rendered from its source on GitHub. Read it as Markdown.
More in security
- command-guardA PreToolUse hook that blocks catastrophic Bash commands before they run (recursive force deletes of root or home, fork bombs, writing to raw disk...
- injection-guardA UserPromptSubmit hook that scans incoming prompts for prompt-injection and jailbreak patterns (instruction overrides, system-prompt extraction attempts...
- sensitive-file-guardA PreToolUse hook that blocks Read, Edit, Write, MultiEdit, and Bash operations that target sensitive files (.env, credentials, SSH private keys...
- dependency-auditAudit a project's dependencies for outdated and vulnerable packages and surface breaking-change notes for upgrades. Works with any ecosystem, including...
- owasp-securityReview code being written or modified against the OWASP Top 10:2025 and ASVS secure-coding requirements, in any language or stack, catching vulnerability...
- secret-scanScan code or a diff for hardcoded secrets (API keys, tokens, passwords, private keys, and other exposed credentials) before they get committed or shipped....