Back to Blog
high SEVERITY7 min read

How Path Traversal happens in Node.js PostCSS and how to fix it

A high-severity path traversal vulnerability in PostCSS versions before 8.5.18 allowed attackers to exploit the `sourceMappingURL` auto-loading mechanism to read arbitrary `.map` files from the filesystem. The fix upgrades PostCSS from 8.5.8 to 8.5.18 and pins the dependency via an npm `overrides` entry, closing the attack surface entirely. Any project using PostCSS as a direct or transitive dependency should apply this upgrade immediately.

O
By Orbis AppSec
Published August 26, 2026Reviewed August 26, 2026

Answer Summary

GHSA-r28c-9q8g-f849 is a high-severity path traversal vulnerability (CWE-22) in PostCSS, a widely-used CSS transformation library for Node.js. In versions prior to 8.5.18, the previous source map auto-loading feature reads a `sourceMappingURL` comment from CSS input and resolves the referenced `.map` file from disk without adequately sanitizing the path. An attacker who can supply crafted CSS input containing a path-traversal sequence in the `sourceMappingURL` comment can cause PostCSS to disclose arbitrary `.map` files outside the intended directory. The fix is to upgrade PostCSS to 8.5.18 (or later) and, for projects where PostCSS is a transitive dependency, add an npm `overrides` entry to enforce the safe version across the entire dependency tree.

Vulnerability at a Glance

cweCWE-22 (Improper Limitation of a Pathname to a Restricted Directory)
fixUpgrade PostCSS from 8.5.8 to 8.5.18 and enforce via npm overrides
riskArbitrary .map file disclosure from the server filesystem
languageJavaScript / Node.js
root causePostCSS resolves sourceMappingURL paths from CSS input without sanitizing path-traversal sequences
vulnerabilityPath Traversal via sourceMappingURL auto-loading

How Path Traversal Happens in Node.js PostCSS and How to Fix It

The Problem: Your CSS Processor Was Reading Files It Shouldn't

The package-lock.json file in this project locked PostCSS at version 8.5.8 — a version containing a high-severity path traversal flaw tracked as GHSA-r28c-9q8g-f849. PostCSS is one of the most downloaded npm packages in existence, used by webpack, Vite, Next.js, and countless build pipelines to transform CSS. A flaw in how it auto-loads previous source maps means that crafted CSS input can trick the library into reading arbitrary .map files off the server's filesystem.

This isn't a theoretical edge case. Any pipeline that processes CSS from an untrusted source — user uploads, third-party stylesheets fetched at build time, or plugin-generated CSS — is potentially exposed.


The Vulnerability Explained

How PostCSS Loads Previous Source Maps

When PostCSS processes a CSS file, it looks for a sourceMappingURL comment at the bottom of the file, like this:

/* Standard sourceMappingURL comment */
/*# sourceMappingURL=styles.css.map */

This is a standard feature: PostCSS reads the referenced .map file from disk so it can chain source maps correctly across multiple transformation passes. The problem in versions before 8.5.18 is that the path in the sourceMappingURL comment was not properly sanitized before being used to read a file.

The Vulnerable Pattern

Consider what happens when an attacker supplies a CSS file containing:

body { color: red; }
/*# sourceMappingURL=../../../../etc/passwd.map */

Or, targeting source maps that may exist alongside sensitive configuration files:

/*# sourceMappingURL=../../../config/database.js.map */

PostCSS's previous-source-map auto-loading code would resolve this path relative to the CSS file's location and attempt to read it from disk. If the file exists (and .map files are the target), its contents would be loaded into memory and potentially exposed through PostCSS's output or error messages.

The vulnerable version pinned in package-lock.json before this fix:

"node_modules/postcss": {
  "version": "8.5.8",
  "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz",
  "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg=="
}

Real-World Attack Scenario

Imagine a web application that accepts CSS file uploads for a "custom theme" feature and processes them through PostCSS at build time or on a background worker. An attacker uploads:

