Rotate in
this order.
You have found a live credential somewhere it was never meant to be. What you do next decides whether this stays a chore or becomes an incident, and the order matters more than the speed.
It usually arrives the same way. You have the network tab open on your own site, or you are reading a build log, or a scanning service emails you — and there it is, a key that still works, sitting somewhere anyone can read it. The instinct is to hit rotate immediately and make the feeling stop. That instinct is right about the urgency and wrong about the order. Rotation is the middle of the procedure, not the start — and depending on the provider, pressing it may not stop the old key at all.
What creating a key actually does.
Creating a new key adds a value alongside the leaked one. Only revoking the key itself, or the secret that signs it, takes the leaked value's ability to work away — a separate step from issuing the replacement. Supabase's API-keys documentation is explicit that the legacy anon and service_role keys are JSON Web Tokens signed by one shared project secret: the leaked key works until that secret is revoked, which also invalidates every other token signed with it, including your users' live sessions.
Stripe's key-rotation documentation builds the same gap into its own rotate button: choose a grace period and the old key can stay live for up to seven days while the new one takes over. Only setting the expiration to Now kills a credential that is already public.
Five steps, in this order.
Name the kill switch
Confirm which family this credential is — a value the provider looks up, which dies the moment its record is deleted, or a signed token, which dies only when its signing secret is revoked, taking every sibling token with it. Name the exact action that ends the old value, and what else it reaches.
Scope what it can reach
Work out what the credential can do — read-only, or does it bypass access controls the way an unrestricted secret key can — and date the exposure window from the commit or deploy that exposed it, rather than when you noticed. Both facts set how far the audit in step five reaches.
Move every consumer first
List everywhere this value is read — production, every preview environment, CI, background workers, a teammate's laptop, the project cloned from this one — and decide whether both keys run during the move or you cut hard. Deploy the replacement to every entry and confirm traffic has moved before step four.
Kill the old value, on purpose
Invalidate the old credential as its own explicit action — revoke it, or set its expiry to now rather than accept whatever grace period the dashboard defaults to. Then call the provider's API with the dead value from a machine holding none of your environment; a rejection is the only evidence that counts.
Audit the window, then sweep for copies
Read the provider's request logs, auth events, and billing across the exposure window from step two. Then search git history, forked repositories, CDN caches, and old screenshots for the value itself — GitHub's own guidance on removing sensitive data notes that a rewritten history does not reach a fork or an existing clone. Treat every copy as still live.
This applies to credentials on infrastructure you own, or infrastructure you've been asked in writing to check. When OOPUO runs this kind of check for a client, it happens against surfaces named in advance, with the disclosure path agreed first — and with your permission, AI-assisted review runs against those approved surfaces only.
One rotated key rarely explains how it got somewhere public in the first place, and that question is what infrastructure hardening looks at — secrets, environment separation, and every other value with the same blast radius. Send what you rotated and what else worries you.