Installation
Install the Secret Scanner Hook using the Claude Code Templates CLI:
npx claude-code-templates@latest --hook security/secret-scanner
This command automatically installs the hook in your project's .claude/settings.json and creates the detection script at .claude/hooks/secret-scanner.py.
The Problem: Accidental Secret Exposure
We've all been there. You're working fast, testing something locally, and suddenly you realize you've committed an API key, password, or secret token to your repository. By the time you notice, it might already be in your git history or worse—pushed to a public repo.
Claude Code hooks provide a deterministic way to catch these mistakes before they happen. Unlike relying on CLAUDE.md suggestions (which Claude might overlook), hooks execute as code every single time.
How Claude Code Hooks Work
Claude Code provides several hook events that run at different points in the workflow:
| Hook Event | When It Runs | Can Block? |
|---|---|---|
| PreToolUse | Before tool execution | Yes (exit code 2) |
| PostToolUse | After tool completion | No |
| Stop | When Claude finishes responding | Yes (can force continuation) |
For secrets detection, we use PreToolUse with a matcher for Edit|Write operations. When exit code 2 is returned, the operation is blocked and Claude receives feedback about what to fix.
The Secrets Detection Hook
Here's the complete hook configuration that detects and blocks commits containing potential secrets:
1. Add the Hook Configuration
Add this to your .claude/settings.json file:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "bash $CLAUDE_PROJECT_DIR/.claude/hooks/detect-secrets.sh"
}
]
}
]
}
}
2. Create the Detection Script
Create the file .claude/hooks/detect-secrets.sh:
#!/bin/bash
set -euo pipefail
# Read hook input from stdin
HOOK_INPUT=$(cat)
# Extract file path and new content from the hook payload
FILE=$(echo "$HOOK_INPUT" | jq -r '.tool_input.file_path // empty')
NEW_CONTENT=$(echo "$HOOK_INPUT" | jq -r '.tool_input.new_string // .tool_input.content // empty')
# Skip if no content to check
if [ -z "$NEW_CONTENT" ]; then
exit 0
fi
# Define patterns for common secrets
SECRET_PATTERNS=(
# API Keys (generic)
'api[_-]?key["\s:=]+["\x27]?[a-zA-Z0-9_-]{20,}["\x27]?'
# AWS Keys
'AKIA[0-9A-Z]{16}'
'aws[_-]?secret[_-]?access[_-]?key'
# GitHub Tokens
'ghp_[a-zA-Z0-9]{36}'
'github[_-]?token'
# Stripe Keys
'sk_live_[a-zA-Z0-9]{24,}'
'sk_test_[a-zA-Z0-9]{24,}'
# Generic secrets
'password["\s:=]+["\x27][^\s"'\'']{8,}["\x27]'
'secret["\s:=]+["\x27][^\s"'\'']{8,}["\x27]'
'token["\s:=]+["\x27][a-zA-Z0-9_-]{20,}["\x27]'
'private[_-]?key'
'client[_-]?secret'
# Database connection strings
'mongodb(\+srv)?://[^\s]+'
'postgres(ql)?://[^\s]+'
'mysql://[^\s]+'
'redis://[^\s]+'
)
# Check for each pattern
for pattern in "${SECRET_PATTERNS[@]}"; do
if echo "$NEW_CONTENT" | grep -iE "$pattern" &>/dev/null; then
echo "BLOCKED: Potential secret detected in $FILE" >&2
echo "" >&2
echo "Pattern matched: $pattern" >&2
echo "" >&2
echo "Please use environment variables instead:" >&2
echo " 1. Store the secret in .env (add .env to .gitignore)" >&2
echo " 2. Reference it via process.env.YOUR_SECRET" >&2
echo "" >&2
exit 2
fi
done
exit 0
3. Make the Script Executable
chmod +x .claude/hooks/detect-secrets.sh
Project Structure
After setup, your project should have this structure:
your-project/
├── .claude/
│ ├── hooks/
│ │ └── detect-secrets.sh
│ └── settings.json
├── .gitignore
└── src/
What Gets Detected
The hook scans for common patterns including:
| Type | Example Pattern |
|---|---|
| AWS Access Keys | AKIA... (20 chars) |
| GitHub Tokens | ghp_... (40 chars) |
| Stripe Keys | sk_live_..., sk_test_... |
| Generic API Keys | api_key = "..." |
| Passwords | password: "..." |
| Database URLs | mongodb://..., postgres://... |
How It Works in Practice
When you ask Claude Code to edit a file that would contain a secret:
Pattern matched: api[_-]?key["\s:=]+...
Please use environment variables instead:
1. Store the secret in .env (add .env to .gitignore)
2. Reference it via process.env.YOUR_SECRET
Claude receives this feedback and automatically adjusts its approach, typically suggesting environment variables instead.
Advanced: Adding More Patterns
You can extend the SECRET_PATTERNS array to catch company-specific patterns:
# Add your company's internal patterns
SECRET_PATTERNS+=(
'MYCOMPANY_[A-Z0-9]{32}'
'internal[_-]?api[_-]?key'
'prod[_-]?secret'
)
Difference: CLAUDE.md vs Hooks
| Approach | Reliability | When to Use |
|---|---|---|
| CLAUDE.md | Suggestions (may be ignored) | Coding style, preferences |
| Hooks | Enforcement (always runs) | Security, compliance, automation |
Conclusion
With this hook in place, you've added a deterministic security layer to your Claude Code workflow. Every file edit and write operation gets scanned for secrets before it can be committed.
This is just one example of what PreToolUse hooks can do. You can apply the same pattern for:
- Blocking edits to production files
- Enforcing code formatting
- Preventing commits with TODO comments
- Validating file naming conventions