OpenCode breach exposed customers’ AI access keys
Some OpenCode customers reported AI usage they did not authorise. Records from an older system reveal how access keys were stored and leave a question about its shutdown.

Listen to this articleListen
OpenCode, an AI coding tool, confirmed a breach on 17 October affecting 1.4% of users of its Console account service. Some customers reported AI usage they said was not theirs. The affected users were among those who had connected outside AI services, such as OpenAI, Anthropic or Google, using their own access keys.
An API key is a secret code that lets an app use an AI service on your behalf. If someone copies a valid key, they may be able to send their own requests and put the charges on your account. They do not necessarily need your account password. That is why a key saved in one app can become a problem at another company.
OpenCode says it is working with the three AI providers. Anthropic and Google staff have described disabling affected keys. But another part of the story sits in an older version of OpenCode’s Console, the system customers used to connect those services.
A follow-up email published by an affected customer says the listed keys “were stored in the affected legacy console system”. YFarmX examined the public code and records from its shutdown. The old code saved keys in readable form. And five days before the breach announcement, parts of that system were removed from the live service, while deleting the database itself was left as a separate manual task.
Those details raise a practical question: when an old service is shut down, what happens to the keys it once held? The records explain how the old design worked and what its retirement involved. They do not yet show how the attacker got in or which store supplied the keys.
The old Console saved readable keys
When a customer entered an AI provider’s key into the old Console, the inspected code saved it unchanged. The code that saves the key and the database definition show the submitted value going directly into a text field.
A text field alone would prove little: it can also hold encrypted information. YFarmX followed the value through the save function, checked the database definition, then examined how the app used a stored key. An earlier version of the code that sends AI requests takes that stored value and uses it as the provider’s API key. Neither inspected path scrambles the key through encryption or decrypts it before use.
credentials value accepted above is passed into the database insert at line 32. Source: OpenCode’s historical provider code. Open the image link to inspect the original lines.
These records come from different points in the app’s history. By the later version cited for storage, requests were being forwarded to a newer service. They show how the old design handled keys, without proving exactly what was running or stored when the breach happened.
The risk is straightforward. Someone who could read those saved values would have usable keys, provided they were still valid and the provider’s restrictions allowed the requests. Encrypting the disks underneath a database would not change what an authorised database reader could see: the app still saved readable secrets.
The code also contains keys issued by OpenCode itself. Its key generator saves the generated secret unchanged, and the database definition lets the app look it up by that raw value. It stores the secret itself rather than a one-way fingerprint, known as a hash. The reviewed disclosure does not establish that these OpenCode-issued keys were taken.
One check only needed a yes or no
Moving customers to the new Console did not immediately remove the old database from the process. A code-change proposal approved on 11 October describes the old database’s remaining job: helping decide where to send some requests and whether a customer had connected their own provider key.
The code immediately before retirement makes a revealing distinction. For requests using qualifying older OpenCode keys, it checks the old database. When checking an AI provider, it asks whether the saved credential field contains anything and returns the provider’s name. It does not retrieve the secret itself for that decision.
That decision only needed a yes or no. A service can remember that a customer connected a provider without keeping a full secret just to answer that question. The earlier code shows how full keys were saved; this later check does not tell us what every saved record still contained. A non-empty placeholder would also pass it.
The forwarding code also expects the new service to recognise connections imported from the old one. That helps explain the intended move between systems. It does not prove that every customer had two complete copies of a key.
Shutting the old system down took several steps
OpenCode’s public changes show the old Console being dismantled in stages. The migration pull request records moving non-blocked workspaces while leaving old tables behind. The subsequent retirement pull request sets out a rollout ending with manual deletion of the PlanetScale database.
The production update on 12 October records removal of the old sign-in service, called an authentication worker, and one database password. The infrastructure change removes the connection to the old database. It does not automatically delete that database: the database existed separately from the resources this update managed.
| Record | Time or date | What it establishes |
|---|---|---|
| Workspace migration | 11 October | Non-blocked workspaces migrated; old tables left behind |
| Authentication worker deletion | 12 October, 07:47:55 UTC | The production job removed the old worker |
| Database password deletion | 12 October, 07:48:18 UTC | The production job removed that password |
| Deployment completed | 12 October, 07:48:30 UTC | The deployment finished |
| Customer-published notification | Earliest possible exposure: 17 October, 00:30 UTC | A reported exposure bound, rather than a timestamp proving extraction |
Sources: the production job and a customer-published notification. Deployment times are shown to the second. The notification is a public screenshot of a purported company email.
Deleting a database password, deleting the database and revoking the customer’s AI key are separate actions. The password controls access to the database. The provider key authorises requests at the AI company. Removing the former does not invalidate the latter.
The retirement pull request’s estimate of up to 257 organisations using an old route describes traffic considered during the migration. It is not a victim count and cannot be substituted for the disclosure’s user percentage. The same change deliberately retains R2 storage described in a comment as historical Zen request and response dumps. The comment says their writer was removed in May 2026; it does not show the retained contents or establish access during the incident.
The email’s earliest possible exposure bound falls five calendar days after the deployment. It also says that other data did not appear exposed at that time and that OpenCode could not confirm unauthorised use of the listed keys. Its reference to the legacy system does not identify PlanetScale as the extraction source or establish that the database survived until 17 October.
PlanetScale’s documented full deletion procedure removes all branches, data and backups. The public record reviewed supplies no completed deletion timestamp. Its organisation audit documentation lists requested and completed database deletions as separate events, alongside password-management actions, with 15-day retention on both listed plans. Those management events could help date retirement. The documented event list contains no database-read or query events, so it does not promise a record of which credential rows were read.
One suspicious request asked for a simple reply
A request screenshot published by an affected customer adds detail about reported suspicious use. The Responses interface displays a Chinese-language instruction meaning “please reply ok”, followed by an ok response.
| Visible field | Displayed value |
|---|---|
| Model | gpt-6.1-sol |
| Input and output | 9 input tokens; 5 output tokens |
| Maximum output | 50 tokens |
| Top P | 0.98 |
| Reasoning effort and verbosity | medium; medium |
| Created | 17 October 2026, 3:12 AM; timezone absent |
Such a small exchange is consistent with checking whether a credential works. That interpretation remains an inference. The configuration may include defaults, and the screenshot supplies no source IP, user agent or destination for stolen credentials. Its language cannot identify the operator or their nationality.
OpenCode describes the incident as a large-scale distillation attack. Distillation uses outputs from one model to help train another; applying that motive to this breach is the company’s characterisation. This single short request establishes neither training activity nor resale.
The customer’s other screenshots also require careful reading. A Claude figure is month-to-date spending, while an OpenAI usage total lacks a visible selected time window. Adding those numbers into a breach loss would turn billing context into a finding the images do not support.
A key can remain valid after you stop using an app
One customer reported alerts from three providers, then said the keys had not been used in OpenCode since February. A later clarification says they had been entered into Console to supplement a Zen subscription. This is a firsthand account of dormant credentials becoming relevant again, not proof of continuous retention in a particular database.
Containment statements address the issuer side. Anthropic’s Lydia Hallie said the company had revoked Claude API keys it believed were affected. Google staff member Evan Otero described blocking exposed Gemini keys and gave replacement and billing guidance. These are public staff statements, rather than complete incident reports.
The practical distinction for customers is between removing a saved copy and invalidating the credential itself. A key that has been copied can remain useful after an application stops displaying or storing it. Revocation at the issuer closes that credential’s authority.
One Reddit commenter says they removed keys roughly three days before reporting subsequent Anthropic use. The public comment supplies neither a deletion receipt nor a provider export. The historical Console’s removal handler deletes a row from its old table, while its sign-in code redirects migrated users towards the new Console. That old handler cannot establish what happened to copies when this customer says they removed a key. A migration-related retention explanation is consistent with the public architecture, but remains unproven.
What other services can learn from this
Services that accept customers’ AI keys take on responsibility for secrets capable of generating bills. The defensive work spans storage, access and revocation. OWASP’s secrets management guidance recommends restricted access, auditing, rotation and prompt revocation of exposed secrets. It also describes encryption with protection of the encryption keys themselves.
For a service making requests on a customer’s behalf, the provider credential must be recoverable when needed. A design can protect the stored value with a separately controlled encryption key or secret-management service. That limits some database-only exposures; an attacker controlling the authorised application may still be able to use its access.
Retiring such a service needs an equally explicit record: which data stores were deleted, which copies were retained for a stated purpose, which access credentials were removed, and which customer credentials were revoked at their issuers. The OpenCode records show why each step deserves its own evidence. The useful completion test is whether every retired credential has lost its authority, with a record showing which system revoked it and when.
Sources
- OpenCode’s breach disclosurex.com
- Historical provider credential implementationgithub.com
- Legacy request-routing lookup before retirementgithub.com
- Historical OpenCode-issued key implementationgithub.com
- Legacy database retirement pull requestgithub.com
- Production deployment jobgithub.com
- Customer-published request logx.com
- Customer-published notification and billing imagesx.com
- Anthropic staff statement on key revocationx.com
- Google staff statement on key blockingx.com
- PlanetScale database deletion documentationplanetscale.com
- PlanetScale organisation audit-log documentationplanetscale.com
- OWASP secrets management guidancecheatsheetseries.owasp.org


