All articles
Dev ToolsBy Petru Popa · 6 min read

Claude Code Cursor Extension: Version 2.1.312 Was Clean on One Registry and Backdoored on Another

Cursor widened its advisory to a version that also exists, unmodified, on Open VSX and the Microsoft marketplace. A check that stops at publisher, ID and version cannot tell the two apart.

The write-ups we read of the Claude Code Cursor extension backdoor center on version 2.1.313. That is the easy case: it never existed upstream, so its name gives it away. Cursor's October 30 update added 2.1.312, and that is the harder one. On October 29, 2.1.312 was clean on Open VSX and the Microsoft marketplace and available in tampered form from Cursor's marketplace, with the same publisher, extension ID, version and folder name.

Our claim: any exposure check or install control that stops at publisher, ID and version gives the wrong answer for 2.1.312, and only file contents or a record of origin separate the copies. Two things would show this wrong: evidence that no tampered 2.1.312 ever reached a machine, or a version-keyed control shown to have told the copies apart.

We call the pattern same version, different bytes.

What happened to the Claude Code Cursor extension

Cursor's notice says modified builds for macOS and Windows were available from about 2:27am to 1:35pm PT on October 29, and that Anthropic did not develop them. Phorion's analysis shows code prepended to extension.js that runs when the file loads, before the extension activates. It appends an SSH key with the comment svc-telemetry, installs persistence, opens a reverse tunnel, and reports host details plus the names, not values, of up to 40 credential-like environment variables. Cursor says the attacker's server is now offline. Its notice says it audited access and rotated keys, but not how the attacker got in.

We checked the upstream record ourselves. The Open VSX API dates 2.1.312 to October 27 at 20:26 UTC and 2.1.314 to October 30 at 01:07 UTC, and returns not found for 2.1.313. The Microsoft gallery API shows the same gap. npm has no 2.1.313, and the Claude Code changelog goes from 2.1.312 straight to 2.1.314.

So 2.1.313 can be blocklisted by name, and 2.1.312 cannot. Cursor's own cleanup script says so in a comment: the tampered bundle keeps the real version number, so it matches on content. The folder Phorion lists is named only for version and platform.

StepSecurity's page, read today, still tells readers to act on 2.1.313 and reports the Open VSX 2.1.312 as clean: accurate for Open VSX, easy to misread on a Cursor host. The figure of at most 5,000 affected users, which The Hacker News quotes from Cursor, does not appear on Cursor's notice as we fetched it, and the notice's update line mentions only indicators and affected versions. Treat 5,000 as unconfirmed for the wider scope.

The earlier instance: OVHcloud's CLI

Twelve days earlier, a different vendor on a different channel had the same failure. OVHcloud's advisory says a compromised developer account replaced release assets for v0.11.0 through v0.15.0 on October 19, for about an hour and a half, with roughly 250 downloads. The source and build chain were untouched, and earlier downloads of those versions are unaffected. One version string meant two binaries, depending on when it was fetched.

OVHcloud names the root cause as missing release immutability and says the repository is no longer at risk. The API adds a detail. v0.16.0 reports immutable: true, while v0.15.0 and every older release we read report false, and GitHub's docs say the setting applies only to future releases. The fix protects what ships next; the flag does not show it covering older tags.

Our LiteLLM post covered a neighboring failure, where a new version went through the real pipeline. The tampered 2.1.312 and OVHcloud's five tags needed no new version at all.

Why teams keep walking into it

The documented controls are mostly name checks. Cursor's allowlist restricts by publisher or extension ID. VS Code's extensions.allowed goes further, to version and platform, but its documentation describes no hash option. Cursor's docs say verification confirms publisher identity, and they warn that one ID can point to different code across registries. A pin of 2.1.312, where a product offers one, names the very thing that was reused.

Inventories have the same limit. code --list-extensions --show-versions reports names and versions, and it reports today's state. Cursor warns the payload can keep running after the extension is gone, so a fleet that has since auto-updated can look clean while the persistence remains.

This is the last-mile problem from why enterprise AI doesn't ship: the blocker is rarely the model. The path that delivers an extension with your user's permissions is an integration decision, and it needs an owner. Cursor's docs describe a proxy rather than a passthrough, and its metadata for 2.1.312 redirects to a cursor-cdn.com address. Review it as a separate copy with its own failure modes.

The cheapest way out

First, sweep every machine that ran Cursor, regardless of the version it shows now. We tested these against fixture files in a throwaway home directory, not a real infected host. They only read.

grep -c svc-telemetry ~/.ssh/authorized_keys
ls ~/.config/.smt ~/.config/.smtk ~/Library/LaunchAgents/com.apple.sysmond.plist
grep -l svc-telemetry ~/.*/extensions/anthropic.claude-code-*/extension.js
nc -z -G1 127.0.0.1 22 && echo listening

A count above 0, any listed file, or any matched bundle means treat the host as affected and follow Cursor's remediation. Cursor names macOS and Windows, while Phorion documents Linux persistence too, so run the applicable lines there as well.

The last line matters. Cursor says login through the tunnel would fail because turning on SSH needs admin rights. That covers machines where SSH was off. Phorion notes the backdoor is ready wherever Remote Login was already enabled, and Cursor's script prints a warning in that case. On those hosts, look for logins from localhost using the planted key, and rotate reachable credentials.

Second, set Cursor's Marketplace Install Cooldown, which defaults to 0, to 48 hours. Twelve hours would outlast the 11-hour-8-minute window for a brand-new 2.1.313. But the real 2.1.312 was already about 37 hours old when the window opened, and nobody has published whether the tampered copy carried that date. 48 hours covers both readings. This advice is wrong if your team needs same-day extension security fixes more than it fears this attack. Do not count on signature verification alone: Cursor's metadata serves the package, signature and public key from one proxy host, and its extensions page does not say where the client anchors trust.

Third, check the release flag on the binaries you install:

gh api repos/OWNER/REPO/releases/latest --jq .immutable

Across the 100 most-starred repositories under topic:cli with at least 5,000 stars whose latest release carries assets, 15 returned true today. For the rest, that flag offers no protection against the swap OVHcloud saw, so record a hash at install time.

Ready to start?

Turn this into a plan for your team.

One week, fixed fee: a working session with your team, a prioritized use-case backlog, and an ROI model for the opportunities worth chasing.

Book an AI Opportunity Sprint