I leaked an API key: the 15-minute response playbook
Short answer: treat the key as stolen the moment it was public. Do these in order: (1) replace it at the provider and revoke the old one, (2) check usage and spend for abuse, (3) change it in every place it lives, (4) only then clean Git history. Deleting the commit is the last step, not the first, because it does not make the key safe.
This is for a key, token or password that was pushed to a repository, pasted into a chat or issue, printed in a log, or left in a file that shipped. Set a 15-minute timer; steps 1 and 3 are the ones that stop damage.
Why speed matters
Attackers scan for keys automatically. In Clutch Security’s 2024 experiment, AWS access keys planted on GitHub and Docker Hub were found and exploited “within a few minutes”; keys on PyPI, Pastebin and Postman were used within hours, and keys on several other sites within one to five days (Help Net Security summary). And old leaks stay dangerous: GitGuardian re-tested secrets it had confirmed valid in 2022 and found more than 64% of them still valid in January 2026 (State of Secrets Sprawl 2026).
Minute 0 to 5: replace and revoke
The order depends on whether you see abuse.
- Abuse visible (unexpected usage, a spend spike, provider alert): revoke the old key now and accept an outage while you ship the new one.
- No sign of abuse and the key is in production: create the new key first, ship it (step 3), then revoke the old one. Total exposure is a few minutes either way; this avoids a self-inflicted outage.
Where the key lives decides the details. Two providers whose guidance we checked:
- GitHub tokens and any secret in a repository. GitHub’s own docs say: “if the sensitive data you need to remove is a secret (e.g. password/token/credential), as is often the case, then as a first step you need to revoke and/or rotate that secret. Once the secret is revoked or rotated, it can no longer be used for access, and that may be sufficient to solve your problem” (GitHub Docs).
- AWS access keys. AWS’s IAM documentation recommends monitoring access key usage with CloudTrail to detect unauthorized access, and notes a user can hold at most two access keys, which is what lets you create the replacement before deleting the old one (IAM docs). Never keep root access keys; AWS says not to create them.
For other providers, find “API keys” in the dashboard, create a new one, and delete (not just disable) the old one. If the provider has per-key spend limits, set one on the new key now.
Minute 5 to 10: check for abuse
- Look at the provider’s usage and billing views for the old key’s window of exposure. Add a spend alert.
- On AWS, search CloudTrail for the key id; look for new IAM users, roles, access keys, instances in regions you don’t use, and changes to billing or contact details.
- If the key could send email or messages, check what was sent. If it could read data, assume it was read and decide whether that triggers a notification obligation for you.
Minute 5 to 15: change it everywhere it lives
This is the step that takes a week when you do it by hand, because the same key is usually in more places than you remember: several services, a CI variable, a preview environment, a teammate’s .env, a shared group, a variable under a different name.
With Dynv this is one previewed operation. We ran it on a local server with three services: api and worker each holding OPENAI_API_KEY, and billing holding the same value as LLM_KEY. The new key comes from the provider via standard input, never as an argument:
$ printf %s "$NEW" | dynv rotate OPENAI_API_KEY --stdin --dry-run
Rotate OPENAI_API_KEY (one new value everywhere), value from stdin
places (2):
api
worker
same value, other name: billing LLM_KEY (add with --also billing:LLM_KEY)
2 services change; 2 restart: api, worker
2 writes, plan 3f0df16fd047
Note the line for billing: Dynv noticed the same current value under another name and suggested it as an alias, which it will not change unless you say so. That is the copy that gets missed. Apply with the alias included:
$ printf %s "$NEW" | dynv rotate OPENAI_API_KEY --also billing:LLM_KEY --stdin --yes
Rotate OPENAI_API_KEY (one new value everywhere), value from stdin
places (3):
api
worker
billing LLM_KEY
3 services change; 3 restart: api, billing, worker
3 writes, plan 5b0708502ed9
job IZgYy3e7otm9suFv: 3 writes done; OPENAI_API_KEY rotated
(No deployment was running in this test, so there was nothing to report propagation for. With live deployments the CLI waits up to 30 seconds and prints “propagated to N/M live deployments”, marking each as applied, pending, refused or offline; see the rotation docs.) Values are encrypted on your machine, and Dynv’s servers never see the key.
What Dynv does and doesn’t do here:
- It finds the copies inside Dynv: own copies, shared groups, and same-value aliases it can see. It cannot find copies in a repository, a teammate’s laptop, a CI provider or a chat log. Search those yourself (below).
- It does not rotate the key at the provider. You create the new key there, then give it to
dynv rotate. - Without
--stdinyou can also generate a fresh value for secrets you control, such asdynv rotate AUTH_SECRET --generate base64:32.
Outside Dynv, search for the old value everywhere else: git grep and the whole history for its prefix, CI/CD variable lists, container environment settings, shared password vaults, wiki pages, ticket and chat attachments.
After the timer: clean up Git history (or don’t)
Once the key is revoked, history is no longer a security problem for that key. GitHub’s docs are blunt that rewriting history “may not be warranted,” and list its costs: collaborators must re-clone or they can re-introduce the secret, commit hashes change, and signatures and PR diffs break (GitHub Docs). Do it when the repository holds other data you cannot leave public, not as a substitute for rotation.
If you do, GitHub points to git-filter-repo. After the force push, assume forks, clones and caches still hold the old data.
Make the next one cheaper
- Turn on push protection / secret scanning in your Git host so a pushed token is blocked or flagged.
- Stop keeping keys in files. If the key never sits in the repository or in a
.envan agent can read, there is far less to leak. See Can Claude Code read your .env?. - Scope and cap keys: one key per service and environment, a spend limit at the provider, the narrowest permissions that work.
- Practice the rotation before you need it. A previewed rotation across your services takes minutes; discovering the alias you forgot during an incident does not.
What this playbook does not cover
- Keys that unlock data you can’t un-leak (customer data read through the key): rotation stops further access, not past access.
- Signing keys and encryption keys that other systems verify: rotating them needs a rollover plan so old tokens or ciphertext stay valid for a while. Dynv’s fallback-value support for this is coming soon; today, plan the overlap yourself.
- Provider-specific steps beyond the two documented above; check each provider’s own guidance.
Facts last verified . Spotted an error? Tell us.