Back to Blog
high SEVERITY9 min read

How Path Traversal happens in PostCSS Source Map Loading and how to fix it

A high-severity path traversal vulnerability (GHSA-r28c-9q8g-f849) in PostCSS versions prior to 8.5.18 allowed attackers to manipulate `sourceMappingURL` comments to load arbitrary `.map` files from the filesystem. The fix upgrades PostCSS from 8.5.15 to 8.5.18 using a pnpm override, ensuring that all transitive dependencies consuming PostCSS are protected. While not confirmed reachable in this specific project, the vulnerability represents an exploit primitive that could be chained with other w

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's previous source map auto-loading feature, affecting versions before 8.5.18. When PostCSS processes a CSS file containing a crafted `sourceMappingURL` comment with path traversal sequences (e.g., `../../sensitive.map`), it can be tricked into reading arbitrary `.map` files from the filesystem, potentially disclosing sensitive source code or build metadata. The fix is to upgrade PostCSS to 8.5.18, which tightens validation of the `sourceMappingURL` path before any file I/O occurs. In projects using pnpm, a package-level override ensures all transitive dependents receive the patched version.

Vulnerability at a Glance

cweCWE-22
fixUpgrade PostCSS from 8.5.15 to 8.5.18 and pin the version via pnpm overrides
riskArbitrary .map file disclosure from the server filesystem
languageJavaScript / Node.js
root causePostCSS did not sanitize path traversal sequences in sourceMappingURL comments before resolving and reading the referenced file
vulnerabilityPath Traversal in sourceMappingURL auto-loading

The Vulnerability at a Glance

Field Detail
Vulnerability Path Traversal in sourceMappingURL auto-loading
CWE CWE-22 – Improper Limitation of a Pathname to a Restricted Directory
Language JavaScript / Node.js
Risk Arbitrary .map file disclosure from the server filesystem
Root Cause PostCSS did not sanitize path traversal sequences in sourceMappingURL before resolving the referenced file
Fix Upgrade PostCSS from 8.5.15 to 8.5.18; pin via pnpm overrides

Introduction

The pnpm-lock.yaml file in this project pins PostCSS at version 8.5.15 as a transitive dependency of @vue/cli-plugin-babel, @vue/cli-plugin-eslint, and @vue/cli-plugin-typescript. PostCSS's job is straightforward: parse, transform, and serialize CSS. As part of that pipeline, it can automatically load previous source maps referenced by sourceMappingURL comments embedded in CSS files. But in versions before 8.5.18, PostCSS failed to validate whether the path embedded in that comment stayed within the expected directory — creating a path traversal primitive that could expose arbitrary .map files from the server's filesystem.

This is GHSA-r28c-9q8g-f849, rated HIGH severity. The fix is a targeted version upgrade enforced through a pnpm override so that every package in the dependency tree that pulls in PostCSS gets the patched build.


The Vulnerability Explained

What is sourceMappingURL Auto-Loading?

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

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

This tells the toolchain where to find the source map for the file — useful for debugging transpiled or minified CSS. PostCSS can automatically load that map when it parses the file, so subsequent transforms can preserve accurate source positions.

The problem: PostCSS used the value of sourceMappingURL as a file path without sanitizing path traversal sequences.

The Vulnerable Pattern

Before the fix (PostCSS 8.5.15), the source map auto-loading logic would resolve a path like:

/* # sourceMappingURL=../../etc/passwd.map */

or more practically:

/* # sourceMappingURL=../../../app/dist/server.js.map */

PostCSS would dutifully resolve that path relative to the CSS file's location and attempt to read it from disk. There was no check confirming that the resolved absolute path remained within the project's expected output directory.

How an Attacker Could Exploit This

Consider a build pipeline or a server-side CSS processing endpoint that:

  1. Accepts CSS content from an external source (e.g., a user-uploaded stylesheet, a third-party CSS bundle fetched from a URL, or content passed through an API).
  2. Passes that CSS through PostCSS for transformation (autoprefixing, minification, etc.).
  3. Returns or logs the PostCSS output, which may include loaded source map data.

An attacker crafts a CSS file containing:

