Security No. 284

Secrets Management skill for Claude Code

Rotate first, rewrite history second. When a key leaks, Claude revokes and rotates it before anything else, checks provider audit logs for misuse, and only then offers a confirmed history purge with git filter-repo. For prevention it wires gitleaks into pre-commit and CI, moves secrets into the right store for each environment, and fixes the quiet leaks in Docker layers, client bundles, and logs.

  • namesecrets-management
  • size186 lines · ≈2,150 tokens
  • files1
  • licenseMIT
  • updated

What Claude sees before loading it

This description is the only part of the skill that sits in context all the time. Claude reads it to decide when to load the full playbook below.

description: Keeps secrets out of code and handles them safely across their lifecycle — gitleaks scanning in pre-commit and CI, .env hygiene, vaults and cloud secret managers, SOPS for encrypted config, rotation, and a step-by-step response when a key leaks. Use when an API key or password appears in code or git history, when setting up secret scanning, choosing where secrets live, rotating credentials, or reviewing env and config handling.

Try asking

  • I accidentally pushed our AWS keys to GitHub — what do I do right now?
  • Set up gitleaks in pre-commit and GitHub Actions for this repo
  • Where should our Stripe and database secrets live across dev, CI, and Kubernetes?

Use it when

  • A credential appears in code, git history, CI logs, or an image
  • Setting up secret scanning for a repo or organization
  • Deciding between env files, vaults, SOPS, and cloud secret managers
  • Planning credential rotation without downtime

Skip it when

  • You need least-privilege permissions for the credentials themselves — use aws-iam instead
  • The question is choosing ciphers or key derivation — use cryptography-basics instead

How to check the result

gitleaks reports no findings across history and the working tree, the leaked credential is rejected by the provider, and a test commit containing a fake key is blocked by the hook.

The common mistake it prevents

Spending the first hour rewriting git history while the leaked key stays active, even though scrapers harvest public keys within minutes.

~/.claude/skills/secrets-management/SKILL.md

--- name: secrets-management description: Keeps secrets out of code and handles them safely across their lifecycle — gitleaks scanning in pre-commit and CI, .env hygiene, vaults and… license: MIT ---

Done well means: no secret ever lands in git, logs, images, or client bundles; every secret has one source of truth, an owner, and a rotation path; and a leaked secret is revoked within minutes, not after a history rewrite debate.

Hard rules

  • A secret that reached a remote (pushed commit, CI log, Slack, image layer) is compromised. Revoke and rotate first. Rewriting history is cleanup, never the fix.
  • Never print, echo, or paste secret values into chat, logs, or commit messages while working. Refer to them by name and location.
  • Never commit .env; commit .env.example with placeholder values only.
  • Anything bundled for the browser or a mobile app is public. NEXT_PUBLIC_*, VITE_*, EXPO_PUBLIC_* variables must never hold secrets.
  • History rewrites and force pushes require explicit user confirmation and coordination with everyone who has a clone.

Workflow: leaked secret response

- [ ] 1. Identify the secret type, scope, and where it leaked
- [ ] 2. Revoke or rotate it at the provider (now)
- [ ] 3. Deploy the new secret to all consumers; confirm the old one fails
- [ ] 4. Check provider audit logs for use between leak and revocation
- [ ] 5. Remove from code; move to a secret store
- [ ] 6. Optionally purge history (with confirmation)
- [ ] 7. Add scanning so it cannot recur; write a short incident note

Step details:

  1. Identify. What is it (AWS access key, Stripe live key, DB password, signing key)? What can it do? Where is it exposed (public repo, private repo, CI log, image)? Public exposure means assume automated scrapers already have it — they find keys on public GitHub within minutes.

  2. Revoke/rotate. Do it at the provider console or CLI. Examples:

    SecretAction
    AWS access keyaws iam create-access-key --user-name X, update consumers, aws iam update-access-key --access-key-id OLD --status Inactive, then aws iam delete-access-key
    GitHub PATRevoke in Settings > Developer settings; prefer fine-grained tokens or a GitHub App
    StripeRoll the key in Dashboard > Developers > API keys (rolling supports an expiry overlap)
    Database passwordALTER ROLE app WITH PASSWORD '...' after creating a second credential or using dual-user rotation
    JWT/HMAC signing keyAdd new key (new kid), sign with new, keep old for verify until max token lifetime passes, remove
    SSH/TLS private keyGenerate new pair, replace authorized_keys/cert, revoke old cert
  3. Confirm the old credential is rejected (call the API with it and expect 401/403).

  4. Audit. AWS CloudTrail filtered by access key ID, GitHub audit log, Stripe logs, DB connection logs. Escalate to incident-response if there is unexplained use.

  5. Remove from code and load from the environment or a secret manager (below).

  6. Purge history only after rotation, and only with confirmation:

    # Requires git-filter-repo (pip install git-filter-repo). Work on a fresh mirror clone.
    git clone --mirror [email protected]:org/repo.git && cd repo.git
    printf 'AKIAOLDKEYVALUE==>REMOVED\n' > ../replacements.txt
    git filter-repo --replace-text ../replacements.txt
    # Or drop a whole file from all history:
    git filter-repo --invert-paths --path config/prod.env
    # DESTRUCTIVE: confirm with the user before pushing rewritten history
    git push --force --mirror

    Then: ask collaborators to re-clone, and on GitHub contact support to purge cached views of the old commits; forks and existing clones still contain the secret.

  7. Prevent (next section) and record: what leaked, when, exposure window, actions.

