Secrets Audit — Credential Exposure and Secrets-Management Review
Two halves: (1) find secrets that have already leaked into source, history, or artifacts, and (2) audit the secrets-management posture that determines whether future leaks happen.
Most secret leaks aren't "we forgot to redact" — they're "we never had a system, so every developer made up their own approach." This skill covers both the cleanup and the prevention.
Cross-references: dependency-audit (CI-related secrets risk in build-time exposure), iam-audit (workload identity federation as the alternative to long-lived keys), owasp-audit A02 (in-source secret patterns).
Part 1 — Find leaked secrets
Provider key prefixes (high-confidence patterns)
The most useful first sweep is grep against known provider key prefixes. False positives are low and matches are almost always real.
# Stripe
grep -rE "(sk_live_|sk_test_|rk_live_|whsec_)[A-Za-z0-9]{20,}" . \
--include="*.{js,ts,jsx,tsx,py,rb,go,java,php,sh,env,yml,yaml,json}"
# AWS access keys
grep -rE "(AKIA|ASIA)[A-Z0-9]{16}" .
# AWS secret keys (40 chars, base64-y) — high FP rate, use with caution
grep -rE "[A-Za-z0-9/+=]{40}" . --include="*.env*" --include="*.json"
# GitHub
grep -rE "gh[pousr]_[A-Za-z0-9]{36}" .
# Google Cloud API key + service-account JSON
grep -rE "AIza[A-Za-z0-9_-]{35}" .
grep -rln '"type": "service_account"' . --include="*.json"
# Slack
grep -rE "xox[baprs]-[A-Za-z0-9-]+" .
# OpenAI / Anthropic
grep -rE "sk-[A-Za-z0-9]{32,}" .
grep -rE "sk-ant-[A-Za-z0-9_-]{90,}" .
# Generic high-entropy strings in env files
grep -rE "^[A-Z_]+=[A-Za-z0-9/+=]{32,}$" . --include="*.env*"
For full repo coverage, use git ls-files to scope to tracked files and avoid node_modules:
git ls-files | xargs grep -lE 'sk_live_|ghp_|AKIA[A-Z0-9]{16}|sk-ant-|AIza[A-Za-z0-9_-]{35}' 2>/dev/null
Tooling
| Tool |
Use |
gitleaks detect |
Fast, low FP, run as pre-commit and in CI; supports custom rules |
trufflehog git file://. |
Verifies findings against the real API (high confidence) |
detect-secrets scan |
Yelp's tool; good baseline file workflow |
| GitHub Secret Scanning |
Free for public repos; covers most providers automatically; pushes get blocked at push time when enabled with push protection |
| GitLab Secret Detection |
Similar, built-in to CI |
| GitGuardian / Doppler / Spectral |
Commercial; add organizational dashboards and historical analysis |
Git history (the part people forget)
A secret deleted in the latest commit is still in history — git log -p, git log -S<secret>, and any fork or local clone all have it.
# Search every commit for a pattern
git log -p -S "sk_live_" --all
# Search only deleted lines
git log -p --all | grep -E "^-.*sk_live_"
# Trufflehog historical scan
trufflehog git file://. --since-commit=<first-commit>
# Git history rewrite — destructive, coordinate first
git filter-repo --invert-paths --path config/secrets.yml
# or
bfg --delete-files secrets.yml
Critical caveat: rewriting history requires every developer to re-clone, every fork is still exposed, and the secret should be considered compromised regardless. Always rotate first, history-rewrite second.
Build artifacts and other forgotten places
Secrets leak in places that aren't .env files:
- Docker images —
docker history <image> shows every ENV line; --build-arg SECRET=... ends up in layers
- CI environment — secrets logged by
set -x, console.log(process.env), error stack traces, debug output
- Frontend bundles —
NEXT_PUBLIC_* / VITE_* / REACT_APP_* env vars are shipped to the browser; grep the bundled JS
- Crash reports — Sentry / Datadog / Bugsnag capturing
process.env snapshots
- Logs — application logs shipped to a SIEM that has weaker access controls than the app
- Backups —
pg_dump of a table that includes user-stored API keys
- Public S3 / blob storage —
.env accidentally uploaded
- Documentation — README.md examples with real keys instead of placeholders
- Slack / Notion / Linear — pasted in a DM "to test," never rotated
- Browser localStorage / cookies — captured in shared screenshots or session replays
Triaging a found secret
When you find a leaked secret:
- Verify it's live — use the provider's verification (
aws sts get-caller-identity, stripe balance retrieve, curl -H "Authorization: Bearer $TOKEN" ...) — don't assume; some leaked keys are already revoked or were sandbox-only
- Determine exposure window — first commit it appeared in, when the repo went public, when CI logs were retained from
- Determine blast radius — what does this key access? What can be done with it? IAM permissions, Stripe live vs test, GitHub
repo vs admin:org
- Rotate immediately — generate a new key, deploy it, then revoke the old one (revoke-first breaks prod)
- Audit for use — provider audit logs (CloudTrail, GitHub audit log, Stripe events) for any activity from the leaked credential
- Then clean — remove from current code, then optionally history-rewrite (low priority once rotated)
- Document — incident report, even if rotation was clean; recurrence patterns surface trends
Part 2 — Audit secrets-management posture
The hierarchy of secret storage (worst → best)
| Tier |
Pattern |
When acceptable |
| ❌ |
Hardcoded in source |
Never |
| ❌ |
Hardcoded in image / build artifact |
Never |
| ❌ |
Plaintext in shared docs / Slack |
Never |
| ⚠️ |
.env file in repo (even with .gitignore — easy to leak via push, backup, archive) |
Bootstrap only; flagged in audit |
| ⚠️ |
Environment variables (only) |
Acceptable for ephemeral dev; weak for prod (visible in /proc, crash dumps, logs) |
| 🟢 |
Secrets manager pulled at deploy time |
Standard for most apps |
| 🟢 |
Workload identity federation (no stored secret at all) |
Best where supported |
Cloud-provider secrets managers
- AWS Secrets Manager / Parameter Store (SecureString) — integrate via IAM-scoped IRSA / task role / Lambda role
- GCP Secret Manager — bind via Workload Identity to GSA, GSA pulls secret
- Azure Key Vault — pull via managed identity
- Doppler / Infisical / 1Password Secrets Automation — cross-cloud, developer-friendly
Audit checklist
- No secrets in Git history (run gitleaks --all)
- No
.env committed — .gitignore covers .env* (with care for .env.example)
- Secrets fetched at runtime, not embedded at build — image rebuild is not required to rotate
- IAM scoped to the secret — service A can read secret A, not secret B
- Rotation cadence — defined per secret class (admin: 30d, service: 90d, customer-shared: per breach response)
- Rotation is automated — if a human runs a script every 90 days, rotation will eventually drift
- Access logged — every Get / Decrypt call is in an audit trail
- No long-lived cloud keys for workloads — workload identity federation everywhere it's supported (see
iam-audit)
- Break-glass procedure — when the secrets manager is down, how do critical services come up? (Usually: cached on disk encrypted, with strict re-fetch on restart)
- Cross-environment isolation — staging cannot read prod secrets, ever (different KMS keys, different IAM)
Common findings
- Rotation never tested — secret stores configured, never actually rotated; first attempt breaks prod
.env.local shipped to staging — environment-specific dev secrets cross the boundary
- CI secrets accessible from PRs from forks — GitHub's default behavior was previously dangerous; verify
pull_request_target and secret accessibility
- Build args used for secrets —
--build-arg AWS_SECRET=... ends up in image history (use --secret/BuildKit instead)
- Logging frameworks dump
process.env on unhandled exception — Sentry / Datadog / Bugsnag scrub config required
- Secret stored in K8s as plain Secret without etcd encryption — base64 is encoding, not encryption (see
container-audit)
- OAuth client secrets in mobile apps — public clients can't hold secrets; PKCE is the answer
Output Format
# Secrets Audit Report
## Scope: [repos / environments / managers covered]
## Date: [date]
### Live leaked secrets found
| Provider | Location | First seen (commit / date) | Verified live? | Rotation status |
|---|---|---|---|---|
### Secrets-management posture
| Category | Status | Notes |
|---|---|---|
### Recommendations
| Priority | Item | Owner | Deadline |
|---|---|---|---|
Disposition rule (Fixed / Deferred / Accepted Risk) per owasp-audit.
Boundaries
- Only audit repositories, CI systems, and infrastructure the user has authorization for
- Never use a found secret to access the provider — verify it's live with a minimal API call (account info, not data extraction); do not pivot
- For history rewrite operations: never proceed without explicit confirmation and a coordinated developer-notification plan
- Refuse to help collect or weaponize leaked secrets found in other people's repos
- If the audit surfaces credentials belonging to a third party (vendor, employee personal accounts), notify and rotate; don't quietly fix
References
- OWASP Cheat Sheet: Secrets Management
- NIST SP 800-57 (Recommendation for Key Management)
- GitGuardian "State of Secrets Sprawl" annual reports — useful for industry context
gitleaks, trufflehog, detect-secrets documentation
- GitHub Secret Scanning + Push Protection documentation
- HashiCorp Vault Architecture / Best Practices
- AWS Secrets Manager Best Practices
- "Twelve-Factor App" — Config principles
1---2name: secrets-audit3description: Find leaked secrets in source code, Git history, build artifacts, and infrastructure — and audit the secrets-management posture preventing future leaks. Use when the user mentions 'secrets audit,' 'secret scanning,' 'leaked credentials,' 'API key in code,' 'gitleaks,' 'trufflehog,' 'git history scan,' 'secrets management,' 'vault audit,' 'rotation policy,' 'AWS Secrets Manager,' 'HashiCorp Vault,' 'Doppler,' '1Password Secrets Automation,' 'sealed-secrets,' 'External Secrets Operator,' or needs to find or prevent credential exposure.4---5
6# Secrets Audit — Credential Exposure and Secrets-Management Review
7
8Two halves: (1) find secrets that have already leaked into source, history, or artifacts, and (2) audit the secrets-management posture that determines whether future leaks happen.
9
10Most secret leaks aren't "we forgot to redact" — they're "we never had a system, so every developer made up their own approach." This skill covers both the cleanup and the prevention.
11
12Cross-references: `dependency-audit` (CI-related secrets risk in build-time exposure), `iam-audit` (workload identity federation as the alternative to long-lived keys), `owasp-audit` A02 (in-source secret patterns).
13
14## Part 1 — Find leaked secrets
15
16### Provider key prefixes (high-confidence patterns)
17
18The most useful first sweep is grep against known provider key prefixes. False positives are low and matches are almost always real.
19
20```bash
21# Stripe
22grep -rE "(sk_live_|sk_test_|rk_live_|whsec_)[A-Za-z0-9]{20,}" . \
23 --include="*.{js,ts,jsx,tsx,py,rb,go,java,php,sh,env,yml,yaml,json}"
24
25# AWS access keys
26grep -rE "(AKIA|ASIA)[A-Z0-9]{16}" .
27
28# AWS secret keys (40 chars, base64-y) — high FP rate, use with caution
29grep -rE "[A-Za-z0-9/+=]{40}" . --include="*.env*" --include="*.json"
30
31# GitHub
32grep -rE "gh[pousr]_[A-Za-z0-9]{36}" .
33
34# Google Cloud API key + service-account JSON
35grep -rE "AIza[A-Za-z0-9_-]{35}" .
36grep -rln '"type": "service_account"' . --include="*.json"
37
38# Slack
39grep -rE "xox[baprs]-[A-Za-z0-9-]+" .
40
41# OpenAI / Anthropic
42grep -rE "sk-[A-Za-z0-9]{32,}" .
43grep -rE "sk-ant-[A-Za-z0-9_-]{90,}" .
44
45# Generic high-entropy strings in env files
46grep -rE "^[A-Z_]+=[A-Za-z0-9/+=]{32,}$" . --include="*.env*"
47```
48
49For full repo coverage, use `git ls-files` to scope to tracked files and avoid `node_modules`:
50
51```bash
52git ls-files | xargs grep -lE 'sk_live_|ghp_|AKIA[A-Z0-9]{16}|sk-ant-|AIza[A-Za-z0-9_-]{35}' 2>/dev/null
53```
54
55### Tooling
56
57| Tool | Use |
58|---|---|
59| `gitleaks detect` | Fast, low FP, run as pre-commit and in CI; supports custom rules |
60| `trufflehog git file://.` | Verifies findings against the real API (high confidence) |
61| `detect-secrets scan` | Yelp's tool; good baseline file workflow |
62| GitHub Secret Scanning | Free for public repos; covers most providers automatically; pushes get blocked at push time when enabled with push protection |
63| GitLab Secret Detection | Similar, built-in to CI |
64| GitGuardian / Doppler / Spectral | Commercial; add organizational dashboards and historical analysis |
65
66### Git history (the part people forget)
67
68A secret deleted in the latest commit is still in history — `git log -p`, `git log -S<secret>`, and any fork or local clone all have it.
69
70```bash
71# Search every commit for a pattern
72git log -p -S "sk_live_" --all
73
74# Search only deleted lines
75git log -p --all | grep -E "^-.*sk_live_"
76
77# Trufflehog historical scan
78trufflehog git file://. --since-commit=<first-commit>
79
80# Git history rewrite — destructive, coordinate first
81git filter-repo --invert-paths --path config/secrets.yml
82# or
83bfg --delete-files secrets.yml
84```
85
86**Critical caveat:** rewriting history requires every developer to re-clone, every fork is still exposed, and the secret should be considered compromised regardless. Always rotate first, history-rewrite second.
87
88### Build artifacts and other forgotten places
89
90Secrets leak in places that aren't `.env` files:
91
92- **Docker images** — `docker history <image>` shows every `ENV` line; `--build-arg SECRET=...` ends up in layers
93- **CI environment** — secrets logged by `set -x`, `console.log(process.env)`, error stack traces, debug output
94- **Frontend bundles** — `NEXT_PUBLIC_*` / `VITE_*` / `REACT_APP_*` env vars are shipped to the browser; grep the bundled JS
95- **Crash reports** — Sentry / Datadog / Bugsnag capturing `process.env` snapshots
96- **Logs** — application logs shipped to a SIEM that has weaker access controls than the app
97- **Backups** — `pg_dump` of a table that includes user-stored API keys
98- **Public S3 / blob storage** — `.env` accidentally uploaded
99- **Documentation** — README.md examples with real keys instead of placeholders
100- **Slack / Notion / Linear** — pasted in a DM "to test," never rotated
101- **Browser localStorage / cookies** — captured in shared screenshots or session replays
102
103### Triaging a found secret
104
105When you find a leaked secret:
106
1071. **Verify it's live** — use the provider's verification (`aws sts get-caller-identity`, `stripe balance retrieve`, `curl -H "Authorization: Bearer $TOKEN" ...`) — don't assume; some leaked keys are already revoked or were sandbox-only
1082. **Determine exposure window** — first commit it appeared in, when the repo went public, when CI logs were retained from
1093. **Determine blast radius** — what does this key access? What can be done with it? IAM permissions, Stripe live vs test, GitHub `repo` vs `admin:org`
1104. **Rotate immediately** — generate a new key, deploy it, then revoke the old one (revoke-first breaks prod)
1115. **Audit for use** — provider audit logs (CloudTrail, GitHub audit log, Stripe events) for any activity from the leaked credential
1126. **Then clean** — remove from current code, then optionally history-rewrite (low priority once rotated)
1137. **Document** — incident report, even if rotation was clean; recurrence patterns surface trends
114
115## Part 2 — Audit secrets-management posture
116
117### The hierarchy of secret storage (worst → best)
118
119| Tier | Pattern | When acceptable |
120|---|---|---|
121| ❌ | Hardcoded in source | Never |
122| ❌ | Hardcoded in image / build artifact | Never |
123| ❌ | Plaintext in shared docs / Slack | Never |
124| ⚠️ | `.env` file in repo (even with .gitignore — easy to leak via push, backup, archive) | Bootstrap only; flagged in audit |
125| ⚠️ | Environment variables (only) | Acceptable for ephemeral dev; weak for prod (visible in /proc, crash dumps, logs) |
126| 🟢 | Secrets manager pulled at deploy time | Standard for most apps |
127| 🟢 | Workload identity federation (no stored secret at all) | Best where supported |
128
129### Cloud-provider secrets managers
130
131- **AWS Secrets Manager / Parameter Store (SecureString)** — integrate via IAM-scoped IRSA / task role / Lambda role
132- **GCP Secret Manager** — bind via Workload Identity to GSA, GSA pulls secret
133- **Azure Key Vault** — pull via managed identity
134- **Doppler / Infisical / 1Password Secrets Automation** — cross-cloud, developer-friendly
135
136### Audit checklist
137
138- **No secrets in Git history** (run gitleaks --all)
139- **No `.env` committed** — `.gitignore` covers `.env*` (with care for `.env.example`)
140- **Secrets fetched at runtime, not embedded at build** — image rebuild is not required to rotate
141- **IAM scoped to the secret** — service A can read secret A, not secret B
142- **Rotation cadence** — defined per secret class (admin: 30d, service: 90d, customer-shared: per breach response)
143- **Rotation is automated** — if a human runs a script every 90 days, rotation will eventually drift
144- **Access logged** — every Get / Decrypt call is in an audit trail
145- **No long-lived cloud keys for workloads** — workload identity federation everywhere it's supported (see `iam-audit`)
146- **Break-glass procedure** — when the secrets manager is down, how do critical services come up? (Usually: cached on disk encrypted, with strict re-fetch on restart)
147- **Cross-environment isolation** — staging cannot read prod secrets, ever (different KMS keys, different IAM)
148
149### Common findings
150
151- **Rotation never tested** — secret stores configured, never actually rotated; first attempt breaks prod
152- **`.env.local` shipped to staging** — environment-specific dev secrets cross the boundary
153- **CI secrets accessible from PRs from forks** — GitHub's default behavior was previously dangerous; verify `pull_request_target` and secret accessibility
154- **Build args used for secrets** — `--build-arg AWS_SECRET=...` ends up in image history (use `--secret`/BuildKit instead)
155- **Logging frameworks dump `process.env`** on unhandled exception — Sentry / Datadog / Bugsnag scrub config required
156- **Secret stored in K8s as plain Secret** without etcd encryption — base64 is encoding, not encryption (see `container-audit`)
157- **OAuth client secrets in mobile apps** — public clients can't hold secrets; PKCE is the answer
158
159## Output Format
160
161```markdown
162# Secrets Audit Report
163## Scope: [repos / environments / managers covered]
164## Date: [date]
165
166### Live leaked secrets found
167| Provider | Location | First seen (commit / date) | Verified live? | Rotation status |
168|---|---|---|---|---|
169
170### Secrets-management posture
171| Category | Status | Notes |
172|---|---|---|
173
174### Recommendations
175| Priority | Item | Owner | Deadline |
176|---|---|---|---|
177```
178
179Disposition rule (Fixed / Deferred / Accepted Risk) per `owasp-audit`.
180
181## Boundaries
182
183- Only audit repositories, CI systems, and infrastructure the user has authorization for
184- Never use a found secret to access the provider — verify it's live with a minimal API call (account info, not data extraction); do not pivot
185- For history rewrite operations: never proceed without explicit confirmation and a coordinated developer-notification plan
186- Refuse to help collect or weaponize leaked secrets found in other people's repos
187- If the audit surfaces credentials belonging to a third party (vendor, employee personal accounts), notify and rotate; don't quietly fix
188
189## References
190
191- OWASP Cheat Sheet: Secrets Management
192- NIST SP 800-57 (Recommendation for Key Management)
193- GitGuardian "State of Secrets Sprawl" annual reports — useful for industry context
194- `gitleaks`, `trufflehog`, `detect-secrets` documentation
195- GitHub Secret Scanning + Push Protection documentation
196- HashiCorp Vault Architecture / Best Practices
197- AWS Secrets Manager Best Practices
198- "Twelve-Factor App" — Config principles