# CanisterWorm npm Supply Chain Compromise — Worm-Enabled Backdoor Across 29+ Packages via Publisher Credential Theft

> A threat actor compromised npm publishing credentials to deploy backdoored versions across 29+ packages in the @emilgroup and @teale.io scopes. The attack employs a worm-like deploy.js component that propagates via stolen npm tokens, installs a Python implant with systemd persistence, and communicates through an Internet Computer blockchain C2 canister shared with the trivy-action compromise (TL-2026-0256).

- **Published:** 2026-03-20T12:00:00Z
- **Last reviewed:** 2026-03-20T12:00:00Z
- **Canonical:** https://intel.threadlinqs.com/threat/TL-2026-0259
- **ID:** TL-2026-0259
- **Severity:** HIGH (CVSS 8.6)
- **Category:** SUPPLY_CHAIN
- **Status:** MONITORING
- **Actor:** TeamPCP
- **Detections:** 9 · **IOCs:** 20 (full data via the Threadlinqs MCP server — Purple tier)

## Description

CanisterWorm is a multi-stage supply chain attack targeting the npm ecosystem, discovered by Socket Security's research team on March 20, 2026. The campaign compromised 58 package versions across 29+ packages primarily within the @emilgroup scope (an insurance/financial SDK provider) and the @teale.io scope.

The attack chain begins with the theft of npm publishing tokens or equivalent CI/CD publishing credentials. The threat actor leveraged a custom worm component (deploy.js) that accepts stolen npm tokens from environment variables, resolves associated usernames via npm's /-/whoami endpoint, enumerates all packages each token can publish, bumps patch versions, and republishes malicious content with --tag latest to maximize installation impact. The worm preserves original READMEs to camouflage the malicious updates.

Compromised packages replace legitimate SDK functionality with a minimal framework containing a postinstall hook that invokes node index.js. The initial staging phase used a base64-encoded payload loaded from process.env.PAYLOAD or package config, exiting silently if no payload was present — a dry-run behavior suggesting careful operational staging. The weaponized phase hardcoded the payload directly, deploying a Python script to ~/.local/share/pgmon/service.py.

The Python implant (service.py) implements a deliberate delayed-execution strategy: it sleeps 300 seconds on startup before entering an endless polling loop at 3,000-second intervals. It queries the Internet Computer Protocol (ICP) blockchain canister at tdtqy-oyaaa-aaaae-af2dq-cai.raw.icp0.io, which functions as a dead-drop C2 channel. When the canister returns a URL, the implant downloads a binary to /tmp/pglog, marks it executable, and launches it as a detached process. Downloaded URLs are logged to /tmp/.pg_state to prevent re-downloads. Notably, URLs containing youtube.com are ignored — indicating a dormant or disarmed state mechanism.

Persistence is achieved through systemd user-level services. The Node.js loader creates ~/.config/systemd/user/pgmon.service with Restart=always, reloads the user daemon, enables the service, and starts it immediately — all without requiring root privileges. The service name pgmon masquerades as a PostgreSQL monitoring process.

A critical mutation was identified in @teale.io/eslint-config (versions 1.8.9 and 1.8.10), which extended the attack by harvesting npm tokens from .npmrc files, environment variables, and npm configuration — making propagation more autonomous and increasing the worm's reach beyond the initially compromised token set.

The C2 infrastructure uses an Internet Computer Protocol canister (tdtqy-oyaaa-aaaae-af2dq-cai), providing blockchain-based resilience against traditional domain takedowns. This same canister is shared with the trivy-action supply chain compromise (TL-2026-0256), where a malicious Trivy scanner binary (version 0.69.4) communicated with the identical ICP endpoint. The trivy-action attack compromised 76 of 77 GitHub Actions release tags, scraped CI/CD runner memory for secrets, and exfiltrated data to a typosquatted domain (scan.aquasecurtiy.org). The shared C2 infrastructure strongly suggests a common threat actor or coordinated campaign across both the npm and GitHub Actions ecosystems.

The @emilgroup packages are SDKs for insurance and financial services, indicating potential targeting of organizations in these sectors. The combination of supply chain compromise, credential harvesting for self-propagation, blockchain-based C2, and cross-ecosystem infrastructure sharing represents a sophisticated and evolving threat to software supply chain security.

## MITRE ATT&CK

- T1195 Supply Chain Compromise
- T1059 Command and Scripting Interpreter
- T1204 User Execution
- T1543 Create or Modify System Process
- T1027 Obfuscated Files or Information
- T1036 Masquerading
- T1070 Indicator Removal
- T1528 Steal Application Access Token
- T1552 Unsecured Credentials
- T1071 Application Layer Protocol
- T1102 Web Service
- T1105 Ingress Tool Transfer
- T1584 Compromise Infrastructure
- T1586 Compromise Accounts
- T1005 Data from Local System

## Sources

- [Socket Security: CanisterWorm — npm Publisher Compromise Deploys Backdoor Across 29+ Packages](https://socket.dev/blog/canisterworm-npm-publisher-compromise-deploys-backdoor-across-29-packages)
- [CrowdStrike: From Scanner to Stealer — Inside the Trivy Action Supply Chain Compromise (Shared C2 Infrastructure)](https://www.crowdstrike.com/en-us/blog/from-scanner-to-stealer-inside-the-trivy-action-supply-chain-compromise/)
- [Aikido Security: Discovery of @teale.io/eslint-config npm Token Harvesting Mutation](https://www.aikido.dev/blog/cutting-through-the-noise-why-malicious-npm-packages-keep-slipping-through)
- [npm Security Advisory: @emilgroup Scope Compromise](https://github.com/nickvdyck/safe-publish#readme)
- [Internet Computer Protocol — Canister Smart Contracts Documentation](https://internetcomputer.org/docs/building-apps/essentials/canisters)
- [MITRE ATT&CK: Supply Chain Compromise — Compromise Software Supply Chain (T1195.002)](https://attack.mitre.org/techniques/T1195/002/)

## Full data

Detection queries (Splunk SPL / Microsoft KQL / Sigma) and IOC values require the Threadlinqs MCP server (Purple tier): https://intel.threadlinqs.com/mcp

Canonical: https://intel.threadlinqs.com/threat/TL-2026-0259
