HomeGuidesAI coding tool breached? Rotate API keys and find every copy

Guide

AI coding tool breached? Rotate API keys and find every copy

OpenCode Console and LiteLLM 1.107.0 exposed AI API keys in October 2026. See if you were hit, rotate OpenAI, Anthropic, Gemini and OpenRouter keys, cap spend.

20 min read

Independent notes from VibeLocked. Not a pentest, and not affiliated with the tools named below.

In one week in October 2026, two tools that sit between vibe coders and their AI providers lost keys. On October 16 to 17, an intruder reached a legacy system behind OpenCode Console and may have exposed the OpenAI, Anthropic, and Google keys some users had saved there. Users reported overnight charges in the hundreds of dollars, and a few above $1,000. On October 21, a poisoned LiteLLM 1.107.0 release on PyPI and Docker stole credentials from every machine that ran it, including AI provider keys, cloud keys, and the config folders of AI coding tools.

The fix is the same for both, and for the next one: revoke the old key at the provider, issue a new key per app with a spend cap, and find every other copy of the old value so you know what else to rotate. This guide shows how to tell if you are affected, the rotation steps for OpenAI, Anthropic, Google Gemini, and OpenRouter, the places keys hide in a vibe-coded project, and how to pin dependencies so a bad release does not reach you.

If a key leaked in another way (a commit, a screenshot, a frontend bundle), the first hour after an API key leaks covers the general order of operations. This guide is about the case where a tool or package you trusted was the leak.

A week later it happened inside the editor itself: on October 29, 2026 a tampered Claude Code extension was served through Cursor's marketplace and tried to plant an SSH backdoor. If you use Cursor, see the malicious Claude Code extension in Cursor: how to check and what to rotate. The rotation steps below apply to it too.

Checked on October 23, 2026 against LiteLLM's incident report, the OSV malicious package record, Datadog and JFrog research, OpenCode's statements as reported by The Hacker News, TechCrunch, BleepingComputer and eSecurity Planet, and the OpenAI, Anthropic, Google, and OpenRouter key and billing docs linked below. The search commands in this guide were tested in a throwaway repo with fake keys (12 checks, all passing). VibeLocked is independent and not affiliated with any of these companies.

The short version

  1. Check if you are affected. OpenCode: did you save a provider key in OpenCode Console and get the October 17 email? LiteLLM: did anything install litellm==1.107.0 or run a latest LiteLLM image on October 21?
  2. Revoke at the provider, not in the tool. Deleting a key from Console, a .env file, or a dashboard does not stop a copy from working.
  3. Issue one new key per app or tool, with an expiration date where the provider supports it, and put it only on the server.
  4. Set a hard spend limit or per-key credit limit, plus usage alerts, on every provider account.
  5. Read the usage dashboards for traffic you did not make, starting at least from the exposure date.
  6. Find every copy of the old key (env files, hosting env vars, agent configs, shell history, CI, git history, backups, bundles) and rotate anything else that sat next to it.
  7. Pin dependencies with a lockfile and Docker image digests, so a release published an hour ago does not install itself into your build.

Incident 1: OpenCode Console (October 16 to 17, 2026)

OpenCode is an open source AI coding agent. Console is its optional hosted service, where a team workspace saves its own OpenAI, Anthropic, or Google key so members can use the models without seeing the key. According to The Hacker News, an intruder reached a legacy OpenCode system on the night of October 16 and may have exposed the provider keys saved in Console workspaces.

What OpenCode and the providers said, as reported by TechCrunch, BleepingComputer, and eSecurity Planet:

  • Who: 1.4% of Console users, all with an external provider linked. OpenCode emailed them on the morning of October 17 with the subject "Action Required: Potential provider key breach" and said anyone who did not get the email was not affected.
  • When: the notification set the earliest possible exposure at October 17, 00:30 UTC (October 16, 5:30pm PT).
  • Not affected: keys added in the OpenCode command line or desktop app. Co-founder Dax Raad wrote that the breach involved "the OpenCode Console, not OpenCode itself" and that those keys "never come to us."
  • Damage reported by users: spend alerts firing overnight, charges in the hundreds of dollars on single accounts, a few totals above $1,000, and one user reporting $1,700. Some Google Cloud projects were suspended.
  • Provider response: Anthropic and Google blocked keys they believed were exposed. OpenAI said it would refund affected API customers for credits used in the attack, and OpenCode said refunds were underway.

