Block API Keys & Secrets from Your Commits with Claude Code Hooks

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.

Want to understand how it works? Keep reading to learn what this hook does under the hood and why it's essential for your workflow.

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:

BLOCKED: Potential secret detected in src/config.js

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
Rule of thumb: If it's critical and must never be violated, use a hook. If it's a preference or guideline, put it in CLAUDE.md.

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
Remember: CLAUDE.md rules are suggestions. Hooks are enforcement. For security, always use hooks.

Explore 800+ Claude Code Components

Discover agents, commands, MCPs, settings, hooks, skills and templates to supercharge your Claude Code workflow

Browse All Components
Back to Blog