Skip to content
This repository was archived by the owner on Aug 31, 2026. It is now read-only.

chore(deps): update dependency postcss to v8.5.18 [security] - #6047

Merged
hacdias merged 1 commit into
masterfrom
renovate/npm-postcss-vulnerability
Jul 27, 2026
Merged

hacdias merged 1 commit into
masterfrom
renovate/npm-postcss-vulnerability

Conversation

@renovate

@renovate renovate Bot commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

This PR contains the following updates:

Package Change Age Confidence
postcss (source) 8.5.14 → 8.5.18 age confidence

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

GHSA-r28c-9q8g-f849

More information

Details

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
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:

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.

Severity

  • CVSS Score: 7.5 / 10 (High)
  • Vector String: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N

References

This data is provided by OSV and the GitHub Advisory Database (CC-BY 4.0).


Release Notes

postcss/postcss (postcss)

v8.5.18

Compare Source

  • Restricted loading previous source maps file to the opts.from folder for security reasons (use unsafeMap: true to disable the check).

v8.5.17

Compare Source

  • Fixed Maximum call stack size exceeded error.
  • Fixed Prototype hijacking for postcss.fromJSON().
  • Fixed Input#origin() for unmapped end position (by @​chatman-media).

v8.5.16

Compare Source

v8.5.15

Compare Source

  • Fixed declaration parsing performance (by @​homanp).

Configuration

📅 Schedule: (UTC)

  • Branch creation
    • At any time (no schedule defined)
  • Automerge
    • At any time (no schedule defined)

🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.

♻ Rebasing: Whenever PR is behind base branch, or you tick the rebase/retry checkbox.

🔕 Ignore: Close this PR and you won't be reminded about this update again.


  • If you want to rebase/retry this PR, check this box

This PR was generated by Mend Renovate. View the repository job log.

