CVE-2026-118664: Quadratic Complexity Denial of Service in decode-uri-component

Vulnerability ID: CVE-2026-118664
CVSS Score: 8.2
Published: 2026-10-29

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.

TL;DR

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.

⚠️ Exploit Status: POC

Technical Details

  • 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

Affected Systems

  • decode-uri-component (npm package)
  • decode-uri-component: < 0.5.1 (Fixed in: 0.5.1)

Code Analysis

Commit: fad01d6

Replace multi-pass replaceMap regex loop with single-pass replacement in customDecodeURIComponent

diff --git a/index.js b/index.js --- a/index.js +++ b/index.js @@ -79,6 +79,15 @@ function decode(input) {   // Continuation byte or invalid lead byte — emit one `%XX` literally. if (sequenceLength === 0) { + const pair = input.slice(position, position + 6); + + // UTF-16 BOM byte pairs become two replacement characters. + if (pair === '%FE%FF' || pair === '%FF%FE') { + output += '\uFFFD\uFFFD'; + position += 6; + continue; + } +  output += input.slice(position, position + 3); position += 3; continue; @@ -111,7 +120,10 @@ function decode(input) { }   // Missing continuation bytes or decode failure — emit the first byte literally. - output += input.slice(position, position + 3); + const literal = input.slice(position, position + 3); + + // A truncated `%C2` becomes a replacement character. + output += literal === '%C2' ? '\uFFFD' : literal;  position += 3; }  @@ -120,41 +132,8 @@ function decode(input) { }   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); - - for (const key of entries) { - // Replace all decoded components - input = input.replace(new RegExp(key, 'g'), replaceMap[key]); - } - - return input; + // Decode each percent-encoded run in place in a single pass. Decoded output is never rescanned. + return input.replace(multiMatcher, run => decode(run));  }   export default function decodeUriComponent(encodedURI) { 

Exploit Details

  • GitHub: Unit test in test.js demonstrating a 32,000-run (~224 KB) payload that triggers quadratic execution time on versions prior to 0.5.1.

Mitigation Strategies

  • Upgrade decode-uri-component to version 0.5.1 or later across all direct and transitive dependency trees.
  • Enforce strict size limits on HTTP query strings, headers, and URL-encoded request bodies at the reverse proxy and application middleware layers.
  • Use native decodeURIComponent() and reject malformed URI inputs with HTTP 400 responses if graceful fallback decoding of invalid escape sequences is not required.
  • Configure Web Application Firewalls (WAF) to detect and block payloads containing thousands of percent-encoded sequences combined with invalid hexadecimal escapes.

Remediation Steps:

  1. Run npm ls decode-uri-component or yarn why decode-uri-component to identify all direct and transitive instances of the package in your project.
  2. Update direct dependencies by executing npm install decode-uri-component@^0.5.1.
  3. If vulnerable versions are pulled transitively via older packages, add an overrides or resolutions entry in package.json forcing "decode-uri-component": "^0.5.1" and regenerate your lockfile.
  4. Verify the remediation by inspecting package-lock.json or yarn.lock to ensure no version prior to 0.5.1 remains installed.

References

Read the full report for CVE-2026-118664 on our website for more details including interactive diagrams and full exploit analysis.