Vulnerabilities in pkg:npm/postcss

PACKAGE
Advisory Identifier: GHSA-r28c-9q8g-f849
Advisory Summary

CVE-2026-73646 is a HIGH (CVSS 7.5) remote code execution vulnerability (CWE-22: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')) affecting PostCSS libraries in the npm ecosystem. The issue involves path traversal in previous source map auto-loading (sourcemappingurl) leads to arbitrary .map file disclosure. Remediation is available by upgrading affected packages to version 8.5.18.

7 Known Advisory Record(s)
Package Vulnerability Intelligence

pkg:npm/postcss

7 Known Advisory Record(s)

Vulnerability advisories affecting package pkg:npm/postcss. Select an advisory below to view technical details or fixes.

Advisory ID CVE / NVD Severity Summary
GHSA-r28c-9q8g-f849 CVE-2026-73646 HIGH PostCSS: Path Traversal in Previous Source Map Auto-Loading (sourceMappingURL) leads to Arbitrary .map File Disclosure
GHSA-fxqj-rqcc-2cmp CVE-2026-69153 MODERATE PostCSS: incomplete fix of GHSA-6g55-p6wh-862q — attacker-controlled sourceMappingURL reads arbitrary .map files when `from` is unset
GHSA-6g55-p6wh-862q CVE-2026-45623 HIGH PostCSS: Arbitrary file read and information disclosure via attacker-controlled sourceMappingURL in CSS comments
GHSA-qx2v-qp2m-jg93 CVE-2026-41305 MODERATE PostCSS has XSS via Unescaped </style> in its CSS Stringify Output
GHSA-7fh5-64p2-3v2j CVE-2023-44270 MODERATE PostCSS line return parsing error
GHSA-566m-qj78-rww5 CVE-2021-23382 MODERATE Regular Expression Denial of Service in postcss
GHSA-hwj9-h5mp-3pm3 CVE-2021-23368 MODERATE Regular Expression Denial of Service in postcss

CVE-2026-73646 at a Glance

CVE Identifier CVE-2026-73646
GHSA Identifier GHSA-r28c-9q8g-f849
Severity HIGH
CVSS v3.1 Base Score 7.5
CWE Classification CWE-22: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')
Affected Product PostCSS
Package Ecosystem npm
Published Date
Last Updated

Security Impact

CVSS: 7.5

The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N).

Attack Vector Network (AV:N)
Attack Complexity Low (AC:L)
Privileges Required None (PR:N)
User Interaction None (UI:N)
Scope Unchanged (S:U)
Confidentiality High (C:H)
Integrity None (I:N)
Availability None (A:N)

How to Fix CVE-2026-73646

To remediate this vulnerability, upgrade all affected PostCSS dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.

postcss fixed: 8.5.18
npm install postcss@8.5.18
Verify Direct and Transitive Dependencies

Run the following command in your project directory to verify that all resolved dependencies use the patched release:

npm ls postcss

How to Check if You Are Affected

To determine whether your Node.js application is affected by CVE-2026-73646, inspect your project dependencies using npm:

npm ls postcss

Analyze the terminal output to locate any version of PostCSS in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 8.5.18), your application is affected.

Note: The npm ls postcss command inspects dependencies declared in your package.json and lockfile; it does not perform automated runtime dynamic scanning.

Technical Details

Vulnerability Mechanism & Technical Analysis

This vulnerability is classified under CWE-22: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal'), where the product 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 product 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. In PostCSS, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.

Source Advisory Statement

