Oct 3, 2026·9 min read·5 visits
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.
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.
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.
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 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.
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.
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.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H| Product | Affected Versions | Fixed Version |
|---|---|---|
probe-image-size nodeca | < 7.4.0 | 7.4.0 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-1333, CWE-400 |
| Attack Vector | Network (AV:N) |
| CVSS Severity Score | 7.5 (High) |
| EPSS Score | 0.00043 |
| Impact | Denial of Service (DoS) |
| Exploit Status | Proof of Concept Available |
| KEV Status | Not Listed |
The product uses a regular expression that can require exponential or quadratic time to process certain inputs, leading to a denial of service.
A critical remote, unauthenticated Denial of Service (DoS) vulnerability in @fastify/busboy (<= 3.2.0) allows attackers to crash the Node.js process. By submitting a crafted multipart/form-data request with a header key matching an inherited property of Object.prototype (like __proto__ or constructor), the internal HeaderParser triggers a synchronous TypeError.
SiYuan is an open-source personal knowledge management system. Its Model Context Protocol (MCP) implementation within the asset.upload tool contains a path-traversal and workspace boundary bypass flaw. This allows remote AI models—acting on behalf of attackers via malicious prompts or documents—to import and read sensitive host-system files, such as private keys and system configurations, through absolute path inputs.
An Server-Side Request Forgery (SSRF) vulnerability via DNS-Rebinding Time-of-Check to Time-of-Use (TOCTOU) has been discovered in SiYuan (思源笔记), an open-source personal knowledge management system. The flaw exists within the AI Agent tools http_request (util.HTTPRequest) and web_fetch (util.WebFetch) of the SiYuan Kernel, allowing unauthenticated remote attackers to bypass SSRF validation and access private internal services or cloud metadata endpoints.
CVE-2026-10032 is a DOM-based Cross-Site Scripting (XSS) vulnerability in Google's @a2ui/web_core Node.js library. The vulnerability is located within the openUrl utility function, which processes and opens dynamic URLs defined in layout configurations. Because the function fails to sanitize or validate the target URL scheme before passing it to the window.open browser sink, an attacker can specify a javascript: pseudo-protocol to execute arbitrary client-side script in the context of the host origin.
CVE-2026-59944 is a path traversal and link-following vulnerability in Composer, the PHP dependency manager. This flaw allows malicious or compromised packages to bypass previous path-hardening protections and perform arbitrary filesystem operations outside of their designated installation directory, leading to unauthorized permission modifications or execution proxy creations.
A critical Broken Object Level Authorization (BOLA) vulnerability was identified in Trigger.dev before version v4.5.2. An authenticated attacker could trigger a run replay and supply an arbitrary target environmentId belonging to a completely different tenant. Because the server failed to validate whether the target environment belonged to the same project or organization as the source run, it would execute the task within the victim's environment, resulting in unauthorized cross-tenant write operations and remote task execution.