Two details matter for everyone, not only OpenCode users. Deleting the key from Console did not revoke it. And a Console budget could not cap the abuse, because the attacker called the provider directly with the copied key, never passing through Console. Limits only work if they are set at the provider.

Incident 2: LiteLLM 1.107.0 on PyPI and Docker (October 21, 2026)

LiteLLM is a popular Python library and proxy that puts one API in front of many model providers. Datadog puts it at about 23 million weekly downloads. LiteLLM's incident report says its release pipeline was compromised early on October 21 and published a malicious 1.107.0:

Artifact Compromised window (UTC, October 21) In Pacific time
PyPI litellm==1.107.0 (all seven wheels) 11:39 to 13:23 4:39am to 6:23am PT
Docker Hub tags v1.107.0, latest, main-latest, main-stable 11:46 to 14:20 4:46am to 7:20am PT
GHCR tags (same names) 11:46 to 13:51 4:46am to 6:51am PT

What the payload did, from the OSV record MAL-2026-2144 and JFrog's analysis:

  • The wheels added cook.pth, cook.py, and sitecustomize.py at the root of site-packages. A .pth file runs at every Python start, so the payload ran even in projects that never imported litellm.
  • It collected browser passwords, SSH keys, shell history, AWS, GCP and Azure credentials, cloud instance metadata, Kubernetes, Docker, Vault and CI secrets, AI developer tool configs (~/.cursor, ~/.claude, ~/.kiro, Windsurf), .npmrc, .pypirc, .git-credentials, and environment variables, then sent them to an attacker server.
  • A second stage checked in every hour to download and run more code.
  • The source tarball and the GitHub tag did not contain the payload files, so reviewing the source would not have caught it.

LiteLLM says all earlier versions are safe and recommends pinning litellm==1.106.2. It is also the second LiteLLM compromise in 2026 (versions 1.82.7 and 1.82.8 were hit in March), and LiteLLM's report says the attacker likely got in through GitHub CLI tokens that ended up in the logs of an internet-exposed internal LiteLLM deployment. That is the lesson for small apps too: a log that records request or response bodies is one more place your keys live.

Step 1: Are you affected?

OpenCode. Search your email for "Action Required: Potential provider key breach". If you used Console with your own provider keys and are not sure, rotating those keys costs a few minutes. If you only used the CLI or desktop app, the Console breach did not include your keys, but do keep reading for the copies those tools keep on your machine.

LiteLLM. Check every place Python runs: your laptop, virtual environments, Docker images, CI runners, and servers. LiteLLM warns that any Python start in an affected environment runs the payload again, including pip, so look from the shell first:

# Run from the folder that holds your virtual environments, or "/" on a server.
# Lists matching paths only. Does not start Python.
find . \( -name 'litellm-1.107.0.dist-info' -o -name 'cook.pth' -o -name 'cook.py' \) 2>/dev/null

# Did a requirements file or lockfile pin the bad version, or leave litellm unpinned?
grep -rnE 'litellm[^a-z]*1\.107\.0|litellm *(>=|~=|>|$)' \
  requirements*.txt pyproject.toml poetry.lock uv.lock Pipfile.lock 2>/dev/null

A litellm-1.107.0.dist-info folder or a cook.pth file confirms exposure. Finding nothing does not prove you are clean if the environment was rebuilt or the container is gone. A sitecustomize.py on its own can be legitimate in some system Python installs, so treat cook.pth and the dist-info folder as the clear signals. For Docker, check whether any deploy pulled one of the mutable tags during the window, and compare your image digest to the compromised index LiteLLM lists (sha256:1bbcb1443bc9... for berriai/litellm).