Prevention: scanning

Default tool: gitleaks (v8). Run it three places.

# Whole history of the current repo
gitleaks git -v --redact
# Working directory or any folder (no git needed)
gitleaks dir . -v --redact
# JSON report for CI artifacts
gitleaks git --redact --report-format json --report-path gitleaks.json

Older docs use gitleaks detect and gitleaks protect; they still work in v8 but git and dir are the current subcommands.

Pre-commit (with the pre-commit framework):

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/gitleaks/gitleaks
    rev: v8.24.2   # pin to the latest release tag you have verified
    hooks:
      - id: gitleaks

CI (GitHub Actions) — fail the build on findings:

- uses: actions/checkout@v4
  with: { fetch-depth: 0 }
- uses: gitleaks/gitleaks-action@v2
  env:
    GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Also enable the platform features: GitHub secret scanning and push protection (free for public repos; included in GitHub Secret Protection for private), GitLab secret detection. For verifying whether found credentials are live, trufflehog git file://. --only-verified checks candidates against provider APIs — run only on repos you own.

False positives: add a .gitleaksignore with the finding fingerprint, or a # gitleaks:allow comment on the line. Never broaden the allowlist to whole directories of real config.

Where secrets live

SituationUse
Local development.env (gitignored) loaded by the framework or dotenv; or op run --env-file=.env.tpl -- npm run dev with 1Password references
CIThe CI secret store (GitHub Actions secrets/environments with required reviewers); prefer OIDC federation to cloud over long-lived keys
AWS workloadsIAM roles for compute (no keys at all); AWS Secrets Manager or SSM Parameter Store SecureString for third-party secrets
GCP / AzureWorkload identity; Secret Manager / Key Vault
KubernetesExternal Secrets Operator syncing from the cloud store; or Sealed Secrets / SOPS for GitOps. Plain Secret objects are base64, not encryption — enable etcd encryption at rest
Config committed to gitSOPS with age or a cloud KMS key
Many services, dynamic credsHashiCorp Vault / OpenBao with dynamic DB credentials and short TTLs

SOPS with age:

age-keygen -o ~/.config/sops/age/keys.txt        # prints the public key
sops --encrypt --age age1yourpublickey... secrets.yaml > secrets.enc.yaml
sops --decrypt secrets.enc.yaml                    # needs the private key

Put creation rules in .sops.yaml so teammates do not pass keys by hand.

Application handling

  • Read secrets once at startup into config, validate presence, and fail fast with the variable name (never value) if missing.
  • Redact in logs: configure logger redaction (pino redact paths, structlog processors) for authorization, cookie, password, token, secret, api_key.
  • Do not pass secrets as CLI arguments (visible in ps and shell history); use env or files with 0600 permissions.
  • Docker: never ARG/ENV a secret — it persists in image layers. Use BuildKit secrets: RUN --mount=type=secret,id=npmrc,target=/root/.npmrc npm ci with docker build --secret id=npmrc,src=$HOME/.npmrc ..
  • Prefer short-lived credentials (OIDC, STS, dynamic DB users) over rotation schedules.

Audit an existing repo

gitleaks git -v --redact                                  # history
gitleaks dir . -v --redact                                # untracked files too
git ls-files | grep -iE '(^|/)\.env($|\.)|\.pem$|id_rsa|\.p12$|\.pfx$|credentials'
grep -rnE "(NEXT_PUBLIC|VITE|EXPO_PUBLIC)_[A-Z_]*(SECRET|KEY|TOKEN)" --include=*.{ts,tsx,js,env*} .
docker history --no-trunc <image> | grep -iE "secret|token|password|key="

Report each finding as: type, location (file:line or commit), exposure (public/private/ image), live or not, action taken.

Gotchas

  • Deleting the file in a new commit leaves the secret in history and in every clone.
  • .gitignore does not untrack files already committed; use git rm --cached.
  • CI logs: masked secrets leak when transformed (base64, URL-encoded, split). Never set -x in steps that handle secrets.
  • Rotating a key used by many services without a dual-key window causes an outage; support two valid keys during the cutover.
  • Terraform state contains secrets in plaintext; store it in an encrypted, access- controlled backend.

Verification

  • gitleaks git and gitleaks dir . report no findings (or only documented, fingerprinted false positives).
  • The leaked credential returns 401/403 when used; the new one works in every consumer.
  • Pre-commit and CI scanning are in place; a test commit with a fake AWS-format key is blocked.

About this skill

Written and maintained by the Claude Skills editors to the same editorial standard as every skill in the directory: a selective trigger, a real workflow, known pitfalls and a verification step. First published , last updated . MIT licensed.

esc