.theme { background: #fff; }
/*# sourceMappingURL=../../../../app/config/secrets.js.map */

If a .map file exists at that traversed path — perhaps generated by a prior build step that compiled secrets.js — PostCSS silently reads it. Depending on how the application surfaces PostCSS errors or outputs, the attacker may be able to exfiltrate source map contents, which often contain original source code, variable names, and logic that was intended to be minified and obscured.

Even without a secrets file, source maps for application JavaScript frequently contain reconstructed source that reveals business logic, API endpoints, and authentication flows.


The Fix

What Changed

The fix involved two coordinated changes across package.json and package-lock.json.

1. Upgrading the resolved PostCSS version in package-lock.json:

 "node_modules/postcss": {
-  "version": "8.5.8",
-  "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz",
-  "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==",
+  "version": "8.5.18",
+  "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.18.tgz",
+  "integrity": "sha512-xdB1oSLHbz1vRWgCDalrCqEFTWzFlhqFC5tIHLMOSUIjhm3XXQ1qrFy8S/ESr1JYRRXqM3c1QFiMZUJdUTqyMQ==",

2. Adding an npm overrides entry to both package.json and package-lock.json:

+  "overrides": {
+    "postcss": "^8.5.18"
+  }

This second change is critical and often overlooked. Because PostCSS appears in this project as a peer dependency (note the "peer": true flag in package-lock.json) of @mermaid-js/mermaid-cli and mermaid, simply updating the top-level version isn't sufficient — npm might still resolve the vulnerable 8.5.8 version for nested dependents. The overrides field forces npm to use ^8.5.18 for every installation of PostCSS in the entire dependency tree, regardless of what the consuming package requested.

3. The nanoid sub-dependency was also bumped:

-  "nanoid": "^3.3.11",
+  "nanoid": "^3.3.12",

PostCSS 8.5.18 tightened its own dependency on nanoid as part of the same security hardening pass.

Why the overrides Approach Matters

Without the overrides entry, a future npm install or npm update could silently re-introduce the vulnerable version if mermaid-cli or mermaid still declare a loose peer dependency range that resolves to 8.5.8. The override acts as a security floor — a guarantee that no matter what the dependency graph looks like, PostCSS will always be at least 8.5.18.


Prevention & Best Practices

1. Treat sourceMappingURL as Untrusted Input

If your application processes CSS from any external source, treat the sourceMappingURL comment as attacker-controlled data. Validate and sanitize it before passing the CSS to PostCSS, or disable previous source map auto-loading if your use case doesn't require it.

2. Use npm overrides (or Yarn resolutions) for Transitive Vulnerabilities

When a vulnerable package is a transitive dependency — meaning you don't depend on it directly — the overrides field is your most reliable tool:

{
  "overrides": {
    "postcss": "^8.5.18"
  }
}

For Yarn users, the equivalent is:

{
  "resolutions": {
    "postcss": "^8.5.18"
  }
}

3. Run Dependency Audits in CI

Integrate npm audit, Trivy, or Snyk into your CI pipeline so vulnerable transitive dependencies are caught before they reach production. This vulnerability was detected by Trivy scanning package-lock.json — the kind of check that should run on every pull request.

4. Pin Integrity Hashes

The integrity field in package-lock.json (the sha512 hash) ensures that even if a registry is compromised, npm will refuse to install a package whose content doesn't match the expected hash. Always commit your package-lock.json.

5. Reference Security Standards

  • OWASP: This falls under A01:2021 – Broken Access Control and the Path Traversal attack category.
  • CWE-22: Improper Limitation of a Pathname to a Restricted Directory.
  • Always validate that resolved file paths begin with the expected base directory using path.resolve() and a prefix check:
const path = require('path');

function safeResolvePath(baseDir, userInput) {
  const resolved = path.resolve(baseDir, userInput);
  if (!resolved.startsWith(path.resolve(baseDir) + path.sep)) {
    throw new Error('Path traversal detected');
  }
  return resolved;
}

Key Takeaways

  • sourceMappingURL comments in CSS are attacker-controlled data — PostCSS 8.5.8 trusted them too much when auto-loading previous source maps, enabling directory traversal.
  • Peer dependencies need explicit overrides — because PostCSS was a peer dep of mermaid-cli, a simple version bump wasn't enough; the overrides entry in package.json was required to enforce the safe version across the full tree.
  • .map files can contain sensitive source code — source maps reconstruct original JavaScript, making them a high-value target even when the original files are not directly accessible.
  • Trivy scanning package-lock.json caught this before exploitation — static analysis of lockfiles is a fast, low-friction way to surface known-vulnerable transitive dependencies.
  • The nanoid bump to ^3.3.12 was a bundled fix — PostCSS 8.5.18 tightened multiple sub-dependencies simultaneously, reinforcing the value of upgrading to the latest patch rather than the minimum fix version.

How Orbis AppSec Detected This

  • Source: A sourceMappingURL comment embedded in CSS input processed by PostCSS, providing an attacker-controlled file path string.
  • Sink: PostCSS's internal previous-source-map auto-loading logic, which reads the file referenced by sourceMappingURL from disk — effectively an unsanitized fs.readFile() call derived from CSS content.
  • Missing control: No path normalization or directory-boundary check was applied to the sourceMappingURL value before it was used to construct the file path, allowing ../ sequences to escape the intended base directory.
  • CWE: CWE-22 — Improper Limitation of a Pathname to a Restricted Directory ("Path Traversal").
  • Fix: PostCSS was upgraded from 8.5.8 to 8.5.18 in package-lock.json, and an overrides entry was added to package.json to enforce this minimum version across all transitive dependency resolutions.

Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.


Conclusion

GHSA-r28c-9q8g-f849 is a reminder that even well-established, widely-trusted build tools can harbor path traversal bugs in features that seem benign — like loading a source map. The sourceMappingURL auto-loading mechanism in PostCSS 8.5.8 trusted user-controlled CSS content to provide a safe file path, and it didn't. The fix in 8.5.18 corrects the path resolution logic, and the overrides entry in this project ensures the safe version is enforced for every consumer in the dependency tree.

If your project uses PostCSS — directly or through a bundler like webpack or Vite — check your package-lock.json now. If you see 8.5.8 anywhere in the resolved versions, add the overrides entry and re-run npm install. It's a two-line change that closes a meaningful attack surface.


References

Frequently Asked Questions

What is a path traversal vulnerability?

A path traversal vulnerability occurs when user-controlled input is used to construct a file path without stripping sequences like `../`, allowing an attacker to access files outside the intended directory.

How do you prevent path traversal in Node.js?

Sanitize and normalize all file paths derived from user input, use `path.resolve()` combined with a prefix check, and never pass raw user-supplied strings directly to file-reading APIs.

What CWE is path traversal?

Path traversal is classified as CWE-22: Improper Limitation of a Pathname to a Restricted Directory ("Path Traversal").

Is input validation alone enough to prevent path traversal in PostCSS?

Not necessarily — the safest fix is to upgrade to PostCSS 8.5.18, which corrects the path resolution logic internally. Input validation at the application layer is a useful defense-in-depth measure but should not replace the library fix.

Can static analysis detect path traversal vulnerabilities like this one?

Yes. Tools like Trivy, Semgrep, and Snyk can flag known-vulnerable package versions in `package-lock.json` and `package.json`, and Semgrep rules can identify unsafe path construction patterns in source code.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #703

Related Articles

critical

How Path Traversal happens in JavaScript i18n loaders and how to fix it

A path traversal vulnerability in `beta/js/i18n-chatrd.js` allowed attackers to manipulate the `lang` URL query parameter to load arbitrary JSON files from the web server by injecting payloads like `../../sensitive-file`. The fix adds input validation to ensure only safe, expected language codes are accepted before they are interpolated into the fetch URL. This type of vulnerability is especially dangerous in internationalization loaders because they are often publicly accessible and designed to

high

How Path Traversal happens in Python FastAPI and how to fix it

A critical path traversal vulnerability was discovered in `SovitsTest/GSVI.py`, a FastAPI-based TTS inference server, where the `/upload` endpoint accepted user-supplied filenames without sanitization. An unauthenticated remote attacker could exploit this to write arbitrary files anywhere on the filesystem — including sensitive system directories like `/etc/cron.d`. The fix adds path validation to prevent filenames from escaping the intended upload directory.

high

How Path Traversal happens in Python Flask routes and how to fix it

A high-severity path traversal vulnerability was discovered in `xkeen-ui/routes/cores_status.py` at line 221, where user-controlled input was passed directly to Python's `open()` function without sanitization. An attacker could exploit this to read arbitrary files on the server by supplying crafted path strings like `../../etc/passwd`. The fix introduces strict path validation using a trusted root directory, ensuring only files within the intended directory can be accessed.

critical

How Path Traversal happens in Vitest UI Server and how to fix it

CVE-2026-47429 is a critical path traversal vulnerability in Vitest's UI server that allows unauthenticated attackers to read and execute arbitrary files on the host system when the UI server is active. The vulnerability was fixed by upgrading Vitest from the vulnerable `^4.0.0` range to the pinned safe release `4.1.0`. Any project running Vitest's UI mode during development or CI is potentially exposed until this upgrade is applied.

critical

How Local File Inclusion/Path Traversal happens in JavaScript PDF generation and how to fix it

CVE-2025-68428 is a critical Local File Inclusion/Path Traversal vulnerability in jsPDF versions prior to 4.0.0 that could allow attackers to read arbitrary files from the server's filesystem through unsanitized path inputs during PDF generation. The vulnerability was present in the `jspdf` dependency declared in `frontend/package-lock.json`, and was resolved by upgrading from version 3.0.4 to 4.0.0. Left unpatched, this flaw could expose sensitive server-side files to unauthorized access via cr

high

How Dependabot Missing Cooldown happens in GitHub Actions and how to fix it

A high-severity misconfiguration in `.github/dependabot.yml` left this Node.js library without a cooldown period on dependency updates, meaning Dependabot could immediately propose upgrades to newly published — potentially malicious or unstable — package versions. The fix adds a `cooldown` block with `default-days: 7` to both the `npm` and `github-actions` ecosystems, introducing a mandatory waiting period before any newly released version is surfaced as an update candidate. Because this project