If a CI job ran pip install litellm without a pin on the morning of October 21, treat the CI secrets of that job as stolen too.

If you are affected, follow LiteLLM's cleanup order: delete the payload files from the shell, kill the background process (it holds /tmp/remote_code_loader.lock), rebuild the environment or image from scratch pinned to 1.106.2, then rotate, from a clean machine, every credential that host could reach. For a LiteLLM proxy, that list includes every provider key stored in the proxy database, the master and virtual keys, the DATABASE_URL password, and the cloud IAM role.

Step 2: Revoke and replace each model key

When the key is known to be exposed (you got the email, or you ran 1.107.0), revoke first and accept a few minutes of downtime. A planned outage is cheaper than a key that keeps working for someone else. When you rotate as a precaution, you can create the new key, deploy it, then revoke the old one, which is the order the providers describe for routine rotation.

Do these steps yourself in each provider's own dashboard. Never paste a key into a chat, an AI coding tool, a support ticket, or someone else's website to "check" it.

OpenAI

  • Revoke and replace: on the API keys page in the OpenAI platform, revoke the exposed key and create a new project key. OpenAI's API key safety guide recommends creating project keys with an expiration date and a regular rotation process, and admins can enforce a maximum key lifetime.
  • One project per app: OpenAI's production best practices suggest separate projects for staging and production, each with its own rate and spend limits.
  • Cap spend: set a monthly spend limit for the organization or project with hard enforcement. OpenAI is explicit that spend alerts alone do not stop API traffic.
  • Check usage: the Usage page shows usage per key once tracking is on. Keys created before December 20, 2023 did not have tracking on by default, and their usage shows as Untracked.

Anthropic (Claude API)

  • Revoke: in the Claude Console, open API keys (platform.claude.com/settings/keys), open the menu next to the key, and choose Delete API key, as described in Anthropic's API key best practices. Create a new key for each environment.
  • Check usage: review the Logs page and the Usage page for requests you did not make.
  • Cap spend: organizations on custom rate limits can set spend limits on the Billing page and workspace rate limits. On standard rate limits, think carefully about auto-reload, because each reload is more credit a stolen key can burn.
  • Anthropic also warns that giving your key to a third-party tool gives that tool's developer access to your account, which is exactly what happened with Console.

Google Gemini API (AI Studio)

  • Replace, then disable: Google's Gemini API key guide says to create a new key in AI Studio or the Cloud console, deploy it, then disable the leaked key in the Cloud console and audit usage. New keys in AI Studio are created as authorization keys bound to a service account, which Google says get faster leaked key enforcement.
  • Restrict: keys should be restricted to the Gemini API only, and you can add IP restrictions in the Cloud console (manage API keys). The Gemini API rejects unrestricted standard keys.
  • Cap spend: the billing docs describe project spend caps in AI Studio (experimental, with about 10 minutes of possible overage) and a monthly auto-charge limit for prepaid auto-reload. Billing alerts are set in the Cloud console with budgets.
  • Not every AIza key is secret. A Firebase web config apiKey also starts with AIza and is meant to be public. The Firebase API key guide explains the difference. A key that can call Gemini is the one to protect.

OpenRouter

  • Revoke and replace: OpenRouter's authentication docs say to go to your key settings page, delete the compromised key, and create a new one. OpenRouter is a GitHub secret scanning partner and emails you if it detects an exposed key.
  • Credit limit on every key: each key can have a credit limit that resets daily, weekly, or monthly. OpenRouter recommends setting one on every key, because a key with no limit can spend your whole balance, including auto top-ups. The limits docs cover per-key limits and workspace budgets.
  • Bring your own key: if you saved your OpenAI or Anthropic keys inside OpenRouter, those are provider keys too. Rotate them at the provider.

Step 3: Read the usage dashboards for abuse

