# An AI agent saw my API key. What now?

> An AI agent read or printed your API key. Rotate it at the provider first, check its logs, then mask the transcript and clean git history if needed.

Canonical: https://ranwhat.com/guides/rotate-leaked-api-key-ai-agent
Last updated: 2026-10-09

Rotate it at the provider first. Cleaning the transcript, the logs and git comes second, and none of it brings the key back.

```
$ uvx ranwhat clean
```

[Install](https://ranwhat.com/install)

Checked against the provider documentation and the ranwhat source listed under Sources. Published 9 October 2026.

- **First**: Revoke or replace the key at the provider that issued it. Do this before you delete anything.
- **Then**: Read the provider's log for use you do not recognise, mask the local transcripts, and clean git only if the key was committed.
- **Never**: Treat masking a transcript or rewriting history as the fix. Both leave a working key somewhere.

## Work out where the key went.

An agent that reads a key sends it to the model as part of the conversation, and Claude Code also writes it to disk. Those are two copies, and a third and fourth appear if the key was committed or printed in CI.

- **Transcript**: Every tool result is saved in plaintext at `~/.claude/projects/<project>/<session>.jsonl`, for 30 days by default. Large outputs go to `<session>/tool-results/` beside it.
- **The model provider**: Claude Code sends prompts and tool output to the model. Anthropic's data usage page gives 30 days of retention for Team, Enterprise and API use, with zero data retention for qualified accounts. For Free, Pro and Max it is 30 days, or 5 years if you allow your data to be used for model improvement. A transcript you share with `/feedback` is kept 5 years. Other agents have their own terms; read them.
- **Git**: A key in a file the agent wrote, or that you committed after it did, is in history. A pushed commit is also on the remote and in every clone and fork.
- **Logs**: CI output, shell history, a pasted bug report, a screenshot, a terminal scrollback. An agent that ran `env` or `cat .env` printed the key into whatever captured its output.

To find which transcripts hold it without installing anything, search for a name or the first characters of the value:

```
grep -rl 'STRIPE_SECRET_KEY' ~/.claude/projects
grep -rl 'sk_live_' ~/.claude/projects
git log --all -S'sk_live_' --oneline        commits whose diff adds or removes it
```

This guide is written for Claude Code, whose files it describes. Other agents keep their own history, and the steps from section 02 on are the same. Reading the transcripts by hand is covered in [Claude Code history](https://ranwhat.com/guides/claude-code-history), and how the file got read in the first place in [Blocking .env reads](https://ranwhat.com/guides/claude-code-env-secrets).

### Assume the key is known

You cannot prove who has read a copy you do not control, and a retention window is not a deletion you can check. Treat any value an agent has seen as exposed, and decide what to do about it by what the key can reach, not by how likely a leak seems.

## Rotate before you clean.

A revoked key is worthless to everyone who holds a copy. Every other step in this guide only shrinks the number of copies.

GitHub's own guidance on leaked secrets says the same: revoke or rotate the secret first, and that alone may solve the problem. Cleaning first costs you time you may not have, and the key keeps working while you do it.

### Replace, then revoke, unless it is already abused

Most providers let you create a second key while the first still works, so you can move your applications and switch the old one off without an outage. That is the right order when nothing looks wrong. If the key may already be in someone else's hands, skip the migration: revoke at once, accept the downtime, and put the new key in afterwards. A grace period that keeps the old key alive is also a grace period for whoever else has it.

### Give the new key a safer path

Do not paste the replacement into the same chat, and do not leave it in a file the agent can read. Put it in your platform's secret store or an environment variable that is set outside the agent's session, and scope it as narrowly as the provider allows. The [token scopes guide](https://ranwhat.com/guides/ai-agent-token-scopes) shows how to find out what a given token can do.

## Revoke it at the provider.

The steps below come from each provider's current documentation. Menu names move, so if one has changed, the page under Sources will have the new one.

### GitHub

- **Personal access token**: Profile picture, **Settings**, **Developer settings**, then under Personal access tokens choose **Fine-grained tokens** or **Tokens (classic)**, and click **Delete** beside the token. Deleting a token that created a deploy key deletes that deploy key too. GitHub's page describes no regenerate step: create a new token, then delete the old one.
- **OAuth app you authorised**: Settings, **Applications**, the **Authorized OAuth Apps** tab, the app's three-dot menu, then **Revoke**. **Revoke all** removes every token for it.
- **OAuth app you own**: The REST API revokes one of its tokens with `DELETE /applications/{client_id}/token`, or every token a user granted with `DELETE /applications/{client_id}/grant`. Both authenticate as the app. GitHub's pages do not say where to regenerate a client secret, so this guide does not either.

### AWS access keys

AWS recommends deactivating before deleting, so you can switch the key back on if something breaks. For a leaked key, do the first two now and the third once you have read the logs in section 04.

```
$ aws iam
aws iam update-access-key --access-key-id OLD_KEY_ID --status Inactive --user-name NAME
aws iam create-access-key --user-name NAME      the new key, active at once
aws iam delete-access-key --access-key-id OLD_KEY_ID --user-name NAME
```

In the console: IAM, **Users**, the user, **Security credentials**, then under Access keys choose **Actions** and **Deactivate**. A user holds at most two keys. If the leaked value starts with `ASIA` it is a temporary credential that expires by itself, but its session can still act until it does, so look at what it did.

### Google Cloud API keys

- **Rotate**: Console, the **Credentials** page, the key's name, **Rotate key**. Name the new key, confirm its restrictions, click **Create**, update your applications, then on the new key's page use **Delete the previous key** in the Previous key section. Rotation exists only in the console.
- **Delete**: `gcloud services api-keys delete KEY_ID`, with the ID from `gcloud services api-keys list`. A deleted key can be restored for 30 days, and anyone holding the string can use it again then. Check the list of deleted keys for one you did not delete.

This covers API keys. A service account key file is a different credential with its own steps, which this guide does not cover.

### Slack

- **Bot token**: `auth.revoke` revokes a token and needs no scopes. Pass the token itself, as an `Authorization` header or a POST parameter. For a bot token it deactivates the bot user and removes its channel memberships, but does not uninstall the app. Run the OAuth flow again for a new one.
- **Token rotation**: Slack's app-level tokens do not expire by default, so a leaked one stays valid until you act. Token rotation, available to granular bot apps, makes access tokens short-lived. Slack's page does not say that turning it on revokes a token that has already leaked, so revoke that one yourself.

### Stripe

Dashboard, **API keys**, the key's overflow menu, **Rotate key**. The old key keeps working for up to 7 days unless you set **Expiration** to **Now**, which deletes it at once. For a leaked key choose Now. If you would rather have no replacement yet, **Expire key** stops the key and creates nothing. Use **View request logs** on the same menu before you rotate, so you can see what the key did.

### OpenAI and Anthropic

- **Anthropic**: Claude Console, the **API keys** page from the profile menu, the three-dot menu beside the key, **Delete API Key**. Anthropic's support article recommends revoking a suspected key immediately. It also says GitHub scans public repositories for Claude API keys and that Anthropic deactivates a key GitHub reports, which helps only if the key reached a public repository.
- **Admin API**: An organisation admin can deactivate a key without deleting it by updating its status to `inactive` in the Admin API.
- **OpenAI**: The API keys page of the dashboard, then delete the key. OpenAI's help article describes the delete control on that page as a trash can icon, and says a deleted key cannot be used. Keys tied to a project are listed under that project.

### Anything else

Use the issuing provider's own console and its own documentation. If you are not sure which provider a value belongs to, section 05 shows how `ranwhat clean` labels what it finds.

## Check whether someone else used it.

Revoking stops the next request. It does not tell you about the ones before. Ask the provider for its record of the key, starting at the time the agent read it.

- **AWS**: Last-used data first, then CloudTrail. Event history keeps 90 days of management events per Region, free, and a lookup can filter on an access key ID. It shows management events only, not data events such as S3 object reads, and a search covers one Region at a time, so run it in each Region you use.
- **GitHub**: Settings, **Security log**, in the Archives section. It lists 90 days of activity, and fine-grained token activity falls under `personal_access_token` and OAuth under `oauth_access`. You can export results as JSON or CSV. GitHub's page does not say where to see when a classic token was last used.
- **Stripe**: **View request logs** on the key's menu opens Workbench's request logs for that key. Look for calls you did not make, such as refunds, payouts, new keys or new webhook endpoints.
- **Others**: Usage dashboards for OpenAI and Anthropic show spend and requests per key or project. Look for a spike, or traffic from a model or region you do not use. This guide has not verified an audit-log feature for Google Cloud API keys or for Slack tokens, so check those providers' documentation for one.

```
$ aws
aws iam get-access-key-last-used --access-key-id OLD_KEY_ID
aws cloudtrail lookup-events --region us-east-1 \
  --lookup-attributes AttributeKey=AccessKeyId,AttributeValue=OLD_KEY_ID
```

The last-used call reports the date, service and Region of the most recent request. It is a hint, not a full history, so read the CloudTrail events for the whole picture. If you find use you cannot explain, the problem is bigger than a key: look for what it created, such as new users, keys, roles, instances and webhooks, and remove those too.

## Mask the key in the local transcripts.

With the key dead, remove the plaintext copy so an infostealer, a backup or the next agent does not find it.

[ranwhat clean](https://ranwhat.com/clean) reads the transcripts for secrets and reports each distinct one with the project it was in and, when the transcript names it, the file it came from. A bare run changes nothing, and opens a review session where `rotate` lists what to rotate, grouped by provider.

```
$ uvx ranwhat clean
```

When the list looks right, mask it. `--apply` masks everything it found without asking, and `mask 3` in the review masks one finding. `--days N` widens the window past the default 30 days.

```
uvx ranwhat clean --apply            mask what it found, backups first
uvx ranwhat clean --apply --days 90  look back further
```

- **Backups**: Before it rewrites a transcript, clean copies the original to `~/.ranwhat/backups`, readable by you alone. Those copies still hold the key. Delete the folder once the result looks right.
- **Safe write**: It parses the rewritten file back before replacing the original. It leaves a file alone if something wrote to it in the last two minutes, so close the session first and run it again if one is skipped.
- **Not read**: It does not read the `tool-results/` folders yet, so search them yourself with `grep -rl` and delete or edit the files by hand.

### Masking is not remediation

ranwhat clean does not revoke the key. It does not touch what the model provider kept, your git history, a CI log or a clipboard. It only stops the transcript on this machine from leaking the value a second time. That is useful, and it is the smallest step in this guide.

## Clean git only if it was committed.

If the key never reached a commit, skip this section. If it did, rotation is still what protects you, and a rewrite is housekeeping.

### History rewriting does not un-leak a pushed secret

GitHub's documentation says that if you only rewrite history and force-push, the commits with the sensitive data may still be accessible elsewhere: in clones, in forks, in cached views and through pull requests that reference them. Once a commit has been pushed, assume someone has it.

### Find it

```
git log --all -S'THE_KEY_VALUE' --oneline   commits that add or remove the string
git log --all --oneline -- path/to/.env       commits that touched a file
```

The first command puts the full key in your shell history. Search for a prefix instead if that matters.

### Rewrite it

GitHub documents `git filter-repo`, version 2.47 or later, with its `--sensitive-data-removal` flag. Run it in a fresh clone:

```
git-filter-repo --sensitive-data-removal --invert-paths --path PATH-TO-FILE
git-filter-repo --sensitive-data-removal --replace-text ../passwords.txt
git push --force --mirror origin
```

The first removes a file from all history, using its full path and again for each old location if it was moved. The second replaces the text listed in a file. Pushes to `refs/pull/*` fail, because GitHub treats those refs as read-only.

- **Clones**: You cannot clean your colleagues' clones. Send them instructions to rebase rather than merge, since a merge can bring the old history back.
- **Forks**: Commits stay reachable in forks. You have to ask each owner to remove them or delete the fork.
- **GitHub Support**: Support can dereference or delete affected pull requests, remove cached views and references, and run server-side garbage collection. GitHub says it helps with sensitive data only when the risk cannot be mitigated by rotating the credentials, which is one more reason to rotate first.
- **Cost**: Every commit hash changes, signatures are lost and open pull requests can break. Merge or close them first.

## Make a repeat harmless.

Keep the key out of the agent's reach, catch it before it is pushed, and make it short-lived so a leak expires.

- **Deny rules**: `Read(./.env)` style rules keep the agent's file tools and the common file commands away from a secrets file. They do not stop `grep -r` or a script that opens the file itself. The sandbox closes that gap. [Blocking .env reads](https://ranwhat.com/guides/claude-code-env-secrets) has the rules and the sandbox settings.
- **Secret scanning**: On GitHub, secret scanning runs automatically and for free on public repositories. It scans history on all branches, plus issues, pull requests, discussions and wikis, and raises an alert that says to rotate. Push protection blocks a push that contains a secret before it reaches the repository; the repository-level version needs GitHub Secret Protection.
- **Short-lived credentials**: Give an agent a token that expires, not a key that lives for years. Stripe restricted keys, AWS temporary credentials and Slack token rotation all shrink what a stolen copy is worth. Stripe also lets you attach an access policy that limits a key to known IP addresses, so a copy used from elsewhere is blocked.
- **Narrow scope**: A key that can only read one thing is a smaller incident. [Token scopes](https://ranwhat.com/guides/ai-agent-token-scopes) covers how to give an agent less.
- **Less on disk**: Lower `cleanupPeriodDays` so mistakes age out sooner, and check your machine as a whole with the [developer OPSEC guide](https://ranwhat.com/guides/developer-opsec).

`uvx ranwhat check` runs the secret search and the action log together and changes nothing, so you can run it after each long session. The rest of the checklist is in [AI coding agent security](https://ranwhat.com/guides/ai-coding-agent-security).

- [Blocking .env readsdeny rules, the sandbox, and what to rotate](https://ranwhat.com/guides/claude-code-env-secrets)
- [Claude Code historywhat ~/.claude keeps, and for how long](https://ranwhat.com/guides/claude-code-history)
- [Token scopesgive a key less to lose](https://ranwhat.com/guides/ai-agent-token-scopes)
- [Developer OPSECthe rest of the laptop](https://ranwhat.com/guides/developer-opsec)
- [AI coding agent securitythe full checklist](https://ranwhat.com/guides/ai-coding-agent-security)

## Where each claim comes from.

- [Claude Code data usageretention by plan, zero data retention, /feedback](https://code.claude.com/docs/en/data-usage)
- [Explore the .claude directorywhere transcripts live, 30-day default](https://code.claude.com/docs/en/claude-directory)
- [Anthropic: API key best practicesConsole steps to delete a key, GitHub scanning](https://support.claude.com/en/articles/9767949-api-key-best-practices-keeping-your-keys-safe-and-secure)
- [Anthropic Admin APIsetting a key's status to inactive](https://platform.claude.com/docs/en/manage-claude/admin-api)
- [OpenAI: how can I delete my API keydelete from the API keys page](https://help.openai.com/en/articles/9047852-how-can-i-delete-my-api-key)
- [GitHub: managing personal access tokensdeleting classic and fine-grained tokens](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens)
- [GitHub: reviewing your authorized OAuth appsRevoke, Revoke all](https://docs.github.com/en/apps/oauth-apps/using-oauth-apps/reviewing-your-authorized-oauth-apps)
- [GitHub: REST API for OAuth appstoken and grant deletion endpoints](https://docs.github.com/en/rest/apps/oauth-applications)
- [GitHub: reviewing your security log90 days, personal_access_token, oauth_access](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/reviewing-your-security-log)
- [GitHub: removing sensitive data from a repositoryrotate first, git-filter-repo, forks, Support](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/removing-sensitive-data-from-a-repository)
- [GitHub: about secret scanningfree for public repositories, scope of the scan](https://docs.github.com/en/code-security/secret-scanning/introduction/about-secret-scanning)
- [GitHub: about push protectionblocks a push before it lands](https://docs.github.com/en/code-security/secret-scanning/introduction/about-push-protection)
- [AWS: update access keysdeactivate, last used, delete](https://docs.aws.amazon.com/IAM/latest/UserGuide/id-credentials-access-keys-update.html)
- [AWS: CloudTrail event history90 days, management events, one Region](https://docs.aws.amazon.com/awscloudtrail/latest/userguide/view-cloudtrail-events.html)
- [aws cloudtrail lookup-eventsthe AccessKeyId lookup attribute](https://docs.aws.amazon.com/cli/latest/reference/cloudtrail/lookup-events.html)
- [Google Cloud: manage API keysRotate key, 30-day undelete](https://docs.cloud.google.com/docs/authentication/api-keys)
- [gcloud services api-keys deletethe delete command](https://docs.cloud.google.com/sdk/gcloud/reference/services/api-keys/delete)
- [Slack: auth.revokewhat it revokes, parameters](https://docs.slack.dev/reference/methods/auth.revoke)
- [Slack: tokensapp-level tokens do not expire, token rotation](https://docs.slack.dev/authentication/tokens)
- [Stripe: API keysRotate key, Expiration, request logs, access policies](https://docs.stripe.com/keys)
- [ranwhat/clean.pymasking, backups, what it reads](https://github.com/MatijaMiki/ranwhat/blob/main/ranwhat/clean.py)
