Live › vulnerability › CVE-2026-49293
js-toml parses hex, octal and binary integer literals with a loop that is quadratic in the literal length, so a single large literal in a TOML document can keep one CPU core busy for tens of seconds. An attacker who can supply TOML to a service that calls load() can cause a denial of service.
Summary written from the sources below. Check the originals before acting.
First seen by InfraStack 2026-11-04 03:46 UTC, 0h before NVD. All times on this page are UTC.
Any application that calls js-toml's load() on TOML it does not fully control, such as upload endpoints, CI systems parsing third-party TOML, IDE plugins, or multi-tenant workers. Projects that only parse their own trusted TOML files are much less exposed, and the advisory notes the impact falls to low when input is kept under about 1 kB.
| Product | Ecosystem | Affected | Fixed in |
|---|---|---|---|
| js-toml | npm | <= 1.1.1 | 1.1.2 |
| sunnyadn js-toml | vendor | < 1.1.1 | 1.1.1 |
npm ls js-toml in each project and check whether any resolved version is 1.1.1 or lower.load() from js-toml on TOML from users, uploads, or third-party repositories.js-toml: move to 1.1.2 or later.Each row is the first time that source carried this signal. The analyst’s rows are its own findings.
js-toml versions up to and including 1.1.0 parse hexadecimal / octal / binary integer literals via a hand-written parseBigInt loop that multiplies a BigInt accumulator by the radix once per input digit. Each iteration performs a BigInt * BigInt operation on an accumulator that grows linearly with the number of digits already consumed, so the whole loop is O(n²) in the literal length. The lexer regex places no upper bound on the literal length, so a single TOML document containing one ~500 kB hex literal pins one CPU core for ~40 seconds on a modern laptop (Apple M-series, Node v22). Memory amplification is bounded but CPU amplification is severe and grows quadratically: doubling the literal length quadruples the work.
A caller that invokes load() on attacker-controlled TOML (configuration upload endpoints, CI/CD systems ingesting third-party *.toml, IDE plugins, build tools) is exposed to a single-request CPU exhaustion DoS.
CWE-1333 (Inefficient Regular Expression Complexity → here, inefficient parser complexity), CWE-400 (Uncontrolled Resource Consumption), CWE-407 (Inefficient Algorithmic Complexity).
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H = 7.5 (HIGH) when the parser is invoked on attacker-controllable input; LOW when the calling application restricts TOML input size to small documents (< 1 kB).
js-toml (npm)>= 0.0.0, <= 1.1.0 (all released versions up to and including the current 1.1.0)load() exported from the package rootsrc/load/tokens/NonDecimalInteger.ts lines 54-84 at SHA-pinned 2470ebf2e9009096aa4cbd1a15e574c54cc36b1a:
const parseBigInt = (string: string, radix: number): bigint => {
let result = BigInt(0);
for (let i = 0; i < string.length; i++) {
const char = string[i];
const digit = parseInt(char, radix);
result = result * BigInt(radix) + BigInt(digit);
}
return result;
};
and the interpreter that dispatches to it at lines 72-84:
registerTokenInterpreter(NonDecimalInteger, (raw: string) => {
const intString = raw.replace(/_/g, '');
const digits = intString.slice(2);
const radix = getRadix(raw);
const int = parseInt(digits, radix);
if (Number.isSafeInteger(int)) {
return int;
}
return parseBigInt(digits, radix);
});
Two compounding problems:
Algorithmic: the loop performs result * BigInt(radix) + BigInt(digit) once per input digit. After i iterations result has O(i) limbs, so the multiply costs O(i). Summed over n digits the total cost is O(n²).
No length guard: the lexer regex at src/load/tokens/NonDecimalInteger.ts#L14-L46 is 0x<hexDigit>(<hexDigit>|_<hexDigit>)* (likewise for 0o / 0b). The literal length is bounded only by the input document size. There is no maxNumberLength / maxLiteralLength option, no chevrotain-level cutoff, and no validation at the interpreter callsite.
By contrast, the DecimalInteger token interpreter at src/load/tokens/DecimalInteger.ts#L12-L19 uses the V8 native BigInt(intString) constructor, which is O(n) and runs in single-digit milliseconds for inputs that take 40 seconds via the hand-written radix loop.
A single attacker-supplied TOML document containing one ~500 kB radix-prefixed integer literal pins one CPU core for ~40 seconds on a modern laptop. Doubling the literal length quadruples the work. With 8 MB of input the parse would block the event loop for many minutes of CPU. In a typical Node.js single-thread process this blocks all concurrent request handling for the duration. The defect is exploitable on any code path that calls load() (the only documented entry point) on attacker-controlled or third-party TOML.
The vulnerable path is the default code path for load(). No options or configuration are required to trigger it. Any caller that exposes load() to attacker-controlled or third-party TOML input reaches it on the first hex / octal / binary literal whose value exceeds Number.MAX_SAFE_INTEGER (i.e. more than 13 hex digits, 18 octal digits, or 53 binary digits).
Realistic exposure surfaces:
js-toml on TOML in third-party repositories or pull requests.v22.x (tested on v22.0.0 and Node v26.0.0)mkdir js-toml-cve && cd js-toml-cve
npm init -y
npm install [email protected] @iarna/toml
poc_full_e2e.mjsimport { load } from 'js-toml';
import iarna from '@iarna/toml';
function timeIt(label, fn) {
const t0 = process.hrtime.bigint();
let result, err;
try { result = fn(); } catch (e) { err = e; }
const t1 = process.hrtime.bigint();
const ms = (Number(t1 - t0) / 1e6).toFixed(1);
if (err) console.log(`${label}: ERROR ${err.message} after ${ms}ms`);
else console.log(`${label}: ${ms}ms${result ? ' ' + result : ''}`);
}
console.log('--- Sanity baseline (small inputs) ---');
timeIt('decimal int 1', () => { load('x = 1'); return ''; });
(, { (); ; });
(, { (); ; });
.();
( n [, , , , , ]) {
hexDigits = .(n);
tomlText = ;
(,
{
r = (tomlText);
;
});
}
.();
( n [, , , ]) {
hexDigits = .(n);
tomlText = ;
(,
{
r = iarna.(tomlText);
;
});
}
.();
( n [, ]) {
octDigits = .(n);
binDigits = .(n);
(,
{ r = (); ; });
(,
{ r = (); ; });
}
[email protected], Node v26.0.0, Apple M-series)# js-toml version: 1.1.0
--- Sanity baseline (small inputs) ---
decimal int 1: 1.3ms
hex 0x10: 0.4ms
hex 0xffff: 0.1ms
--- Amplification curve: js-toml.load() with 0x<N hex digits> ---
hex 10,000 digits (10006 bytes input): 15.0ms bits=40000
hex 20,000 digits (20006 bytes input): 29.8ms bits=80000
hex 50,000 digits (50006 bytes input): 214.7ms bits=200000
hex 100,000 digits (100006 bytes input): 693.0ms bits=400000
hex 200,000 digits (200006 bytes input): 3239.6ms bits=800000
hex , digits ( ): ms bits=
--- Negative control: same via @iarna/toml ---
This page as .md · .json · in your agent: get_signal("CVE-2026-49293")
GitHub Advisory Database, CC-BY 4.0 · NVD data is provided by NIST and is public domain. Posts quoted belong to their authors.