# Cursor's breach chain started in plain-text agent traces: 23,276 public repos carry the same leak

> Source: <https://dev.to/piekwerk/cursors-breach-chain-started-in-plain-text-agent-traces-23276-public-repos-carry-the-same-leak-2d42>
> Published: 2026-11-15 07:10:09+00:00

Cursor published its [final incident report](https://cursor.com/blog/security-incident-update) on November 13, two weeks after a malicious copy of Anthropic's Claude Code extension appeared in its VS Code extension marketplace. I read the whole thing. The interesting part is not the extension. It is the first link in the chain.

The report reconstructs the attack like this:

Read step 3 again. The breach did not start with a zero-day. It started with working credentials sitting in plain text inside stored agent transcripts. Everything after that was climbing. If you run sessions outside Privacy Mode, or you paste a token into a command to unblock an agent at 6pm, you are producing the same artifact class that made step 3 possible.

Cursor says traces were exposed for fewer than 0.05% of users, and API credentials for less than 0.01% of customers. Small percentages, and they still yielded enough working credentials to reach internal systems and poison a marketplace cache.

That is the arithmetic of stored secrets. An attacker does not need a large share of users leaking credentials. One valid key with the right scope is enough. Cursor's own response reads like agreement: the team rotated every credential in use across internal systems, not just the exposed ones, moved all inference traffic off the vendor, and added secret scanning across internal data sources.

Two weeks later, on the file side of the same problem, GitGuardian published a count that dwarfs the numbers Cursor had to contain. Per [CSO Online's report](https://www.csoonline.com/article/4247336/claude-codes-dont-ask-again-button-has-a-secrets-spilling-problem.html) of the research: 31,088 distinct secrets in `.claude/settings.local.json` files across 23,276 public GitHub repositories, with 5,343 of them still valid when detected. Of the occurrences GitGuardian could check, 67% were working credentials.

I wrote about the mechanism when Apiiro's smaller count came out: approving a command with "Yes, and don't ask again" saves the exact command string, secret included, into a project-level file. [That article](https://dev.to/piekwerk/claude-codes-dont-ask-again-wrote-1379-secrets-into-public-github-files-check-your-repos-before-d16) covered 1,379 distinct secrets in 1,122 files. Apiiro counted default branches. GitGuardian crawled every public commit back to January 2025 and found twenty times more.

The pattern holds everywhere agent tooling writes down what happened:

`.claude/settings.json`, carried another 8,271 secrets, 1,283 of them valid, in GitGuardian's `COPY . .` ignores your `.gitignore`. One valid Cloudflare token appeared across 559 image tags.
Apiiro ran gitleaks and TruffleHog, the two most used open-source secret scanners, over all 104,185 public copies of the file. Of the 1,122 files holding a real secret, [215 were flagged by neither](https://thehackernews.com/2026/11/claude-codes-dont-ask-again-file.html), close to one in five. The reason is structural. Scanners look for provider formats, a prefix followed by high-entropy string. A password after `PGPASSWORD=`, or inside an `sshpass` command, is just a word on a command line. Agent artifacts record what ran, not what was sensitive, so the exact place agents write secrets is the place format-based detection covers worst.

Order your cleanup by how the attacker actually used the data, not by how easy each step is.

First, rotate anything you ever pasted into a vendor support chat or ran through a session whose logs you do not control. Cursor's report lists "some credentials users had sent to the Cursor support team" among the exposed data. That class of credential outlives your account with the vendor.

Second, rotate credentials that reached a committed file, then clean the file. Rotation first, always, because git history and forks keep old copies. Check whether the file was ever committed on any branch:

```
git log --all --full-history --oneline -- .claude/settings.local.json
```

Third, rotate anything baked into a published image. Deleting the file from the repo does not recall published tags; one database password in GitGuardian's data sits in 643 tags across seven image repositories.

Run this audit across all five, in one pass:

`.claude/settings.local.json`, the file behind the 23,276 repos.`.dockerignore` to stop it.
A quick pattern probe for the config side:

```
grep -rnE "(PGPASSWORD=|sshpass|Bearer |AKIA[0-9A-Z]{16}|BEGIN (RSA|OPENSSH) PRIVATE KEY)" \
  .claude ~/.claude/settings.json 2>/dev/null
```

It will not catch everything, that is the one-in-five lesson, but it catches the shapes scanners skip.

Four habits remove most of this surface:

`.claude/` and `.env*` to 

```
.claude/
**/.claude/
.env*
**/.env*
```

Per Ground Truth's coverage of CSO's reporting, Boris Cherny, who leads Claude Code, said the tool has not done secrets scrubbing and that built-in secret scanning and an auto mode are being explored. Until something ships there, the file-level discipline is on you.

The extension malware was the loud part, and I covered the config-boundary questions when it dropped. The quiet part is that a bucket of 2025 transcripts contained working credentials at all. Your exposure is probably not a marketplace cache. It is the artifacts your own tooling writes on every machine you own, and the copies of those artifacts you never audited. Rotate first, then audit, then write the rules that keep the vault empty.

If you want the incident timeline and the indicators of compromise, the primary sources are [Cursor's report](https://cursor.com/blog/security-incident-update) and their October 30 security notice. My earlier piece on [what agent configs can and cannot contain](https://dev.to/piekwerk/malware-posed-as-claude-code-in-cursors-marketplace-what-your-agent-config-can-and-cannot-contain-3f61) covers the extension side of this event.