body { color: red; }
/* # sourceMappingURL=../../../../secrets/build-metadata.js.map */

PostCSS auto-loads ../../../../secrets/build-metadata.js.map, and the map's contents — which may include original source code paths, environment variable names embedded in build tooling, or internal module structure — become accessible to the attacker through the processing result or error output.

Even in a pure build-time context, if the build system processes CSS from untrusted repositories (e.g., in a CI pipeline that builds third-party packages), a malicious sourceMappingURL in a dependency's CSS could read .map files from sensitive locations on the build agent.

Real-World Impact for This Project

In this Vue.js project, the affected packages are:

  • @vue/cli-plugin-babel@5.0.9 (previously resolved against postcss@8.5.15)
  • @vue/cli-plugin-eslint@5.0.9 (previously resolved against postcss@8.5.15)
  • @vue/cli-plugin-typescript@5.0.9 (previously resolved against postcss@8.5.15)

The assessment notes the vulnerability is present in the dependency tree but not confirmed reachable — meaning there is no direct code path in this application today that passes untrusted CSS through PostCSS's source map loader. However, the primitive exists in the installed code, and future changes to the project (adding a CSS processing endpoint, upgrading Vue CLI plugins, or integrating a new build plugin) could activate it without any obvious security review trigger.


The Fix

Strategy: pnpm Overrides

Because PostCSS is a transitive dependency — not declared directly in dependencies or devDependencies — a simple npm install postcss@8.5.18 would not guarantee that @vue/cli-plugin-* packages use the patched version. They pin their own peer dependency ranges, and the lock file would continue resolving the old version for those packages.

The correct approach for pnpm is a package-level override, which forces every package in the dependency tree that requires postcss to receive version 8.5.18 regardless of what range they specify.

Changes in package.json

Before:

{
  "devDependencies": {
    "webpack": "^5.73.0",
    "webpack-cli": "^4.10.0",
    "webpack-dev-server": "^4.9.3"
  }
}

After:

{
  "devDependencies": {
    "webpack": "^5.73.0",
    "webpack-cli": "^4.10.0",
    "webpack-dev-server": "^4.9.3"
  },
  "pnpm": {
    "overrides": {
      "postcss": "8.5.18"
    }
  }
}

The pnpm.overrides block tells pnpm's resolver: no matter what version of PostCSS any package in this tree requests, install exactly 8.5.18.

Changes in pnpm-lock.yaml

The lock file reflects the override at the top level:

overrides:
  postcss: 8.5.18

And all resolved peer dependency strings for the affected Vue CLI plugins change from postcss@8.5.15 to postcss@8.5.18:

Before:

'@vue/cli-plugin-babel':
  specifier: ^5.0.8
  version: 5.0.9(...)(postcss@8.5.15)(...)

'@vue/cli-plugin-eslint':
  specifier: ^5.0.8
  version: 5.0.9(...)(postcss@8.5.15)(...)

After:

'@vue/cli-plugin-babel':
  specifier: ^5.0.8
  version: 5.0.9(...)(postcss@8.5.18)(...)

'@vue/cli-plugin-eslint':
  specifier: ^5.0.8
  version: 5.0.9(...)(postcss@8.5.18)(...)

What PostCSS 8.5.18 Actually Changes

The patch in PostCSS 8.5.18 tightens the path resolution logic for sourceMappingURL values. Before reading any referenced .map file, the resolved absolute path is validated to confirm it does not escape the base directory of the CSS file being processed. Any sourceMappingURL value containing traversal sequences (../, encoded variants, or absolute paths pointing outside the allowed scope) is rejected, and the auto-loading is skipped safely.

This change is backward-compatible: valid sourceMappingURL values pointing to map files within the expected directory continue to work exactly as before.


Prevention & Best Practices

1. Always Validate Paths Before File I/O

The canonical defense against path traversal in Node.js is to resolve the full absolute path and assert it starts with the expected base directory:

const path = require('path');

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

PostCSS 8.5.18 applies exactly this pattern to sourceMappingURL resolution.

2. Use Package Manager Overrides for Transitive Dependencies

