# Secret Scanning Guardrails

> Pre-commit and pre-push hooks that prevent secrets from ever reaching git history. Once a secret is committed, it's compromised — prevention is the only real defense.

## When to Use

- Setting up any new repo
- After discovering a leaked secret
- When onboarding AI agents (they can accidentally include secrets in code)
- As part of security hardening

## The Pattern

Two-layer defense:

```
Layer 1: Pre-commit hook
  → Scans staged diff for secret patterns
  → Blocks commit if secrets detected

Layer 2: Pre-push hook
  → Scans all tracked files
  → Blocks push if secrets detected

Layer 3: CI (optional)
  → Scans on PR
  → Fails pipeline if secrets detected
```

## Step-by-Step

### 1. Create the staged-diff scanner

`scripts/secret-scan-staged.sh`:

```bash
#!/usr/bin/env bash
# Scan staged changes for secrets before commit
set -euo pipefail

PATTERNS=(
  '[A-Za-z0-9/+]{40}'                    # Azure/AWS key-like (40+ base64 chars)
  'AccountKey=[A-Za-z0-9/+=]+'           # Azure Storage account key
  'sk-[A-Za-z0-9]{20,}'                  # OpenAI API key
  'sk_live_[A-Za-z0-9]+'                 # Stripe live key
  'sk_test_[A-Za-z0-9]+'                 # Stripe test key
  'whsec_[A-Za-z0-9]+'                   # Stripe webhook secret
  'AKIA[0-9A-Z]{16}'                     # AWS access key ID
  'AIza[0-9A-Za-z_-]{35}'               # Google API key
  'ghp_[A-Za-z0-9]{36}'                  # GitHub PAT
  'gho_[A-Za-z0-9]{36}'                  # GitHub OAuth token
  'password\s*[:=]\s*["\x27][^"\x27]+'   # Hardcoded password
  'secret\s*[:=]\s*["\x27][^"\x27]+'     # Hardcoded secret
)

STAGED=$(git diff --cached --unified=0 -- ':(exclude)*.lock' ':(exclude)package-lock.json' ':(exclude)pnpm-lock.yaml')
[ -z "$STAGED" ] && exit 0

FOUND=0
for pattern in "${PATTERNS[@]}"; do
  MATCHES=$(echo "$STAGED" | grep -Pn "^\+.*$pattern" 2>/dev/null || true)
  if [ -n "$MATCHES" ]; then
    echo "SECRET DETECTED in staged changes:"
    echo "$MATCHES" | head -5
    echo ""
    FOUND=1
  fi
done

if [ "$FOUND" -eq 1 ]; then
  echo "COMMIT BLOCKED: Remove secrets before committing."
  echo "If this is a false positive, use: git commit --no-verify"
  exit 1
fi
```

### 2. Create the tracked-file scanner

`scripts/secret-scan-repo.sh`:

```bash
#!/usr/bin/env bash
# Scan all tracked files for secrets (broader than staged-diff scan)
set -euo pipefail

EXCLUDE='--exclude-dir=node_modules --exclude-dir=.git --exclude-dir=dist'
EXCLUDE="$EXCLUDE --exclude=*.lock --exclude=package-lock.json --exclude=pnpm-lock.yaml"
EXCLUDE="$EXCLUDE --exclude=secret-scan-staged.sh --exclude=secret-scan-repo.sh"

echo "Scanning tracked files for secrets..."
FOUND=0

# Check for common secret patterns
for pattern in \
  'AccountKey=[A-Za-z0-9/+=]+' \
  'sk-[A-Za-z0-9]{20,}' \
  'sk_live_[A-Za-z0-9]+' \
  'AKIA[0-9A-Z]{16}' \
  'AIza[0-9A-Za-z_-]{35}'; do

  MATCHES=$(git ls-files -z | xargs -0 grep -Prl "$pattern" $EXCLUDE 2>/dev/null || true)
  if [ -n "$MATCHES" ]; then
    echo "POSSIBLE SECRET ($pattern) in:"
    echo "$MATCHES" | sed 's/^/  /'
    FOUND=1
  fi
done

if [ "$FOUND" -eq 1 ]; then
  echo ""
  echo "PUSH BLOCKED: Review above files for secrets."
  exit 1
fi

echo "No secrets detected."
```

### 3. Wire up git hooks

**Using Husky (Node.js projects):**

```bash
npx husky init
```

`.husky/pre-commit`:

```bash
bash scripts/secret-scan-staged.sh
npx lint-staged
```

`.husky/pre-push`:

```bash
bash scripts/secret-scan-repo.sh
```

**Using git core.hooksPath (any project):**

```bash
mkdir -p .githooks
# Create .githooks/pre-commit and .githooks/pre-push
git config core.hooksPath .githooks
```

### 4. Update .gitignore

```gitignore
# Secrets & credentials
.env
.env.local
.env.*.local
*.pem
*.p12
*.pfx
*.key
```

### 5. Create .env.example templates

For every `.env` file, create a `.env.example` with placeholders:

```bash
# .env.example — Copy to .env and fill in real values
DB_ENDPOINT=https://your-account.example.com:443/
DB_KEY=<your-db-key-here>
JWT_SECRET=<your-jwt-secret-here>
PAYMENT_KEY=<your-payment-key-here>
```

## If a Secret Was Already Committed

1. **Treat it as compromised immediately** — Don't wait for "later cleanup"
2. **Rotate the secret** — Generate a new key/password in the provider
3. **Update Key Vault / env** — Deploy the new secret
4. **Consider git history cleanup** — `git filter-branch` or BFG Repo-Cleaner
5. **Add the pattern to your scanner** — Prevent recurrence

## Integration with AI Agents

AI agents can accidentally include secrets when:

- Copying from environment files
- Generating example configs with real values
- Reading .env and including contents in code

**Prevention:**

- AGENTS.md rule: "Never hardcode secrets or API keys"
- .gitignore covers .env files
- Pre-commit hook catches accidental inclusions
- Use `PLACEHOLDER_HERE` in examples

## Anti-Patterns

- **No scanning at all** — One commit can expose all your secrets
- **Only scanning in CI** — Too late; the secret is already in git history
- **`--no-verify` as habit** — Bypassing hooks defeats the purpose
- **Scanning only new files** — Secrets can be added to existing files
- **Complex scanning tools** — A simple grep-based script catches 95% of leaks

## Related Skills

- [11 — AI-Driven Security Auditing](./11-security-auditing.md)
- [18 — Environment & Secrets Management](./18-environment-management.md)
- [12 — Pre-Release Quality Gates](./12-quality-gates.md)
