top of page

Fake Claude Code Extension in Cursor: Hunt the SSH Backdoor

Writer: Inception Security
Inception Security
26 minutes ago
12 min read
Inceptor points at a glowing SSH key held by Glitch, who squeezes out of a cracked Claude Code extension icon on a laptop (fake Claude Code extension in Cursor hunt comic)

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 kind

Elastic


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, kind

CrowdStrike


#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, kind

Splunk 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 DestinationPort

Elastic


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.port

CrowdStrike


#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, RemotePort

Splunk 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 TargetFilename

Elastic


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, fpath

CrowdStrike


#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, SHA256

Sysmon 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 TargetFilename

Elastic


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, fpath

CrowdStrike


#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, FolderPath

Sysmon 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 parent

Elastic


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, parent

CrowdStrike


#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, parent

The 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 hit

Elastic


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, hit

CrowdStrike


#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, hit

In 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 Image

Elastic


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, proc

CrowdStrike


#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, InitiatingProcessFileName

Folder 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



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.


bg-map-white.png

INCEPTION SECURITY™

A cybersecurity company with in depth knowledge of the threat landscape and security controls.

NAVIGATION

GET IN TOUCH

© 2025 All Rights Reserved by INCEPTION SECURITY™ .

bottom of page