Found an API key in your bundle? The first 60 minutes, in order.
Rotate first, audit usage second, fix source third, verify deploy fourth. Step-by-step runbook — free scan to start.
Panic skips steps; this order is complete. Sixty minutes, four phases, one outcome: exposure closed and evidence captured without breaking production.
What actually happened
A finding is confirmed — yours or reported. Minutes matter less than sequence now: each phase enables the next, and skipping ahead creates outages that obscure incidents.
- Phase check: confirm classification (secret vs public-by-design) before touching anything.
- Capture provider-side evidence windows while they are fresh — usage graphs, event logs, audit trails.
- Only then proceed to rotation mechanics below.
Every step above is individually reasonable and none of them prints a warning. The value crosses into the bundle during substitution, not execution, so nothing in your runtime ever sees the moment it happened.
What someone can do with it
Why rotation outranks investigation:
- Every minute unrotated is attacker-usable time.
- Investigation never blocks revocation; revocation never blocks forensics — logs persist post-revocation.
Rotate first
Do the rotation first. From the moment this value reached a public URL, treat it as public knowledge: browser caches, shared proxies and automated scrapers all hold copies you cannot recall. Provider-by-provider paths: Supabase Settings→API · OpenAI platform/api-keys · Stripe apikeys (create→deploy→revoke) · AWS IAM create-migrate-suspend · GitHub tokens page. Code changes come after, because a clean repository with a compromised key is still compromised.
A rotated key left in old deploys is still discoverable in CDN caches and archived copies. Rotation plus redeploy closes both halves; either alone leaves the door ajar.
Move the call somewhere the browser cannot read
The structural fix is always the same shape: the call moves to a context that holds the key without serving it, and the browser asks your server instead.
// found:
const t = "xoxb-0000-FAKE000000000000000";
// fixed + monitored:
const t = process.env.SLACK_BOT_TOKEN;
Check whether yours is exposed
You can check manually right now: open the site, view source or open DevTools, and search the built JavaScript for eyJ. A hit means the string shipped; decode or prefix-check it before deciding how bad the news is.
The faster path is to let a machine do the fetching. KeyDrift downloads the same JavaScript a visitor gets — HTML, every referenced chunk including ones named only in the route manifest, and the server-streamed data frameworks inline into the document — and reports credentials with a masked prefix, a fingerprint, and the exact file they live in. Paste your deployed URL into the scanner; no account needed.
Keep it from coming back
It bears saying because it happens constantly: the fix holds until the next prompt that needs the query to return rows. Drift monitoring exists for precisely this — it diffs consecutive scans and pages you when a previously resolved finding reappears, naming the regression as a regression rather than repeating the first alert.
Minutes 10–30: audit what it did
- OpenAI/Anthropic: usage per endpoint, unknown IP bursts.
- Stripe: events export around discovery window; refund/charge anomalies.
- AWS: CloudTrail unusual-region/resource-create events.
- GitHub: security log new OAuth grants/clones.
- Supabase: auth logs for foreign sessions.
Minutes 30–45: find how it shipped
Your report names the chunk; map chunk→source file, then git log -S the literal shape. Usual suspects: prefix rename during a build error, agent-added client call, pasted demo snippet.
Minutes 45–60: close the loop
- Structural fix deployed (server route/proxy).
- Re-scan clean — full fetch, not cache.
- Monitoring enabled: regression alerts armed for the sequel attempt.
- Written summary filed while memory is fresh.
If you cannot rotate yet
Interim mitigations ranked honestly: spend caps first (limits damage), IP restrictions where providers offer them, scoped replacement keys shadowing the old ones — then rotate the moment consumers allow. None substitute rotation; all buy time safely.
Not legal advice
Disclosure questions (customers, regulators) depend on data touched and jurisdiction — the considerations page frames those conversations for counsel rather than answering them here.
Why this keeps happening industry-wide
It helps to name the economics honestly. Fixing this class of leak costs minutes when caught at deploy time and days when caught at invoice time, because by then the credential has been harvested, validated, resold or drained — often all four. Detection latency is the entire game, which is why the monitoring half of KeyDrift exists alongside the scanning half.
How KeyDrift reports this exact finding
When KeyDrift finds this on your deployment, the report shows a masked value (first 8 and last 4 characters only), a salted fingerprint for tracking, the exact chunk filename carrying it, and a severity with written rationale. Public-by-design neighbours — anon keys, publishable keys, Firebase web constants — appear as informational context rather than noise, because knowing what should be there is what makes the real findings credible.
Manual check, step by step
A five-minute version you can run anywhere: view-source on the landing page, copy every src= script URL, fetch each and search the results for eyJ. It misses manifest-only chunks and streamed payloads — which is precisely the gap between "I checked" and "it is clean" — but it catches the loud majority and builds the pattern-recognition that makes scanner output legible.
Close the loop with monitoring
Monitoring closes the loop that one-time verification leaves open. A scheduled scan refetches everything, diffs against history, and fires only on transitions: created, regressed, resolved. Regression alerts matter most here — they fire when a previously fixed finding returns, which in agent-era codebases is less a possibility than a schedule.
Common questions
Key was revoked months ago — why care now?
Process signal: whatever shipped it may ship again; monitoring turns that pattern visible.
Delete git history too?
Rotation makes history inert; rewriting adds coordination cost. Sequence explained on the history page.
Run a free scan at keydrift.dev/scan — paste a URL or the bundle source itself, no account. Findings arrive masked, with the exact chunk they live in.