Keys go in headers, never in URLs
A URL is not a private channel. It is written to the server log, the CDN log, the browser history, the error tracker's breadcrumbs, and anything that stores "the request" for debugging. A header is not. The provider's own header exists for exactly this reason.
Where it bit
The lead-gen auditor called Gemini with the key as ?key= because that is the shortest way to make the call work, and the model wrote it that way. Failed calls logged the full URL into the local database. 54 rows held the live key. The same key was shared with a second tool, so scrubbing the rows did not un-leak anything. Rotated same day.
The practice
Key in the x-goog-api-key or Authorization: Bearer header. A scrub_secrets() pass on anything that gets logged. A regression test that fails if a key pattern ever appears in a URL again. Tests went from 27 to 44.
Check it
git log -p -S "?key=" --all | grep -n "key=" | head
grep -rnE "(api_?key|token)=[A-Za-z0-9_-]{16,}" logs/ *.db 2>/dev/null | headGet this check as a script you can run tonight
What to do with this
If you run a business on something AI built and the checks came back with more than you expected, that is worth a conversation.
We do a free 30-minute Health Check for service businesses that want to know exactly where their biggest leaks are. No slide deck. No pitch. We ask questions, find the gaps, and tell you what we see. If there is no obvious fix, we will tell you that too.
Blinkz finds what is broken in how a business runs, then fixes it. AI only where it earns its place.