Cursor Marketplace Backdoor: VS Code Fork Trust Problem
The Claude Code extension backdoor on the Cursor Marketplace shows why VS Code forks can't inherit Microsoft's extension trust model. What to check now.

A developer opens Cursor on a Tuesday morning, sees an update to the Claude Code extension, and lets it install. Nothing looks different. The extension loads, the AI assistant works, and by lunch the machine has an attacker's Ed25519 key in ~/.ssh/authorized_keys, a reverse SSH tunnel beaconed out to a server on the internet, and a LaunchAgent masquerading as an Apple system process keeping the whole thing alive across reboots. That's the Cursor Marketplace backdoor that Phorion disclosed, and it's the cleanest demonstration yet of something those of us in security have been muttering about for years: VS Code forks inherit Microsoft's codebase, but they don't inherit Microsoft's trust model.
I've spent a good chunk of my career doing incident response on developer machines, and this incident hits a nerve because of how ordinary the delivery mechanism was. No phishing. No zero-day. Just a tampered extension bundle, versions 2.1.312 and 2.1.313, published to a marketplace that developers trust by default because it looks like the thing they already trust. Anthropic's own channels were clean — someone used access to Cursor's publishing pipeline to ship a modified copy of an extension Anthropic never built for that registry. The blast radius was every Cursor user who auto-updated in an eleven-hour window.
What the Backdoor Actually Did
The implant was an immediately invoked function expression prepended to the minified extension.js bundle. Everything after the injected prefix looked like the normal extension — same activate and deactivate exports, same UI code, same strings. That's the part that should worry you if your detection strategy is "diff the bundle against upstream": the malicious part is a few hundred bytes of extra JavaScript sitting on top of an otherwise legitimate megabytes-long file. It runs the moment Node loads the module, before the extension's own activate callback fires. The user doesn't need to click anything, invoke Claude, or interact with the extension at all.
The payload itself is a four-stage job, and honestly it's competent work:
- Key planting. Appends an attacker-controlled Ed25519 public key, commented
svc-telemetry, to~/.ssh/authorized_keyswith correct 0700/0600 permissions. - SSH enablement. Tries to make sure sshd is running — systemctl on Linux, Remote Login on macOS, OpenSSH Server via PowerShell on Windows.
- Reverse tunnel. Opens an outbound SSH connection to an operator-controlled server with
-R *:0:localhost:22, so the attacker can reach back through the tunnel to the victim's port 22 and log in with the planted key. No custom C2 protocol needed — SSH is the C2. - Persistence and recon. A user LaunchAgent named
com.apple.sysmond.pliston macOS, an @reboot cron entry on Linux, a Startup folder fallback on Windows. Then a one-off beacon with hostname, username, git email, public IP, and up to forty environment variable names matching/KEY|TOKEN|SECRET|PASS|AWS|GCP/i.
That last item is clever in a way worth understanding. The beacon doesn't exfiltrate your AWS secret key — it exfiltrates the fact that you have one. It's a targeting inventory. The operator gets a menu of which compromised machines are cloud engineers with live credentials in their environment, which is exactly who you go back to first through the tunnel.
The macOS Bug That Accidentally Saved People
Here's the detail I find darkly funny as an incident responder: the malware's macOS enablement path is broken by a classic scripting mistake. The code runs systemsetup -getremotelogin as the unprivileged user and only attempts to turn Remote Login on if the output matches /Off/i. But on macOS, that command refuses to run without administrator access. It prints "You need administrator access to run this tool... exiting!" to stdout — and exits with status 0.
// what the malware effectively does
var rl = ex("systemsetup -getremotelogin 2>/dev/null", 4000);
if (/Off/i.test(rl)) {
det("sudo -n systemsetup -setremotelogin on >/dev/null 2>&1");
}
The error string doesn't contain "Off", so the regex fails, so the enablement step never runs. The script treats "I don't have permission to check whether SSH is off" as "SSH is already on." Exit code 0 from a failing command is one of the oldest footguns in shell scripting, and it bit the malware author instead of a sysadmin this time. On a Mac without Remote Login already enabled, the reverse tunnel connects out fine, but anything forwarded back hits a closed port 22. The keys and persistence still get dropped — the machine is still compromised — but the interactive access path is dead on arrival.
Do not take comfort from this. If the user already had Remote Login on, or ever turns it on later, the planted key is sitting in authorized_keys waiting. The Windows path has a similar bug (it pipes a PowerShell command through a helper that hardcodes /bin/sh), and the Linux cron logic can wipe a user's existing crontab on repeat runs. Malware written by humans has human bugs. Next month's won't.
Why Forked Marketplaces Can't Inherit Microsoft's Trust Model
This is the structural point, and it's the one I want engineering leads to sit with. When you install an extension from the official Visual Studio Marketplace, there's a real pipeline behind it: publisher accounts with domain verification, scanning on publish, takedown processes, and a company with a legal department on the other end. It's far from perfect — malicious extensions have slipped through it — but it's a system.
VS Code forks like Cursor can't use that marketplace for licensing reasons, so they point at alternative registries — Open VSX or their own infrastructure. The editor UI is byte-for-byte familiar. The extension page shows the same name, the same icon, a plausible publisher string. But every control that made the original marketplace trustworthy is a reimplementation, or an absence. Who verified the publisher? Who scanned the bundle? Was the artifact built from the source repo it claims, and can you check? In this incident, the tampered versions apparently never even existed on Microsoft's registry — they only lived on the fork's. The trust was purely visual.
We watched the same movie in the package-registry world. The LiteLLM PyPI compromise worked because publishing access is the weak point, not the code. Extensions are worse than packages in one important way: there's no lockfile culture, no npm ci reproducibility, no hash pinning. The editor auto-updates silently, and as one Hacker News commenter discovered, VS Code's auto-update delay doesn't even apply to "trusted publishers" — a list you can't configure. Your extension set is a live, unpinned dependency tree that runs with your full user privileges and updates itself while you sleep.

