Fake Claude Code Extension in Cursor: Hunt the SSH Backdoor


What this means for your clients: an add-on with a trusted brand name ran in a developer's session and tried to open a way back in. Developer laptops often hold code and cloud credentials.
On October 29, anyone who installed or updated Claude Code in Cursor's extension marketplace may have received a fake Claude Code extension, tampered Anthropic.claude-code 2.1.312 or 2.1.313 that Cursor says Anthropic did not develop. Cursor says those macOS and Windows builds were available from about 2:27 AM to 1:35 PM Pacific time. The extension kept working while its loader tried to plant an SSH key, open a reverse tunnel and set up startup mechanisms to relaunch the connection.
Cursor pulled the versions, shut the marketplace down, found no other extension tampered with, and rebuilt. Microsoft says the packages were not published or distributed through the Visual Studio Marketplace or the official Claude Code npm package. Anthropic told Microsoft its systems were not compromised. Microsoft says all evidence so far suggests the incident is limited to Cursor's marketplace. Neither notice lists a CVE, how the tampered build was published, or how many hosts were hit. Microsoft says its ATT&CK mapping does not establish attribution.
Check where the build came from
The tampered build used the real extension ID, Anthropic.claude-code.
Microsoft says the loader collected names containing terms such as KEY, TOKEN, SECRET, PASS, AWS or GCP, up to forty, but not their values; those names reveal which kinds of secrets are configured. The tunnel connects outward, so inbound firewall rules alone may not stop it.
How the loader worked
Microsoft says the injected JavaScript runs on module load, before normal code, and suppresses errors. It collects hostname, user, OS, architecture, home and working directories, and the global Git email; Cursor adds public IP. Microsoft says it downloads no further executable.
It checks authorized_keys for the comment svc-telemetry and, if missing, appends an Ed25519 key. A separate embedded private key lands in a hidden temp file named .stk_ plus ten hex characters. SSH uses that key to connect out to tunnel@134[.]209[.]82[.]225 and reverse-forward local port 22.
Microsoft says it prefers autossh on Unix, a hidden PowerShell loop on Windows, and a sixty-second retry after SSH exits. A delayed callback POSTs host details to hxxp://134[.]209[.]82[.]225:8099/beacon; Cursor also lists a local beacon attempt to 127[.]0[.]0[.]1:8099/beacon.
Persistence lands outside the extension folder: on macOS a LaunchAgent at ~/Library/LaunchAgents/com.apple.sysmond.plist plus ~/.config/.smt and ~/.config/.smtk; on Windows an attempted scheduled task named SysMon that requests SYSTEM, with a sysmon.cmd Startup fallback and smt.cmd and smtk under %USERPROFILE%\.config.
Cursor lists affected builds as Windows and macOS, yet its indicators include Linux paths and an @reboot cron entry. Microsoft describes Linux cron persistence as code behavior. Neither says a Linux build shipped.
What failed, and why uninstalling is not enough
Cursor says turning on the local SSH service needs admin rights the extension lacks on most macOS and Windows hosts, and errors in the code also break that step, so the attacker could not log in through the tunnel on those hosts. Cursor says the extension added the attacker's SSH key, installed persistence and sent host details to the attacker; Microsoft says keys and persistence may be written even when the tunnel fails. Hosts already running an SSH server did not need that step, so review their SSH logins. Cursor says the attacker server no longer functions. Microsoft adds that a running ssh process does not prove access.
Removing the extension leaves the key in authorized_keys, the .stk_ and .config files, and any LaunchAgent, scheduled task, Startup script or cron entry set up to relaunch them.
What to do
Preserve packages, keys, scripts and startup entries, then isolate hosts with evidence. Run Cursor's check for svc-telemetry in authorized_keys and, on Windows, administrators_authorized_keys, then confirm by fingerprint SHA256:0Z6R+DF64Do2nwQic8a7kkfDLNNk4IiK+2U2Rci4htk before removing that line. Cursor lists the tunnel key fingerprint as SHA256:KOAU57+ZG6u3MG4WwzPzPX2O5YEe9qdOC1Bl+6MVyDQ; .stk_ files should match it, and .smtk likely does.
Then remove the tampered extension, key, persistence, tunnel processes and .stk_ files, or run Cursor's remediation scripts, and confirm nothing returns after login or restart. Rotate credentials and revoke sessions from a clean device, and rebuild when device trust cannot be restored.
Microsoft points to Cursor's hardening guidance: approved publishers only, and an install cooldown that delays newly published versions, which Microsoft says can help limit exposure to malicious releases. A publisher allowlist alone would likely not have stopped a build under the Anthropic.claude-code ID.
We covered a similar pattern in our LiteLLM supply chain hunt.
Hunts
The hunts combine adaptations of Microsoft's network and file hunts with our own queries based on the reported behavior. Every hunt covers 30 days; set CrowdStrike and Kibana time pickers to match. We have not run these live; treat hits as leads.
Test query
Splunk
index=sysmon (EventCode=1 OR EventCode=3 OR EventCode=11) earliest=-30d
| eval proc=lower(replace(Image, "^.*[\\\\/]", ""))
| where like(proc, "%cursor%") OR proc="node" OR proc="node.exe"
| eval kind=case(EventCode=1, "process", EventCode=3, "network", true(), "file")
| fillnull value="-" host proc kind
| stats count as events by host proc kindElastic
FROM logs-endpoint.events.network-*, logs-endpoint.events.file-*, logs-endpoint.events.process-*
| WHERE @timestamp >= NOW() - 30 days
| EVAL proc = TO_LOWER(COALESCE(TO_STRING(process.name), "-")), kind = TO_STRING(event.category)
| WHERE LOCATE(proc, "cursor") > 0 OR proc IN ("node", "node.exe")
| STATS events = COUNT(*) BY host.name, proc, kindCrowdStrike
#event_simpleName=/^(NetworkConnectIP4|NewScriptWritten|NewExecutableWritten|ProcessRollup2)$/
| case { #event_simpleName=ProcessRollup2 | proc := FileName ; * | proc := ContextBaseFileName }
| proc=/cursor|^node(\.exe)?$/i
| match(file="aid_master_main.csv", field=aid, include=ComputerName, strict=false)
| groupBy([ComputerName, proc, #event_simpleName], function=count(as=events))Microsoft KQL
union
(DeviceNetworkEvents | extend kind = "network", proc = tolower(InitiatingProcessFileName)),
(DeviceFileEvents | extend kind = "file", proc = tolower(InitiatingProcessFileName)),
(DeviceProcessEvents | extend kind = "process", proc = tolower(FileName))
| where Timestamp > ago(30d)
| where proc contains "cursor" or proc in ("node", "node.exe")
| summarize events = count() by DeviceName, proc, kindSplunk assumes Sysmon for Windows and Linux in index=sysmon, so Macs need another source. Elastic assumes Elastic Defend. CrowdStrike and Elastic field names below are unconfirmed, and Falcon misses many writes. ES|QL defaults to 1,000 rows; add LIMIT if needed.
Hunt 1: Connections to the attacker IP or port 8099
Splunk
index=sysmon EventCode=3 earliest=-30d (DestinationIp="134.209.82.225" OR DestinationPort=8099)
| fillnull value="-" Image
| stats count as events min(_time) as first_seen by host Image DestinationIp DestinationPortElastic
FROM logs-endpoint.events.network-*
| WHERE @timestamp >= NOW() - 30 days
| WHERE TO_STRING(destination.ip) == "134.209.82.225" OR destination.port == 8099
| EVAL proc = COALESCE(TO_STRING(process.name), "-")
| STATS events = COUNT(*), first_seen = MIN(@timestamp) BY host.name, proc, destination.ip, destination.portCrowdStrike
#event_simpleName=NetworkConnectIP4
| case { RemoteAddressIP4="134.209.82.225" | hit := 1 ; RemotePort=8099 | hit := 1 ; * | hit := 0 }
| hit=1
| default(field=ContextBaseFileName, value="-")
| match(file="aid_master_main.csv", field=aid, include=ComputerName, strict=false)
| groupBy([ComputerName, ContextBaseFileName, RemoteAddressIP4, RemotePort], function=[count(as=events), min(@timestamp, as=first_seen)])Microsoft KQL
DeviceNetworkEvents
| where Timestamp > ago(30d)
| where RemoteIP == "134.209.82.225" or RemotePort == 8099
| extend InitiatingProcessFileName = iif(isempty(InitiatingProcessFileName), "-", InitiatingProcessFileName)
| summarize events = count(), first_seen = min(Timestamp) by DeviceName, InitiatingProcessFileName, RemoteIP, RemotePortSplunk needs Sysmon Event ID 3 enabled and retained; it is off by default. Sysmon network events lack command lines, so all versions report process name only. We dropped Microsoft's RemoteUrl test so all four versions match; behind a proxy, also search proxy logs for 134.209.82.225. The Falcon version is IPv4 only. Expect noise on 8099.
Hunt 2: Payload hash and persistence file names
Splunk
index=sysmon EventCode=11 earliest=-30d
| eval fname=lower(replace(TargetFilename, "^.*[\\\\/]", ""))
| where in(fname, ".smt", ".smtk", "smt.cmd", "smtk", "sysmon.cmd", "com.apple.sysmond.plist") OR match(fname, "^\\.stk_")
| fillnull value="-" Image TargetFilename
| stats count as events min(_time) as first_seen by host Image TargetFilenameElastic
FROM logs-endpoint.events.file-*
| WHERE @timestamp >= NOW() - 30 days
| EVAL fname = TO_LOWER(COALESCE(TO_STRING(file.name), "-")), sha = TO_LOWER(COALESCE(TO_STRING(file.hash.sha256), "-"))
| WHERE sha == "ecb58cb29078d9646ad1dcdbe51526f2d9cfea06be5fb2ab183e8ecc3ab6bf15" OR sha == "7e0a52885fadee05544a9e99eb18c792bec05900a5e1a66d68f8383decb0e278" OR fname IN (".smt", ".smtk", "smt.cmd", "smtk", "sysmon.cmd", "com.apple.sysmond.plist") OR STARTS_WITH(fname, ".stk_")
| EVAL proc = COALESCE(TO_STRING(process.name), "-"), fpath = COALESCE(TO_STRING(file.path), "-")
| STATS events = COUNT(*), first_seen = MIN(@timestamp) BY host.name, proc, fpathCrowdStrike
#event_simpleName=/Written$/
| regex("(?<fname>[^\\\\/]+)$", field=TargetFileName, strict=false)
| case {
SHA256HashData=/^(ecb58cb29078d9646ad1dcdbe51526f2d9cfea06be5fb2ab183e8ecc3ab6bf15|7e0a52885fadee05544a9e99eb18c792bec05900a5e1a66d68f8383decb0e278)$/i | hit := 1 ;
fname=/^(\.smt|\.smtk|smt\.cmd|smtk|sysmon\.cmd|com\.apple\.sysmond\.plist)$/i | hit := 1 ;
fname=/^\.stk_/i | hit := 1 ;
* | hit := 0 }
| hit=1
| default(field=[ContextBaseFileName, TargetFileName], value="-")
| match(file="aid_master_main.csv", field=aid, include=ComputerName, strict=false)
| groupBy([ComputerName, ContextBaseFileName, TargetFileName], function=[count(as=events), min(@timestamp, as=first_seen)])Microsoft KQL
DeviceFileEvents
| where Timestamp > ago(30d)
| extend fname = tolower(FileName)
| where SHA256 in ("ecb58cb29078d9646ad1dcdbe51526f2d9cfea06be5fb2ab183e8ecc3ab6bf15", "7e0a52885fadee05544a9e99eb18c792bec05900a5e1a66d68f8383decb0e278") or fname in (".smt", ".smtk", "smt.cmd", "smtk", "sysmon.cmd", "com.apple.sysmond.plist") or fname startswith ".stk_"
| extend InitiatingProcessFileName = iif(isempty(InitiatingProcessFileName), "-", InitiatingProcessFileName)
| summarize events = count(), first_seen = min(Timestamp) by DeviceName, InitiatingProcessFileName, FolderPath, FileName, SHA256Sysmon Event ID 11 carries no file hash, so Splunk matches names only. ecb58cb2... is extension.js (Cursor and Microsoft) and 7e0a5288... is the decoded tunnel private key file (Microsoft). Neither is a VSIX hash. Elastic file.hash.sha256 and Defender SHA256 are often empty; then names still match, so use Hunt 6 and hash preserved files. Cursor's four VSIX hashes are in Sources.
Hunt 3: authorized_keys writes by Cursor, node or a shell
Splunk
index=sysmon EventCode=11 earliest=-30d
| eval fname=lower(replace(TargetFilename, "^.*[\\\\/]", "")), proc=lower(replace(Image, "^.*[\\\\/]", ""))
| where in(fname, "authorized_keys", "administrators_authorized_keys")
| where like(proc, "%cursor%") OR in(proc, "node", "node.exe", "sh", "bash", "zsh", "cmd.exe", "powershell.exe", "pwsh.exe")
| fillnull value="-" TargetFilename
| stats count as events min(_time) as first_seen by host proc TargetFilenameElastic
FROM logs-endpoint.events.file-*
| WHERE @timestamp >= NOW() - 30 days
| EVAL fname = TO_LOWER(COALESCE(TO_STRING(file.name), "-")), proc = TO_LOWER(COALESCE(TO_STRING(process.name), "-"))
| WHERE fname IN ("authorized_keys", "administrators_authorized_keys")
| WHERE event.action != "deletion"
| WHERE LOCATE(proc, "cursor") > 0 OR proc IN ("node", "node.exe", "sh", "bash", "zsh", "cmd.exe", "powershell.exe", "pwsh.exe")
| EVAL fpath = COALESCE(TO_STRING(file.path), "-")
| STATS events = COUNT(*), first_seen = MIN(@timestamp) BY host.name, proc, fpathCrowdStrike
#event_simpleName=/Written$/
| regex("(?<fname>[^\\\\/]+)$", field=TargetFileName, strict=false)
| fname=/^(administrators_)?authorized_keys$/i
| ContextBaseFileName=/cursor|^(node|node\.exe|sh|bash|zsh|cmd\.exe|powershell\.exe|pwsh\.exe)$/i
| match(file="aid_master_main.csv", field=aid, include=ComputerName, strict=false)
| groupBy([ComputerName, ContextBaseFileName, TargetFileName], function=[count(as=events), min(@timestamp, as=first_seen)])Microsoft KQL
DeviceFileEvents
| where Timestamp > ago(30d)
| extend fname = tolower(FileName), proc = tolower(InitiatingProcessFileName)
| where fname in ("authorized_keys", "administrators_authorized_keys")
| where ActionType in ("FileCreated", "FileModified")
| where proc contains "cursor" or proc in ("node", "node.exe", "sh", "bash", "zsh", "cmd.exe", "powershell.exe", "pwsh.exe")
| summarize events = count(), first_seen = min(Timestamp) by DeviceName, proc, FolderPathSysmon and Falcon file events lack a parent field, so tie shell hits to Cursor by hand. Sysmon logs creation, not appends, so Splunk only catches hosts with no prior authorized_keys; CrowdStrike likely catches nothing. Elastic's deletion filter is unconfirmed.
Hunt 4: SSH tunnels with incident markers or selected options
Splunk
index=sysmon EventCode=1 earliest=-30d
| eval proc=lower(replace(Image, "^.*[\\\\/]", "")), parent=lower(replace(ParentImage, "^.*[\\\\/]", "")), cmd=lower(CommandLine)
| where in(proc, "ssh", "ssh.exe", "autossh") AND (like(cmd, "% -r %") OR like(cmd, "%tunnel@134.209.82.225%") OR match(cmd, "\\.stk_"))
| where like(cmd, "%stricthostkeychecking=no%") OR like(cmd, "%stricthostkeychecking no%") OR like(cmd, "%exitonforwardfailure%") OR in(parent, "powershell.exe", "pwsh.exe") OR like(cmd, "%tunnel@134.209.82.225%") OR match(cmd, "\\.stk_")
| fillnull value="-" parent CommandLine
| stats count as events min(_time) as first_seen values(CommandLine) as cmdlines by host proc parentElastic
FROM logs-endpoint.events.process-*
| WHERE @timestamp >= NOW() - 30 days
| EVAL proc = TO_LOWER(COALESCE(TO_STRING(process.name), "-")), parent = TO_LOWER(COALESCE(TO_STRING(process.parent.name), "-")), cmd = TO_LOWER(COALESCE(TO_STRING(process.command_line), "-")), cmd_orig = COALESCE(TO_STRING(process.command_line), "-")
| WHERE proc IN ("ssh", "ssh.exe", "autossh") AND (LOCATE(cmd, " -r ") > 0 OR LOCATE(cmd, "tunnel@134.209.82.225") > 0 OR LOCATE(cmd, ".stk_") > 0)
| WHERE LOCATE(cmd, "stricthostkeychecking=no") > 0 OR LOCATE(cmd, "stricthostkeychecking no") > 0 OR LOCATE(cmd, "exitonforwardfailure") > 0 OR parent IN ("powershell.exe", "pwsh.exe") OR LOCATE(cmd, "tunnel@134.209.82.225") > 0 OR LOCATE(cmd, ".stk_") > 0
| STATS events = COUNT(*), first_seen = MIN(@timestamp), cmdlines = VALUES(cmd_orig) BY host.name, proc, parentCrowdStrike
#event_simpleName=ProcessRollup2
| FileName=/^(ssh|ssh\.exe|autossh)$/i
| case {
CommandLine=/ -r /i | pass := 1 ;
CommandLine=/tunnel@134\.209\.82\.225/i | pass := 1 ;
CommandLine=/\.stk_/i | pass := 1 ;
* | pass := 0 }
| pass=1
| case {
CommandLine=/stricthostkeychecking=no/i | hit := 1 ;
CommandLine=/stricthostkeychecking no/i | hit := 1 ;
CommandLine=/exitonforwardfailure/i | hit := 1 ;
ParentBaseFileName=/^(powershell|pwsh)\.exe$/i | hit := 1 ;
CommandLine=/tunnel@134\.209\.82\.225/i | hit := 1 ;
CommandLine=/\.stk_/i | hit := 1 ;
* | hit := 0 }
| hit=1
| default(field=ParentBaseFileName, value="-")
| match(file="aid_master_main.csv", field=aid, include=ComputerName, strict=false)
| groupBy([ComputerName, FileName, ParentBaseFileName], function=[count(as=events), min(@timestamp, as=first_seen), collect(CommandLine)])Microsoft KQL
DeviceProcessEvents
| where Timestamp > ago(30d)
| extend proc = tolower(FileName), parent = tolower(InitiatingProcessFileName), cmd = tolower(ProcessCommandLine)
| where proc in ("ssh", "ssh.exe", "autossh") and (cmd contains " -r " or cmd contains "tunnel@134.209.82.225" or cmd contains ".stk_")
| where cmd contains "stricthostkeychecking=no" or cmd contains "stricthostkeychecking no" or cmd contains "exitonforwardfailure" or parent in ("powershell.exe", "pwsh.exe") or cmd contains "tunnel@134.209.82.225" or cmd contains ".stk_"
| summarize events = count(), first_seen = min(Timestamp), cmdlines = make_set(ProcessCommandLine) by DeviceName, proc, parentThe parent branch skips a hidden-PowerShell check. The -r match misses glued forms such as -R0:host:22, so the queries also look for tunnel@134.209.82.225 and .stk_. Generic reverse tunnels without these markers are not covered.
Hunt 5: Selected persistence and SSH setup commands
Cleanup commands can match too.
Splunk
index=sysmon EventCode=1 earliest=-30d
| eval cmd=lower(CommandLine)
| eval hit=case(like(cmd, "%schtasks%") AND like(cmd, "%/create%") AND like(cmd, "%sysmon%"), "schtask_sysmon", like(cmd, "%com.apple.sysmond%"), "launchagent", like(cmd, "%.config/.smt%") OR match(cmd, "\\.config\\\\smt"), "smt_script", like(cmd, "%-setremotelogin on%"), "macos_ssh_on", like(cmd, "%add-windowscapability%") AND like(cmd, "%openssh.server%"), "win_sshd_add", true(), "none")
| where hit!="none"
| stats count as events min(_time) as first_seen values(CommandLine) as cmdlines by host hitElastic
FROM logs-endpoint.events.process-*
| WHERE @timestamp >= NOW() - 30 days
| EVAL cmd = TO_LOWER(COALESCE(TO_STRING(process.command_line), "-")), cmd_orig = COALESCE(TO_STRING(process.command_line), "-")
| EVAL hit = CASE(LOCATE(cmd, "schtasks") > 0 AND LOCATE(cmd, "/create") > 0 AND LOCATE(cmd, "sysmon") > 0, "schtask_sysmon", LOCATE(cmd, "com.apple.sysmond") > 0, "launchagent", LOCATE(cmd, ".config/.smt") > 0 OR LOCATE(cmd, ".config\\smt") > 0, "smt_script", LOCATE(cmd, "-setremotelogin on") > 0, "macos_ssh_on", LOCATE(cmd, "add-windowscapability") > 0 AND LOCATE(cmd, "openssh.server") > 0, "win_sshd_add", "none")
| WHERE hit != "none"
| STATS events = COUNT(*), first_seen = MIN(@timestamp), cmdlines = VALUES(cmd_orig) BY host.name, hitCrowdStrike
#event_simpleName=ProcessRollup2
| case {
CommandLine=/(schtasks.*\/create.*sysmon|schtasks.*sysmon.*\/create)/i | hit := "schtask_sysmon" ;
CommandLine=/com\.apple\.sysmond/i | hit := "launchagent" ;
CommandLine=/\.config(\/\.|\\)smt/i | hit := "smt_script" ;
CommandLine=/-setremotelogin on/i | hit := "macos_ssh_on" ;
CommandLine=/(add-windowscapability.*openssh\.server|openssh\.server.*add-windowscapability)/i | hit := "win_sshd_add" ;
* | hit := "none" }
| hit!="none"
| match(file="aid_master_main.csv", field=aid, include=ComputerName, strict=false)
| groupBy([ComputerName, hit], function=[count(as=events), min(@timestamp, as=first_seen), collect(CommandLine)])Microsoft KQL
DeviceProcessEvents
| where Timestamp > ago(30d)
| extend cmd = tolower(ProcessCommandLine)
| extend hit = case(cmd contains "schtasks" and cmd contains "/create" and cmd contains "sysmon", "schtask_sysmon", cmd contains "com.apple.sysmond", "launchagent", cmd contains ".config/.smt" or cmd contains @".config\smt", "smt_script", cmd contains "-setremotelogin on", "macos_ssh_on", cmd contains "add-windowscapability" and cmd contains "openssh.server", "win_sshd_add", "none")
| where hit != "none"
| summarize events = count(), first_seen = min(Timestamp), cmdlines = make_set(ProcessCommandLine) by DeviceName, hitIn Defender, also run Microsoft's DeviceEvents task and plist queries. A crontab written through standard input hides the .smt path; schtasks may also match admin tasks that manage Sysinternals Sysmon. The macOS branches only fire in Elastic, CrowdStrike and Defender. Starting an already installed Windows sshd is not covered.
Hunt 6: Files from the tampered extension versions
Splunk
index=sysmon EventCode=11 earliest=-30d
| eval p=lower(TargetFilename)
| where like(p, "%.cursor%") AND match(p, "anthropic\\.claude-code-2\\.1\\.31[23]([^0-9]|$)")
| fillnull value="-" Image
| stats count as events min(_time) as first_seen values(TargetFilename) as paths by host ImageElastic
FROM logs-endpoint.events.file-*
| WHERE @timestamp >= NOW() - 30 days
| EVAL p = TO_LOWER(COALESCE(TO_STRING(file.path), "-")), proc = COALESCE(TO_STRING(process.name), "-")
| WHERE LOCATE(p, ".cursor") > 0 AND p RLIKE ".*anthropic\\.claude-code-2\\.1\\.31[23]([^0-9].*)?"
| STATS events = COUNT(*), first_seen = MIN(@timestamp), paths = VALUES(p) BY host.name, procCrowdStrike
#event_simpleName=/Written$/
| TargetFileName=/\.cursor/i
| regex(regex="anthropic\\.claude-code-2\\.1\\.31[23]([^0-9]|$)", field=TargetFileName, flags="i")
| default(field=ContextBaseFileName, value="-")
| match(file="aid_master_main.csv", field=aid, include=ComputerName, strict=false)
| groupBy([ComputerName, ContextBaseFileName], function=[count(as=events), min(@timestamp, as=first_seen), collect(TargetFileName)])Microsoft KQL
DeviceFileEvents
| where Timestamp > ago(30d)
| extend p = tolower(FolderPath)
| where p contains ".cursor" and p matches regex @"(?i)anthropic\.claude-code-2\.1\.31[23]([^0-9]|$)"
| extend InitiatingProcessFileName = iif(isempty(InitiatingProcessFileName), "-", InitiatingProcessFileName)
| summarize events = count(), first_seen = min(Timestamp), paths = make_set(FolderPath) by DeviceName, InitiatingProcessFileNameFolder naming is our assumption; a miss still needs a host check. The match needs a non-digit after the version.
Interpret before you escalate
Signal | Usually benign | Escalate when |
Traffic to port 8099 | Another local tool on that port | Any connection to 134[.]209[.]82[.]225, or 8099 from Cursor or node |
.smt, .stk_, sysmon.cmd or plist name | A coincidental file name | It appears in the persistence paths above or soon after a Cursor update on October 29 |
authorized_keys written by a shell | A developer adding their own key, or remote development tools writing the file | The host check shows svc-telemetry or the published fingerprint |
ssh -R with host key checks off | A developer's own tunnel for remote work | The destination is the attacker IP, or the parent is a hidden PowerShell or a .smt script |
SysMon task or com.apple.sysmond | Windows: an administrator-created maintenance task; verify owner, executable and arguments. macOS: possibly a real Apple service of that name (unconfirmed) | The plist is a LaunchAgent under the user's ~/Library/LaunchAgents, or the command or task action points at ~/.config/.smt or smt.cmd |
Claude Code extension files | Backups, renamed folders or remote .cursor-server copies can match | Confirm the install time and compare extension.js against ecb58cb2... (full value) or the VSIX against Cursor's four published VSIX hashes |
Lookups to ifconfig.me or api.ipify.org are not hunted here; Cursor says these are not indicators alone.
What to alert on
We would alert on the exact payload hash in Hunt 2 and on Hunt 1 or Hunt 4 hits to the attacker IP. A Hunt 3 hit where Cursor or node wrote the file deserves a same-day host check. Generic name, process and persistence matches need context.
The svc-telemetry comment and fingerprint are host checks the queries cannot confirm.
Check affected developer machines
Run Cursor's check on every macOS and Windows developer machine that installed or updated Claude Code on October 29, check Linux hosts by hand, and review hunt results even where no key turns up. Clean up and rotate where you confirm the key or persistence, then add an install cooldown, a publisher allowlist and endpoint telemetry on developer laptops.
Sources
Cursor modified VSIX SHA-256 (2.1.312 darwin-arm64 / win32-x64; 2.1.313 darwin-arm64 / win32-x64): fcbb327e38e61c4fec795e0d0591f52cd1014b813d20a5912104953c3a17c364, 3de1722901165769893bef57c72594e9a54459faa23f3091b136b79966edd081, 80402fdcad8799285fd93044976973ca000da7995fb93363f819bb8e4f84ee6c, 6786ec90cbd45949695bd7805c9fbb26cfc91bc5fc897de0183d9bcb9cb21731
Cursor, Security notice: malicious version of Anthropic.claude-code (2.1.312 and 2.1.313) identified in the Cursor VS Code Extension Marketplace: https://cursor.com/blog/claude-code-extension-security-notice
Microsoft Security Blog, Inside a malicious Claude Code-branded extension and its persistent SSH backdoor: https://www.microsoft.com/en-us/security/blog/2026/10/30/inside-malicious-claude-code-branded-cursor-extension-persistent-ssh-backdoor/
Inception Protection
Inception Protection is our managed detection and response service. Our analysts review endpoint, network, identity and cloud alerts around the clock, including developer laptops.
Inception Foresight
Our free Inception Foresight M365 Assessment is read-only and checks identity, email, data sharing, devices, and audit and governance settings, including whether logging and alerting are turned on. It also looks at whether the signals you rely on, such as an unexpected outbound SSH tunnel from a developer machine, would reach your Microsoft security tools at all. You get a prioritized remediation plan: https://www.inceptionsecurity.com/m365assessment.



