generated client sends the API key as a query param - the docs said header-only and the key leaked into logs
A playbook for fixing API-key-in-query-param leaks: move the key to the Authorization header, rotate the exposed key, scrub the logs that captured it, and add a check that blocks query-param secrets in generated code. Use when a generated client sends the API key as a query parameter against docs that say header-only and the key ends up in logs. Not for OAuth token leaks, webhook secret mismatches, or keys committed to git.
TL;DR
Move the API key out of the query string and into the Authorization header, then rotate the key because the old one is already in your logs. Query params get logged everywhere: access logs, proxies, APM tools, error trackers. The fix is three parts: header-only transport, key rotation, and a lint rule so generated code never puts secrets in query params again.
The query
generated client sends the API key as a query param - the docs said header-only and the key leaked into logsUse this when
- The generated client builds URLs like
/v1/thingswith the key appended as a query parameter - The provider's docs say to send the key in a header
- You can find the key value in log aggregators or request logs
- The agent read a docs example that showed the key in the query string
Not for
- OAuth access tokens leaking (different flow, different fix)
- Webhook signing secrets failing verification
- API keys committed to a git repo (rotate plus history rewrite, different playbook)
- Keys leaking through error messages or stack traces
Steps
1. Confirm where the key is exposed
Search your logs for the key's prefix or a known fragment: the access log, the APM request traces, the error tracker. Write down every system that captured the full URL.
Expected output: a list of log systems that contain the key in query strings.
2. Rewrite the client to send the key as a header
Change the generated client to put the key in the Authorization header (or the provider's named header) and never in the URL. Strip any code that appends the key to query params.
headers = {"Authorization": f"Bearer {api_key}"} # provider-specific scheme
resp = requests.get("https://YOUR-provider-domain/v1/things", headers=headers)Expected output: requests succeed with no key material in the URL.
3. Rotate the key
The old key is compromised the moment it hit a log. Generate a new key in the provider's dashboard, deploy it, then revoke the old one. Do this even if the logs are "internal."
Expected output: old key revoked, new key live, zero downtime if you deploy-then-revoke.
4. Scrub or expire the exposed logs
For each system in step 1's list: delete the affected log entries, redact the key, or confirm the retention window has passed. At minimum, verify the old key no longer works so any retained copy is useless.
Expected output: no working key retrievable from any log system.
5. Add a guard to the scaffolder
Give the agent a rule: generated code must never interpolate a secret into a URL or query string. Add a post-generation check that greps the output for key-like values in URL-building code and fails the generation if found.
Expected output: future generated clients fail loudly at generation time instead of leaking at runtime.
Variant phrasings
api key leaked into logs from generated client
Steps 1-4 are the incident response; step 5 prevents the repeat. Do not skip rotation.
generated client logged the full request on error
Same family. The error-logging code captured the URL with the key in it; fix the transport (step 2) and the logging (log headers redacted, never full URLs).
docs example showed the key as a query param
Docs examples are often wrong about this. The provider's auth reference section is the authority; the quickstart example is not.
Why it happens
Query-param auth is the easiest thing to write in generated code: string-concatenate the key onto the URL and it works. The agent optimizes for "the request succeeds," and it does. The leak is invisible at generation time because nothing in the request fails. The damage shows up later in the log aggregator, where full URLs are stored, indexed, and searchable by everyone with log access.
Edge cases
- Providers that genuinely require query-param keys: rare, but they exist. If the provider's auth reference mandates it, scope the key tightly, shorten its lifetime, and make sure request logging strips query strings.
- The key is also in browser history or shared curl commands: rotation (step 3) covers this, since you cannot scrub someone's laptop.
- Webhook URLs with embedded secrets: same rule applies. Move the secret to a header; if the provider does not support it, rotate frequently and log-strip.
- The agent cached the old generated code: check for stale copies of the client (step 5's guard should also run against the existing codebase, not just new generations).
Provenance
Resolved from the public thread: https://vectle.com/posts/pst_WZz4mGkY0hqsM0ekHoKZBA
Maintainer review
No maintainer verification is recorded for this version.
This records the version a maintainer checked. It does not assert that the version is the latest upstream release.