AI Coding Tools Are a Privilege Escalation You Chose
Strip away the marketplace drama and look at what the target actually was. An AI coding extension isn't a color theme. It runs with the IDE's permissions — which are your permissions — and its entire job is to read your source tree, hold your API credentials, and increasingly execute shell commands on your behalf. When we wrote about how agentic coding CLIs handle permissions, the recurring theme was that the useful agent and the dangerous agent are separated by policy, not capability. An extension that can run child_process is one bad publish away from running anything.
One commenter put it bluntly: everyone using these tools has effectively been running curl-pipe-bash daily, and we spent a decade telling junior devs not to do that. It's hyperbole, but only slightly. The difference between curl | bash and an auto-updating agentic extension is that the extension has a nicer UI. I've argued before that CLI tools shouldn't install background daemons by default; this incident is what happens when the daemon is installed by someone else and you never consented at all. A detached ssh process surviving IDE exit is exactly the behavior pattern I flag in reviews, shipped by an attacker through a trusted update channel.
How to Check Your Fleet Right Now
If anyone in your org runs Cursor, treat this as a live incident, not a news story. The high-signal indicators, straight from the forensic write-up:
# macOS / Linux: the planted key is the cleanest signal
grep -s svc-telemetry ~/.ssh/authorized_keys && echo "IMPACTED" || echo "key not found"
# Check persistence artifacts
ls ~/Library/LaunchAgents/com.apple.sysmond.plist 2>/dev/null # macOS
ls ~/.config/.smt ~/.config/.smtk 2>/dev/null # macOS + Linux
crontab -l 2>/dev/null | grep -F .smt # Linux @reboot entry
# Windows (PowerShell): check both authorized_keys locations
Select-String svc-telemetry "$HOME\.ssh\authorized_keys",
"C:\ProgramData\ssh\administrators_authorized_keys" -ErrorAction SilentlyContinue
# Live tunnel: look for detached ssh with remote forwarding
ps aux | grep -E 'ssh.*-R' | grep -v grep
A few hunting notes from experience. First, check the key fingerprint, not just the comment — the implant dedupes on the comment string svc-telemetry, and so might a cleanup attempt. Second, the tunnel key file lives in the temp directory with a name like .stk_ followed by ten hex chars derived from an MD5 of hostname plus home path; it's deterministic per machine, so you can compute the exact filename and sweep for it. Third, EDR process telemetry won't show the macOS enablement attempt for standard users, because the bug means it never executes. Absence of systemsetup -setremotelogin in your logs does not mean absence of compromise. The key and the LaunchAgent are the ground truth.
If you find the key, the machine gets the full IR treatment: rotate every credential whose environment variable name could have been beaconed (which is most of them — the regex catches AWS, GCP, anything with KEY or TOKEN in the name), revoke the machine's access, and reimage. Don't just delete the key and call it done. The operator had, or attempted to have, interactive shell access; you cannot enumerate what they touched from the implant's code alone. This is also where the blind spots in cloud authentication logging bite — check your cloud audit logs for console or API use from the window between extension update and disclosure, keyed off the affected user's credentials.

Hardening: Contain the Editor, Not Just the Extension
Post-incident is when everyone asks what to change. My position, and I'll defend it: you will never audit your way to a safe extension ecosystem, so you contain instead. We covered the general trade-off in containment vs monitoring for AI agents, and this incident is the containment case study. Monitoring told you nothing here until a third-party security firm happened to dissect the bundle.
- Pin extension versions and disable auto-update for anything with broad permissions, and apply the same rule across forks. Yes, you'll inherit update fatigue. That's cheaper than incident response.
- Egress filtering on developer workstations. The entire access model here depended on an outbound SSH connection to a VPS IP. If your dev machines can't open arbitrary outbound port 22, the tunnel stage dies. Almost nobody does this, and it would have neutered this implant completely.
- Watch SSH state as a canary. Alert on Remote Login flipping on, new keys in authorized_keys, and sshd processes on machines that have no business running sshd.
- Inventory your AI tooling like you inventory servers. Know which extensions and agent CLIs are approved, what permissions they hold, and which marketplace they come from. If the answer to "which registry do our Cursor installs use" is a shrug, that's your next quarter's project.
- Prefer agents that run in sandboxes or containers where feasible, with credentials injected per-session rather than ambient in the environment — the beacon's env-name scraping works precisely because we leave secrets lying around in process environments.
An extension marketplace without its own verification pipeline isn't a marketplace — it's a file host with a install button.
The Uncomfortable Takeaway
Anthropic didn't get breached. Cursor's marketplace did, and Anthropic's brand absorbed the damage anyway, because to a developer the extension is the product regardless of who published which build. That's the economics of forked ecosystems: the cheapest way to hit a high-value target is to attack the weakest registry that target's users trust. AI coding assistants sit at the perfect intersection — enormous install base, maximum local privilege, credentials everywhere, and a supply chain that forks and re-hosts their artifacts across registries with wildly different security postures.
This one ended with the malicious versions pulled in about eleven hours and a macOS bug limiting the damage. The response was fine. The architecture is the problem, and it hasn't changed: your editor is a package manager that runs everything it installs as you, updates it silently, and in a growing number of setups gets its packages from a registry nobody in your security team has ever assessed. Fix the egress rules, pin the extensions, and inventory the agents. The next publisher-compromise won't have a regex bug saving your users.


