Skip to content

Instantly share code, notes, and snippets.

@sgasser
Last active January 26, 2026 08:17
Show Gist options
  • Select an option

  • Save sgasser/efeb186bad7e68c146d6692ec05c1a57 to your computer and use it in GitHub Desktop.

Select an option

Save sgasser/efeb186bad7e68c146d6692ec05c1a57 to your computer and use it in GitHub Desktop.
Claude Code security hook - blocks dangerous commands, credential files, and bypass attempts

Claude Code Security Setup

Two-layer protection for sensitive files and destructive commands.

Setup

  1. Copy settings.json content to ~/.claude/settings.json
  2. Save security-validator.py to ~/.claude/hooks/security-validator.py
  3. Make executable: chmod +x ~/.claude/hooks/security-validator.py

How it works

permissions.deny - Blocks Claude from reading sensitive files:

  • .env files
  • SSH keys, PEM certificates
  • Cloud credentials (AWS, Azure, GCloud)
  • Kubernetes configs, Docker configs
  • Database passwords, shell history
  • And more...

Hook (security-validator.py) - Blocks destructive Bash commands:

  • rm -rf patterns
  • Recursive deletion at root/home level

Why both?

  • permissions.deny is native to Claude Code and blocks the Read tool
  • The hook provides extra protection for --dangerously-skip-permissions mode
#!/usr/bin/env python3
"""
Security validator hook for Claude Code.
Blocks destructive commands like 'rm -rf'.
For file access blocking, use permissions.deny in settings.json instead.
"""
import json
import sys
import re
def check_bash_command(command: str) -> tuple[bool, str]:
"""
Validate bash commands for dangerous patterns.
Returns: (is_allowed, error_message)
"""
# Block rm -rf / rm -r -f patterns
if re.search(r'\brm\s+(-[a-zA-Z]*r[a-zA-Z]*f|-[a-zA-Z]*f[a-zA-Z]*r)\b', command):
return False, "rm -rf is blocked. Use explicit rm commands with specific file paths."
# Block rm -r at root/home level
if re.search(r'\brm\s+-[a-zA-Z]*r[a-zA-Z]*\s+[/~]', command):
return False, "Recursive deletion at root/home level is blocked."
return True, ""
def main():
try:
input_data = json.load(sys.stdin)
except json.JSONDecodeError as e:
print(f"Error: Invalid JSON input: {e}", file=sys.stderr)
sys.exit(1)
tool_name = input_data.get("tool_name", "")
tool_input = input_data.get("tool_input", {})
# Only validate Bash commands
if tool_name != "Bash":
sys.exit(0)
command = tool_input.get("command", "")
if not command:
sys.exit(0)
is_allowed, error_msg = check_bash_command(command)
if not is_allowed:
# Exit code 2 = block with error message
print(f"SECURITY BLOCK: {error_msg}", file=sys.stderr)
sys.exit(2)
sys.exit(0)
if __name__ == "__main__":
main()
{
"permissions": {
"deny": [
"Read(**/.env)",
"Read(**/.env.*)",
"Read(**/.ssh/id_*)",
"Read(**/id_rsa)",
"Read(**/id_ed25519)",
"Read(**/*.pem)",
"Read(**/*.p12)",
"Read(**/*.pfx)",
"Read(**/*.ppk)",
"Read(**/*.gpg)",
"Read(**/*.pgp)",
"Read(**/*.asc)",
"Read(**/.aws/credentials)",
"Read(**/.azure/**)",
"Read(**/.config/gcloud/**)",
"Read(**/.kube/config)",
"Read(**/*vault_pass*)",
"Read(**/secrets.yml)",
"Read(**/secrets.yaml)",
"Read(**/secrets.json)",
"Read(**/credentials.json)",
"Read(**/.htpasswd)",
"Read(**/.netrc)",
"Read(**/.npmrc)",
"Read(**/.pypirc)",
"Read(**/application.properties)",
"Read(**/appsettings.json)",
"Read(**/*.tfstate)",
"Read(**/.git/config)",
"Read(**/.git-credentials)",
"Read(**/.bash_history)",
"Read(**/.zsh_history)",
"Read(**/.pgpass)",
"Read(**/.my.cnf)",
"Read(**/.docker/config.json)",
"Read(**/*.jks)",
"Read(**/*.keystore)"
]
},
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "~/.claude/hooks/security-validator.py"
}
]
}
]
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment