IDs can ship. Keys cannot. Know which is which
curl -s https:/ / YOURSITE/ _next/ static/ chunks/ *. js | grep -oE "eyJ[A-Za-z0-9_-]{30, }\. [A-Za-z0-9_-]{30, }" | head -3# paste one into jwt. io; "role": "service_role" means the browser owns the database
Get every Fix AI Slop Code episode
The code it wrote, the code it should have written and a check, for every episode
What is going on
A GA4 measurement ID, a Meta Pixel ID, a Supabase publishable key are designed to sit in the browser. A Resend key, a service-role key, a bot token are not. The model does not distinguish; both are "strings the code needs." The Supabase publishable key is only safe because row-level security is supposed to be doing the work behind it.
Where it bit
The good case: blinkz-site. GA and Pixel IDs hardcoded on purpose; the Resend key only ever via wrangler secret put; zero key hits in 50 commits. The bad case in the research: a service-role JWT in the bundle, found by fetching the JS and pasting the token into jwt.io.
The practice
Keep a two-column list per project: ships to the browser, never leaves the server. Legacy Supabase anon and service_role keys are deprecated at the end of 2026; move to sb_publishable_ in clients and one sb_secret_ per backend service.
Get this check as a script you can run tonight

The coding agent never holds production credentials
An agent with a production database URL will sooner or later run a migration or a cleanup against it, so production secrets never enter its env
Keys go in headers, never in URLs
A URL is written to server logs, CDN logs, browser history and error trackers, so an API key in a query string is a key in five places
Backups the app cannot reach, and one restore you have actually done
A backup on the same account the app or an agent can delete from is not a backup, and a restore you have never run is a number you do not have
If this check came back with more than you expected, that is worth a conversation