On December 19, 2024, malicious versions of @rspack/core (1.1.7) and @rspack/cli (1.1.7) landed on npm. Rspack is a Rust-based bundler gaining adoption as a webpack replacement, with the two packages pulling roughly 500,000 weekly downloads combined. The malicious releases shipped a cryptocurrency miner targeting Linux systems and ran during post-install scripts. The compromise was detected within hours, but within that window the malicious versions reached CI systems and developer laptops across many organizations.
This post covers what happened, why the response was faster than average, and which controls would have prevented the propagation.
How did the attackers get publish access?
A compromised npm token with publish rights to the @rspack scope was used to push malicious versions.
The Rspack maintainers disclosed that an npm access token used by the release pipeline was exfiltrated. The exact theft vector was not publicly confirmed but the pattern is consistent with prior incidents: a developer's machine, a leaky CI log, or a phishing flow targeting npm accounts. The token had publish scope on the @rspack organization and did not require interactive 2FA for publishes, which is typical for CI-automated release tokens.
Once the attackers had the token, they published @rspack/core@1.1.7 and @rspack/cli@1.1.7 using it. The version bump from 1.1.6 was a legitimate-looking patch increment, meaning any ^1.1.6 or >=1.1.6 constraint would resolve to the malicious version on the next install.
What did the payload do?
The payload was an XMRig-based Monero miner invoked from a post-install script that ran on Linux hosts.
During npm install, npm executes postinstall scripts from dependencies by default. The poisoned Rspack package included a post-install hook that detected the platform, downloaded a miner binary from an attacker-controlled Cloudflare R2 bucket, wrote it to /tmp, and spawned it as a background process. On Windows and macOS the script exited without action. The miner used a standard pool configuration and consumed significant CPU, which is typically how victims first noticed.
There was also a reconnaissance component. The script attempted to read environment variables and specific file paths (~/.aws/credentials, ~/.npmrc, ~/.ssh/id_rsa) and exfiltrate them to the same C2 endpoint. This is a meaningful upgrade over pure cryptomining: the incident had a credential-theft dimension, which means affected teams needed to rotate secrets, not just kill miner processes.
How was this detected so quickly?
Community reports on GitHub and social media flagged unexpected post-install behavior within hours of publication.
A developer running npm install in a CI pipeline noticed an unexpected download from an unfamiliar host in network logs. They reported it on the Rspack GitHub repository, and within a few hours the maintainers had confirmed the compromise, unpublished the malicious versions, rotated tokens, and pushed clean versions. npm's security team followed up with organization-scope policies to prevent re-publication.
The speed of detection here is not a coincidence. Rspack is a security-conscious project with an active community, and the user base skews toward engineers who read network logs. For less-observed packages, the same attack could have run undetected for days. The lesson is not "community response works." It is "community response works only when the community is watching, which is not a scalable defense."
What was the blast radius?
CI systems and developer laptops that ran npm install during the exposure window executed the miner and leaked credentials.
Affected scenarios included any project with a floating version constraint on @rspack/core or @rspack/cli, any CI build that ran during the window, and any container image built during the window that installed Rspack as a build-time dependency. Because the miner also exfiltrated AWS credentials and SSH keys, the follow-on exposure included cloud account takeover risks for organizations that kept long-lived AWS keys on developer machines or in CI environment variables.
Teams following hash-pinned installs (package-lock.json with integrity hashes, or --frozen-lockfile) were not affected because the lockfile hashes did not match. Teams using exact version pins (1.1.6, not ^1.1.6) were also unaffected. Teams running npm install without a lockfile, which is unfortunately still common in production Docker builds, took the full exposure.
What structural controls should teams enforce?
Four controls, in order of cost-to-value.
First, disable post-install scripts globally. npm supports --ignore-scripts and this can be set as a default in .npmrc. Some packages (node-sass, sharp) legitimately need post-install behavior, but those can be allowlisted explicitly. A global default of "no scripts run" would have blocked the Rspack payload entirely. Very few organizations do this because it breaks some packages at install time, but the risk math has shifted.
Second, commit and enforce lockfiles with integrity hashes, use npm ci (not npm install) in CI, and refuse any dependency update that changes a hash without an associated review. This is the single most effective control against published-malware supply chain attacks.
Third, separate build-time credentials from runtime credentials. If your CI is running npm install with AWS keys in environment variables, you have coupled two trust domains that should be separate. Use OIDC federation for CI, short-lived credentials, and no long-lived secrets on developer laptops.
Fourth, behavioral sandboxing for install steps. Tools that run npm install in a network-isolated sandbox and observe outbound traffic or file writes can flag the Rspack payload on first execution. This class of tool is the most direct defense against the broader "npm postinstall runs malware" pattern.
Why does the post-install script model keep enabling these attacks?
npm's post-install mechanism gives every dependency arbitrary code execution on every machine that runs npm install, and the ecosystem has not collectively grappled with what that means.
When you run npm install, npm executes the postinstall script of every package in your dependency tree. A medium-sized project has hundreds to thousands of transitive dependencies. Each of those packages was, at some point, published by someone whose credentials could be stolen. Each of those postinstall scripts runs with the full privileges of your developer account or CI runner. This is, structurally, a remote code execution primitive that the entire JavaScript ecosystem has chosen to live with for ergonomic reasons: some packages genuinely need to compile native code at install time, and post-install is the primitive that supports that.
The ergonomics argument is weaker than it appears. A small minority of packages actually need post-install (native modules like bcrypt, sharp, node-gyp). The vast majority of postinstall scripts either do nothing useful or perform analytics and telemetry that the user probably did not consent to. A reasonable default of "no scripts run unless explicitly allowlisted" would eliminate a large class of attacks at the cost of some one-time configuration for the packages that legitimately need it.
Setting ignore-scripts=true in a root .npmrc is a one-line change. Most teams do not do it because of the perceived friction. The Rspack incident is a direct argument for reconsidering that tradeoff. The question is not whether you can afford to disable post-install; it is whether you can afford not to.
How Safeguard.sh Helps
Safeguard.sh's reachability analysis correlates npm post-install capabilities with what's actually invoked in your build path, applying the 60-80% noise reduction to keep this class of finding at the top of the queue. Griffin AI builds behavioral baselines for post-install scripts across every package version and flags the kind of new network egress and credential-adjacent file reads that characterized the Rspack payload, without waiting for public disclosure. Our SBOM pipeline tracks build-time dependencies at 100-level depth, including transitive post-install hooks, and TPRM workflows gate the packages tied to critical build infrastructure. Container self-healing rolls back affected images automatically once a compromise is confirmed, so teams do not manually audit every runner.