Secrets scanning

Catch leaks at commit and PR time: rule sets, history scans, and rotation playbooks after true positives.

The SKILL should define scan scope (LFS, submodules, large-file exclusions), pinned rule versions, and CI failure policy; prioritize vendor-shaped secrets over generic high-entropy blobs.

When history hits, assess public exposure, revoke/rotate credentials, and retain audit logs; agent outputs should link to your incident runbooks.

gitleaks full configuration and pre-commit hook

The .gitleaks.toml below covers three custom rule types (AWS key, GitHub token, DB password), along with path exclusions and a false-positive allowlist.

# .gitleaks.toml — pin gitleaks v8.x
[extend]
useDefault = true

[[rules]]
id = "aws-access-key-custom"
description = "AWS Access Key ID"
regex = '''(?i)(aws[_\-\. ]?)(access[_\-\. ]?key[_\-\. ]?id|aki)[^a-z0-9]*(=|:|:=)[\s"']*([A-Z0-9]{20})'''
tags = ["key", "aws"]
severity = "critical"

[[rules]]
id = "github-personal-token"
description = "GitHub Personal Access Token (classic or fine-grained)"
regex = '''ghp_[A-Za-z0-9]{36}|github_pat_[A-Za-z0-9_]{82}'''
tags = ["token", "github"]
severity = "critical"

[[rules]]
id = "db-password-in-url"
description = "Database password in connection string"
regex = '''(postgres|mysql|mongodb)://[^:]+:([^@\s"']{8,})@'''
tags = ["password", "database"]
secretGroup = 2
severity = "high"

[allowlist]
description = "Global exclusion: test fixtures, documentation examples"
regexes = [
  '''AKIAIOSFODNN7EXAMPLE''',
  '''wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY''',
]
paths = [
  '''.*_test\.go$''',
  '''.*\.md$''',
  '''tests/fixtures/.*''',
]

pre-commit hook configuration (.pre-commit-config.yaml)

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/gitleaks/gitleaks
    rev: v8.18.4
    hooks:
      - id: gitleaks
        args: ["--config=.gitleaks.toml", "--baseline-path=.gitleaks-baseline.json"]

  - repo: https://github.com/Yelp/detect-secrets
    rev: v1.5.0
    hooks:
      - id: detect-secrets
        args: ["--baseline", ".secrets.baseline"]
        exclude: package-lock\.json
  • Pin tool versions in CI; explicitly declare whether submodules and LFS pointers are in scope.
  • Failure policy: true positives block merges; allowlist changes require ticket linkage within a time-boxed window.

detect-secrets scan commands and baseline file

detect-secrets uses a baseline file to manage known false positives; new secrets cause CI to fail.

# Initialize baseline (first run — capture existing false positives)
detect-secrets scan --baseline .secrets.baseline \
  --exclude-files '\.lock$' \
  --exclude-files 'node_modules/'

# Audit new findings (interactive — mark false positives)
detect-secrets audit .secrets.baseline

# CI validation (non-zero exit if new secrets detected)
detect-secrets scan | \
  python -c "import sys,json; d=json.load(sys.stdin); sys.exit(1 if d['results'] else 0)"

Baseline file format (.secrets.baseline key fields)

{
  "version": "1.5.0",
  "plugins_used": [
    {"name": "AWSKeyDetector"},
    {"name": "JwtTokenDetector"},
    {"name": "KeywordDetector", "keyword_exclude": "example|test|fake|dummy"},
    {"name": "HexHighEntropyString", "limit": 3.0},
    {"name": "Base64HighEntropyString", "limit": 4.5}
  ],
  "results": {
    "config/database.yml": [
      {
        "type": "Secret Keyword",
        "filename": "config/database.yml",
        "hashed_secret": "3d20558fe59359...",
        "is_verified": false,
        "line_number": 12
      }
    ]
  },
  "generated_at": "2025-01-01T00:00:00Z"
}
Tip: entropy-only thresholds drown real findings; pair them with rule IDs, file classes, and secret lifecycle steps in the SKILL.

False-positive allowlist examples

False positives are managed via allowlists: each entry must include reason, owner, review cadence, and a traceable PR; global rule disables without comments are forbidden.

# gitleaks.toml — rule-level allowlist (minimum granularity)
[[rules]]
id = "github-personal-token"
# ... rule definition ...
[rules.allowlist]
  description = "Placeholder token in test fixtures, reviewed in PR#421, re-review 2026-01-01"
  regexes = ['ghp_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX']
  paths = ['tests/fixtures/auth_test\.go$']
  commits = ["abc123def456"]  # Exempt specific commit only (historical fix)

