// free · no signup · copy-paste friendly

API Key Hygiene Cheatsheet

The steps I run through every time a key leaks — and the hooks I set up to stop it happening again. Written for engineers with twenty minutes and the worst Slack message of their week.

1. prevent 2. detect 3. rotate 4. scrub history 5. incident checklist

1. Prevent — pre-commit hooks

If your repo doesn't run something on git commit, keys will ship. These three tools cover 99% of real leaks.

pre-commit + gitleaks (recommended)

One config file, runs locally and in CI. Fast enough you won't disable it.

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/gitleaks/gitleaks
    rev: v8.21.2
    hooks:
      - id: gitleaks
# install once:
#   pipx install pre-commit
#   pre-commit install
# every `git commit` now blocks on gitleaks findings

trufflehog (deep scan)

Run on PRs. Verifies keys against live APIs so you get a signal not a pile of regexes.

# .github/workflows/trufflehog.yml
name: secret-scan
on: [pull_request]
jobs:
  scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with: { fetch-depth: 0 }
      - uses: trufflesecurity/trufflehog@main
        with:
          extra_args: --only-verified --fail

keyhound (this project)

Scans public GitHub surface for your org on a schedule. Catches what slips past pre-commit.

# one-shot scan against your org
keyhound gh your-org-name --format=json

# continuous hourly monitoring (see monitor/ in the repo)
cron: '13 * * * *'  # runs hourly, diffs, emails only NEW live keys

⚡ Ship pre-commit AND a CI check. Pre-commit catches the dev, CI catches the dev who disabled pre-commit.

2. Detect — find leaks that already shipped

The keys are already public. Finding them fast is most of the fix.

Scan your entire git history

# gitleaks — full history, every branch
gitleaks detect --source . --log-opts="--all"

# trufflehog — full history, verify live
trufflehog git file://. --only-verified

# keyhound — public GitHub surface (search, not clone)
keyhound gh your-org-name

# detect-secrets — scan + baseline for noisy repos
detect-secrets scan > .secrets.baseline

GitHub advanced search (manual, works on any repo)

# Swap "acmecorp" for your org/user
org:acmecorp "sk-ant-api03-"
org:acmecorp "AKIA" "AWS_SECRET_ACCESS_KEY"
org:acmecorp filename:.env
org:acmecorp "xoxb-" OR "xoxp-"
org:acmecorp filename:service-account.json

Check if your key was in a public leak

# GitHub's secret-scanning alerts (if you enabled it)
https://github.com/your-org/your-repo/security/secret-scanning

# search your repo's PRs + issues — keys often leak in diffs and logs
gh pr list   --state all --search "sk-"  --limit 100
gh issue list --state all --search "AKIA" --limit 100

# search Gists for keys with your org string
gh api "search/code?q=sk-ant-api03-+user:your-org" --jq '.items[].html_url'

3. Rotate — provider-by-provider

Revoke first. Rotate second. Audit third. Until the old key is invalidated at the provider, nothing else you do matters.

Anthropic

console.anthropic.com/settings/keys

Delete the key. Check the Usage tab for calls you didn't make.

OpenAI

platform.openai.com/api-keys

Delete + regenerate. Review billing.

AWS

IAM → Users → the user → Security credentials → Make inactive, then Delete. Review CloudTrail for the exposure window.

GCP

Console → APIs & Services → Credentials. Disable first, then delete. Check Logs Explorer for the key ID.

GitHub PAT

github.com/settings/tokens

Delete. If it had push rights, check the Audit log for pushes you didn't make.

Stripe

dashboard.stripe.com/apikeys

Roll the key. Review Events + Logs. If you see anomalies, contact Stripe support — they can help reverse.

Slack bot token

App config → OAuth & Permissions → Revoke Token → re-install to regenerate. Review Audit logs if you're on Enterprise Grid.

SendGrid / Twilio

Delete key, rotate. For Twilio, also rotate the Auth Token at the account level — not just the API key.

⚠ Deactivation stops new authentication — the key stops working. What it doesn't do is remove the key from your inventory, which means a month from now an auditor will see it sitting there and won't immediately know whether it's the dead-and-rotated one or a live one someone forgot about. Delete once rotation is verified.

4. Scrub — remove the key from git history

Rotation kills the key. Scrubbing removes it from the commit graph so it stops showing up in scanners, forks, CI caches, Docker layers, and GitHub's permanent commit URLs. A rotated key still in history is a loud "we leaked once" flag that confuses future audits and keeps landing in other people's dashboards.

git-filter-repo (preferred, modern)

# Install:  pipx install git-filter-repo
# (make a backup clone first — this rewrites history)

git clone --mirror git@github.com:your-org/your-repo.git repo-backup
cd your-repo

# 1) Remove the file entirely from history
git filter-repo --invert-paths --path config/.env --path secrets.json

# 2) Or: redact a specific string everywhere it appears
echo 'sk-ant-api03-XXXXXXXXXXXXXXXXXXXXXXXX==><REDACTED>' > replacements.txt
git filter-repo --replace-text replacements.txt

# 3) Force-push the rewritten history
git push origin --force --all
git push origin --force --tags

BFG Repo-Cleaner (faster on huge repos)

java -jar bfg.jar --delete-files .env my-repo.git
java -jar bfg.jar --replace-text secrets.txt my-repo.git
cd my-repo.git
git reflog expire --expire=now --all
git gc --prune=now --aggressive
git push --force

⚡ Force-push isn't the end. GitHub keeps dangling commits reachable by SHA for a while — anyone who knows the hash can still pull the patch from github.com/<org>/<repo>/commit/<sha>.patch. Open a support ticket asking them to purge the cached refs, and assume any fork you can't control still has the key.

5. Incident checklist — the twenty-minute version

Print this. Follow it top-to-bottom when a leak lands in your inbox.

// did this save you time?

Send the next leak our way

If you spot a leak — yours or someone else's — we route it through a signed, dated, RFC-9116-compliant disclosure. No ransom, no drama.