After the old key is dead, look back at least to the exposure date (October 16 for OpenCode, October 21 for LiteLLM, or the date you first saved the key in the affected tool). Signs of abuse:

  • Usage on a key in hours when nobody on your team works, or in a steady stream that does not match your app's traffic.
  • Models your app does not use, often the most expensive ones.
  • A key you had forgotten about, with recent usage.
  • A spike that lines up with the breach, then stops when the key was revoked.

Screenshot what you find. If the charges are unauthorized, contact the provider through its support page. OpenAI offered refunds to affected OpenCode users, but that was a provider decision for that incident, not a rule.

Step 4: Find every copy of the old key

A key you pasted into one tool usually lives in five other places. The LiteLLM payload went straight for most of them. Search your own machine and your own accounts; the commands below print file names only, never key values, so the output is safe to read on screen.

# Key prefixes and common variable names. Adjust to the providers you use.
PAT='sk-ant-|sk-proj-|sk-or-v1-|AIza[0-9A-Za-z_-]{35}|(OPENAI|ANTHROPIC|GEMINI|GOOGLE|OPENROUTER)_API_KEY'

# 1. Files in the project, including .env copies and build output.
grep -rlIE --exclude-dir=node_modules --exclude-dir=.git "$PAT" .

# 2. Every commit that added or removed a matching line, on every branch.
git log --all -G 'sk-ant-|sk-proj-|sk-or-v1-|AIza' --format='%h %ad %s' --date=short

# 3. Shell history.
grep -lE "$PAT" ~/.bash_history ~/.zsh_history 2>/dev/null

