How rollup@4.39.0 Turns Your CI Pipeline Into a Secret Vault Leak
A single unpinned build dependency in rollup@4.39.0 can turn your CI/CD pipeline into an attacker's remote execution environment — and the median supply chain compromise goes undetected for 287 days.
What Happens When This Gets Exploited
The attacker identifies that the target project's package.json pins rollup@4.39.0, which is publicly listed as affected by GHSA-mw96-cpmx-2vgc. They confirm this via the OSV database in under 90 seconds usingosv-scanner --lockfile package-lock.json
Leveraging the prototype pollution vector in rollup's module resolution logic, the attacker crafts a malicious plugin or import specifier that injects properties onto Object.prototype during the bundle compilation phase — silently, with no build error emitted.
The polluted prototype propagates through the Node.js build process. The attacker escalates to arbitrary code execution within the build worker, gaining access to all environment variables present in the CI runner — includingNPM_TOKEN, GITHUB_TOKEN, and any cloud provider credentials injected as CI secrets.
With a valid NPM_TOKEN in hand, the attacker publishes a backdoored patch release of the target package to the npm registry. Every downstream project that runsnpm install in the next 24 hours silently installs the compromised version, extending the blast radius to hundreds of thousands of dependent applications.
Worst Case: If exploited in a CI/CD pipeline for a library like Zod — which has over 28 million weekly npm downloads — a single stolen NPM_TOKEN enables a malicious publish that poisons every project that installs or updates the package within hours. Regulatory exposure includes GDPR fines up to €20 million or 4% of global annual turnover for downstream data processors, plus SOC 2 Type II audit failure for any company whose SCA controls did not catch the vulnerable component before build.
This Isn't Theoretical
A malicious actor social-engineered the maintainer of the event-stream npm package into transferring publish rights, then injected a backdoored dependency (flatmap-stream) that targeted Bitcoin wallets in the Copay application. The compromised build dependency was present in the npm registry for 59 days before discovery, downloaded approximately 8 million times.
Consequence: The attack directly targeted Copay users' Bitcoin private keys. The incident triggered a full security audit of npm's publish permission model and prompted GitHub to acquire npm in 2020 partly to enforce 2FA on high-impact packages. It remains the canonical case study cited in CISA's 2023 “Securing the Software Supply Chain” guidance.
The Technical Reality
GHSA-mw96-cpmx-2vgc is a prototype pollution vulnerability in rollup versions prior to the patched release, rooted in how rollup's module resolution and plugin hook system handles untrusted object keys during the AST transformation and bundling pipeline. Specifically, when rollup processes certain crafted module specifiers or plugin-returned metadata objects, it performs unsafe property assignment — such as obj[key] = value where key is attacker-influenced — without validating that the key is not __proto__, constructor, or prototype. This allows properties to be injected onto the global Object prototype, affecting every subsequent object instantiation in the same Node.js process.
Developers pin rollup at a specific version for build reproducibility — a sound engineering practice — but without automated SCA in the CI pipeline, that pinned version becomes a liability the moment a CVE is published against it. The false sense of security comes from the fact that rollup is a devDependency: it never ships in the production bundle, so developers intuitively treat it as lower risk. This mental model is precisely what supply chain attackers exploit. The build environment is trusted, credential-rich, and almost never hardened to the same standard as the production runtime.
The attack mechanics work as follows: an attacker with the ability to influence build inputs — through a malicious npm package in the dependency tree, a compromised plugin, or a crafted source file in a pull request — triggers rollup's vulnerable code path during npm run build or equivalent. The prototype pollution executes synchronously in the rollup worker process. Because CI runners inject secrets as environment variables (process.env.NPM_TOKEN, process.env.AWS_ACCESS_KEY_ID, etc.) for deployment steps, those values are readable from any code executing in that process after pollution takes hold. A crafted exfiltration payload — an HTTP POST to an attacker-controlled endpoint — can be wired in via the polluted prototype before the build finishes, leaving no artifact in the output bundle.
Code review alone cannot reliably catch this class of vulnerability because the dangerous code lives inside node_modules, not in the application source. A reviewer auditing package.json sees only a version string: "rollup": "4.39.0". Nothing looks wrong. Automated detection works differently: an SCA tool like Dependabot, Socket.dev, or osv-scanner cross-references every resolved package version against the OSV, NVD, and GitHub Advisory databases at install time and on every CI run, flagging GHSA-mw96-cpmx-2vgc with a direct upgrade path before a single build executes against the vulnerable version.
Vulnerable vs. Secure
// package.json (excerpt) — rollup pinned at vulnerable version
{
"name": "zod",
"version": "3.24.2",
"devDependencies": {
// ❌ GHSA-mw96-cpmx-2vgc: prototype pollution in module resolution
// Attacker can pollute Object.prototype during bundle compilation
"rollup": "4.39.0",
"@rollup/plugin-typescript": "^12.1.2",
"@rollup/plugin-node-resolve": "^16.0.1"
},
"scripts": {
// ❌ This build step executes in a process with full access to CI secrets
// A polluted prototype here reaches process.env.NPM_TOKEN, GITHUB_TOKEN, etc.
"build": "rollup -c rollup.config.ts --configPlugin typescript",
"prepublishOnly": "npm run build"
}
// ❌ No .npmrc resolutions block, no Dependabot config, no osv-scanner in CI
}// package.json (patched) — rollup upgraded past GHSA-mw96-cpmx-2vgc
{
"name": "zod",
"version": "3.24.2",
"devDependencies": {
// ✓ Upgrade rollup to the patched version resolving GHSA-mw96-cpmx-2vgc
// Run: npm install rollup@latest --save-dev
"rollup": "4.40.0",
"@rollup/plugin-typescript": "^12.1.2",
"@rollup/plugin-node-resolve": "^16.0.1"
},
"scripts": {
"build": "rollup -c rollup.config.ts --configPlugin typescript",
"prepublishOnly": "npm run build",
// ✓ Run SCA check before every build — fails CI if any HIGH/CRITICAL CVE found
"audit:ci": "osv-scanner --lockfile package-lock.json"
},
// ✓ Force all nested dependents to use the patched rollup version
"overrides": {
"rollup": ">=4.40.0"
}
// ✓ Pair with .github/dependabot.yml: package-ecosystem npm, daily schedule
// ✓ Add 'osv-scanner' step in GitHub Actions before the build job
}How Long Until Someone Notices?
Mean time to identify a supply chain compromise
GHSA-mw96-cpmx-2vgc specifically affects the build phase rather than runtime, meaning no application-layer monitoring, WAF, or SIEM rule will fire. Detection requires explicit Software Composition Analysis (SCA) integrated into CI, which fewer than 38% of open-source projects had enabled as of the 2023 OpenSSF Scorecard aggregate study. Source: 2024 IBM Cost of a Data Breach Report.
How Custodia Detects This
Custodia runs a multi-layer scan that combines OSV database cross-referencing, GHSA advisory matching, and dependency graph traversal across your entire package.json and lockfile — including transitive dependencies. When rollup@4.39.0 appears anywhere in the resolved dependency tree, SEC-05 fires with the full GHSA advisory reference, affected file path, and a one-line fix command. This finding is surfaced in under 90 seconds.
Unlike npm audit alone, Custodia maps each finding to its business impact, OWASP category, and CWE identifiers — giving security teams the context to prioritize and giving developers the exact remediation step. The free tier covers unlimited public repositories with no account required.
You can also run the Custodia CLI locally against any project. Point it at a directory and get a full vulnerability report in one command:
npx @custodia/cli scan .Frequently Asked Questions
What exactly is GHSA-mw96-cpmx-2vgc in rollup and which versions are affected?
GHSA-mw96-cpmx-2vgc is a prototype pollution vulnerability in the rollup JavaScript bundler, confirmed in rollup@4.39.0 and earlier versions within the 4.x line. Prototype pollution occurs when rollup's internal object handling allows untrusted keys — such as __proto__ or constructor — to be written onto the global Object prototype during module resolution or plugin processing. You can verify whether your project is affected by running npx osv-scanner --lockfile package-lock.json and looking for the GHSA-mw96-cpmx-2vgc advisory in the output. Upgrading to rollup@4.40.0 or later resolves the issue.
Can a build-time prototype pollution vulnerability really lead to CI secret theft?
Yes — and this is the most underestimated aspect of build toolchain vulnerabilities. When rollup executes during npm run build in a CI environment, it runs in the same Node.js process that has access to all injected environment variables, including NPM_TOKEN, GITHUB_TOKEN, AWS_ACCESS_KEY_ID, and any other secrets your pipeline injects for deployment. A prototype pollution exploit that executes arbitrary code in that process can read process.env in its entirety and exfiltrate it via an HTTP request to an attacker-controlled server — all before the build output is written to disk. The build log shows success, the artifact looks clean, and the secret is gone.
How can I detect a vulnerable rollup version in my project without installing extra tools?
The fastest no-install check is npm audit, which cross-references your package-lock.json against the npm advisory database: run npm audit --audit-level=high and look for any rollup entry. For a more comprehensive check against the OSV database (which includes GitHub Security Advisories like GHSA-mw96-cpmx-2vgc), run npx osv-scanner --lockfile package-lock.json — no global install required. You can also grep your lockfile directly: grep -A2 "rollup" package-lock.json | grep resolved will show the exact resolved version. If you see 4.39.0 or lower in the 4.x range, you are affected.
What is the complete remediation strategy to prevent this and future build dependency CVEs?
Immediate fix: run npm install rollup@latest --save-dev to upgrade past GHSA-mw96-cpmx-2vgc, and add an "overrides" block in package.json — "overrides": { "rollup": ">=4.40.0" } — to force any transitive dependents to use the patched version. For permanent prevention, implement three controls: (1) Add a Dependabot config at .github/dependabot.yml with package-ecosystem: npm and schedule interval: daily so version bumps are automated; (2) Add an osv-scanner --lockfile package-lock.json step in your GitHub Actions workflow that runs before the build job and fails the pipeline on HIGH or CRITICAL findings; (3) Enable npm provenance attestations (npm publish --provenance) so downstream consumers can verify your published artifacts were built from the expected source commit.
Scan Your Code in 60 Seconds
Custodia scans your GitHub repository for GHSA-mw96-cpmx-2vgc and 1,400+ other supply chain, secrets, and configuration vulnerabilities in under 90 seconds. Free tier, no credit card, works on any public repo.