# gitleaks per-commit inline allowlist (line-level)
const API_KEY = "test-key-for-unit-tests"; // gitleaks:allow
  • Keep granularity minimal: path-scoped, rule-ID-scoped, or reviewed regex—avoid * wildcards.
  • Version with code and review; write expiration dates into entries or tickets to prevent "forever" exemptions.
  • After fixing a true positive, remove the corresponding allowlist line—do not leave a "fixed" comment as a placeholder.

Secret leakage SOP (5 steps) and GitHub Actions

5-step emergency SOP after a true positive is confirmed:

# Step 1: Immediately revoke the secret (AWS example)
aws iam delete-access-key --access-key-id AKIAIOSFODNN7ABCDEF

# Step 2: Audit usage during the exposure window
aws cloudtrail lookup-events \
  --lookup-attributes AttributeKey=AccessKeyId,AttributeValue=AKIAIOSFODNN7ABCDEF \
  --start-time $(date -d '30 days ago' --iso-8601) \
  --query 'Events[*].{Time:EventTime,Name:EventName,Source:SourceIPAddress}'

# Step 3: Rotate new key (generate + store in Secrets Manager)
NEW_KEY=$(aws iam create-access-key --user-name deploy-bot)
aws secretsmanager put-secret-value \
  --secret-id prod/deploy-bot-key \
  --secret-string "$NEW_KEY"

# Step 4: Remove from git history (using BFG Repo-Cleaner)
bfg --replace-text secrets-to-remove.txt --no-blob-protection
git reflog expire --expire=now --all && git gc --prune=now --aggressive
git push --force --all && git push --force --tags

# Step 5: Notify + retain audit record
# In the security ticket: record exposure window, impact assessment, revocation timestamp, rotation confirmation

GitHub Actions secret scanning workflow

# .github/workflows/secret-scan.yml
name: Secret Scan
on:
  push:
    branches: ["main", "develop"]
  pull_request:
jobs:
  gitleaks:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0  # scan full history
      - name: Run Gitleaks
        uses: gitleaks/gitleaks-action@v2
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          GITLEAKS_CONFIG: .gitleaks.toml
          GITLEAKS_ENABLE_COMMENTS: true  # comment on PR

  detect-secrets:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: pip install detect-secrets==1.5.0
      - run: |
          detect-secrets scan --baseline .secrets.baseline
          git diff --exit-code .secrets.baseline || \
            (echo "::error::New secret detected, run detect-secrets audit" && exit 1)

Fixed order: classify true vs false positive, then rotate or allowlist; historical leaks follow incident response separately.

  [ Local: pre-commit / pre-push scan ]
                    │
                    ▼
         [ CI / PR: full tree + history (per policy) ]
                    │
           ┌────────┴────────┐
           ▼                 ▼
    [ Rules: vendor shapes ]   [ Heuristics: entropy / broad match ]
           │                 │
           └────────┬────────┘
                    ▼
              [ Human / agent triage ]
                    │
           ┌────────┴────────┐
           ▼                 ▼
    [ True positive: rotate / revoke / audit ]   [ False positive: allowlist proposal + PR review ]
           │                 │
           └────────┬────────┘
                    ▼
         [ Merge: pin rule version + archive report ]

False-positive record template

The form below only assembles text in your browser—nothing is uploaded. Use it for PR descriptions or security ticket attachments.

Fill the fields, click Generate, then Copy; redact sensitive values before pasting.

SKILL snippet

---
name: secrets-scan
description: Configure gitleaks/detect-secrets, handle true positives, govern allowlists, with secret leakage SOP
---
# Steps
1. Verify .gitleaks.toml or equivalent: custom rules covering AWS/GitHub/DB secret types
2. Verify .pre-commit-config.yaml: gitleaks + detect-secrets hooks are configured
3. Verify .secrets.baseline: exists and has been reviewed with detect-secrets audit
4. CI/CD: secret-scan workflow runs on PR with fetch-depth=0 to scan full history
5. Allowlist: each entry has reason, owner, review date; no wildcard (*) global disables
6. High-entropy false positives: use path-scoped exclusions rather than disabling the whole rule
7. True positive response: revoke immediately → audit usage → rotate → clean history → retain audit record
8. Git history cleanup: use bfg or git-filter-repo; cannot rely on git reset
9. Allowlist expiry reminders: write into ticket or use scheduled review
10. Consolidate with SBOM and dependency scan alerts on a security dashboard

# Anti-patterns
- Do NOT commit real secrets to any repository, even private repos
- Do NOT use uncommented global allowlists (*)
- After fixing a true positive, the allowlist entry MUST be removed

Back to skills More skills