A new wave of supply chain attacks is hitting the npm ecosystem through a self-propagating malware campaign known as CanisterWorm.
The threat, linked to a group tracked as “TeamPCP,” compromises legitimate publisher namespaces and pushes poisoned package versions, effectively turning trusted developer tools into silent delivery mechanisms for credential-stealing code.
CanisterWorm first came to public attention through early reports from security researchers at Socket and Endor Labs, who traced a recurring pattern of malicious package updates across multiple npm publisher accounts.
The malware is carefully concealed inside what appear to be routine SDK version bumps, making it easy for developers to install it without raising any suspicion.
JFrog researchers identified new, previously unreported compromised package versions tied to the CanisterWorm attack, extending the known scope of the campaign well beyond what earlier disclosures had covered.
Their continuous monitoring pipeline surfaced additional affected versions across multiple namespaces — including packages maintained under @emilgroup and @teale.io — that had gone undetected in prior public reports.
The impact of a successful infection goes far beyond a single machine. Once a developer installs one of the tainted packages, CanisterWorm drops a Python backdoor onto the host, begins harvesting npm authentication tokens, and uses those credentials to autonomously spread itself to every package the compromised developer maintains.
This chain-reaction behavior means one affected developer account can become a launchpad for poisoning dozens of downstream packages, putting a wide range of dependent projects and their users at immediate risk.
What makes CanisterWorm particularly difficult to contain is how naturally it blends into normal development workflows.
By hiding behind version updates and leveraging decentralized infrastructure for command communication, it sidesteps the traditional indicators that most security tools look for, quietly expanding its reach before anyone notices something is wrong.
Inside the Worm’s Infection Chain
The infection process begins the moment a developer runs npm install on a compromised package.
A malicious postinstall hook embedded inside the package.json file executes automatically and silently drops a Python backdoor onto the host system without any visible warning.
On Linux machines, the worm then registers a persistent background service named pgmon through systemd, ensuring it stays active and survives system reboots.
Once established, the backdoor continuously polls an Internet Computer Protocol (ICP) canister — a decentralized, blockchain-hosted endpoint — that acts as a dead-drop command-and-control server.
This design makes the malicious traffic blend naturally with regular web requests, significantly hampering detection by network monitoring tools.
Secondary payloads retrieved through this channel are written to /tmp/pglog, while the worm tracks its execution state inside /tmp/.pg_state.
The most dangerous phase of the attack is the autonomous spreading routine. The malware scans .npmrc files across the project directory, the user’s home folder, and system-wide configuration paths, searching for _authToken values.
It also reads the NPM_TOKEN and NPM_TOKENS environment variables. With those stolen credentials, a built-in deploy.js script queries the npm registry, locates every package the victim maintains, increments each one’s patch version number, and publishes the tainted update automatically.
Known Compromised Packages:-
| Package Name | Compromised Version(s) | JFrog X-ray ID |
|---|---|---|
@pypestream/floating-ui-dom | 2.15.1 | XRAY-955001 |
@leafnoise/mirage | 2.0.3 | XRAY-954938 |
@opengov/ppf-backend-types | 1.141.2 | XRAY-954962 |
eslint-config-ppf | 0.128.2 | XRAY-954936 |
react-leaflet-marker-layer | 0.1.5 | XRAY-954942 |
react-leaflet-cluster-layer | 0.0.4 | XRAY-954943 |
react-autolink-text | 2.0.1 | XRAY-954959 |
opengov-k6-core | 1.0.2 | XRAY-954926 |
jest-preset-ppf | 0.0.2 | XRAY-954956 |
cit-playwright-tests | 1.0.1 | XRAY-954934 |
eslint-config-service-users | 0.0.3 | XRAY-954950 |
babel-plugin-react-pure-component | 0.1.6 | XRAY-954955 |
@opengov/form-renderer | 0.2.20 | XRAY-955058 |
@opengov/qa-record-types-api | 1.0.3 | XRAY-954970 |
@opengov/form-builder | 0.12.3 | XRAY-954953 |
@opengov/ppf-eslint-config | 0.1.11 | XRAY-954967 |
@opengov/form-utils | 0.7.2 | XRAY-954958 |
react-leaflet-heatmap-layer | 2.0.1 | XRAY-954931 |
@virtahealth/substrate-root | 1.0.1 | XRAY-955055 |
@airtm/uuid-base32 | 1.0.2 | XRAY-954937 |
@emilgroup/setting-sdk | 0.2.3, 0.2.2, 0.2.1 | XRAY-955067 |
@emilgroup/partner-portal-sdk | 1.1.3, 1.1.2, 1.1.1 | XRAY-955063 |
@emilgroup/gdv-sdk-node | 2.6.3, 2.6.2, 2.6.1 | XRAY-955060 |
@emilgroup/docxtemplater-util | 1.1.4, 1.1.3, 1.1.2 | XRAY-955062 |
@emilgroup/accounting-sdk | 1.27.3, 1.27.2, 1.27.1 | XRAY-955054 |
@emilgroup/task-sdk | 1.0.4, 1.0.3, 1.0.2 | XRAY-955056 |
@emilgroup/setting-sdk-node | 0.2.3, 0.2.2, 0.2.1 | XRAY-955064 |
@emilgroup/task-sdk-node | 1.0.4, 1.0.3, 1.0.2 | XRAY-954923 |
@emilgroup/partner-sdk | 1.19.3, 1.19.2, 1.19.1 | XRAY-955065 |
@emilgroup/numbergenerator-sdk-node | 1.3.3, 1.3.2, 1.3.1 | XRAY-955066 |
@emilgroup/customer-sdk | 1.54.5, 1.54.4, 1.54.3, 1.54.2, 1.54.1 | XRAY-954924 |
@emilgroup/commission-sdk | 1.0.3, 1.0.2, 1.0.1 | XRAY-955068 |
@emilgroup/process-manager-sdk | 1.4.2, 1.4.1 | XRAY-955069 |
@emilgroup/changelog-sdk-node | 1.0.3, 1.0.2 | XRAY-955061 |
@emilgroup/document-sdk-node | 1.43.6, 1.43.5, 1.43.4, 1.43.3, 1.43.2, 1.43.1 | XRAY-954947 |
@emilgroup/commission-sdk-node | 1.0.3, 1.0.2, 1.0.1 | XRAY-955053 |
@emilgroup/document-uploader | 0.0.12, 0.0.11, 0.0.10 | XRAY-955057 |
@emilgroup/discount-sdk | 1.5.3, 1.5.2, 1.5.1 | XRAY-954929 |
@emilgroup/discount-sdk-node | 1.5.2, 1.5.1 | XRAY-955059 |
@teale.io/eslint-config | 1.8.16–1.8.9 (8 versions) | XRAY-954945 |
@emilgroup/insurance-sdk | 1.97.6–1.97.1 (6 versions) | XRAY-954928 |
@emilgroup/account-sdk | 1.41.2, 1.41.1 | XRAY-954949 |
@emilgroup/account-sdk-node | 1.40.2, 1.40.1 | XRAY-954927 |
@emilgroup/accounting-sdk-node | 1.26.2, 1.26.1 | XRAY-954965 |
@emilgroup/api-documentation | 1.19.2, 1.19.1 | XRAY-954960 |
@emilgroup/auth-sdk | 1.25.2, 1.25.1 | XRAY-954966 |
@emilgroup/auth-sdk-node | 1.21.2, 1.21.1 | XRAY-954964 |
@emilgroup/billing-sdk | 1.56.2, 1.56.1 | XRAY-954951 |
@emilgroup/billing-sdk-node | 1.57.2, 1.57.1 | XRAY-954948 |
@emilgroup/claim-sdk | 1.41.2, 1.41.1 | XRAY-954961 |
@emilgroup/claim-sdk-node | 1.39.2, 1.39.1 | XRAY-954925 |
@emilgroup/customer-sdk-node | 1.55.2, 1.55.1 | XRAY-954944 |
@emilgroup/document-sdk | 1.45.2, 1.45.1 | XRAY-954941 |
@emilgroup/gdv-sdk | 2.6.2, 2.6.1 | XRAY-954930 |
@emilgroup/insurance-sdk-node | 1.95.2, 1.95.1 | XRAY-954933 |
@emilgroup/notification-sdk-node | 1.4.2, 1.4.1 | XRAY-954957 |
@emilgroup/partner-portal-sdk-node | 1.1.2, 1.1.1 | XRAY-954952 |
@emilgroup/partner-sdk-node | 1.19.2, 1.19.1 | XRAY-954935 |
@emilgroup/payment-sdk | 1.15.2, 1.15.1 | XRAY-954963 |
@emilgroup/payment-sdk-node | 1.23.2, 1.23.1 | XRAY-954969 |
@emilgroup/process-manager-sdk-node | 1.13.2, 1.13.1 | XRAY-954939 |
@emilgroup/public-api-sdk | 1.33.2, 1.33.1 | XRAY-954940 |
@emilgroup/public-api-sdk-node | 1.35.2, 1.35.1 | XRAY-954946 |
@emilgroup/tenant-sdk | 1.34.2, 1.34.1 | XRAY-954932 |
@emilgroup/tenant-sdk-node | 1.33.2, 1.33.1 | XRAY-954954 |
@emilgroup/translation-sdk-node | 1.1.2, 1.1.1 | XRAY-954968 |
Anyone running any of the identified compromised package versions should treat their environment as already infected. Developers must immediately rotate all npm publishing tokens stored in .npmrc files, environment variables, and CI/CD pipeline secrets.
On Linux, the pgmon service must be stopped and disabled using systemctl, with its associated service files and directories removed entirely.
The temporary files /tmp/pglog and /tmp/.pg_state must be deleted. Affected node_modules directories should be purged and rebuilt from scratch using verified, safe package versions.
Developers whose tokens were stolen must manually unpublish the compromised package versions from the npm registry, since simply publishing a newer version does not protect downstream users who may still install the infected release.
Running npm config set ignore-scripts true globally prevents postinstall hooks from firing silently on future installs, serving as a practical defensive measure against this class of supply chain attack.
Follow us on Google News, LinkedIn, and X to Get More Instant Updates, Set CSN as a Preferred Source in Google.

