GUIDE · HOW-TO 10 min ·

Revoke a leaked LLM API keyStop the bleed. Audit. Rotate. Clean up.

Bots scan new commits within roughly 60 seconds of push. If your OpenAI, Anthropic, or Gemini key landed in a repo, the clock started the moment git push completed.

TL;DR· the answer, in twenty seconds

What: An LLM API key in a git commit is public the moment the push completes. GitGuardian's 2026 State of Secrets Sprawl documents bots hitting fresh leaks within roughly 60 seconds of push.

Fix: Revoke the key in the provider dashboard first, then audit usage logs, then rotate every consumer. Run git filter-repo --path-glob '*.env' --invert-paths last so you preserve the usage evidence needed for the audit.

Lesson: Rotating in the dashboard without propagating to CI and downstream services leaves your pipeline broken on a dead key. Treat credential rotation as a deployment, not a one-time dashboard click.

You pushed a commit. Thirty seconds later GitGuardian sent an alert, or a teammate spotted it in the diff, or a Slack bot fired. Your OpenAI key, Anthropic key, or Gemini service account is now in a public repository.

The exposure window opened when the push completed. GitGuardian's 2026 State of Secrets Sprawl documents bots scanning new commits and hitting fresh leaks within roughly 60 seconds. That window may already be closed. The question now is how much happened inside it.

The order matters more than the speed. Revoke first. Audit second. Rotate consumers third. Clean git history last. Reversing those steps loses evidence you need for the audit, and rotating history before you know your consumers produces broken deployments.

The short version

  1. Go to the provider dashboard. Revoke the key. Do not wait.
  2. Screenshot or export the usage summary before the key disappears from the UI.
  3. Leave git history alone until after the usage audit.
  4. Create a replacement key. Do not store it anywhere a tool can commit it.
  5. Update every consumer: CI secrets, production env vars, secrets manager entries. All of them, in one pass.

Stop the bleed, per vendor

OpenAI

Go to platform.openai.com/api-keys. Find the exposed key by name or prefix. Click the trash icon and confirm.

Before the key leaves the UI, open Usage and filter by API key. Copy or screenshot the per-key usage for the last 30 days. The usage data persists after deletion, but capture it now while the connection is obvious.

If the key belonged to a service account rather than a user account, revoke it through Organization settings under Members and then Service accounts. Service-account keys live in a separate key management screen inside the service account record. Revoking the user-level API key does nothing to a service-account key.

Set rate limits on the replacement key before you hand it to any consumer. Under Limits, add a monthly spend ceiling and a per-minute token ceiling. The exposed key probably had none.

Anthropic

Go to console.anthropic.com, open API Keys, and revoke the exposed key.

Per-key usage is under Usage → API usage. Filter by key name. The breakdown shows request counts and token volumes by model and by day. Download the CSV if usage looks high or anomalous before you navigate away.

Anthropic separates workspace keys from organization-level keys. If the leaked key is an org-level key used for provisioning workspaces or managing billing, revoke it under Organization settings, not the workspace API keys panel. Org-level rotation requires owner permissions. A workspace admin cannot do it.

Name the replacement key explicitly with the service or project it belongs to. The new key does not inherit the old key's name, and unnamed keys make the next audit harder.

Google Gemini / AI Studio

A Gemini API key is a Google Cloud API key tied to a project. Go to console.cloud.google.com, open APIs and Services, then Credentials. Find the key, open the three-dot menu, and click Delete.

Before you delete: check the key's restrictions tab. If it had no API restrictions and no application restrictions, it could have been used against any Google Cloud API in that project, not just Gemini. Widen your audit scope accordingly.

Set restrictions on the replacement key. Restrict it to the Gemini API (or the Vertex AI endpoint if you use Vertex). In APIs and Services → Quotas, set a per-minute and per-day ceiling on the Gemini API for that project. A documented incident involving a leaked Google Cloud credential produced an $82,000 charge before the owner noticed.

For IAM audit logs, go to Logging → Logs Explorer and filter by resource.type="audited_resource" and protoPayload.serviceName="generativelanguage.googleapis.com". Requests you did not originate are evidence of exploitation.

Mistral, Together, OpenRouter, and smaller providers

The steps are the same. Find the API keys section. Revoke. Generate a replacement. Rotate consumers.

Smaller providers often have thinner usage logs. If per-key audit data is not available, treat the exposure as unquantified and rotate all consumers.

Audit what was used

The audit tells you whether to file an incident report, whether to notify users, and whether unexpected charges are coming.

Pull the usage data for the key you just revoked. Look for:

  • Requests outside your normal usage hours (nights, weekends, geographic anomalies in IP data where visible)
  • Models you do not use (gpt-4o-realtime, claude-opus-4, gemini-2.0-flash-exp)
  • Request volumes larger than your daily peaks

Anomalous usage during the exposure window (from push timestamp to revocation timestamp) is evidence of exploitation. Document it: provider name, key ID, exposure start, revocation time, request count, token volume, any anomalous patterns. You may need this for a billing dispute or a disclosure.

If usage looks normal, the window was probably too short for meaningful abuse. Plan around "probably" anyway. Rotate every consumer regardless.

Rotate every consumer

Rotating in the dashboard and nowhere else is the most common mistake in this scenario. The new key does nothing until every service that was using the old key switches to it.

List the consumers before you start:

# Find hardcoded key references in the working tree
grep -r "sk-" . --include="*.env" --include="*.json" --include="*.yaml" --include="*.toml"

# Anthropic keys (sk-ant-*) and Google keys (AIza*)
grep -r "sk-ant-\|AIza" . --include="*.env" --include="*.json" --include="*.yaml"

