Sep 9, 2026·7 min read·55 visits
A 5-byte malformed TOML payload can block the single-threaded Node.js event loop indefinitely, causing 100% CPU utilization and Denial of Service.
Prior to version 1.7.1, smol-toml is vulnerable to an infinite loop Denial of Service when parsing a malformed TOML payload containing an unclosed comment inside an array or inline table.
The vulnerability CVE-2026-85730 is a critical Denial of Service (DoS) flaw within the smol-toml Node.js library, a performance-focused parser and serializer for the TOML format. It stems from an algorithmic flaw in how the parser skips whitespace and comments when analyzing nested structures such as arrays or inline tables. Unauthenticated remote attackers can leverage this flaw to cause a permanent Denial of Service by submitting highly compact, malformed payloads.
The vulnerability represents an "Infinite Loop" weakness categorized under CWE-835 and CWE-606. When processing untrusted inputs containing specific structural anomalies, the parsing utility fails to recognize the End-of-File (EOF) boundary and continually cycles over the same block of data. Because the Node.js runtime employs a single-threaded event loop, this continuous CPU utilization completely starves other asynchronous processes, disabling the server's capability to process concurrent requests.
The attack surface resides in any exposed application endpoint that accepts TOML payloads and parses them using the default parse interface of smol-toml. This makes microservices, configuration endpoints, and ingestion pipelines primary targets. Given that the minimum payload required to trigger this state is merely five bytes, the processing footprint of the attack is negligible while the impact is absolute for the affected thread.
The core of the vulnerability is found within the character-skipping routine skipUntil located in src/util.ts. This utility function is designed to scan through strings, ignoring non-structural tokens and comments, until a target separator or boundary character is encountered. In the case of arrays, the parser looks for separators like commas or the terminating bracket, while bypassing nested comments marked by the hash symbol (#).
During execution on a malformed payload like a=[1#, the pointer is positioned on the hash character at the end of the string. The function encounters the # and attempts to skip past the comment by identifying the next newline character. To do this, it calls the internal utility helper indexOfNewline(str, i). Under normal conditions, this returns the index of the next newline, allowing the loop to skip the comment block entirely and resume parsing on the following line.
When the input document abruptly terminates with a comment and contains no trailing newline, the indexOfNewline function fails to match any newline character. Consequently, it returns -1 to signal the failure. The parser directly assigns this return value to the loop index variable i. Since -1 is less than the current index, the pointer is unexpectedly warped backward to the beginning of the file.
The structure of the loop depends on standard iteration mechanics, applying the post-iteration update expression i++ once the loop block finishes. This increments the index i from -1 to 0. During the next iteration check, the condition i < str.length evaluates to true, causing the parser to restart scanning from index 0. This loop repeats indefinitely, continuously scanning the same string from the beginning, finding the same comment, warping back to -1, and incrementing back to 0 as shown in the following visual pipeline:
The following comparison demonstrates the exact loop-handling flaw before and after the application of the official security patch.
// Vulnerable Code (prior to v1.7.1)
export function skipUntil(str: string, ptr: number, sep: string, end?: string, banNewLines: boolean = false) {
if (!end) {
ptr = indexOfNewline(str, ptr)
}
for (let i = ptr; i < str.length; i++) {
let c = str[i]
if (c === '#') {
// Vulnerability: No validation of the returned index is performed.
// If no newline character exists, indexOfNewline returns -1.
i = indexOfNewline(str, i)
} else if (c === sep) {
return i + 1
} else if (c === end || (banNewLines && (c === '\n' || (c === '\r' && str[i + 1] === '\n')))) {
return i
}
}
return str.length
}In the unpatched code shown above, the return value of indexOfNewline(str, i) is blindly assigned to i. When the search fails, i becomes -1. The loop's step mechanism then increments i to 0 before evaluating i < str.length. Because the string length is greater than zero, the iteration condition is satisfied, and the engine begins scanning from the start of the string again.
To resolve this execution logic defect, the patch introduces immediate evaluation of the search outcome.
// Patched Code (v1.7.1)
export function skipUntil(str: string, ptr: number, sep: string, end?: string, banNewLines: boolean = false) {
if (!end) {
ptr = indexOfNewline(str, ptr)
}
for (let i = ptr; i < str.length; i++) {
let c = str[i]
if (c === '#') {
i = indexOfNewline(str, i)
// Security Fix: Break out of the loop immediately if no newline is found
if (i < 0) break
} else if (c === sep) {
return i + 1
} else if (c === end || (banNewLines && (c === '\n' || (c === '\r' && str[i + 1] === '\n')))) {
return i
}
}
return str.length
}The check if (i < 0) break successfully prevents the negative assignment from being incremented and re-evaluated inside the iteration block. Breaking out of the loop allows the function to execute return str.length, accurately marking the end-of-file condition and terminating the parsing execution pathway.
Exploitation of CVE-2026-85730 requires very little effort. The attack relies on an architectural asymmetry where an incredibly small payload can lead to complete service exhaustion. The prerequisite is that the target Node.js application must ingest untrusted TOML configurations or raw input payloads and process them using the parse method of smol-toml.
An attacker constructs a request payload containing an unclosed array or table with an unclosed inline comment block. Two common variations are available to trigger this state:
# Variant 1: Unclosed Array Payload (5 bytes)
a=[1#
# Variant 2: Unclosed Inline Table Payload (7 bytes)
a={k=1#Once received by the application, the library attempts to find the end-of-comment indicator. Failing to locate a newline character, the execution loop is permanently trapped in a cycle of reset iterations. In a standard single-threaded Node.js deployment, this immediately freezes the main event loop, preventing the application from handling concurrent requests or system heartbeats.
The structural failure inside smol-toml results in an immediate loss of service availability. Although there is no risk of remote code execution or data exposure, the absolute exhaustion of resources makes the impact of this vulnerability substantial. This vulnerability receives a CVSS v4.0 Base Score of 8.2.
Because Node.js runs on a single thread, any operation that forces an infinite loop blocks the event queue entirely. Consequently, even if a service employs multiple CPUs, a simple series of malicious requests corresponding to the number of server processes can completely paralyze the infrastructure.
Furthermore, because the exploitation payload is small, detecting the attack based on input size filters is impractical. This enables actors to bypass traditional packet size limits easily, stressing the necessity of patching underlying parser utilities.
The primary recommendation is to update the smol-toml dependency to version 1.7.1 or higher. This update resolves the execution logic bug by checking for negative index results and terminating the loop immediately.
When immediate library upgrades are not feasible, you can employ temporary mitigations. Implementing a Web Application Firewall (WAF) rule to block incoming request bodies containing specific malformed TOML structures can block exploit attempts. For example, rules can check for the presence of # inside brackets without a trailing newline character.
Deploying application processes under robust cluster managers or container environments with active liveness probes can minimize downtime. If the event loop freezes, a container orchestrator will fail its health checks and restart the container automatically, mitigating prolonged denial of service states.
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 |
|---|---|---|
smol-toml squirrelchat | < 1.7.1 | 1.7.1 |
| Attribute | Detail |
|---|---|
| Vulnerability ID | CVE-2026-85730 |
| CWE ID | CWE-835 / CWE-606 |
| CVSS v4.0 | 8.2 (High) |
| Attack Vector | Network (AV:N) |
| Exploit Status | Proof of Concept (PoC) |
| CISA KEV Listed | No |
| Affected Ecosystem | npm (Node.js) |
The program contains an iteration loop for which the exit condition is never met, causing execution to continue indefinitely.
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.
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.
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.
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.
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.
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.