Feed
CriticalPublished 20 Mar 202629 packages · 58 versions

CanisterWorm: @emilgroup and @teale.io npm publisher compromise (29+ packages)

Summary

An attacker compromised the @emilgroup and @teale.io npm namespaces, replacing 58 package-versions with a Python backdoor that polls an Internet Computer Protocol (ICP) canister for follow-on payloads. The implant persists via user-level systemd and includes worm-style republishing via deploy.js. Wiz later linked the tradecraft to TeamPCP; Socket declined firm attribution.

wormmaintainer-takeovercredential-theftinfostealer
Threat actor
TeamPCP
Detected by
Socket · Wiz · Aikido
Also known as
CanisterWorm
Ecosystems
npm
Packages tracked
29

What happened

On 2026-03-20, an attacker pushed 58 poisoned package-versions across 29 packages in the @emilgroup and @teale.io npm scopes. @emilgroup ships SDKs widely used in European insurance workflows; @teale.io/eslint-config is shared internal tooling — both compromises put the implant on developer laptops and CI runners alike.

Implant

The malicious postinstall writes a Python implant to ~/.local/share/pgmon/service.py and registers a user-level pgmon.service via ~/.config/systemd/user/pgmon.service. The implant polls an Internet Computer Protocol (ICP) canister at tdtqy-oyaaa-aaaae-af2dq-cai.raw.icp0.io roughly every 3000s, fetching follow-on payload URLs that are written to /tmp/pglog for execution. State tracking lives in /tmp/.pg_state.

Using an ICP canister as the C2 hop makes takedown materially harder: there is no DNS or hosting provider to send a notice to. Socket dubbed the family CanisterWorm.

Worm component

A deploy.js shipped alongside the implant attempts to re-publish trojanised versions using any npm credentials it finds in ~/.npmrc or environment, which is how the malware spread from a single takeover to 29 packages in one session.

Attribution

Wiz linked the tradecraft to TeamPCP (the same actor behind the 2026-03-19 Trivy cascade), based on shared exfil infrastructure and operator artifacts. Socket and Aikido (Charlie Eriksen) declined firm attribution but documented mutations of the implant inside @teale.io/eslint-config that share code with the Trivy-cascade tooling.

Affected packages (29)

  • npm@emilgroup/account-sdk
    1.41.11.41.2
  • npm@emilgroup/account-sdk-node
    1.40.11.40.2
  • npm@emilgroup/accounting-sdk-node
    1.26.11.26.2
  • npm@emilgroup/api-documentation
    1.19.11.19.2
  • npm@emilgroup/auth-sdk
    1.25.11.25.2
  • npm@emilgroup/auth-sdk-node
    1.21.11.21.2
  • npm@emilgroup/billing-sdk
    1.56.11.56.2
  • npm@emilgroup/billing-sdk-node
    1.57.11.57.2
  • npm@emilgroup/claim-sdk
    1.41.11.41.2
  • npm@emilgroup/claim-sdk-node
    1.39.11.39.2
  • npm@emilgroup/customer-sdk
    1.54.11.54.2
  • npm@emilgroup/customer-sdk-node
    1.55.11.55.2
  • npm@emilgroup/document-sdk
    1.45.11.45.2
  • npm@emilgroup/document-sdk-node
    1.43.11.43.2
  • npm@emilgroup/gdv-sdk
    2.6.12.6.2
  • npm@emilgroup/insurance-sdk
    1.97.11.97.2
  • npm@emilgroup/insurance-sdk-node
    1.95.11.95.2
  • npm@emilgroup/notification-sdk-node
    1.4.11.4.2
  • npm@emilgroup/partner-portal-sdk-node
    1.1.11.1.2
  • npm@emilgroup/partner-sdk-node
    1.19.11.19.2
  • npm@emilgroup/payment-sdk
    1.15.11.15.2
  • npm@emilgroup/payment-sdk-node
    1.23.11.23.2
  • npm@emilgroup/process-manager-sdk-node
    1.13.11.13.2
  • npm@emilgroup/public-api-sdk
    1.33.11.33.2
  • npm@emilgroup/public-api-sdk-node
    1.35.11.35.2
  • npm@emilgroup/tenant-sdk
    1.34.11.34.2
  • npm@emilgroup/tenant-sdk-node
    1.33.11.33.2
  • npm@emilgroup/translation-sdk-node
    1.1.11.1.2
  • npm@teale.io/eslint-config
    1.8.91.8.10

Impact

  • Insurance-industry SDKs (@emilgroup) and shared ESLint tooling (@teale.io) backdoored
  • Persistent ICP-based C2 evades takedown via decentralised infrastructure
  • ~3000s canister polling interval for follow-on payloads

What to do

  1. 1Revert @emilgroup/* and @teale.io/eslint-config to last clean versions and rotate developer credentials
  2. 2Hunt for ~/.local/share/pgmon/service.py and a pgmon systemd user service
  3. 3Block egress to *.raw.icp0.io canister endpoints; specifically tdtqy-oyaaa-aaaae-af2dq-cai.raw.icp0.io

References

npm-2026-03-20-canisterworm-emilgroup-teale