Dev.to AI 🤖 Ai 👁 0 📖 6 min read

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

Cursor published its final incident report 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

Cursor published its final incident report 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 chain, in five steps

The report reconstructs the attack like this:

  1. An attacker obtained an access key Cursor had issued to Fireworks, an inference provider. The key was exposed on Fireworks' side.
  2. With that key, the attacker copied data from a read-only storage bucket Cursor used to share training data with Fireworks. The bucket held coding agent traces, mostly from 2025, from sessions that were not in Privacy Mode.
  3. A small number of those traces contained credentials that had not been redacted. Cursor says less than 0.01% of customers had API credentials exposed this way.
  4. Starting from those credentials, the attacker reached SaaS tools Cursor uses internally, then archived source repositories, support data, email conversations, and some credentials users had sent to the support team.
  5. On October 29, using a stolen credential from that data, the attacker placed malicious builds of the Claude Code extension directly on the cache that served the marketplace. Open VSX never carried them, and Anthropic published nothing. At most 5,000 users who requested specific cached versions could have received the bad build, which lived for about 11 hours. BleepingComputer reported the payload installed persistent SSH backdoors.

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.

Two numbers worth sitting with

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.

The same leak, sitting on your disk

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 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 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:

  • The shared team file, .claude/settings.json, carried another 8,271 secrets, 1,283 of them valid, in GitGuardian's tally relayed by Ground Truth.
  • Public Docker Hub images held 613 distinct secrets in Claude Code settings files across 543 image repositories, because a Dockerfile COPY . . ignores your .gitignore. One valid Cloudflare token appeared across 559 image tags.

Why your scanner misses one in five

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, 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.

A rotation order that matches the chain

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.

Five places credentials sit in plain text

Run this audit across all five, in one pass:

  1. Agent traces and transcripts, local session logs included. You are checking for commands that carried a token or password inline.
  2. .claude/settings.local.json, the file behind the 23,276 repos.
  3. .claude/settings.json, committed on purpose by teams, so it spreads by design.
  4. Container images, via a COPY . . that had no .dockerignore to stop it.
  5. Support conversations and shared debugging threads.

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.

Rules that shrink the vault

Four habits remove most of this surface:

  • Never approve a secret-bearing command with "don't ask again". Write the allow rule as a pattern without the secret, and pass the secret as an environment variable.
  • Add .claude/ and .env* to .dockerignore in every image that builds from a repo where agents run:
.claude/
**/.claude/
.env*
**/.env*
  • Treat agent config files as secret-bearing in CI, with pattern checks for command-line secret shapes, not just key-format detectors. I keep a 22-rule scanner for exactly this class of file.
  • Decide about traces the way you decide about screen recordings. If a command would be dangerous to replay in public, it does not belong in any session that logs it.

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.

What I took from the report

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 and their October 30 security notice. My earlier piece on what agent configs can and cannot contain covers the extension side of this event.

📰 Read the original article on Dev.to AI

Originally published by Dev.to AI. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.