Feb 26, 2026·6 min read·13 visits
Critical path traversal in Rollup allows arbitrary file writes. Attackers can escape the 'dist' folder and overwrite system files (like .bashrc) to gain RCE. Patched in versions 2.80.0, 3.30.0, and 4.59.0.
Rollup, the omnipresent JavaScript module bundler used by virtually every modern frontend framework, contained a critical flaw in how it handled output filenames. Due to a reliance on a fragile regular expression blacklist rather than proper path normalization, attackers could bypass sanitization mechanisms. This vulnerability allowed malicious plugins or build configurations to traverse directories and write files anywhere on the host system, effectively turning a standard `npm run build` command into a Remote Code Execution (RCE) vector via arbitrary file overwrites.
In the modern JavaScript ecosystem, the bundler is God. It takes your chaotic sprawl of TypeScript, SCSS, and images, and packs them into neat little boxes for the browser. We trust tools like Rollup implicitly. We run them in our terminals, our CI/CD pipelines, and our production servers, often with elevated privileges. But what happens when the box maker decides to think outside the box—literally?
CVE-2026-27606 is not your garden-variety cross-site scripting bug. It is a logic flaw in the core engine of Rollup that turns the bundler into a weapon. The premise is simple: Rollup is supposed to write files to an output directory (usually dist/ or build/). However, due to lazy input validation, it was possible to trick Rollup into writing those files anywhere else.
Imagine pulling a repository, running npm install && npm run build, and suddenly finding that your SSH keys have been overwritten or a backdoor has been planted in your shell configuration. This isn't theoretical; it's exactly what this vulnerability allows. It targets the very machinery developers trust to be safe.
The root cause of this vulnerability is a tale as old as time: a developer tried to sanitize input using a blacklist, and they used a Regular Expression to do it. The specific file in question was src/utils/sanitizeFileName.ts. The goal was noble enough—strip out characters that are invalid in filenames to prevent crashes.
However, sanitizing for validity is not the same as sanitizing for security. The engine relied on INVALID_CHAR_REGEX to scrub inputs. This regex looked for things like null bytes, control characters, and characters illegal on Windows (like < or >).
> [!WARNING]
> The Fatal Mistake: The regex failed to flag the most dangerous characters in filesystem navigation: the dot (.) and the directory separator (/ or \).
Because the sanitization logic didn't strip ../, an attacker could supply a filename like ../../../../../../etc/passwd. When Rollup eventually called path.resolve(outputDir, fileName), Node.js did exactly what it was told: it resolved the path relative to the output directory, collapsing the traversal sequences and pointing the file handle straight at the host's sensitive system files.
Let's look at the smoking gun. The vulnerability existed because the validation logic was effectively nonexistent for path traversal. The fix, applied in commit c60770d7aaf750e512c1b2774989ea4596e660b2 (for v4), introduces a strict validation step before any file writing occurs.
Here is a simplified view of the vulnerable logic vs. the patched logic:
The Vulnerable Logic (Conceptual):
// src/utils/sanitizeFileName.ts
// This regex misses directory traversal characters!
const INVALID_CHAR_REGEX = /[\x00-\x1f\x80-\x9f:?*|"><]/g;
export function sanitizeFileName(name) {
return name.replace(INVALID_CHAR_REGEX, '_');
}
// Later, this runs: fs.writeFile(path.resolve(dist, sanitizedName), ...)The Fix (Bundle.ts):
The maintainers introduced a dedicated validator validateOutputBundleFileNames. Notice the shift from "sanitizing" (trying to clean it) to "validating" (rejecting it if it's bad).
// src/utils/outputBundleFileName.ts
export function isFileNameOutsideOutputDirectory(fileName: string): boolean {
// 1. Normalize path separators to forward slashes
// 2. Check if it starts with '../' or is absolute
return (
fileName.startsWith('../') ||
fileName.includes('/../') ||
fileName === '..' ||
isAbsolute(fileName)
);
}The patch ensures that before any write operation happens, the engine checks if the resolved path attempts to escape the jail. If isFileNameOutsideOutputDirectory returns true, the build aborts immediately.
Exploiting this requires controlling the output filename. In Rollup, this is easier than it sounds. An attacker can achieve this via a malicious plugin or a crafted rollup.config.js in a shared project.
The Attack Chain:
../../../../home/user/.bashrc.../, the path resolves to the user's home directory..bashrc.The next time the victim opens their terminal, the code injected into .bashrc executes. This is a persistent, silent, and devastating compromise of the developer's machine.
Why is this a CVSS 9.8? Because build tools run everywhere. They run on developer laptops, they run in Docker containers, and most critically, they run in CI/CD pipelines often configured with excessive permissions.
Scenarios:
rollup.config.js (perhaps disguised in a "test" folder). Any developer contributing to that library gets pwned the moment they try to build it./etc/passwd or /etc/shadow, locking admins out or adding a new root user.This vulnerability bridges the gap between "messing with a project" and "owning the infrastructure."
The remediation is straightforward but urgent. The Rollup team has backported fixes to all active major versions.
Required Actions:
package.json and lockfiles.
If you are a plugin author, ensure you are not passing raw user input into emitFile names without your own validation, although the core Rollup update will now catch this downstream.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H| Product | Affected Versions | Fixed Version |
|---|---|---|
Rollup rollupjs | < 2.80.0 | 2.80.0 |
Rollup rollupjs | >= 3.0.0, < 3.30.0 | 3.30.0 |
Rollup rollupjs | >= 4.0.0, < 4.59.0 | 4.59.0 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-22 |
| Attack Vector | Network (via malicious config/plugin) |
| CVSS | 9.8 (Critical) |
| Impact | Arbitrary File Write / RCE |
| Exploit Status | PoC Available |
| KEV Listed | No |
The software uses external input to construct a pathname that is intended to identify a file or directory that is located underneath a restricted parent directory, but the software does not properly neutralize special elements within the pathname that can cause the pathname to resolve to a location that is outside of the restricted directory.