Find, rotate, and prevent leaked credentials across repositories and disk. Covers leak detection with gitleaks and trufflehog, rotation order (the leaked secret first, then outward), git history purge with git-filter-repo, and prevention via pre-commit scanning. Invoke when a secret was committed to git, when a private repo went public, or as periodic audit.
Install to Claude Code
npx -y skills add GoldenWing-360/claude-security-skills --skill secret-hygiene --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Secret Hygiene?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/goldenwing-360-secret-hygiene)More formats (shields.io, HTML) on the badges page.
---
name: secret-hygiene
description: Find, rotate, and prevent leaked credentials across repositories and disk. Covers leak detection with gitleaks and trufflehog, rotation order (the leaked secret first, then outward), git history purge with git-filter-repo, and prevention via pre-commit scanning. Invoke when a secret was committed to git, when a private repo went public, or as periodic audit.
---
# Secret Hygiene
A practical workflow for credential management: detecting leaks, rotating cleanly, and preventing recurrence.
## When to invoke
- A secret was committed to git, or a private repo went public
- A contributor leaves the project, or access scope changes
- A credential turned up in a public dump, paste, or leak feed
- Periodic audit (quarterly is reasonable)
- Onboarding a repo, hosting account, or VPS you inherited
## Step 1 — Inventory what you have
You cannot rotate secrets you cannot enumerate. Build a list, even a rough one.
```
- Hosting panel: <provider> creds in: 1Password vault X
- DB user: <name> creds in: server .env, 1Password
- API keys:
- Stripe live + test creds in: backend .env, Stripe dashboard
- Mailgun creds in: backend .env
- GitHub PAT creds in: ~/.gitconfig (BAD — move to keychain)
- SSH keys: ~/.ssh/id_* on machines: workstation, CI, VPS
- CI secrets: GitHub Actions visible at: repo/settings/secrets
```
Keep this in a password manager or a private repo — never in plaintext on disk.
## Step 2 — Scan for leaks (local + repos)
### Local filesystem
```bash
# Files commonly containing secrets
find ~/Code -maxdepth 4 -type f \( -name '.env*' -o -name 'credentials*' -o -name 'secrets*' \) -not -path '*/node_modules/*' 2>/dev/null
# Permissions check — .env should be 600
find ~/Code -name '.env*' -not -path '*/node_modules/*' -exec stat -f '%Lp %N' {} \; 2>/dev/null
# Did you commit a .env by accident?
cd ~/Code/<repo>
git log --all --full-history -- '.env*' 2>/dev/null
```
### Git history
```bash
# Quick grep across all history for high-signal patterns
git log -p --all -S 'AKIA' # AWS access key
git log -p --all -S 'sk-' # OpenAI / many SaaS
git log -p --all -S 'ghp_' # GitHub PAT
git log -p --all -S 'xoxb-' # Slack bot token
git log -p --all -S 'BEGIN RSA PRIVATE KEY'
git log -p --all -S 'BEGIN OPENSSH PRIVATE KEY'
# Better: dedicated scanners
gitleaks detect --source . --report-format json --report-path gitleaks.json
trufflehog git file://. --json
```
### Hosted (GitHub)
GitHub runs secret scanning for public repos automatically. For private repos with paid tiers, enable it. Push protection blocks future leaks at `git push` time — turn it on.
## Step 3 — Rotation order
When a secret leaks, rotate **outward from the access it grants**. Common order:
1. **The leaked secret itself** — invalidate first.
2. **Anything that secret could have been used to read** (e.g. a leaked DB password → audit recent DB queries, then rotate DB user).
3. **Anything that secret could have been used to write** (e.g. a leaked API key with write scope → check for unauthorized writes, then rotate).
4. **Adjacent secrets stored in the same place** — if a `.env` leaked, assume *every* value in that `.env` leaked.
5. **Inbound credentials** that depend on the rotated outbound credential (downstream services, webhooks).
For each rotation, log: **what, when, by whom, who was notified**.
## Step 4 — Purging git history (last resort)
Rewriting history is disruptive — every collaborator must re-clone, and tags/CI references may break. Do it only if a high-value secret is in history and rotating is genuinely insufficient (rare; usually rotation is enough).
### Modern tool: `git-filter-repo`
```bash
# Install once: brew install git-filter-repo
# Backup first
git clone --mirror git@github.com:owner/repo.git repo-backup.git
# Remove a specific file from all history
cd repo-clone
git filter-repo --invert-paths --path .env
git filter-repo --invert-paths --path config/secrets.yml
# Or replace a literal string everywhere
echo 'AKIAIOSFODNN7EXAMPLE==>REDACTED' > replace.txt
git filter-repo --replace-text replace.txt
# Force-push (coordinate with the team — everyone re-clones after this)
git push --force --all
git push --force --tags
```
After force-push:
- Every collaborator must `git clone` fresh; rebasing existing forks is fragile
- Any CI/CD that pinned a commit SHA may break
- GitHub may still serve the old commit by SHA for a while — open a support ticket if cleanup is critical
- **Rotate the secret anyway** — assume it was already harvested before the purge
## Step 5 — Prevention
### Pre-commit scanning
Install once per workstation:
```bash
# Option A: gitleaks pre-commit hook
brew install gitleaks
gitleaks protect --staged --no-banner
```
Or via `pre-commit` framework — example `.pre-commit-config.yaml`:
```yaml
repos:
- repo: https://github.com/gitleaks/gitleaks
rev: v8.18.0
hooks:
- id: gitleaks
```
### `.gitignore` baseline
```
# Never commit these
.env
.env.*
!.env.example
*.pem
*.key
*.p12
id_rsa
id_ed25519
credentials.json
config/secrets.yml
```
### `.env.example` convention
Commit a placeholder file with the **keys but not the values**:
```
# .env.example
DATABASE_URL=postgresql://user:password@localhost:5432/dbname
STRIPE_SECRET_KEY=sk_test_REPLACE_ME
GITHUB_TOKEN=ghp_REPLACE_ME
```
### Server-side secrets
- `.env` permissions: `chmod 600 .env`, owned by the app user
- Never bake secrets into Docker images (use runtime env, mounted files, or a secret store)
- For CI: use the platform's secret store (GitHub Actions secrets, GitLab CI variables) — never echo them into build logs
### Token scope minimization
- GitHub PATs: prefer fine-grained tokens, scoped to a single repo/org with the minimum scopes
- Database users: separate read-only and read-write users for separate processes
- Cloud IAM: avoid root keys; use scoped service accounts with explicit, minimal IAM
## Quick reference — common token prefixes
Useful for grep-based scans:
| Prefix | Provider |
|---|---|
| `AKIA`, `ASIA` | AWS access key |
| `ghp_`, `gho_`, `ghu_`, `ghs_`, `ghr_` | GitHub tokens |
| `xoxb-`, `xoxp-`, `xoxa-` | Slack tokens |
| `sk_live_`, `sk_test_` | Stripe |
| `key-` (Mailgun), `re_` (Resend) | Mail providers |
| `sk-`, `sk-proj-` | OpenAI / Anthropic-style |
| `glpat-` | GitLab PAT |
| `dckr_pat_` | Docker Hub PAT |
| `npm_` | npm tokens |
| `-----BEGIN OPENSSH PRIVATE KEY-----` | SSH private key |
## What this skill will not do
- Help retrieve or use credentials that are not yours.
- Scan repositories you do not have authorization to scan.
- Recommend storing secrets in source control "just for now" — there is no "just for now" with secrets.
Scanned 5/27/2026
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!