GHSA-fxqj-rqcc-2cmp: PostCSS: incomplete fix of GHSA-6g55-p6wh-862q — attacker-controlled sourceMappingURL reads arbitrary .map files when `from` is unset
Summary
The fix for GHSA-6g55-p6wh-862q added a guard in lib/previous-map.js PreviousMap.loadFile() that restricts an attacker-controlled sourceMappingURL (from a CSS comment) to a .map extension and, for untrusted maps, rejects .. traversal and absolute paths. The traversal/absolute rejection is nested inside if (cssFile) { ... }. When PostCSS is invoked without the from option, cssFile is falsy and that branch is skipped, leaving only the .map extension check.
PreviousMap is constructed by lib/input.js whenever pathAvailable && sourceMapAvailable (under Node with source-map available), independent of opts.from/opts.map (the constructor returns early only for opts.map === false). So postcss([]).process(css) on attacker CSS reaches loadFile with cssFile undefined, and an attacker /*# sourceMappingURL=/abs/path/x.map */ (or ../-traversing path) is read via readFileSync. When the file is valid JSON, its sources (filesystem paths) and sourcesContent (source contents) are disclosed in the generated source map.
Affected code (v8.5.22 — the release carrying the GHSA-6g55 fix)
// lib/previous-map.js
loadFile(path, cssFile, trusted) {
if (!trusted && !this.unsafeMap) {
if (!/\.map$/i.test(path)) {
return undefined
}
if (cssFile) { // guard runs ONLY when from is set
let relativePath = relative(dirname(cssFile), path)
if (relativePath === '..' ||
relativePath.startsWith('..' + sep) ||
isAbsolute(relativePath)) {
return undefined
}
}
}
this.root = dirname(path)
if (existsSync(path)) {
this.mapFile = path
return readFileSync(path, 'utf-8').toString().trim() // sink
}
}
// loadMap(): untrusted annotation path, trusted=false; file === opts.from
} else if (this.annotation) {
let map = this.annotation
if (file) map = join(dirname(file), map) // no from -> map stays the raw URL
let unknown = this.loadFile(map, file, false) // file undefined -> cssFile falsy
Proof of concept (verified on postcss 8.5.22)
const postcss = require('postcss')
const fs = require('fs')
// a 'secret
Details
Original advisory: https://github.com/advisories/GHSA-fxqj-rqcc-2cmp
Exploitation outlook
EPSS (FIRST.org) estimates each CVE’s probability of exploitation in the next 30 days — here is the CSIRTS.com read on those numbers.
- Low exploitation riskCVE-2026-691530.31% 30-day exploitation probability — currently an unlikely target, but scores change as exploit code circulates. Riskier than 24% of all EPSS-scored CVEs.
Referenced CVEs
| CVE | CSIRTS overview | External |
|---|---|---|
| CVE-2026-69153 | coverage & exploitation status | NVD · CVE.org |
Same CVEs, other sources
How other CERTs, PSIRTs and databases cover the vulnerabilities in this advisory.
More from GitHub Security Advisories
- mediumGHSA-9p7c-v5x3-rfx8: Craft CMS: Missing authorization check allows non-admin control panel users to reorder Gl…2026-08-06
- highGHSA-265m-7826-wjqm: Craft CMS: Authenticated RCE via `condition.config` JSON cleanse bypass2026-08-06
- mediumGHSA-mj63-m3rc-8ppr: league/commonmark: Denial of service via deeply nested XML output2026-08-06
- highGHSA-mh25-x5hq-wrqp: league/commonmark: Denial of service via colliding heading slugs2026-08-06
- highGHSA-jfm3-95jq-q3rf: league/commonmark: Denial of service via duplicate footnote definitions2026-08-06