Keeping credentials out of source, and what to do once one is in: rotate before removing, because deleting the line does not un-leak it. Covers where a secret may live, safe .env templates, why a client-reachable config value is public, and running a scanner rather than eyeballing for patterns. Use when writing code that reads a credential, creating config or .env templates, before committing, or after discovering a secret in a repository.
Installs into .claude/skills of the current project.
Are you the author of Secret Detection?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/shieldnet-360-secret-detection-f91d1d00)
---
id: secret-detection
version: "2.0.0"
title: "Secret Detection"
description: "Keeping credentials out of source, and what to do once one is in: rotate before removing, because deleting the line does not un-leak it. Covers where a secret may live, safe .env templates, why a client-reachable config value is public, and running a scanner rather than eyeballing for patterns. Use when writing code that reads a credential, creating config or .env templates, before committing, or after discovering a secret in a repository."
category: prevention
severity: critical
applies_to:
- "when generating code that reads or passes a credential"
- "when creating .env, config templates, or CI secret wiring"
- "before committing, and when reviewing a diff that touches credentials"
- "after finding a credential in source, history, or a log"
languages: ["*"]
token_budget:
minimal: 1150
compact: 1700
full: 2050
rules_path: "checklists/"
tests_path: "tests/"
related_skills: ["logging-security", "cicd-security", "container-security", "supply-chain-security"]
last_updated: "2026-08-14"
sources:
- "OWASP Secrets Management Cheat Sheet"
- "CWE-798: Use of Hard-coded Credentials"
- "CWE-259: Use of Hard-coded Password"
- "NIST SP 800-57 Part 1 Rev. 5: Key Management"
external_tools:
- name: gitleaks
purpose: "scan the working tree"
command: "gitleaks dir ."
- name: gitleaks
purpose: "scan git history — a different question, and the one that matters after a leak"
command: "gitleaks git ."
---
# Secret Detection
## Rules (for AI agents)
### ALWAYS
- On finding a committed credential, **rotate or revoke it at the provider first**.
Deleting the line does not remediate it, and neither does `git rm`, an amended
commit, or a history rewrite: the value reached a remote, a clone, a fork, a CI log
and a backup, and it is compromised from the moment it was pushed. Rotate, then
clean the history, then add the guard that stops it recurring — in that order,
because only the first one is time-critical.
- **Run a scanner**; do not scan by eye. `scan_secrets` and `check_secret_pattern`
carry the full pattern set with per-pattern entropy floors and hotword proximity,
and a person reading a diff matches none of that reliably. A working-tree scan and a
history scan answer different questions — the first says what you are about to
commit, the second says what is already public.
- Read the credential from the environment or a secret manager at the point of use —
`os.environ`, `process.env`, `os.Getenv`, a Vault / AWS Secrets Manager / GCP Secret
Manager / 1Password client. Nothing about the value should be reconstructible from
the repository.
- Ship a **`.env.example`** that is committed and contains every key with an empty or
obviously-fake value, alongside a `.env` that is ignored and never committed. The
example file is documentation, so it must never be produced by copying a working
`.env` and editing it — that is how a real value survives into the template.
- Ignore the file classes that carry key material, in one list:
`.env`, `.env.*` (but not `.env.example`), `*.pem`, `*.key`, `*.p12`, `*.pfx`,
`*.ppk`, `id_rsa*`, `id_dsa*`, `id_ecdsa*`, `id_ed25519*`, `*credentials*`,
`*secret*`. A `.gitignore` entry is a guard against habit, not a control — it does
nothing for a file already tracked, and `git add -f` overrides it.
- Treat every **client-reachable config value as public**: a shipped bundle, a
`/config` endpoint, Firebase Remote Config, a feature-flag payload, a mobile app's
build-time constants. A client may hold public identifiers and nothing else.
- Prefer credentials that expire and are scoped. A short-lived, narrowly-scoped token
bounds what a leak costs, and it is the only control that still helps after every
other one has failed — which, given the rule above about rotation, is the case worth
designing for.
### NEVER
- Hardcode an API key, token, password, connection string, or private key in source,
and do not hide one in a default parameter, a test constant, a Dockerfile `ARG`, or
a committed CI variable.
- Put a real secret in a **test fixture**. Use the documented placeholders —
`AKIAIOSFODNN7EXAMPLE`, `wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY`,
`xoxb-EXAMPLE-EXAMPLE` — which every scanner already excludes by name.
- Distribute a bearer secret to clients at **runtime** through a config or
feature-flag service. Anyone holding the app's public configuration can fetch it, so
it is exactly as exposed as a hardcoded one. Proxy the privileged call through an
authenticated backend instead.
- Write a credential into a place designed to be read later: a log line, an error
payload, a commit message, a PR or issue body, a CI job name, a screenshot in a
ticket. `logging-security` owns the log sink; the others have no redactor at all.
- Treat a **pre-commit hook** as the control. `--no-verify` skips it, `git clone` does
not carry it, and it never runs on the server or on a force-push. It is a
convenience that catches accidents; the blocking CI check is the control, and
`cicd-security` owns that gate.
### KNOWN FALSE POSITIVES
- The documented AWS example pair, `AKIAIOSFODNN7EXAMPLE` and
`wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY`.
- A value whose text is itself a placeholder — `your-key-here`, `REPLACE_ME`,
`CHANGEME`, `xxx`, `dummy` — or a credential-shaped string with a documentation
marker in the surrounding prose.
- High-entropy strings that are not credentials: git commit SHAs in a changelog, CSS
colour literals, base64-encoded test fixtures, a UUID used as a correlation id, an
integrity `sha384-` hash in a `<script>` tag.
- **Publishable** keys that are designed to be in a client: a Stripe `pk_live_`, a
Firebase web `apiKey` in a `firebaseConfig` block, a public Sentry DSN. These are
identifiers, not credentials — the question is whether the backend authorizes
anything on them.
- A **test-mode** key is *not* in this list. `sk_test_` authenticates against a real
test API, reads real test data, and is a credential whose name merely contains the
word "test".
## Context (for humans)
The rule ordering is the whole skill. Everything else here is standard advice; the one
thing worth being emphatic about is that removal is not remediation, because it is the
step people reach for first and the one that does nothing. A secret that reached a
remote is public, and every minute spent rewriting history before rotating is a minute
the credential still works.
The second thing is the split between detection and prevention. Prevention is
deterministic and happens while the code is written: the value is read from the
environment, so there is nothing to detect. Detection is probabilistic — regex,
entropy, proximity — and it has both false positives and false negatives, which means
a clean scan is evidence and not proof. Skills that treat a green scanner as the goal
end up tuning the scanner.
Which literal strings count as credentials is data, not prose: it lives in
`checklists/secret_detection.yaml` with per-pattern entropy floors, hotwords and
exclusions, and it is what `scan_secrets` reads. That is why the rules above tell the
agent to run the scanner rather than reproducing a prefix list here — an inline list
is a copy that goes stale the first time the file gains a pattern.
## References
- `references/verifying-findings.md` — confirm or refute a finding, then rotate,
clean and lock it
- `checklists/secret_detection.yaml` — the pattern set the scanner reads: regexes,
prefixes, hotwords, entropy floors, and the `exclusions:` block
- `tests/corpus.json` — fixtures for validation
- [OWASP Secrets Management Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Secrets_Management_Cheat_Sheet.html)
- [CWE-798](https://cwe.mitre.org/data/definitions/798.html) — Use of Hard-coded Credentials.
- [CWE-259](https://cwe.mitre.org/data/definitions/259.html) — Use of Hard-coded Password.