@renovate
renovate Bot requested a review from a team as a code owner July 26, 2026 20:42
@renovate
renovate Bot force-pushed the renovate/npm-postcss-vulnerability branch from d13cdaa to 4d41668 Compare July 27, 2026 05:42
@renovate
renovate Bot force-pushed the renovate/npm-postcss-vulnerability branch from 4d41668 to 0b9ed09 Compare July 27, 2026 05:45
@hacdias
hacdias merged commit 9dd7c7d into master Jul 27, 2026
7 checks passed
@hacdias
hacdias deleted the renovate/npm-postcss-vulnerability branch July 27, 2026 05:47
aeltawela added a commit to aeltawela/filebrowser that referenced this pull request Aug 22, 2026
* chore: update translations (filebrowser#5990)

* fix: preserve SRT subtitle line breaks (filebrowser#6002)

* fix: match admin share paths by owner scope (filebrowser#5992)

* fix: normalize recursive listing paths to forward slashes (filebrowser#6003)

* docs,cmd: warn about broad scope for self-signup users (GHSA-6759-996p-gpj6)

When Signup is enabled with the default scope and createUserDir off, every
self-registered user inherits the served root and can read/modify/delete all
files. Add a startup WARNING for this configuration and document the risk and
the --createUserDir mitigation. No behavior or default change.

* fix(raw): neutralize backslashes in archive entry names (GHSA-83xp-526h-j3ww)

The fix for CVE-2026-54093 rewrote backslashes to the path separator "/" in
archive entry names. On POSIX hosts a backslash is a legal filename byte, so
that rewrite manufactured a traversal sequence ("..\..\x" -> "../../x") out
of a single in-scope file, turning a Windows-only zip-slip into a cross-platform
one. Neutralize backslashes to an inert character instead, and reject any entry
whose name is not already a normalized root-relative path. Adds a regression
test that downloads a folder containing a backslash-named file as a zip.

* fix(auth): reject signup when normalized home dir collides (GHSA-7rc3-g7h6-22m7)

cleanUsername is many-to-one, so distinct usernames (e.g. "teamone/x" and
"teamone-x") can normalize to the same home directory. With CreateUserDir
enabled, the second registrant silently reused the first user's directory,
breaking per-user isolation. Add a GetByScope lookup and reject a signup whose
derived scope is already taken. The check is gated on CreateUserDir: when it is
off, signups intentionally share the configured default scope. Adds a regression
test for the colliding-username case.

* fix(share): stop exposing password hash and bypass token in share API (GHSA-833g-cqhp-h72j)

The share management endpoints serialized the storage struct directly, returning
the bcrypt password_hash (crackable offline) and the bypass token for every
share an authenticated caller could list, with admins seeing them for all users.
Return a response DTO that exposes only whether a share is password-protected
(hasPassword) and drops both secrets. The storage struct keeps its tags so the
secrets stay persisted and the server-side auth/public flows are unchanged.
Updates the frontend to use hasPassword and adds a regression test.

* fix(share): delete exact directory share on trailing-slash delete (GHSA-pp88-jhwj-5qh5)

DeleteWithPathPrefix queried the share index with the raw path, so deleting a
directory through a trailing-slash path (e.g. DELETE /api/resources/a/) only
matched descendants like /a/child and missed the exact /a share, leaving it in
storage. If the same path was later recreated, the stale public share re-exposed
the new content. Normalize the path before the prefix query so the exact share
and its descendants are both removed. Adds a regression test.

* chore(release): 2.63.17

* docs: warning about hook executor

* fix(preview): keep the EPUB table-of-contents button clear of the header (filebrowser#6010)

* fix: avoid recursive conflict checks for copy and move (filebrowser#6009)

* chore: update dependencies

* fix: deduplicate PT language

* chore: update translations

* chore(release): 2.63.18

* fix(http): run upload hooks for directories (filebrowser#6034)

* chore: update translations (filebrowser#6019)

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>

* fix: return error instead of panicking on an unreadable directory during copy (filebrowser#6020)

Co-authored-by: Claude Fable 5 <[email protected]>

* docs: fix typo

* fix: process --FollowExternalSymlinks

* fix(upload): handle encoded path conflicts safely (filebrowser#6040)

* fix: accessibility and security improvements (filebrowser#6033)

* chore: update translations

* docs: update readme

* docs: remove go report

* fix(auth): isolate auto-provisioned proxy and hook users to their own home

Proxy- and hook-authenticated users were auto-provisioned by applying the
default scope (".") and passing it straight to MakeUserDir, which normalizes
"." to "/". With CreateUserDir enabled, every provisioned user therefore
received the server root as its scope instead of a per-user home directory,
letting one user read, overwrite and delete another user's files.

The signup handler already cleared the scope before deriving the home
directory. Centralize that logic into Settings.CreateUserHome (clear the
scope when CreateUserDir is on and no explicit scope was supplied, derive the
home dir, then reject a scope already owned by another user) and use it from
signup, proxy and hook auth so the three provisioning paths cannot diverge.

Refs GHSA-j7jh-37pf-mf8h, GHSA-j2fc-28fx-hc8q

* fix(storage): reject case-folded home directory collisions

GetByScope compared scopes with a case-sensitive exact match, so on a
case-insensitive filesystem two accounts whose scopes differ only in case
(e.g. /users/CaseVictim and /users/casevictim) were treated as distinct even
though both resolve to the same physical home directory. The second account
could then read, overwrite and delete the first account's files.

Match the scope case-insensitively so the collision check performed during
signup, proxy and hook provisioning catches these aliases and rejects the
duplicate.

Refs GHSA-576v-w77m-gr84

* fix(http): enforce download permission on the checksum branch

The ?checksum= branch of the resource GET handler read the whole file to
compute its digest and returned it without checking Perm.Download, unlike the
sibling raw, preview, subtitle and X-Encoding paths. A user provisioned with
Perm.Download=false could therefore obtain md5/sha1/sha256/sha512 digests of
files they are not allowed to download, giving a file-content hash oracle, and
force a full-file read.

Return 202 Accepted before computing the checksum when the user lacks
Perm.Download, matching the other read paths. This completes the fix for
CVE-2026-35606, whose original patch gated only the content-returning paths.

Refs GHSA-7whw-q6gh-xr59

* fix(http): delete abandoned TUS uploads through the scoped filesystem

The in-memory upload cache deleted expired incomplete uploads with a raw
os.Remove on the absolute path it had stored, bypassing ScopedFs entirely.
Because the stored path is only lexically cleaned (no symlink evaluation) and
os.Remove resolves symlinked parent directories, a Create-only user could
register an upload and then, within the 3-minute TTL, swap an in-scope
ancestor directory for a symlink so the eviction deleted an arbitrary file
outside their scope.

Carry a removal callback with each cache entry and invoke it on eviction
instead of os.Remove. For TUS uploads the callback deletes via the uploading
user's scoped filesystem, whose Remove is guarded by the same within() check
that CVE-2026-55667 added, so eviction can no longer follow a symlink out of
scope. The redis backend ignores the callback (it never deleted partial files).

Refs GHSA-m9f5-2232-frp6

* fix(http): enforce declared Upload-Length on TUS uploads

The TUS PATCH handler copied the whole request body to disk and used the
declared Upload-Length only to decide when the upload was complete. A client
could declare a tiny Upload-Length and then stream an arbitrarily large body
in a single PATCH, writing it all to disk, so any user with create permission
could exhaust the filesystem.

Bound each write to the bytes still expected (Upload-Length - Upload-Offset),
reject a PATCH whose offset already exceeds the declared length, and roll back
and reject a body that exceeds it. This complements the existing negative
Upload-Length handling.

Refs GHSA-ffv3-7h97-993q

* test(auth): cover per-user scope isolation for proxy and hook provisioning

Unit tests for Settings.CreateUserHome (derives a per-user home, rejects a
colliding scope, preserves an explicit scope) plus proxy and hook regression
tests asserting that with CreateUserDir enabled two provisioned users receive
distinct home directories instead of the server root.

Refs GHSA-j7jh-37pf-mf8h, GHSA-j2fc-28fx-hc8q

* test(storage): cover case-insensitive GetByScope matching

Refs GHSA-576v-w77m-gr84

* test(http): cover download-permission gate on the checksum branch

Refs GHSA-7whw-q6gh-xr59

* test(http): cover scope-safe removal on TUS upload eviction

Refs GHSA-m9f5-2232-frp6

* test(http): cover TUS Upload-Length enforcement

Refs GHSA-ffv3-7h97-993q

* chore(release): 2.63.19

* docs: update SECURITY.md and add CLAUDE.md

* docs: update CLAUDE.md

* docs: update CLAUDE instructions

* fix: use aria-selected

* fix(users): make the provisioned scope check atomic with the save

The check that stops two usernames normalizing to the same home directory
ran as a storage operation separate from the save, so two first-time users
provisioned concurrently could both observe a free scope and both be saved
into it, ending up sharing one home directory.

Move the check into users.Storage.SaveProvisioned, which holds a single
lock across the lookup and the save, and reduce CreateUserHome to deriving
and creating the directory. This covers all three provisioning paths:
signup, proxy auth and hook auth.

Refs GHSA-j7jh-37pf-mf8h

* test(users): cover concurrent provisioning into one scope

Two usernames that normalize to the same home directory must not both be
provisioned when their requests are handled concurrently. Also cover that
an explicit, non-derived scope may still be legitimately shared.

Refs GHSA-j7jh-37pf-mf8h

* chore(release): 2.63.20

* chore(deps): update all non-major dependencies (filebrowser#5946)

* Revert "chore(deps): update all non-major dependencies (filebrowser#5946)"

This reverts commit 032271b.

* chore: update Go deps

* docs: update CLAUDE.md

* fix(http): canonicalize paths before checking access rules (filebrowser#6045)

* chore(release): 2.63.21

* chore: update renovate.json

* chore(deps): update dependency dompurify to v3.4.12 [security] (filebrowser#6046)

* chore: fix renovate.json order

* chore(deps): update dependency postcss to v8.5.18 [security] (filebrowser#6047)

* fix: unblock stalled and frozen uploads

A TUS PATCH rejected before its body was read left the client still streaming
a chunk. net/http gives up draining an unread request body after 256KiB and
then closes the connection, so the status we replied with was lost and the
browser saw a bare transport error instead. The client could not tell a
conflict from a network fault, retried blindly, and the upload stalled after
the first chunk — while curl, which reads the response as it writes, still
reported the status and appeared to work. Drain what is left of a rejected
chunk so the answer arrives on a connection that stays usable. The explicit
r.Body.Close() had to go with it: it ran before the drain, and the server
closes the request body itself once the handler returns.

Pressing Upload awaited a recursive listing of the whole destination subtree
before a single byte was sent, with no sign that anything was happening. On a
large destination that reads as a dead button, and the walk kept running after
the client gave up, then reported the failed write as a 500. A flat upload can
only collide with a direct child of the destination, so walk recursively only
for folder uploads, stop the walk once the request is done, and show the action
as busy while the destination is checked.

Along the way: a failed upload was credited as fully sent, so the progress bar
reported 100% when nothing had been written; the progress timer was recreated
without clearing the previous one; and the chunk size field threw on a value
with no unit and raced its own debounce on submit, so it appeared to refuse
every change.

Fixes filebrowser#6006
Refs filebrowser#5987

* docs: add wind down notice

* docs: fix date

* fix: make the sidebar scrollable when content overflows (filebrowser#6031)

* fix: enforce rules on recursive operations and expired proxy tokens (filebrowser#6053)

* fix: use absolute URLs for the PWA manifest icon sources (filebrowser#6032)

* chore: update translations

* chore: remove renovate.json

* chore(release): 2.63.22

* docs: clear inconsistencies, point at repo instead of website

* docs: cleanup header

* chore(release): 2.63.23

* docs: include issue summary in readme

* docs: update post link

---------

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
Co-authored-by: Rayan Salhab <[email protected]>
Co-authored-by: JinHyuk Sung <[email protected]>
Co-authored-by: Henrique Dias <[email protected]>
Co-authored-by: Aditya Raj Singh <[email protected]>
Co-authored-by: 林桉 <[email protected]>
Co-authored-by: tianrking <[email protected]>
Co-authored-by: TowyTowy <[email protected]>
Co-authored-by: Claude Fable 5 <[email protected]>
Co-authored-by: Archit Goyal <[email protected]>
Co-authored-by: superafun <[email protected]>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant