Core 1 · Secrets & Keys
Time: ~40 minutes · The single highest-value hour of this course
The idea
Your app has two kinds of configuration values: ones that are fine for the whole world to see (your site URL, a public map style) and ones that are the keys to the building (database credentials, payment secrets, AI provider keys). Every breach story in this genre starts the same way: a building key treated like a business card.
Three rules cover almost everything:
- Secrets live server-side, in environment variables. Never in source files, never in the client bundle, never “temporarily” hardcoded.
- Anything that has ever been committed to git is burned. Deleting the line doesn’t help — git history is forever, and scanners crawl public repos within minutes of a push. Burned keys get rotated, not hidden.
- Each key gets the least power that works. Most providers let you scope keys (read-only, single-service). A leaked read-only key is an incident; a leaked admin key is an obituary.
Why AI assistants get this wrong
When your assistant needs a key to “work,” the shortest path is putting it
where the code can see it — which is often the frontend. The AI optimizes
for runs, not for safe. It will also cheerfully commit a .env file if
.gitignore doesn’t say otherwise.
The checklist
- List every external service your app talks to (database, auth, payments, AI APIs, email). That list is your key inventory.
- For each key: is it referenced anywhere in frontend code or in a client-exposed variable? (Your stack track shows exactly how to check.)
- Search your git history for each key:
git log -S "sk_live" --oneline(repeat for each key’s prefix). - Rotate every key that has ever appeared in code or history, then place the replacement in server-side env vars only.
- Confirm
.env*is in.gitignore— and that no.envwas committed before it got there. - Scope each replacement key to the minimum permissions that work.
The AI audit prompt
Audit this codebase for secret handling. 1) List every API key, token, password, or connection string that appears in source files, config files, or client-side environment variables — with file and line. 2) For each, say whether it would be included in the code shipped to a visitor’s browser, and why. 3) Check .gitignore coverage for env files. 4) Output a table: secret, location, exposed-to-browser yes/no, rotation needed yes/no. Do not print full secret values — first 6 characters only.
The fix prompt
Fix secret handling in this codebase, one secret at a time. For each: move it to a server-side environment variable, update every reference, and delete the hardcoded value. Where a secret was reaching client code, restructure so the work happens server-side rather than shipping the key. Add .env* to .gitignore if missing. Two hard rules: never print a full secret value, and do NOT try to rotate anything yourself — instead give me a list of every key I must rotate by hand in its provider dashboard, because a key that has been committed cannot be un-committed. Show me a diff per file and wait for approval before applying it.
The proof prompt
Prove the secret fixes actually hold. Do not summarise — show me commands and their real output. 1) Build the app, then search the client bundle for the first 6 characters of each secret; I expect no matches, and I want to see the search command you ran. 2) For each secret, show the single server-side file that reads it and confirm nothing client-side imports that file. 3) Run git log -S for each secret’s prefix and show whether it appears in history — if it does, that key still needs rotating regardless of the code being fixed. Report PASS or FAIL per secret with the evidence attached.
Done when
Every secret lives server-side, every once-committed key has been rotated, and you have a written list of which key does what. That list becomes your incident map in Core 8.