Then check every environment that holds the key:

  • CI/CD variables: GitHub Actions secrets, GitLab CI variables, CircleCI environment variables, Vercel and Netlify environment variables, Railway variables
  • Kubernetes secrets and ConfigMaps
  • Docker Compose .env files
  • Staging and production .env files on servers
  • Secrets manager entries: AWS Secrets Manager, HashiCorp Vault, 1Password for Teams
  • Any other developer machines that received a copy of the key

Update each location. Verify the service starts and API calls succeed before moving on to the next. A rotation that breaks production for hours because one CI job still holds the old key is a predictable outcome when the list is incomplete.

If a broker like hasp sits in the path, this consumer hunt collapses. The broker holds the canonical value in one place; every consumer reads through a reference. Rotation is a single vault update instead of a cross-repo grep followed by a dozen manual edits across CI, production, and dev machines.

Clean the history, after the audit

git filter-repo rewrites every commit in the branch to remove the file or the string. Install it:

pip install git-filter-repo

To remove a file from history:

git filter-repo --path .env --invert-paths

To remove a specific key string that appeared inline in source:

git filter-repo --replace-text <(printf 'sk-abc123abc123abc123==>REDACTED\n')

Force-push after the rewrite. Tell every contributor to re-clone the repository. Any existing local clone with the old history can reintroduce the key if someone force-pushes from it.

On GitHub: after the force-push, contact GitHub Support to flush the CDN cache of the old commits. Previously viewed raw file URLs can serve cached versions for a few hours after the history rewrite.

This step comes last because the rewritten history makes per-commit forensics harder. Finish the audit, document the exposure window, then clean.

What actually gets missed

Rotating in the dashboard without updating CI. Teams do this constantly. The deployment breaks. Someone adds the old key back from memory or from a Slack message. The key is now live again in production while the dashboard says it is revoked. Rotate every consumer in a single coordinated pass, not one at a time across two days.

Running filter-repo before the usage audit. The rewritten history does not destroy the provider's server-side logs, but it destroys the git-level evidence you need to correlate when the key first appeared with when anomalous usage started. Do the audit first.

Not setting rate limits on the new key. The exposed key probably had no caps. A second exposure of the replacement key hits the same uncapped surface. Two minutes in the provider console setting a spend ceiling and a per-minute cap closes that surface.

Assuming a private repo is safe. Private repos on GitHub are readable by all contributors, installed GitHub Apps, and any third-party integration granted repository access. GitGuardian's 2026 State of Secrets Sprawl shows AI-assisted commits leak keys at roughly double the baseline rate. Private repos account for a meaningful share of those leaks.

Treating deletion as rotation. Deleting the key in a later commit does nothing. Git history holds the value. An npm tarball or PyPI distribution that shipped the file holds it permanently on the registry. Delete the key in the provider dashboard. Delete it from history with filter-repo. Both.

Checklist you can paste into a PR or incident ticket

## LLM key rotation checklist

- [ ] Identified provider, key ID, and push timestamp
- [ ] Screenshot or export of usage logs captured before revoking
- [ ] Key revoked in the provider dashboard
- [ ] Usage reviewed for anomalous requests (hours, models, volumes)
- [ ] Anomalous usage documented, or confirmed clean
- [ ] New key created with spend limit and per-minute rate limit set
- [ ] New key added to secrets manager or vault (not to .env in repo)
- [ ] CI environment variables updated (GitHub Actions, GitLab, CircleCI, etc.)
- [ ] Kubernetes secrets / ConfigMaps updated
- [ ] Staging and production env vars updated
- [ ] All developer machines that held the old key notified
- [ ] Services verified working on new key before proceeding
- [ ] git filter-repo run to remove key from history
- [ ] Force-push completed; team notified to re-clone
- [ ] GitHub Support contacted to flush CDN cache (if GitHub)
- [ ] Incident documented with exposure window and usage summary
- [ ] .gitignore updated to block .env and other secret-bearing files going forward

What this means for your stack

The rotation checklist above handles the immediate incident. The category problem is that long-lived credential strings in files are portable by design, which is useful and dangerous for the same reason. Every .env, every CI variable, every secrets manager entry is a copy of the raw string. The number of copies grows over time. Each copy is an exposure surface.

The durable fix is a runtime model where the raw string never lands in a file a tool can commit, a pipeline can log, or a publish step can ship. Credentials sit in an encrypted local vault. Coding agents, CI runners, and shell scripts receive a reference or a scoped temporary credential at execution time, not the string itself. An HMAC-chained audit log records each grant. State files the agent writes contain references. The next vendor that ships a debug file capturing environment data finds nothing worth taking.

hasp is one working implementation. curl -fsSL https://gethasp.com/install.sh | sh, hasp setup, connect a project, hand the next session a reference instead of a key. Source-available (FCL-1.0), local-first, macOS and Linux, no account.

The checklist above is the right immediate response. Not putting the string in the repo at all is the only approach that does not require a second rotation six months from now.

Sources· cited above, in one place

NEXT STEP~90 seconds

Stop handing the agent your real keys.

hasp keeps secrets in one local encrypted vault, brokers them into the child process at exec, and never lets the agent read the value.

  • Local, encrypted vault — no account, no cloud, no telemetry by default.
  • Brokered run — agent gets a reference, the child process gets the value.
  • Pre-commit + pre-push hooks catch managed values before they ship.
  • Append-only HMAC audit log answers "did the agent touch the prod token?" in seconds.
→ okvault unlocked · binding ./api
→ okgrant once · pid 88421
→ okagent never read

macOS & Linux. Source-available (FCL-1.0, converts to Apache 2.0). No account.

Browse all clusters· eight threads, one index