## Vulnerability Details **File**: `lib/previous-map.js` **Line**: 87-98 (`loadFile`), 129-144 (`loadMap`) ### Root Cause PostCSS auto-detects a `/*# sourceMappingURL=... */` comment inside the CSS text it is asked to parse and, unless the caller explicitly passes `map: false`, attempts to load that path from disk as a "previous source map." This happens on every `postcss.parse()` / `postcss().process()` call by default (opt-out, not opt-in). `loadMap()` builds the candidate path via `join(dirname(opts.from), annotation)`, where `annotation` is the raw, attacker-controlled string from the CSS comment. `path.join()` normalizes but does not sandbox `..` segments, so a `../../../` prefix walks the resolved path outside the intended directory. If `opts.from` is not set at all, the annotation is used completely unmodified — an absolute path in the CSS comment is read verbatim. 8.5.12 already fixed a strictly worse variant of this (any file, any extension, could be read) by requiring the resolved path to end in `.map` (`loadFile()`). That fix did not address the traversal itself, only the target extension. Since the `join(dirname(file), map)` logic has existed unchanged since PostCSS 8.0.0 (Feb 2020), any file ending in `.map` remains readable through this path in the current release (8.5.16). Once loaded, `MapGenerator.isMap()` treats the mere presence of a loaded "previous map" as an implicit request to generate `result.map`, even when the caller never set the `map` option. If the loaded map has a `sourcesContent` field (common for maps emitted by bundlers/transpilers), that content is merged into `result.map` and returned to the caller — disclosing the traversed-to file's content to whoever supplied the CSS. ### Attack Scenario 1. A service accepts user-submitted CSS and runs it through PostCSS to lint/format/transform it, e.g. `postcss().process(userCss, { from: '/app/uploads/user123/input.css', to: '/app/uploads/user123/output.css' })` — idiomatic usage; `map` option untouched. 2. Attacker submits CSS containing `/*# sourceMappingURL=../../../../some/other/app/dist/bundle.js.map */` (or an absolute path if `from` is unset). 3. PostCSS reads that `.map` file and folds its `sourcesContent` into `result.map`. 4. The service does what most build pipelines do with a truthy `result.map` — writes it next to the CSS output or returns it via API (source maps are meant to be consumed by browser devtools, so this is commonly public/served). 5. Attacker retrieves the emitted map and reads out the traversed file's content. ### Impact Disclosure of the contents of arbitrary `.map` files reachable via path traversal (or absolute path when `from` is unset) from the process's filesystem. Affects any application processing CSS it does not fully trust without explicitly passing `map: false`. No authentication or user interaction beyond submitting CSS text is required. ### Vulnerable Code ```js loadFile(path, cssFile, trusted) { if (!trusted && !this.unsafeMap) { if (!/\.map$/i.test(path)) { return undefined } } this.root = dirname(path) if (existsSync(path)) { this.mapFile = path return readFileSync(path, 'utf-8').toString().trim() } } loadMap(file, prev) { ... } else if (this.annotation) { let map = this.annotation if (file) map = join(dirname(file), map) let unknown = this.loadFile(map, file, false) ... } } ``` ### Recommended Fix Constrain the resolved path to remain inside the CSS file's own directory instead of relying solely on a filename-extension check: ```js loadFile(path, cssFile, trusted) { if (!trusted && !this.unsafeMap) { if (!/\.map$/i.test(path)) { return undefined } if (!cssFile) return undefined let root = resolve(dirname(cssFile)) let resolvedPath = resolve(root, path) if (resolvedPath !== root && !resolvedPath.startsWith(root + sep)) { return undefined } } this.root = dirname(path) if (existsSync(path)) { this.mapFile = path return readFileSync(path, 'utf-8').toString().trim() } } ``` I've implemented, tested (full existing test suite — 660/660 passing, plus new PoC-based regression checks for both the traversal and legitimate same-directory cases), and can share this fix on request or via a private fork if invited. ### Verification Dynamically confirmed on v8.5.16 (current npm release / repo HEAD) via a standalone Node.js harness against `lib/postcss.js`: a "secret" `.map` file placed two directories outside a simulated project directory was read via a crafted `sourceMappingURL` comment in otherwise-innocuous CSS, with its `sourcesContent` appearing verbatim in `result.map.toString()` — with no `map` option set by the caller. A second harness confirmed the simpler no-`from` case reads an absolute path directly. A third harness confirmed `map: false` is the only current workaround. The attached fix branch closes both vectors while keeping all 660 existing unit tests green.

CVE-2026-73646 Affected PostCSS Versions

1 component(s)
Fixed Versions:
8.5.18
Product / Package Ecosystem Affected Versions Fixed Version
postcss
npm
< 8.5.18
8.5.18

References

Frequently Asked Questions

