Oct 29, 2026·9 min read·2 visits
Versions of decode-uri-component prior to 0.5.1 are vulnerable to an algorithmic complexity Denial of Service (CVE-2026-118664). By sending an unauthenticated payload of approximately 224 KB containing thousands of unique percent-encoded sequences and one malformed escape token, an attacker can force the fallback decoder into quadratic processing time, blocking the Node.js event loop for over 20 seconds per request.
An inefficient algorithmic complexity vulnerability exists in the npm package decode-uri-component prior to version 0.5.1. When processing malformed URI strings containing invalid percent-escape sequences, the library falls back to a custom decoder that collects distinct percent-encoded runs into a dictionary and executes a full-string regular expression replacement for each entry. Supplying a payload with thousands of distinct percent-encoded sequences alongside a malformed escape triggers quadratic O(K * L) execution time, starving the single-threaded Node.js event loop and resulting in an unauthenticated Denial of Service.
The decode-uri-component npm package is a foundational utility within the Node.js ecosystem designed to provide a resilient alternative to JavaScript's built-in decodeURIComponent function. Native decodeURIComponent throws a URIError exception whenever it encounters malformed percent-encoding, such as incomplete multi-byte UTF-8 sequences or invalid hexadecimal characters like %zz. Many web frameworks, routing libraries, and query-string parsers rely on decode-uri-component to parse untrusted user input gracefully without crashing the request handling pipeline.
In versions prior to 0.5.1, the library exhibits an algorithmic complexity flaw classified under CWE-407 (Inefficient Algorithmic Complexity) and CWE-400 (Uncontrolled Resource Consumption). When the native decoder throws an exception, decode-uri-component transitions execution to an internal fallback function named customDecodeURIComponent. This fallback parser attempts to salvage valid percent-encoded substrings while preserving malformed sequences as literal text.
Because customDecodeURIComponent processes distinct percent-encoded runs by iteratively executing global regular expression replacements across the entire input string, the computational cost scales quadratically with the number of unique encoded sequences. In a single-threaded Node.js runtime, an attacker can exploit this behavior over the network by submitting a moderately sized payload (approximately 224 KB) containing tens of thousands of unique percent-encoded tokens. This stalls the V8 JavaScript engine, blocks the Node.js event loop, and renders the application unresponsive to all concurrent client connections.
The vulnerability originates in the two-phase parsing and substitution strategy implemented inside customDecodeURIComponent in index.js. When decodeUriComponent(encodedURI) is invoked, it first attempts to decode the entire string using native decodeURIComponent(encodedURI) inside a try...catch block. If the input contains even a single invalid sequence (for example, appending %zz to the end of a string), the native call throws a URIError and control transfers to customDecodeURIComponent(input).
During the first phase of customDecodeURIComponent, the function initializes a lookup object named replaceMap pre-populated with UTF-16 Byte Order Mark (BOM) tokens (%FE%FF and %FF%FE). It then scans the input string using a regular expression (multiMatcher) that matches contiguous runs of valid hexadecimal percent-encoded octets (%(?:[0-9a-fA-F]{2})+). For every match found, the function attempts to decode the substring and stores the mapping of the raw encoded run to its decoded output as a key-value pair in replaceMap.
The root cause of the denial of service lies in the second phase of customDecodeURIComponent. After populating replaceMap with $K$ distinct keys, the function retrieves all keys via Object.keys(replaceMap) and iterates over them in a for...of loop. Inside this loop, it dynamically compiles a new global regular expression for each key and executes a full-string scan and replacement:
const entries = Object.keys(replaceMap);
for (const key of entries) {
input = input.replace(new RegExp(key, 'g'), replaceMap[key]);
}If an input of length $L$ is constructed such that almost every percent-encoded sequence is distinct, the number of keys $K$ in replaceMap grows linearly with $L$. Executing $K$ separate full-string regular expression replacements over a string of length $L$ yields an asymptotic time complexity of $O(K \times L)$, or $O(N^2)$ with respect to input size. Furthermore, each iteration allocates a new string and compiles a new RegExp object, creating substantial garbage collection pressure alongside raw CPU saturation.
The remediation was introduced in commit fad01d65b93bd3f2f0dc32dfd8d57a38b57bc608 and released in version 0.5.1. The patch completely removes the replaceMap dictionary and the multi-pass regular expression loop, replacing them with a single-pass replacement callback that delegates directly to the byte-level decode state machine.
Below is the vulnerable implementation of customDecodeURIComponent prior to version 0.5.1:
function customDecodeURIComponent(input) {
// Keep track of all the replacements and prefill the map with the `BOM`
const replaceMap = {
'%FE%FF': '\uFFFD\uFFFD',
'%FF%FE': '\uFFFD\uFFFD',
};
// Find percent-encoded runs separated by literal text or lone `%` characters.
let match = multiMatcher.exec(input);
while (match) {
try {
// Decode as big chunks as possible
replaceMap[match[0]] = decodeURIComponent(match[0]);
} catch {
const result = decode(match[0]);
if (result !== match[0]) {
replaceMap[match[0]] = result;
}
}
match = multiMatcher.exec(input);
}
// Add `%C2` at the end of the map to make sure it does not replace the combinator before everything else
replaceMap['%C2'] = '\uFFFD';
const entries = Object.keys(replaceMap);
// VULNERABLE: Iterates K times, rescanning the entire string L on each pass
for (const key of entries) {
// Replace all decoded components
input = input.replace(new RegExp(key, 'g'), replaceMap[key]);
}
return input;
}In commit fad01d65b93bd3f2f0dc32dfd8d57a38b57bc608, the maintainer refactored decode(input) to handle the UTF-16 BOM sequences (%FE%FF and %FF%FE) and truncated %C2 sequences inline during byte-stream parsing, simplifying customDecodeURIComponent to a single line:
function decode(input) {
// ... [existing UTF-8 byte sequence length calculation] ...
// Continuation byte or invalid lead byte — emit one `%XX` literally.
if (sequenceLength === 0) {
const pair = input.slice(position, position + 6);
// FIX: Handle UTF-16 BOM byte pairs inline instead of via replaceMap
if (pair === '%FE%FF' || pair === '%FF%FE') {
output += '\uFFFD\uFFFD';
position += 6;
continue;
}
output += input.slice(position, position + 3);
position += 3;
continue;
}
// ... [multi-byte decoding attempt] ...
// Missing continuation bytes or decode failure — emit the first byte literally.
const literal = input.slice(position, position + 3);
// FIX: Handle truncated `%C2` inline instead of via a final replaceMap pass
output += literal === '%C2' ? '\uFFFD' : literal;
position += 3;
}
function customDecodeURIComponent(input) {
// FIX: Decode each percent-encoded run in place in a single pass.
// Decoded output is never rescanned, guaranteeing O(N) linear execution.
return input.replace(multiMatcher, run => decode(run));
}> [!NOTE]
> Fix Completeness Assessment: The patch is comprehensive and eliminates both the algorithmic complexity flaw and potential secondary mutation bugs. By using input.replace(multiMatcher, run => decode(run)), the JavaScript engine scans the input string exactly once from left to right. Furthermore, because decoded outputs are never rescanned by subsequent regular expression passes, an attacker cannot craft multi-layered encodings that decode into literal %C2 or %FE%FF sequences to trigger unintended second-pass replacements.
Exploiting CVE-2026-118664 requires two specific properties in the attacker-supplied string: a large number of distinct valid percent-encoded runs separated by non-encoded delimiter characters, and at least one malformed percent-escape sequence to bypass the fast path (native decodeURIComponent) and force execution into customDecodeURIComponent.
Because each percent-encoded run matched by multiMatcher consists of contiguous %XX tokens, an attacker can maximize the number of distinct entries in replaceMap by separating two-byte percent-encoded pairs (%XX%YY) with a single literal ASCII character such as x. With two hexadecimal bytes, there are $256 \times 256 = 65,536$ possible unique combinations, allowing an attacker to generate tens of thousands of unique 7-byte tokens (%XX%YYx).
The following proof-of-concept, adapted from the regression test added in commit fad01d65b93bd3f2f0dc32dfd8d57a38b57bc608, demonstrates how a 224 KB string forces severe event-loop blocking:
import decodeUriComponent from 'decode-uri-component';
let payload = '';
// Construct 32,000 distinct 7-character runs (~224 KB total)
for (let index = 0; index < 32_000; index++) {
const high = Math.floor(index / 256).toString(16).padStart(2, '0');
const low = (index % 256).toString(16).padStart(2, '0');
payload += `%${high}%${low}x`;
}
// Append an invalid hex escape to force native decodeURIComponent() to throw URIError
payload += '%zz';
const startTime = Date.now();
decodeUriComponent(payload);
const elapsed = Date.now() - startTime;
console.log(`Execution time: ${elapsed} ms`);
// Vulnerable (< 0.5.1): ~20,000+ ms (blocks Node.js event loop)
// Patched (0.5.1): < 50 msIn a real-world deployment, exploitation depends on how the target application ingests URI-encoded data. While reverse proxies and HTTP servers (such as Nginx, Apache, or Cloudflare) typically restrict HTTP request line and URL header lengths to between 8 KB and 16 KB—limiting the CPU spike achievable via GET query strings—applications frequently invoke decode-uri-component on application/x-www-form-urlencoded request bodies, custom JSON fields, or multipart form payloads where default size limits range from 100 KB to several megabytes.
The primary impact of CVE-2026-118664 is a complete loss of availability for the affected Node.js process. Because Node.js executes JavaScript on a single main event-loop thread, synchronous CPU-bound operations prevent the runtime from processing incoming network I/O, timers, health checks, or existing client requests.
When an unauthenticated attacker submits a single 224 KB payload to a vulnerable endpoint, the process freezes for approximately 20 seconds. By transmitting a low-rate stream of concurrent or sequential requests (for example, 3 to 5 requests per minute), an attacker can permanently pin the CPU at 100% utilization. In containerized orchestration environments like Kubernetes, this prolonged unresponsiveness frequently causes liveness probes to fail, triggering cascading pod restarts and cluster-wide service degradation.
The vulnerability is assigned a CVSS v4.0 Base Score of 8.2 (HIGH) with the vector CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N. The Attack Requirements (AT:P) metric reflects that the attacker's payload must reach decodeUriComponent at a sufficient size (such as through a request body parser or an environment with permissive URL length limits) to induce significant delay. Confidentiality (VC:N) and Integrity (VI:N) are unaffected, as the flaw does not permit memory corruption or unauthorized data disclosure.
To remediate CVE-2026-118664, upgrade decode-uri-component to version 0.5.1 or later. Because decode-uri-component has zero runtime dependencies and maintains full backward compatibility in its API signature and return values, upgrading is a drop-in replacement.
For direct dependencies, update the package using your package manager:
npm install decode-uri-component@^0.5.1
# or
yarn add decode-uri-component@^0.5.1
# or
pnpm update decode-uri-componentIn many Node.js projects, decode-uri-component is installed as a transitive dependency of higher-level routing or query parsing packages (such as query-string). If an upstream dependency locks decode-uri-component to a vulnerable version range (e.g., ^0.2.0 or ^0.4.1), enforce resolution to 0.5.1 using overrides in package.json (for npm) or resolutions (for Yarn):
{
"overrides": {
"decode-uri-component": "^0.5.1"
},
"resolutions": {
"decode-uri-component": "^0.5.1"
}
}If immediate patching is not feasible, implement defense-in-depth controls at the network edge and application boundary:
body-parser) and reverse proxies to restrict maximum request body sizes and maximum parameter counts (parameterLimit) to the minimum required by the application.decodeURIComponent() and immediately return an HTTP 400 Bad Request response if a URIError is thrown.%) combined with non-hexadecimal escape sequences.CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N| Product | Affected Versions | Fixed Version |
|---|---|---|
decode-uri-component Sam Verschueren | < 0.5.1 | 0.5.1 |
| Attribute | Detail |
|---|---|
| Vulnerability Type | Inefficient Algorithmic Complexity / Denial of Service |
| CWE ID | CWE-407 / CWE-400 |
| CVSS v4.0 Score | 8.2 HIGH (CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N) |
| EPSS Score | 0.32% (24.75th percentile) |
| Attack Vector | Network (Unauthenticated HTTP Request) |
| Affected Component | decode-uri-component (< 0.5.1) |
| Patched Version | 0.5.1 |
| CISA KEV Status | Not Listed |
An algorithm in a product has an inefficient worst-case computational complexity that is detrimental to system performance and can be triggered by an attacker crafting specific inputs to exhaust CPU resources.
Twisted versions up to and including 26.4.0 are vulnerable to an unauthenticated remote denial-of-service attack via the HTTP/2 Rapid Reset technique (a variant of CVE-2023-44487). The HTTP/2 connection handler in twisted.web processes pipelined HEADERS and RST_STREAM frames sequentially within the same TCP segment. Because the request dispatch and synchronous rendering occur upon receiving the StreamEnded event before the subsequent StreamReset event is evaluated, an attacker can force the server to expend CPU cycles rendering responses while immediately evicting stream state to bypass concurrent stream limits.
Open Neural Network Exchange (ONNX) versions 1.4.0 prior to 1.22.0 contain a null pointer dereference (CWE-476) and a signed-to-unsigned integer conversion error (CWE-195) in the shape inference functions for the Scan operator. When processing an untrusted ONNX model, the functions ScanInferenceFunction, ScanInferenceFunction_opset8, and ScanInferenceFunction_opset9 fail to validate the presence and value of the required num_scan_inputs attribute. Omitting the attribute results in a direct null pointer dereference that crashes the host process via SIGSEGV, while supplying a negative value triggers an integer wrap-around during type narrowing that leads to an unhandled allocation failure. Notably, models containing a negative num_scan_inputs value bypass standard validation via onnx.checker.check_model().
CVE-2026-91403 is a critical cryptographic trust validation flaw (CWE-327) in the Java Secure Socket Extension (JSSE) component of Oracle Java SE, Oracle GraalVM, and OpenJDK builds. Triggered by the compromise of private signing keys for eight intermediate Certificate Authorities operated by eMudhra (emSign), unpatched Java runtime environments implicitly trust fraudulent TLS and code-signing certificates issued by these compromised intermediates. Because Java disables online certificate revocation checking (OCSP/CRL) by default, an unauthenticated network attacker in an Adversary-in-the-Middle (AiTM) position can intercept, decrypt, and modify encrypted TLS communications or bypass code signature verification without triggering security exceptions.
Open Neural Network Exchange (ONNX) prior to version 1.22.0 contains a memory exhaustion vulnerability (CWE-789) in its Python tensor unpacking helpers and C++ model checker. Specifically, the _unpack_2bit() and _unpack_4bit() functions in onnx.numpy_helper unconditionally resize unpacked NumPy arrays to match attacker-controlled TensorProto.dims metadata without verifying that the underlying raw_data or int32_data buffers contain a corresponding number of packed bits. Furthermore, onnx.checker.check_tensor() previously bypassed length verification for sub-byte data types. Processing an untrusted ONNX model with packed sub-byte tensors (UINT2, INT2, UINT4, INT4, or FLOAT4E2M1) allows an unauthenticated attacker to trigger disproportionate memory allocations from minimal payloads, resulting in an out-of-memory (OOM) crash and denial of service.
An out-of-bounds read vulnerability (CWE-125) exists in the Open Neural Network Exchange (ONNX) library across versions 1.2.1 prior to 1.22.0. The flaw occurs within the shape inference routines for the ConvTranspose operator when processing a model with a weight tensor rank less than three and an omitted kernel_shape attribute. Unchecked indexing into an empty vector triggers a memory segmentation fault, resulting in a Denial of Service (DoS) against applications inspecting or loading untrusted ONNX models.
A session-binding vulnerability in Python Social Auth (social-core) versions prior to 6.2.0 allows an attacker to perform authorization code injection against the OpenID Connect authentication backend. By storing generated OIDC nonces in a global database table without binding them to the initiating browser session, the library accepts any valid, unexpired nonce returned in an ID token regardless of which session originated the authentication transaction. When combined with the default disablement of Proof Key for Code Exchange (PKCE), an attacker who intercepts an unused authorization code can inject it into their own authentication flow to achieve full account takeover.