# PostCSS: Path Traversal in Previous Source Map Auto-Loading (sourceMappingURL) leads to Arbitrary .map File Disclosure

PostCSS auto-loads sourceMappingURL paths from untrusted CSS, letting traversal read arbitrary .map files and leak their sourcesContent via result.map.

- Payout: Research
- Severity: high
- Program: PostCSS
- Found by: iaohkut-from-NightWolf-Team
- Disclosed: 2026-07-24
- CVE: CVE-2026-73646
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-r28c-9q8g-f849
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-r28c-9q8g-f849
- Tags: area:oss domain:oss impact:data-leak kind:research platform:ghsa sev:high tech:nodejs technique:incomplete-fix technique:source-review vuln:info-disclosure vuln:path-traversal

**Target:** npm library lib/previous-map.js source map auto-loading
**Root cause:** loadMap() joins the raw sourceMappingURL annotation with dirname(opts.from) via path.join, which normalizes but does not sandbox '..' segments; with no opts.from the annotation is used verbatim as an absolute path. Only a .map extension check gates the read.
**Steps:**
1. Call postcss().process(userCss, { from: '/app/uploads/user123/input.css', to: '/app/uploads/user123/output.css' }) without setting map.
2. Submit CSS containing `/*# sourceMappingURL=../../../../some/other/app/dist/bundle.js.map */`.
3. PostCSS resolves the path via join(dirname(from), annotation), passes the .map extension check, and readFileSync's the target.
4. MapGenerator.isMap() sees a loaded previous map and emits result.map, merging the target's sourcesContent.
5. Retrieve the emitted map (written next to output or returned via API) and read the traversed file's content.
6. Variant: omit opts.from entirely and use an absolute path in the annotation to read it verbatim.
**How found:** Noticed the 8.5.12 fix only added a .map extension check without addressing the join(dirname(file), map) traversal, and that map loading is opt-out rather than opt-in; verified with Node harnesses for the traversal, no-from absolute path, and map:false workaround cases.
**Exploitation:** Attacker supplies CSS to any service that runs PostCSS on untrusted input with default map options; the service's own pipeline emits result.map containing the traversed .map file's sourcesContent, which the attacker then reads from the served/written output.
**Insight:** When a prior fix only narrows the target (extension allowlist) rather than the path resolution, re-test the same sink for traversal; also check whether a library's 'auto-detect' feature turns attacker-controlled input into a filesystem read that surfaces in output the app serves.

## Proof of concept (copied exactly from the original report)

**Traversal sourceMappingURL payload in CSS comment**

```
Attacker submits CSS containing `/*# sourceMappingURL=../../../../some/other/app/dist/bundle.js.map */` (or an absolute path if `from` is unset).
```