What is CVE-2026-73646?
CVE-2026-73646 is a security vulnerability in PostCSS involving Path Traversal in Previous Source Map Auto-Loading (sourceMappingURL) leads to Arbitrary .map File Disclosure under CWE-22: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').
Which PostCSS versions are affected?
Affected versions include releases prior to 8.5.18 across affected packages: postcss.
What version fixes CVE-2026-73646?
Upgrading to 8.5.18 (or the latest patched release for your respective branch) resolves the vulnerability.
Is postcss affected?
Yes, postcss is affected in releases prior to 8.5.18. Upgrade your dependency to 8.5.18 or later.
What is the CVSS score of CVE-2026-73646?
CVE-2026-73646 has a CVSS v3.1 base score of 7.5 (HIGH) with vector string CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N.
What CWE is associated with CVE-2026-73646?
CVE-2026-73646 is classified under CWE-22: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal') (The product 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 product 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.).
How can I check npm dependencies for CVE-2026-73646?
Run the dependency tree command: 'npm ls postcss'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "schema_version" : "1.4.0",
  "id" : "GHSA-r28c-9q8g-f849",
  "modified" : "2026-08-13T17:55:54Z",
  "published" : "2026-07-24T16:24:55Z",
  "aliases" : [ "CVE-2026-73646" ],
  "summary" : "PostCSS: Path Traversal in Previous Source Map Auto-Loading (sourceMappingURL) leads to Arbitrary .map File Disclosure",
  "details" : "## Vulnerability Details\n\n**File**: `lib/previous-map.js`\n**Line**: 87-98 (`loadFile`), 129-144 (`loadMap`)\n\n\n### Root Cause\nPostCSS auto-detects a `/*# sourceMappingURL=... */` comment inside the CSS text it is asked to parse and, unless the caller explicitly passes `map: false`, attempts to load that path from disk as a \"previous source map.\" This happens on every `postcss.parse()` / `postcss().process()` call by default (opt-out, not opt-in).\n\n`loadMap()` builds the candidate path via `join(dirname(opts.from), annotation)`, where `annotation` is the raw, attacker-controlled string from the CSS comment. `path.join()` normalizes but does not sandbox `..` segments, so a `../../../` prefix walks the resolved path outside the intended directory. If `opts.from` is not set at all, the annotation is used completely unmodified — an absolute path in the CSS comment is read verbatim.\n\n8.5.12 already fixed a strictly worse variant of this (any file, any extension, could be read) by requiring the resolved path to end in `.map` (`loadFile()`). That fix did not address the traversal itself, only the target extension. Since the `join(dirname(file), map)` logic has existed unchanged since PostCSS 8.0.0 (Feb 2020), any file ending in `.map` remains readable through this path in the current release (8.5.16).\n\nOnce loaded, `MapGenerator.isMap()` treats the mere presence of a loaded \"previous map\" as an implicit request to generate `result.map`, even when the caller never set the `map` option. If the loaded map has a `sourcesContent` field (common for maps emitted by bundlers/transpilers), that content is merged into `result.map` and returned to the caller — disclosing the traversed-to file's content to whoever supplied the CSS.\n\n### Attack Scenario\n1. A service accepts user-submitted CSS and runs it through PostCSS to lint/format/transform it, e.g. `postcss().process(userCss, { from: '/app/uploads/user123/input.css', to: '/app/uploads/user123/output.css' })` — idiomatic usage; `map` option untouched.\n2. Attacker submits CSS containing `/*# sourceMappingURL=../../../../some/other/app/dist/bundle.js.map */` (or an absolute path if `from` is unset).\n3. PostCSS reads that `.map` file and folds its `sourcesContent` into `result.map`.\n4. The service does what most build pipelines do with a truthy `result.map` — writes it next to the CSS output or returns it via API (source maps are meant to be consumed by browser devtools, so this is commonly public/served).\n5. Attacker retrieves the emitted map and reads out the traversed file's content.\n\n### Impact\nDisclosure of the contents of arbitrary `.map` files reachable via path traversal (or absolute path when `from` is unset) from the process's filesystem. Affects any application processing CSS it does not fully trust without explicitly passing `map: false`. No authentication or user interaction beyond submitting CSS text is required.\n\n### Vulnerable Code\n```js\nloadFile(path, cssFile, trusted) {\n  if (!trusted && !this.unsafeMap) {\n    if (!/\\.map$/i.test(path)) {\n      return undefined\n    }\n  }\n  this.root = dirname(path)\n  if (existsSync(path)) {\n    this.mapFile = path\n    return readFileSync(path, 'utf-8').toString().trim()\n  }\n}\n\nloadMap(file, prev) {\n  ...\n  } else if (this.annotation) {\n    let map = this.annotation\n    if (file) map = join(dirname(file), map)\n    let unknown = this.loadFile(map, file, false)\n    ...\n  }\n}\n```\n\n### Recommended Fix\nConstrain the resolved path to remain inside the CSS file's own directory instead of relying solely on a filename-extension check:\n```js\nloadFile(path, cssFile, trusted) {\n  if (!trusted && !this.unsafeMap) {\n    if (!/\\.map$/i.test(path)) {\n      return undefined\n    }\n    if (!cssFile) return undefined\n    let root = resolve(dirname(cssFile))\n    let resolvedPath = resolve(root, path)\n    if (resolvedPath !== root && !resolvedPath.startsWith(root + sep)) {\n      return undefined\n    }\n  }\n  this.root = dirname(path)\n  if (existsSync(path)) {\n    this.mapFile = path\n    return readFileSync(path, 'utf-8').toString().trim()\n  }\n}\n```\nI've implemented, tested (full existing test suite — 660/660 passing, plus new PoC-based regression checks for both the traversal and legitimate same-directory cases), and can share this fix on request or via a private fork if invited.\n\n### Verification\nDynamically confirmed on v8.5.16 (current npm release / repo HEAD) via a standalone Node.js harness against `lib/postcss.js`: a \"secret\" `.map` file placed two directories outside a simulated project directory was read via a crafted `sourceMappingURL` comment in otherwise-innocuous CSS, with its `sourcesContent` appearing verbatim in `result.map.toString()` — with no `map` option set by the caller. A second harness confirmed the simpler no-`from` case reads an absolute path directly. A third harness confirmed `map: false` is the only current workaround. The attached fix branch closes both vectors while keeping all 660 existing unit tests green.",
  "severity" : [ {
    "type" : "CVSS_V3",
    "score" : "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "npm",
      "name" : "postcss"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "0"
      }, {
        "fixed" : "8.5.18"
      } ]
    } ],
    "database_specific" : {
      "last_known_affected_version_range" : "<= 8.5.17"
    }
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/postcss/postcss/security/advisories/GHSA-r28c-9q8g-f849"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/postcss/postcss/commit/95663d3eb7ba26f4854dd19d3b4f4425760cf56c"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/postcss/postcss"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/postcss/postcss/releases/tag/8.5.18"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-22" ],
    "severity" : "HIGH",
    "github_reviewed" : true,
    "github_reviewed_at" : "2026-07-24T16:24:55Z",
    "nvd_published_at" : null
  }
}