When a vulnerability exists in a transitive dependency, don't rely on indirect updates propagating through the tree. Use your package manager's override mechanism explicitly:

  • pnpm: "pnpm": { "overrides": { "package": "version" } } in package.json
  • npm: "overrides": { "package": "version" } in package.json
  • yarn: "resolutions": { "package": "version" } in package.json

3. Audit Your Dependency Tree Regularly

The Trivy scanner that detected this vulnerability works by scanning lock files for known-vulnerable package versions. Integrate it into your CI pipeline:

trivy fs --scanners vuln pnpm-lock.yaml

This catches vulnerabilities in transitive dependencies that manual code review would miss.

4. Treat CSS from Untrusted Sources as Untrusted Input

If your application processes CSS files from external sources (user uploads, third-party fetches, CI builds of external repos), treat them with the same scrutiny as any other user input. Consider stripping or validating sourceMappingURL comments before passing CSS to any processor.

5. Relevant Standards


Key Takeaways

  • sourceMappingURL values are attacker-controlled data if the CSS being processed originates from any external source — they must be validated as paths, not trusted as safe strings.
  • PostCSS 8.5.15 and earlier will read arbitrary .map files if given a crafted sourceMappingURL with ../ sequences; upgrading to 8.5.18 closes this path.
  • Transitive dependency vulnerabilities require explicit overrides in pnpm — a lock file entry for postcss@8.5.15 persists until you force the resolution with pnpm.overrides.
  • "Not confirmed reachable" is not the same as "not exploitable" — the vulnerable code exists in the installed node_modules and could be activated by future project changes without a new security review.
  • Path traversal primitives are valuable to automated exploit chaining tools — even without a direct exploit path today, removing them proactively reduces the attack surface against increasingly capable automated tooling.

How Orbis AppSec Detected This

  • Source: The sourceMappingURL comment value embedded in a CSS file processed by PostCSS — content that can be controlled by whoever supplies the CSS input.
  • Sink: PostCSS's internal source map auto-loading logic in versions ≤8.5.15, which called Node.js fs.readFileSync() (or equivalent) using the unsanitized sourceMappingURL path value resolved relative to the CSS file's directory.
  • Missing control: No path containment check — PostCSS did not verify that the resolved absolute path of the .map file remained within the CSS file's directory before performing the read operation.
  • CWE: CWE-22 — Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')
  • Fix: Upgraded PostCSS from 8.5.15 to 8.5.18 via a pnpm.overrides entry in package.json, ensuring all transitive dependents receive the version that validates sourceMappingURL paths before file I/O.

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 security vulnerabilities in build tooling are just as consequential as those in runtime application code. PostCSS sits at the heart of nearly every modern JavaScript frontend build pipeline, and its source map auto-loading feature — a convenience for developers — became a path traversal vector because one input (the sourceMappingURL value) was not validated before being used to construct a filesystem path.

The fix is surgical and backward-compatible: PostCSS 8.5.18 adds a path containment check that rejects traversal sequences while leaving all legitimate source map references working as expected. Combined with a pnpm override to ensure every package in the dependency tree gets the patched version, this upgrade closes the vulnerability across all three affected Vue CLI plugins in a single, auditable change.

When you encounter similar patterns — any place where a string from an external source is used to construct a file path — apply the same principle: resolve to an absolute path, assert it starts with the expected base directory, and reject anything that doesn't. That single check is the difference between a useful feature and a path traversal vulnerability.


References

Frequently Asked Questions

What is a path traversal vulnerability?

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

How do you prevent path traversal in Node.js?

Validate and sanitize file paths by resolving them with `path.resolve()` or `path.normalize()`, then confirm the resolved path starts with the expected base directory before performing any file I/O.

What CWE is path traversal?

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

Is input escaping enough to prevent path traversal?

No. Escaping alone is insufficient; you must resolve the full absolute path and verify it is contained within the allowed directory before reading or writing any file.

Can static analysis detect path traversal?

Yes. Tools like Semgrep, CodeQL, and Trivy can flag tainted data flowing from external inputs (such as CSS comment content) into file-system APIs without path containment checks.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #18

Related Articles

high

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.

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