Between 2:27am and 1:35pm PT on October 29, version 2.1.313 of the Anthropic.claude-code extension in Cursor's VS Code extension marketplace was not what it claimed to be. Cursor published a security notice the same day: the package was not built by Anthropic, at most 5,000 users installed or updated to it, and it ran code that tried to plant a persistent SSH backdoor.
I write about agent configuration all week, so the question I care about is narrow: which of the boundaries we configure in coding agents would have mattered here, and which ones were never in the path. The answer is uncomfortable in one direction and genuinely useful in another.
What actually shipped
The window was eleven hours. During it, a modified build of a widely trusted extension identifier sat in a marketplace that developers treat as infrastructure. Cursor removed the package on detection, then shut the whole VS Code extension marketplace down, audited access, rotated keys, rebuilt the marketplace, and worked with the attacker's hosting provider to take the command server offline. They found no evidence that any other extension was tampered with.
Only 2.1.313 was affected. Other versions and the official distribution channels were not. That is worth stating plainly, because the instinct after an incident like this is to distrust the tool itself. The tool was fine. The distribution path was the problem.
What the payload actually did
The behavior list from the notice reads like a compact spec of what malware does on a developer machine:
- Collected host details: hostname, username, OS, architecture, home directory path, environment variable names, working directories, configured Git email, and public IP. Names, not values, for the environment variables. That distinction matters later when you decide what to rotate.
- Appended an attacker-controlled SSH key, labeled
svc-telemetry, to~/.ssh/authorized_keys. - Tried to enable the local SSH service and open a reverse tunnel to an external host, using options like
StrictHostKeyChecking=noandUserKnownHostsFile=/dev/nullso the connection leaves no host-key trace. - Installed persistence: a LaunchAgent named
com.apple.sysmondon macOS, chosen to impersonate a system label, or a scheduled task calledSysMontrying to run as SYSTEM on Windows, with a startup-script fallback. Background scripts retried the tunnel roughly every 60 seconds. - Reported host details and backdoor status as an HTTP POST to
/beaconon port 8099.
One mercy, documented by Cursor: enabling the SSH service needs admin rights, which the extension did not have on most hosts, so the login-through-the-tunnel step generally failed. The key, the persistence, and the beacon still landed. If you were in the window, you remediate regardless.
The one-minute check
On macOS:
# the marker the payload leaves behind
grep -qs svc-telemetry ~/.ssh/authorized_keys && echo "IMPACTED" || echo "no key found"
# persistence and staging files
ls -la ~/.config/.smt ~/.config/.smtk 2>/dev/null
ls ~/Library/LaunchAgents/ | grep -i sysmond
# which version you actually have
cursor --list-extensions --show-versions | grep claude-code
If the key is found, do not stop at uninstalling the extension. Cursor's notice is explicit that the payload drops standalone persistence scripts that keep running after the extension is gone. Use their remediation scripts for macOS and Windows, consider whether the host should be rebuilt, and rotate credentials and sessions reachable from that machine from a trusted system. The environment variable collection was names-only, but a live tunnel would not have stayed names-only.
Why your agent's rules could not stop this
Here is the honest part. The deny rules, permission modes, and sandbox settings we tune in Claude Code and Cursor govern the agent's tool calls. An editor extension is not a tool call. It runs as your user, at install time, outside every permission system the agent has. No deny rule in settings.json inspects extension activation code.
If your mental model is "my agent is locked down, so my editor environment is contained", this incident is the correction. The agent's boundary and the editor's boundary are two different perimeters, and yesterday only one of them was attacked. I keep seeing setups where the agent config is hardened to a serious standard while the same machine auto-updates a dozen marketplace extensions without review.
The write boundary that landed today
The timing is almost editorial. On October 30, Claude Code 2.1.314 shipped permissions.blockWritesOutsideWorkingDirectories: Write, Edit, and NotebookEdit now refuse paths outside the working directories in every permission mode.
{
"permissions": {
"blockWritesOutsideWorkingDirectories": true
}
}
To be clear about scope: this setting would not have stopped yesterday's package, because the payload was not the agent writing files. What it does is shrink the blast radius of a compromised agent process, so it cannot quietly append to ~/.ssh/authorized_keys itself. After the string of guardrail bypasses patched in 2.1.309 and 2.1.310, I treat write boundaries as load-bearing config, not decoration. The same release also makes claude ultrareview and claude remote-control refuse an untrusted folder immediately when CI is set, which is the same principle applied to entry points.
Marketplaces are code execution with a UI
Every extension install is code execution that happens to have a nice interface. The delivery mechanism here was an update to a trusted identifier, which is why "I only install well-known extensions" is weaker than it sounds. Three practices that hold up better:
Pin what you can, and prefer first-party channels where you control the version. I install Claude Code from npm, so yesterday's package was never a risk to my setup, and my version changes only when I change it.
Verify when it matters. Cursor published SHA-256 hashes for both modified platform builds, and the tell was inside the package: extension.js contained a raw IP literal and the svc-telemetry string. A ten-second grep of a VSIX you are about to trust is not paranoia.
Watch the trust features that agents and platforms are actually shipping, because they are converging on this problem from two sides. GitHub's package firewall for npm installs checks packages before your agent installs them, and the plugin surface inside Claude Code needed its own audit after a 7-line mod overrode a deny rule last month.
What I did within the hour
I ran the grep on every machine I code on, plus the LaunchAgents listing. I listed which marketplace extensions auto-update on each one, because auto-update was the delivery vector here. I enabled blockWritesOutsideWorkingDirectories in the settings I manage, since 2.1.314 is current anyway. And I moved my remaining marketplace installs to a reviewed, pinned list.
None of that is dramatic. It took less time than reading the notice. The lesson I am keeping is the perimeter one: the agent's permission system and the editor's install surface are separate trust boundaries, and a setup that only hardens one of them was never as contained as it looked.
Top comments (0)