No secrets committed in the repository
A secret scan over the full history finds nothing live.
- Severity
- blocker (one caps the grade at D, two at F)
- Tier
- Local: runs on your machine with your own keys
- Category
- Security
- Fix
- Free, in full, below
- Id
- repo-secrets
What this means
Passing means a scan over your repository's full history, not just the current files, finds no live credential. Failing means a secret is in a commit somewhere, and anyone with the repository, now or later, holds it. Deleting the file in a later commit changes nothing: the value is still in history and the scan still finds it.
How we test it
This check runs on your own machine through the CLI: npx securealy scan-repo from the repository root. It scans the working tree, the build output and every commit for value-shaped secrets and for tracked env, key and backup files; nothing leaves your machine and no value is sent to us. Pass is zero findings. Fail is any finding, and the check is a launch blocker; the CLI prints each hit's file path and a redacted prefix, never the value.
Why it matters
A committed secret is public even after the commit is deleted. Clones, forks, CI caches and the hosting provider's build all carry the history, and a scan for a known secret name misses the ones under other names. In the Fix Kit's own audit a .env.local.bak had been committed with the hosting, GitHub, database-management and email tokens in it, because the ignore rule was .env*.local and the backup had a date suffix. A later sweep for credential-shaped assignments found a published password whose variable was named CORRECT_PASSWORD and whose value appeared nowhere anyone had already looked.
The fix
Rotate first, then remove, then make the next one impossible. A found secret is not a dead secret, and a history rewrite is not a fix.
- Identify by value, not by name. The same secret often lives under three variable names across projects:
grep -rn "FIRST8CHARS" ~/projects --include=* -l
Rotate each hit using
templates/rotate-leaked-credential.md(Module 3). A human does the dashboard steps: create the new credential first, set it everywhere it was (printf %s 'VALUE' | vercel env add NAME production, neverecho), deploy and verify with one request, revoke the old one, then prove the old value gets 401 or 403. Supabase legacy JWT keys rotate together and sign every user out; prefer the new-formatsb_secret_keys, which revoke individually. Stripe offers a grace window on roll; use it.Remove the file from tracking and ignore the whole family:
cat >> .gitignore <<'G'
.env
.env.*
*.bak
*.bak-*
G
git ls-files | grep -E '\.env|\.bak' ; echo "---- tracked env or bak files above; expect none"
Say plainly in your notes that the value remains in git history. A rewrite with git filter-repo is optional after rotation and needs every collaborator to re-clone.
- Scan for the shape of a secret, not the secrets you know, across the repo, the build output and history:
bash MODULE-03-lock-it-down/templates/secret-scan.sh ; echo "exit $?"
gitleaks detect --source . --log-opts="--all" --redact ; echo "exit $?"
Expect exit 0 from both and no hits. A placeholder that trips the scanner should be made obviously a placeholder (REPLACE_ME), not a value-shaped string.
- Block the next one before it is committed, and on a schedule. Install the kit's pre-commit hook from Module 3 step 10, which runs
gitleaks protect --staged --redactand refuses the commit on a hit, then copy the scheduled audit workflow:
mkdir -p .github/workflows
cp MODULE-03-lock-it-down/templates/audit.yml .github/workflows/security-audit.yml
Also turn on GitHub secret scanning and push protection: repository Settings, Code security. Free on public repositories; private ones need the paid tier, and the pre-commit hook is the substitute if you skip it.
- Run
npx securealy scan-repoagain. Expect zero findings.
A deeper runbook for this class is in the Fix Kit, Module 03.
Prompt for your AI
Paste this into the coding agent that built the app.
Run `npx securealy scan-repo` from the repository root and show me the output, with every value redacted to its first four characters. For each finding, tell me the file, the commit it first appeared in (git log -S with the first eight characters), the variable name, and which provider the credential belongs to. STOP. I will rotate every credential at the provider dashboards myself; do not touch any provider, do not rewrite history, and never print a full value. After I confirm rotation, remove each file from tracking with git rm --cached, add the .env, .env.*, *.bak and *.bak-* rules to .gitignore, and install the gitleaks pre-commit hook from Module 3 step 10. Commit those changes and confirm the hook refuses a staged file containing a fake key shaped like sk_test_ followed by 24 letters. Run `npx securealy scan-repo` once more and show me the output; expected: zero findings.
Grade your own URL
Every grade lists this check with its evidence for your site. Two free grades a day.