In our test repo with fake keys, the first command found .env, a forgotten .env.backup, a key in .cursor/mcp.json, and a key inside a built dist/assets/*.js bundle, while skipping node_modules. The second listed both the commit that added a hardcoded key and the commit that "removed" it.

The full list of places to check:

Where What to look for What to do
.env, .env.local, .env.backup, .env.old Every copy, not only the one your app loads Update the live one, delete the stale copies
Hosting env vars (Vercel, Netlify, Cloudflare, Replit) Production, preview, and development values, per project Update each one and redeploy. Vercel's env var docs say changes only apply to new deployments. Netlify keeps values per deploy context (Netlify env vars). On Railway, see what to rotate after the Railway breach
Cloudflare Workers secrets Each Worker that calls a model Replace the secret (Workers secrets)
Agent and MCP configs .cursor/mcp.json, ~/.cursor/mcp.json, .mcp.json, ~/.claude.json, .vscode/mcp.json, Windsurf mcp_config.json, keys saved inside AI coding tools Replace with an env var reference where the tool supports it. The MCP server security guide shows where each tool keeps them
Shell history and dotfiles export OPENAI_API_KEY=... in ~/.zshrc, ~/.bash_profile, history files Delete the lines, prefer a secrets manager or a per-project env file
CI secrets GitHub Actions repository, environment, and organization secrets Update them (GitHub Actions secrets). If CI ran the bad package, rotate every secret that job could read
Git history Old commits, other branches, forks Rotation makes these dead. Cleanup is covered in remove an API key from GitHub history, and checking if a key leaked on GitHub covers GitHub's own alerts
Backups and copies Zipped project exports, copied project folders, cloud-synced folders, database dumps of a proxy config Delete or re-encrypt, and rotate anything they held
Logs Proxy or API logs that store request headers or bodies Stop logging auth headers and full bodies, then purge old logs
Client bundles Model keys in NEXT_PUBLIC_ or VITE_ variables, or hardcoded in frontend code Move the call to a server route. A model key in the browser is already public (keep secrets out of frontend code)

CI logs count as a copy too. If a workflow ever printed a model key, even encoded or in pieces, treat it as leaked and rotate it. GitHub masks exact values, but in our test of the GitHub Actions runner's masker hex, wrapped base64, split values and single fields from a JSON blob secret were printed in clear, and a public repo's logs are readable by anyone. The same guide covers the pull_request_target setup that hands repo secrets to fork pull requests.

Remember the neighbors. If a stealer read your environment, it did not take only the OpenAI key. Supabase secret or service_role keys, Stripe keys, database URLs, GitHub tokens, and cloud credentials on the same machine need the same treatment. The first hour guide has the revoke links for those.

Check your MCP and agent configs for a Supabase personal access token too. It starts with sbp_ and often sits in SUPABASE_ACCESS_TOKEN or in the Supabase MCP server entry. It is not a project key. It drives the Supabase CLI, the MCP server and the Management API, and an older full access token reaches every organization and project you belong to. Delete it on the Access Tokens page in your Supabase account settings. Then create a scoped replacement limited to the one project the agent works on, with only the permissions it uses and an expiry, and put the new value only where that agent runs. Our scoped personal access tokens section lists the permissions for common CLI and MCP jobs, and the first hour guide has the full rotation note.

To stop the next copy from landing in git, turn on GitHub secret scanning and a Gitleaks pre-commit hook, as shown in stop committing API keys with Gitleaks and push protection.

To see what your deployed app ships to every visitor, run its URL through LeakCheck. It downloads your public JavaScript like a visitor would and flags known secret key patterns, with values masked.

Step 5: One key per app, with limits

The damage in both incidents was bigger for people who used one key everywhere. Revoking it broke every project at once, and the usage page could not say which tool leaked it.

  • One key per app and per environment. Your production app, your staging app, your laptop, your AI coding tool, and each hosted tool get their own key. OpenAI and Anthropic both recommend separate keys or projects per environment or use.
  • Name keys after where they live, for example myapp-prod-vercel or cursor-laptop. When a tool is breached, you know exactly which key to kill.
  • Give hosted tools their own key with a low limit, or skip saving keys in hosted tools you do not need. A key you hand to a third-party service is only as safe as that service.
  • Expire keys where you can (OpenAI project keys support expiration dates) and rotate on a schedule. Anthropic suggests every 90 days as an example.
  • Put the hard limit at the provider, not only in the tool. A tool's budget does not apply when someone uses the copied key directly.
  • Keep model keys on the server behind a route that checks who is calling. Stop strangers from running up your OpenAI bill shows how, and rate limiting AI API routes adds per-user caps.

Step 6: Pin dependencies so a bad release does not install itself

The LiteLLM release was live on PyPI for under two hours. LiteLLM says you were affected if you installed an unpinned version in that window or ran one of the mutable Docker tags. Pinning does not make a package safe, but it means a brand new release reaches you only when you choose to upgrade.

  • JavaScript: commit package-lock.json (or your package manager's lockfile) and install with npm ci in CI. The npm ci docs say it fails if the lockfile and package.json disagree and never writes to the lockfile.
  • Python: pin exact versions (litellm==1.106.2, not litellm>=1.100), and use a lockfile with hashes. pip's secure installs guide covers --require-hashes, and uv's uv sync --locked fails if the lockfile is out of date.
  • Docker: do not deploy :latest or main-stable. Pin an exact version tag, or better, an image digest (image@sha256:...), as described in the docker pull docs.
  • MCP servers and npx -y: a line like npx -y some-server installs the newest version every time your editor starts. Pin a version, as covered in the MCP guide.
  • Wait before upgrading packages that touch your keys. Upgrading a day after a release, after others have looked at it, would have skipped this one.
  • Check what your AI tool adds. Agents happily run pip install or npm install with no version. See AI hallucinated packages and slopsquatting for a quick check before you install anything new.

Common AI-generated mistakes

  • One OPENAI_API_KEY shared by every project, copied from the first tutorial.
  • Model keys in NEXT_PUBLIC_ or VITE_ variables so the frontend can call the model directly.
  • Keys written straight into mcp.json or agent settings instead of an env var reference.
  • pip install litellm or image: berriai/litellm:latest with no version.
  • Logging the full request in an AI proxy or API route, including the Authorization header.
  • "Rotating" by deleting the key from .env and committing, without revoking it at the provider.
  • No spend limit, or only an email alert, which does not stop traffic.

Checklist

  • I know whether I used OpenCode Console with my own provider keys, and searched for the October 17 email
  • No environment, image, or CI job installed litellm==1.107.0 or ran a mutable LiteLLM tag on October 21, or I cleaned and rebuilt it
  • Every exposed key is revoked at the provider, not only deleted from a tool
  • Each app, environment, and tool has its own named key
  • OpenAI has a monthly limit with hard enforcement; OpenRouter keys have credit limits; Gemini projects have spend caps; Anthropic spend limits or auto-reload are set deliberately
  • I reviewed usage on each provider back to the exposure date
  • The search commands above find no old key copies in the project, history files, or build output
  • Hosting env vars, Workers secrets, and CI secrets hold the new key, and I redeployed
  • Neighboring secrets on any compromised machine (database, Stripe, Supabase, GitHub, cloud) are rotated too
  • Any Supabase personal access token (sbp_) in an agent, MCP or CI config is deleted and replaced with a scoped token for one project, with an expiry
  • Dependencies are pinned with a lockfile, and Docker images by version or digest
  • No model key reaches the browser

Copy-paste prompt for your AI coding tool

Paste this into Cursor, Claude Code, Lovable, or Bolt. It asks for file names and variable names only. Never paste key values, and do not let the tool print them.

Audit this project for AI provider API keys without printing any key values. 1) List every file (including .env files, backups, config files, MCP and agent settings, CI workflow files, Docker files, and build output folders) that contains an OpenAI, Anthropic, Google Gemini, or OpenRouter key or references OPENAI_API_KEY, ANTHROPIC_API_KEY, GEMINI_API_KEY, GOOGLE_API_KEY, or OPENROUTER_API_KEY. Show file paths and variable names only, never the values. 2) Flag any key that is hardcoded, used in a NEXT_PUBLIC_ or VITE_ variable, or used from frontend code, and propose moving the call to a server route. 3) Flag any place that logs request headers or full request bodies to an AI provider. 4) List every Python and JavaScript dependency and Docker image that is not pinned to an exact version, including litellm, and propose exact pins and lockfile commands. 5) Suggest a key naming scheme with one key per app and environment. Do not change any files until I approve the plan.

Rotate the keys yourself in each provider's dashboard. More prompts are in the security review prompt guide.

Sources

Questions

Was I affected by the OpenCode breach?

Only if you saved an OpenAI, Anthropic, Google, or other provider key in a workspace on OpenCode Console, the hosted service. OpenCode said 1.4% of Console users with external providers linked were affected, that it emailed all of them on October 17, 2026, and that anyone who did not get the email was not affected. Keys you added in the OpenCode command line or desktop app stay on your machine and were not part of it, according to co-founder Dax Raad.

Was I affected by the LiteLLM 1.107.0 compromise?

Treat a machine, container, or CI job as compromised if it installed litellm 1.107.0 from PyPI between 11:39 and 13:23 UTC on October 21, 2026, or ran the LiteLLM Docker tags v1.107.0, latest, main-latest, or main-stable between 11:46 and 14:20 UTC. The payload ran at every Python start, even if your code never imported litellm, and it collected cloud keys, SSH keys, shell history, AI tool config folders, and environment variables. Pin 1.106.2 and rotate everything that host could reach.

If I delete the key from the tool, is it safe?

No. A copied key keeps working until the provider revokes it. Deleting it from a tool, a settings page, or a file only removes your copy. Revoke the key at OpenAI, Anthropic, Google, or OpenRouter, then put a new key only where it is needed.

Does a spend limit stop a stolen key?

It caps the damage, it does not stop the theft. OpenAI says spend alerts alone do not stop API traffic and recommends a monthly limit with hard enforcement. OpenRouter lets you set a credit limit on each key. Google's Gemini API has project spend caps in AI Studio, with about 10 minutes of possible overage. Anthropic offers spend limits for custom rate limit organizations and auto-reload settings for standard ones. Set them before you need them.

Do I need to rewrite git history after rotating?

Usually not. Once the old key is revoked it cannot be used, so rotation is the fix. Rewriting history is cleanup, and only worth it if the repository is public or shared widely. Find the copies first so you know which keys to rotate.