Rotate a leaked secret properly: revoke, rotate, check usage logs
What to do in the first hour after a GitHub token, AWS access key, or Stripe API key leaks: contain it, move consumers to a new credential, read the logs back to the leak date, and stop the next one.
- Admin rights on the system that issued the secret (GitHub org owner, IAM admin, Stripe admin)
- AWS CLI v2 configured with a separate admin identity, not the leaked key
- A list of where the secret is deployed (CI variables, secret stores, config)
- The date the secret was first exposed, from the commit or log that leaked it
Rotating a leaked secret is four steps in a fixed order: contain it (revoke or deactivate the credential now, accepting some breakage), rotate consumers onto a new credential, investigate every use of the old one back to the date it leaked, and prevent the next leak with push protection and short-lived credentials. Deleting the commit, rewriting history, or making the repo private is not remediation. In the LiteLLM incident, an admin key was deleted from a public repo about three hours after it was committed but never revoked, and an attacker used it months later to reach a GitHub org owner's token and publish a poisoned release.
Before you start: triage
| Secret | Contain | Logs to read |
|---|---|---|
| GitHub PAT or OAuth token | Delete it, or POST /credentials/revoke |
Enterprise audit log by hashed_token |
| GitHub App private key | New key, then delete the old one | Audit log events by the app |
| AWS access key | update-access-key --status Inactive plus deny-all |
CloudTrail by AccessKeyId |
| Stripe secret or restricted key | Rotate key, Expiration Now | Workbench request logs |
Assume the secret was used. The secrets in repositories and CI and static API key abuse technique pages explain why attackers find these within minutes.
1. Contain: kill the credential
GitHub personal access token you own. Go to Settings > Developer settings > Personal access tokens, pick Fine-grained tokens or Tokens (classic), and click Delete (managing tokens). Org owners can revoke members' fine-grained tokens under Organization settings > Personal access tokens > Active tokens (reviewing tokens); classic tokens are not listed there.
GitHub token you found but do not own. Use the credential revocation API. It must be called unauthenticated (authenticated calls return 403), takes up to 1,000 tokens per request, is limited to 60 requests per hour, and notifies the owner. It covers ghp_, github_pat_, gho_, ghu_, and ghr_ tokens.
curl -s -X POST https://api.github.com/credentials/revoke \
-H "Accept: application/vnd.github+json" \
-d '{"credentials":["ghp_EXAMPLE0000000000000000000000000000"]}'
# expect HTTP 202
GitHub App private key. Under Settings > Developer settings > GitHub Apps > Edit, generate a new key, deploy it, then delete the leaked key (managing private keys). Keys never expire, an app can hold up to 25, and you must add a new key before deleting the only one. Installation tokens minted with the old key expire after 1 hour.
AWS access key. Deactivate it, then attach AWSDenyAll to the user. Deactivation alone is not enough: temporary credentials are valid until they expire (up to 36 hours), but their permissions are evaluated on every request, so removing the user's permissions stops them.
aws iam update-access-key --user-name ci-deployer \
--access-key-id AKIAIOSFODNN7EXAMPLE --status Inactive
aws iam attach-user-policy --user-name ci-deployer \
--policy-arn arn:aws:iam::aws:policy/AWSDenyAll
If AWS detected the leak first, it may have attached AWSCompromisedKeyQuarantineV3 to the user and opened a support case. AWS says not to remove that policy; follow the case instructions.
Stripe key. On the API keys page, open the key's menu, choose Rotate key, and set Expiration to Now. The up-to-7-day grace period is for planned rotation, not a leak.
Check: a call with the old credential is rejected by GitHub, AWS, or Stripe (Stripe returns an authentication error for an expired key).
Common failure: production breaks because the leaked credential was also the live one. That is the expected cost; move straight to step 2.
Rollback: only for AWS: update-access-key --status Active restores the key. Do it only if you have proof the key never left your control.
2. Rotate consumers
Issue the replacement on a fresh identity where possible, because a deny-all user cannot be reused. For AWS, the long-term fix is a role assumed through OIDC (GitHub Actions OIDC to AWS and Azure); if you must keep a key:
aws iam create-user --user-name ci-deployer-2
aws iam create-access-key --user-name ci-deployer-2
# attach the same least-privilege policy, update the secret store, redeploy
aws iam delete-access-key --user-name ci-deployer --access-key-id AKIAIOSFODNN7EXAMPLE
Update every location from your inventory: CI variables, AWS Secrets Manager or another vault, and runtime environment variables. For GitHub, prefer a GitHub App or a fine-grained token scoped to the repos it needs, with an expiration.
Check: each consumer runs green on the new credential, and aws iam list-access-keys --user-name ci-deployer returns no keys.
Common failure: a forgotten consumer (a cron job, a laptop profile) keeps failing; its errors are your inventory of places you missed.
Rollback: redeploy the previous config only with a different, non-leaked credential.
3. Investigate back to the leak date
AWS. Find the last use, then pull every management event signed by the key. lookup-events covers 90 days, one Region per call, at two requests per second.
aws iam get-access-key-last-used --access-key-id AKIAIOSFODNN7EXAMPLE
for r in $(aws ec2 describe-regions --query 'Regions[].RegionName' --output text); do
aws cloudtrail lookup-events --region "$r" --start-time 2026-05-27T00:00:00Z \
--lookup-attributes AttributeKey=AccessKeyId,AttributeValue=AKIAIOSFODNN7EXAMPLE \
--query 'Events[].[EventTime,EventName,EventSource]' --output text
done
Look for CreateAccessKey, CreateUser, AttachUserPolicy, CreateRole, and GetSessionToken. Temporary credentials have their own access key ID, so search again for each one GetSessionToken returned. lookup-events returns management events only; S3 object reads and other data events are visible only if a trail or event data store was capturing them.
GitHub. Hash the token and search the enterprise audit log (token events):
echo -n "$TOKEN" | openssl dgst -sha256 -binary | base64
# search: hashed_token:"<value>"
Git events are not returned by search; export the audit log for clone and push activity.
Stripe. Open the key's menu and select View request logs to see its calls in Workbench.
Check: you can explain every event between the leak date and containment. Common failure: you start from the detection date and miss months of use. Rollback: not applicable. Anything unexplained becomes an incident.
4. Prevent the next one
Turn on secret scanning with validity checks and push protection, which blocks pushes containing known secret formats. Dedicated scanners such as GitGuardian can run alongside it. Replace static keys with federation (workload identity federation) and automate secrets rotation for what remains (API key rotation guide).
Check: the repository's secret scanning settings show push protection enabled, and new alerts reach an owner who can revoke. Common failure: developers bypass push protection with a reason code and nobody reviews bypasses. Rollback: not applicable.
Verify it works
aws sts get-caller-identityrun with the old key fails.curl -H "Authorization: Bearer $OLD" https://api.github.com/useris rejected instead of returning a user.- A Stripe call with the old key returns an authentication error; the new key succeeds.
- The CloudTrail and GitHub audit log queries from step 3 show no events after containment.
- The secret scanning alert's validity check shows the secret as
inactive, and the alert is closed with the investigation linked.
Related
Frequently asked questions
- Is deleting a leaked secret from a Git commit enough?
- No. Anyone who cloned, forked, or scraped the repository already has it, and the credential keeps working until you revoke it. LiteLLM's October 2026 incident started with an admin key that was deleted from a public repo within hours but never revoked, then used months later.
- How do I revoke a leaked GitHub token I do not own?
- Send it to GitHub's credential revocation endpoint, POST /credentials/revoke, without authentication. It accepts up to 1,000 tokens per request for classic and fine-grained personal access tokens, OAuth app tokens, and GitHub App user tokens, and GitHub notifies the owner.
- What should I do first when an AWS access key leaks?
- Set the key to Inactive with aws iam update-access-key, then attach a deny-all policy to the IAM user, because temporary credentials minted from the key with GetSessionToken stay valid until they expire. Then check CloudTrail for every call the key made.
- How do I rotate a Stripe API key immediately?
- On the Dashboard API keys page, open the key's menu, choose Rotate key, and set Expiration to Now. The old key stops working at once, so deploy the new key right away; a later expiration gives up to 7 days of overlap, which is only appropriate when the key was not exposed.
- How far back should I check logs after a secret leak?
- To the first moment the secret was exposed, not the moment you noticed. CloudTrail event history lookups cover the last 90 days per Region, so older exposures need a trail or CloudTrail Lake.
Related on Start with Identity
- BlogGheist turns stolen GitHub tokens into secret dumps through Actions logs
Wiz reported on October 25 that the Gheist campaign used stolen GitHub tokens from infostealer logs and past supply-chain leaks to plant more than 13,500 ci-syn
- Blog1.6 million RingCentral records leaked, and the entry point was one phone call
ShinyHunters voice-phished a RingCentral employee out of their password in July, took 623GB, and dumped 280GB after the company refused to pay. Have I Been Pwne
- Blog94 percent of enterprises say they can revoke access in 24 hours. 35 percent hit delays or failures doing it.
FIDO Alliance and HID surveyed 500 IT and security decision-makers for The State of Physical and Digital Identity in the Enterprise. The headline gap: near-univ
- CVEAuth0 node-jws HS256 verification bypass via secret lookup
node-jws before 3.2.3 / 4.0.1 can accept an HS256 JWT when the caller looks up the secret from attacker-controlled input. Medium on paper, but it is a signature
- CVECheck Point Remote Access VPN lets attackers in without a password over IKEv1
A certificate validation logic flaw in Check Point Remote Access and Mobile Access over deprecated IKEv1 let attackers open a VPN session without a valid passwo
- CVECheck Point Security Gateway information disclosure of password hashes
An unauthenticated read on Check Point Security Gateways leaked password hashes, including those used for VPN and local admin. CISA KEV. May 2024. Hash disclosu