Security patches (stable/1.x) - #449
Open
renovate[bot] wants to merge 1 commit into
Open
Conversation
Author
|
renovate
Bot
force-pushed
the
renovate/stable/1.x-security-patches
branch
2 times, most recently
from
July 25, 2026 07:49
f6c4c88 to
a9c9003
Compare
renovate
Bot
force-pushed
the
renovate/stable/1.x-security-patches
branch
2 times, most recently
from
August 3, 2026 23:50
96c2b2f to
83c866f
Compare
renovate
Bot
force-pushed
the
renovate/stable/1.x-security-patches
branch
2 times, most recently
from
August 22, 2026 00:02
6ef6c49 to
47ed3b9
Compare
renovate
Bot
force-pushed
the
renovate/stable/1.x-security-patches
branch
2 times, most recently
from
September 3, 2026 01:55
2157a88 to
147b827
Compare
renovate
Bot
force-pushed
the
renovate/stable/1.x-security-patches
branch
from
September 6, 2026 03:55
147b827 to
4fb2fb9
Compare
renovate
Bot
force-pushed
the
renovate/stable/1.x-security-patches
branch
from
September 11, 2026 00:01
4fb2fb9 to
c2f3d9a
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR contains the following updates:
7.29.0→7.29.64.1.3→4.1.48.5.8→8.5.236.15.3→6.16.06.30.4→6.30.67.3.1→7.3.54.0.18→4.1.110.5.4→0.5.50.5.4→0.5.5@babel/core: Arbitrary File Read via sourceMappingURL Comment
CVE-2026-49356 / GHSA-4x5r-pxfx-6jf8
More information
Details
Impact
Using
@babel/coreto compile maliciously crafted code can allow ab attacker to read any source map from the system that is running Babel, if these conditions are all true:Users that only compile trusted code are not impacted.
Patches
The vulnerability has been fixed in
@babel/core@7.29.6and@babel/core@8.0.0-rc.6.Workarounds
Callers can mitigate the issue without upgrading by setting
inputSourceMap: falsein their Babel options.Callers can also manually extract the
#sourceMappingURLcomment from the input source code, validate whether the source map that it links to is allowed to be read, and if it is pass an object toinputSourceMap(passingfalsewhen it's not).Credits
Thanks Teodor-Cristian Radoi for reporting the vulnerability.
Severity
CVSS:3.1/AV:L/AC:H/PR:N/UI:N/S:C/C:L/I:N/A:NReferences
This data is provided by OSV and the GitHub Advisory Database (CC-BY 4.0).
XSS in ammonia via SVG
animateandsetanimation tagsGHSA-m6mh-2hw2-555x / RUSTSEC-2026-0213
More information
Details
The following SVG will produce a link with a javascript scheme.
If the user clicks this link, they will run it.
Ammonia did not apply attribute filters based on
attributeName,so the contents of the
to,from, andvaluestags were not sanitized as URLs.Applications that do not explicitly allow either of these tags should not be affected,
since neither are allowed by default.
Discovered by: Younghun Ko (@koyokr)
Severity
Unknown
References
This data is provided by OSV and the Rust Advisory Database (CC0 1.0).
PostCSS: Path Traversal in Previous Source Map Auto-Loading (sourceMappingURL) leads to Arbitrary .map File Disclosure
CVE-2026-73646 / GHSA-r28c-9q8g-f849
More information
Details
Vulnerability Details
File:
lib/previous-map.jsLine: 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 passesmap: false, attempts to load that path from disk as a "previous source map." This happens on everypostcss.parse()/postcss().process()call by default (opt-out, not opt-in).loadMap()builds the candidate path viajoin(dirname(opts.from), annotation), whereannotationis 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. Ifopts.fromis 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 thejoin(dirname(file), map)logic has existed unchanged since PostCSS 8.0.0 (Feb 2020), any file ending in.mapremains 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 generateresult.map, even when the caller never set themapoption. If the loaded map has asourcesContentfield (common for maps emitted by bundlers/transpilers), that content is merged intoresult.mapand returned to the caller — disclosing the traversed-to file's content to whoever supplied the CSS.Attack Scenario
postcss().process(userCss, { from: '/app/uploads/user123/input.css', to: '/app/uploads/user123/output.css' })— idiomatic usage;mapoption untouched./*# sourceMappingURL=../../../../some/other/app/dist/bundle.js.map */(or an absolute path iffromis unset)..mapfile and folds itssourcesContentintoresult.map.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).Impact
Disclosure of the contents of arbitrary
.mapfiles reachable via path traversal (or absolute path whenfromis unset) from the process's filesystem. Affects any application processing CSS it does not fully trust without explicitly passingmap: false. No authentication or user interaction beyond submitting CSS text is required.Vulnerable Code
Recommended Fix
Constrain the resolved path to remain inside the CSS file's own directory instead of relying solely on a filename-extension check:
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".mapfile placed two directories outside a simulated project directory was read via a craftedsourceMappingURLcomment in otherwise-innocuous CSS, with itssourcesContentappearing verbatim inresult.map.toString()— with nomapoption set by the caller. A second harness confirmed the simpler no-fromcase reads an absolute path directly. A third harness confirmedmap: falseis the only current workaround. The attached fix branch closes both vectors while keeping all 660 existing unit tests green.Severity
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:NReferences
This data is provided by the GitHub Advisory Database (CC-BY 4.0).
PostCSS: incomplete fix of GHSA-6g55-p6wh-862q — attacker-controlled sourceMappingURL reads arbitrary .map files when
fromis unsetCVE-2026-69153 / GHSA-fxqj-rqcc-2cmp
More information
Details
Summary
The fix for GHSA-6g55-p6wh-862q added a guard in
lib/previous-map.jsPreviousMap.loadFile()that restricts an attacker-controlledsourceMappingURL(from a CSS comment) to a.mapextension and, for untrusted maps, rejects..traversal and absolute paths. The traversal/absolute rejection is nested insideif (cssFile) { ... }. When PostCSS is invoked without thefromoption,cssFileis falsy and that branch is skipped, leaving only the.mapextension check.PreviousMapis constructed bylib/input.jswheneverpathAvailable && sourceMapAvailable(under Node with source-map available), independent ofopts.from/opts.map(the constructor returns early only foropts.map === false). Sopostcss([]).process(css)on attacker CSS reachesloadFilewithcssFileundefined, and an attacker/*# sourceMappingURL=/abs/path/x.map */(or../-traversing path) is read viareadFileSync. When the file is valid JSON, itssources(filesystem paths) andsourcesContent(source contents) are disclosed in the generated source map.Affected code (v8.5.22 — the release carrying the GHSA-6g55 fix)
Proof of concept (verified on postcss 8.5.22)
Observed output on postcss 8.5.22:
../traversal (nofrom) also succeeds; non-.maptargets (.txt,?x=.map,#.map) are blocked by the.mapcheck. The tested build contains the GHSA-6g55 fix (this.json = JSON.parse(...)inloadMap,consumer()usesthis.json || this.text), so this is a residual of that fix.Impact
Arbitrary
.map-file read (absolute path or../traversal) and disclosure of the target map'ssources(local filesystem paths) andsourcesContent(source) into the generated source map, for any consumer that runs PostCSS on attacker-influenced CSS without afromoption and exposesresult.map(online CSS playgrounds, minify/lint services, string-input build steps). Bounded to files ending in.mapthat parse as JSON.Suggested fix
Apply the traversal/absolute-path rejection to the untrusted map path regardless of whether
cssFileis present (resolve againstprocess.cwd()when there is nocssFile, and reject absolute paths and..escape in all untrusted cases), or refuse to load an untrusted external map when no base file is known.Severity
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:L/VI:N/VA:N/SC:N/SI:N/SA:NReferences
This data is provided by the GitHub Advisory Database (CC-BY 4.0).
PostCSS has XSS via Unescaped </style> in its CSS Stringify Output
CVE-2026-41305 / GHSA-qx2v-qp2m-jg93
More information
Details
PostCSS: XSS via Unescaped
</style>in CSS Stringify OutputSummary
PostCSS v8.5.5 (latest) does not escape
</style>sequences when stringifying CSS ASTs. When user-submitted CSS is parsed and re-stringified for embedding in HTML<style>tags,</style>in CSS values breaks out of the style context, enabling XSS.Proof of Concept
Tested output (Node.js v22, postcss v8.5.5):
Impact
Impact non-bundler use cases since bundlers for XSS on their own. Requires some PostCSS plugin to have malware code, which can inject XSS to website.
Suggested Fix
Escape
</stylein all stringified output values:Credits
Discovered and reported by Sunil Kumar (@TharVid)
Severity
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:NReferences
This data is provided by OSV and the GitHub Advisory Database (CC-BY 4.0).
PostCSS: Arbitrary file read and information disclosure via attacker-controlled sourceMappingURL in CSS comments
CVE-2026-45623 / GHSA-6g55-p6wh-862q
More information
Details
Summary
PostCSS's
PreviousMapparses the/*# sourceMappingURL=PATH */comment from any CSS string passed toprocess()and dereferencesPATHagainst the local filesystem with no scheme, allowlist, or traversal check. An attacker who controls the CSS input can cause the host process to read any file readable by Node and leak the first ~10 bytes of its content through the resultingJSON.parseSyntaxErrormessage. The bug also yields a precise file-existence oracle and a controllable-read primitive that may be combined with large-file targets for DoS. The behaviour is triggered with PostCSS's default options — nofrom, nomap, no plugins required — and is therefore reachable from any pipeline that runs untrusted CSS through PostCSS (CMS themes, user-uploaded styles, browser-extension/userstyle processors, build pipelines for third-party packages, blog comment renderers, etc.).Details
The dangerous chain lives in
lib/previous-map.jsand is wired into everyInputconstruction atlib/input.js:70-77.Inputconstructor (lib/input.js:70-77):PreviousMapconstructor (lib/previous-map.js:17-29):Note
opts.map === falseis the only short-circuit. With default options (opts.map === undefined), the rest of the constructor — including the filesystem read — executes.loadAnnotation(lib/previous-map.js:72-84) extracts the URL without sanitisation:getAnnotationURL(lib/previous-map.js:59-61) only strips the/*# sourceMappingURL=prefix and trims whitespace — no scheme check, no path normalisation, no allowlist.loadMap(lib/previous-map.js:124-128) — whenprevis absent and the annotation is not an inlinedata:URI:opts.fromis unset,fileis undefined and the raw attacker-supplied path (e.g./etc/passwd) is used directly.opts.fromis set,path.join(dirname(file), attackerPath)is used.path.joindoes not block..segments, so../../../../../etc/passwdresolves outside the intended directory.loadFile(lib/previous-map.js:86-92) is the sink:The bytes are stored in
this.text.Inputimmediately invokesmap.consumer()(lib/input.js:74), which constructs aSourceMapConsumer(lib/previous-map.js:33). When the file is not valid source-map JSON (the common case),source-map-jscallsJSON.parse, and V8'sSyntaxErrormessage embeds the first ~10 bytes of the file content:This error is propagated back to the caller. Any application that surfaces PostCSS errors (logs, HTTP 500 responses, build-tool output, debug pages) discloses those bytes to the attacker.
Trust-boundary analysis:
postcss().process(css, opts?)./etc/passwd,/proc/self/environ, etc.startWith(annotation, 'data:')) routes inline URIs todecodeInline; everything else hitsloadFile.Primitives obtained:
JSON.parseSyntaxErrormessage.loadFile(existsSyncis false → returns undefined → no map text → no consumer call → no error). Existent non-JSON paths throw. Existent JSON paths succeed silently. Three distinguishable states./dev/zero, very large files, or device files can stall or crash the process.PoC
All commands executed against this repository's HEAD (postcss 8.5.10) on Node v22.12.0.
Vector 1 — Absolute path, default options (no
from, nomap):The first 10 bytes of
/etc/passwd(root:x:0:0) are leaked.Vector 2 — Relative
..traversal withopts.fromset (simulates a build pipeline that pinsfromto the source file):path.join('/var/www/html/styles', '../../../../../etc/passwd')resolves to/etc/passwd.Vector 3 — File-existence oracle:
Vector 4 — Custom file-content leak:
The first 10 bytes of
/tmp/server-secret.env(API_KEY=sk) are leaked — sufficient to confirm a token's presence and, in many cases, recover its prefix.Filesystem-call trace (proves the read happens with no opts at all):
Impact
postcss-loader, vite, parcel, Next.js, Gatsby, etc.) and for runtime CSS-handling libraries (CSS Modules tools, CSS minifiers, theme processors). Any pipeline that runs untrusted user CSS — CMS theme uploads, user-styled blog posts, browser-extension/userstyle services, multi-tenant build farms, third-party-package build pipelines — is exposed.JSON.parseSyntaxError. This is enough to recover SSH-key headers, environment-variable prefixes (API_KEY=sk…),/etc/passwdrecords, the start of/proc/self/environ, and other high-value secrets, and to fingerprint the host (Debian-tri…from/etc/hostname).JSON.parseerror, no-such-file silence), enabling reconnaissance of the host filesystem layout and confirmation of installed software, user accounts, and configuration files./dev/zero,/proc/kcore, very large files, or named pipes —readFileSyncis a synchronous, unbounded read.postcss().process(css)and no options. The only configuration that disables the bug is the explicit, undocumented-for-this-purpose{ map: false }.Recommended Fix
The root cause is that
loadFileaccepts any path the attacker supplies inside a CSS comment. The annotation is meant for tooling, not for production CSS processing of untrusted input. Two layered fixes:Refuse traversal/absolute paths in
loadMap(defence-in-depth):Require explicit opt-in to follow on-disk source-map annotations: gate the
loadFile(map)call inloadMapbehind an option such asopts.map.annotation === trueoropts.map.followAnnotation === true. Today, the only way to opt out is{ map: false }, which also disables in-memory previous-map handling. Inverting the default — only follow disk-resident annotations when explicitly asked — eliminates the entire attack surface for callers that pass untrusted CSS, while preserving build-tool use cases where the annotation is trusted.A user-facing changelog entry should warn that
postcss().process(untrustedCss)previously read attacker-controlled paths, and recommend auditing applications that surfaced PostCSS errors to end users.Severity
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:NReferences
This data is provided by OSV and the GitHub Advisory Database (CC-BY 4.0).
PostCSS: Path Traversal in Previous Source Map Auto-Loading (sourceMappingURL) leads to Arbitrary .map File Disclosure
CVE-2026-73646 / GHSA-r28c-9q8g-f849
More information
Details
Vulnerability Details
File:
lib/previous-map.jsLine: 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 passesmap: false, attempts to load that path from disk as a "previous source map." This happens on everypostcss.parse()/postcss().process()call by default (opt-out, not opt-in).loadMap()builds the candidate path viajoin(dirname(opts.from), annotation), whereannotationis 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. Ifopts.fromis 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 thejoin(dirname(file), map)logic has existed unchanged since PostCSS 8.0.0 (Feb 2020), any file ending in.mapremains 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 generateresult.map, even when the caller never set themapoption. If the loaded map has asourcesContentfield (common for maps emitted by bundlers/transpilers), that content is merged intoresult.mapand returned to the caller — disclosing the traversed-to file's content to whoever supplied the CSS.Attack Scenario
postcss().process(userCss, { from: '/app/uploads/user123/input.css', to: '/app/uploads/user123/output.css' })— idiomatic usage;mapoption untouched./*# sourceMappingURL=../../../../some/other/app/dist/bundle.js.map */(or an absolute path iffromis unset)..mapfile and folds itssourcesContentintoresult.map.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).Impact
Disclosure of the contents of arbitrary
.mapfiles reachable via path traversal (or absolute path whenfromis unset) from the process's filesystem. Affects any application processing CSS it does not fully trust without explicitly passingmap: false. No authentication or user interaction beyond submitting CSS text is required.Vulnerable Code
Recommended Fix
Constrain the resolved path to remain inside the CSS file's own directory instead of relying solely on a filename-extension check:
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".mapfile placed two directories outside a simulated project directory was read via a craftedsourceMappingURLcomment in otherwise-innocuous CSS, with itssourcesContentappearing verbatim inresult.map.toString()— with nomapoption set by the caller. A second harness confirmed the simpler no-fromcase reads an absolute path directly. A third harness confirmedmap: falseis the only current workaround. The attached fix branch closes both vectors while keeping all 660 existing unit tests green.Severity
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:NReferences
This data is provided by OSV and the GitHub Advisory Database (CC-BY 4.0).
PostCSS: incomplete fix of GHSA-6g55-p6wh-862q — attacker-controlled sourceMappingURL reads arbitrary .map files when
fromis unsetCVE-2026-69153 / GHSA-fxqj-rqcc-2cmp
More information
Details
Summary
The fix for GHSA-6g55-p6wh-862q added a guard in
lib/previous-map.jsPreviousMap.loadFile()that restricts an attacker-controlledsourceMappingURL(from a CSS comment) to a.mapextension and, for untrusted maps, rejects..traversal and absolute paths. The traversal/absolute rejection is nested insideif (cssFile) { ... }. When PostCSS is invoked without thefromoption,cssFileis falsy and that branch is skipped, leaving only the.mapextension check.PreviousMapis constructed bylib/input.jswheneverpathAvailable && sourceMapAvailable(under Node with source-map available), independent ofopts.from/opts.map(the constructor returns early only foropts.map === false). Sopostcss([]).process(css)on attacker CSS reachesloadFilewithcssFileundefined, and an attacker/*# sourceMappingURL=/abs/path/x.map */(or../-traversing path) is read viareadFileSync. When the file is valid JSON, itssources(filesystem paths) andsourcesContent(source contents) are disclosed in the generated source map.Affected code (v8.5.22 — the release carrying the GHSA-6g55 fix)
Proof of concept (verified on postcss 8.5.22)
Observed output on postcss 8.5.22:
../traversal (nofrom) also succeeds; non-.maptargets (.txt,?x=.map,#.map) are blocked by the.mapcheck. The tested build contains the GHSA-6g55 fix (this.json = JSON.parse(...)inloadMap,consumer()usesthis.json || this.text), so this is a residual of that fix.Impact
Arbitrary
.map-file read (absolute path or../traversal) and disclosure of the target map'ssources(local filesystem paths) andsourcesContent(source) into the generated source map, for any consumer that runs PostCSS on attacker-influenced CSS without afromoption and exposesresult.map(online CSS playgrounds, minify/lint services, string-input build steps). Bounded to files ending in.mapthat parse as JSON.Suggested fix
Apply the traversal/absolute-path rejection to the untrusted map path regardless of whether
cssFileis present (resolve againstprocess.cwd()when there is nocssFile, and reject absolute paths and..escape in all untrusted cases), or refuse to load an untrusted external map when no base file is known.Severity
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:L/VI:N/VA:N/SC:N/SI:N/SA:NReferences
This data is provided by OSV and the GitHub Advisory Database (CC-BY 4.0).
qs array-limit bypass via bracket-key comma parsing
CVE-2026-82562 / GHSA-x5fp-wj9c-mxmx
More information
Details
Summary
qsv6.15.3allows bracket-key input to bypassarrayLimitandthrowOnLimitExceededwhencomma: true. The inputa[]=1,2,3,4succeeds witharrayLimit: 3, while the equivalent plain-key input is rejected.Affected version tested:
Details
parseArrayValue()enforces the comma limit only for flat values. Thea[]form is marked non-flat, so its comma-separated value is wrapped after parsing and the inner array is not checked. A single parameter can therefore materialize arbitrarily large arrays.PoC
On
v6.15.3, the first input parses successfully and the second creates an array with 1,000,001 elements. The equivalenta=1,2,3,4input throwsRangeErroras expected.Impact
An attacker who can supply a query string or form body can bypass configured array limits and force excessive memory allocation, causing denial of service. The limit must be applied after comma splitting and before the resulting array is wrapped.
Severity
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:NReferences
This data is provided by the GitHub Advisory Database (CC-BY 4.0).
qs: Denial of Service via Attacker Controlled isBuffer
CVE-2026-82417 / GHSA-4mjr-xmp4-gh2g
More information
Details
Summary
qs.stringify()callsutils.isBuffer()on every value it serializes, andutils.isBuffer()invokesobj.constructor.isBuffer(obj)without checking that it is callable. A value whose ownconstructor.isBufferis a non-function makesqscall a non-callable and throwTypeError. Such a value is produced byqs.parseitself from an untrusted query string whenplainObjects: trueorallowPrototypes: trueis set, so a pure-qsparse→stringifyround-trip — noJSON.parse— turns an unauthenticated query string into an uncaught throw.An attacker-controlled
parseinput reaches the host application's availability asset — viaqs's own recommendedplainObjectsmitigation — and triggers an uncaught exception during aparse→stringifyround-trip.Details
utils.isBufferruns atlib/stringify.js:127for every serialized value:utils.isBuffer(lib/utils.js:327-333) invokesobj.constructor.isBufferwithout verifying it is callable:constructorandisBufferare ordinary keys.qs.parsewithplainObjects: trueorallowPrototypes: truekeeps them as own properties, so the parsed value carries a non-functionconstructor.isBuffer;stringifythen calls a non-callable and throwsTypeError. By contrastutils.isRegExpuses a brand check (Object.prototype.toString); the missing guard here is an internal inconsistency, not a platform limitation.Trust Boundary Note
qs.stringifyalone treats its input as caller-constructed, so serializing a hostile object could be argued outside its contract. This report does not depend on that framing: the malicious shape is produced byqs.parse, whose input is untrusted by design.qs.parsenormally strips aconstructorkey via its prototype guard, but with the documented optionsplainObjects: trueorallowPrototypes: truethe key survives and lands as an own property. Feeding the parsed object back intoqs.stringify— the standard round-trip in gateways and request-forwarders — then hits the unchecked call.PoC
poc02c_isBuffer_qs_only_roundtrip.js— pure-qschain, noJSON.parse; an untrusted query string alone reaches the throw:poc02_isBuffer.js— the minimal defect:poc02b_isBuffer_async_crash.js— worker death in an async sink:Execution Steps
Reproduction Evidence
poc02c_isBuffer_qs_only_roundtrip.js:poc02_isBuffer.js:poc02b_isBuffer_async_crash.js:The pure-
qsround-trip shows the malicious shape originates fromqs.parseof an untrusted query string, with noJSON.parse. The synchronoustry/catchin the async case does not catch the throw; the process exits with code 1, denying service to all requests on that worker.Impact
An unauthenticated request degrades any endpoint that re-serializes deserialized client data with
qs.stringify. The primary impact is a per-request failure: the handler throws and the framework returns HTTP 500. Where the call sits in an unguarded async continuation, the throw escapes and the worker process exits, denying service to all requests it was handling, which means a higher impact that depends on the application's error handling, not onqs.Recommended Fix
Replace the duck-type with a brand check mirroring
utils.isRegExp:If duck-typing must remain, require
typeof obj.constructor.isBuffer === 'function'before invoking and wrap the call intry/catch.Severity
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:NReferences
This data is provided by the GitHub Advisory Database (CC-BY 4.0).
qs: Denial of Service via Attacker Controlled isBuffer
CVE-2026-82417 / GHSA-4mjr-xmp4-gh2g
More information
Details
Summary
qs.stringify()callsutils.isBuffer()on every value it serializes, andutils.isBuffer()invokesobj.constructor.isBuffer(obj)without checking that it is callable. A value whose ownconstructor.isBufferis a non-function makesqscall a non-callable and throwTypeError. Such a value is produced byqs.parseitself from an untrusted query string whenplainObjects: trueorallowPrototypes: trueis set, so a pure-qsparse→stringifyround-trip — noJSON.parse— turns an unauthenticated query string into an uncaught throw.An attacker-controlled
parseinput reaches the host application's availability asset — viaqs's own recommendedplainObjectsmitigation — and triggers an uncaught exception during aparse→stringifyround-trip.Details
utils.isBufferruns atlib/stringify.js:127for every serialized value:utils.isBuffer(lib/utils.js:327-333) invokesobj.constructor.isBufferwithout verifying it is callable:constructorandisBufferare ordinary keys.qs.parsewithplainObjects: trueor `allowPrototypes: