{"id":"CVE-2026-107392","aliases":["EUVD-2026-95248","GHSA-8j4c-6x6g-rq3j"],"status":"published","title":"music-metadata: uncatchable process crash parsing a crafted `.dsf` (residual of CVE-2026-32256)","description":"music-metadata is a metadata parser for audio and video media files. Prior to 11.15.0, the DSF parser handles an unrecognized chunk by calling tokenizer.ignore without awaiting the returned promise and without first rejecting a chunk size smaller than the 12-byte chunk header. A crafted DSF input can produce a negative ignore length; with strtok3 10.3.5 or later, the resulting RangeError is detached from the parseBuffer promise and becomes an unhandled rejection under Node.js default behavior. The parse call can appear to resolve before the process crashes, bypassing per-parse try/catch handling. The demonstrated impact is availability loss only and requires the DSF parsing path. This issue is fixed in version 11.15.0.","severity":{"version":"3.1","score":6.2,"vector":"CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H","level":"medium","assessor":"cna","from":"cve"},"epss":{"day":"2026-11-12","score":0.00355,"percentile":0.29687,"model":"v2026.06.15"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":false,"fix_available":true},"cwes":["CWE-248","CWE-400"],"published":"2026-10-08T19:13:06Z","modified":"2026-10-28T02:30:59Z","fixes":[{"ecosystem":"npm","package":"music-metadata","fixed_in":["11.15.0"],"sources":["ghsa","osv"]}],"affected":[{"source":"ghsa","ecosystem":"npm","package":"music-metadata","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"last_affected":"11.14.0"},{"fixed":"11.15.0"}]}]},{"source":"osv","ecosystem":"npm","package":"music-metadata","ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"11.15.0"}]}]}],"products":[{"vendor":"borewit","product":"music-metadata"}],"references":[{"url":"https://github.com/Borewit/music-metadata/security/advisories/GHSA-8j4c-6x6g-rq3j","tags":["x_refsource_CONFIRM","exploit","Exploit","Vendor Advisory","advisory","web"]},{"url":"https://github.com/Borewit/music-metadata/pull/2700","tags":["x_refsource_MISC","Patch","fix","web"]},{"url":"https://github.com/Borewit/music-metadata/commit/e7fc27a96e789d41ece41fdac590fc7618274a41","tags":["x_refsource_MISC","Patch","fix","web"]},{"url":"https://github.com/Borewit/music-metadata/releases/tag/v11.15.0","tags":["x_refsource_MISC","Patch","Release Notes","Vendor Advisory","web"]},{"url":"https://github.com/advisories/GHSA-8j4c-6x6g-rq3j"},{"url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/107xxx/CVE-2026-107392.json","tags":["advisory"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-107392","tags":["advisory"]},{"url":"https://github.com/Borewit/music-metadata","tags":["package"]}],"sources":{"cve":[{"id":"CVE-2026-107392","title":"music-metadata: uncatchable process crash parsing a crafted `.dsf` (residual of CVE-2026-32256)","description":"music-metadata is a metadata parser for audio and video media files. Prior to 11.15.0, the DSF parser handles an unrecognized chunk by calling tokenizer.ignore without awaiting the returned promise and without first rejecting a chunk size smaller than the 12-byte chunk header. A crafted DSF input can produce a negative ignore length; with strtok3 10.3.5 or later, the resulting RangeError is detached from the parseBuffer promise and becomes an unhandled rejection under Node.js default behavior. The parse call can appear to resolve before the process crashes, bypassing per-parse try/catch handling. The demonstrated impact is availability loss only and requires the DSF parsing path. This issue is fixed in version 11.15.0.","status":"published","published":"2026-10-08T19:13:06.392Z","modified":"2026-10-08T19:53:23.64Z","cvss":[{"version":"3.1","score":6.2,"vector":"CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H","level":"medium","assessor":"cna"}],"cwes":["CWE-248","CWE-400"],"updated_at":"2026-11-02T13:45:14.072357Z"}],"euvd":[{"id":"EUVD-2026-95248","description":"music-metadata is a metadata parser for audio and video media files. Prior to 11.15.0, the DSF parser handles an unrecognized chunk by calling tokenizer.ignore without awaiting the returned promise and without first rejecting a chunk size smaller than the 12-byte chunk header. A crafted DSF input can produce a negative ignore length; with strtok3 10.3.5 or later, the resulting RangeError is detached from the parseBuffer promise and becomes an unhandled rejection under Node.js default behavior. The parse call can appear to resolve before the process crashes, bypassing per-parse try/catch handling. The demonstrated impact is availability loss only and requires the DSF parsing path. This issue is fixed in version 11.15.0.","status":"published","published":"2026-10-08T19:13:06Z","modified":"2026-10-08T19:53:23Z","cvss":[{"version":"3.1","score":6.2,"vector":"CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H","level":"medium"}],"updated_at":"2026-11-02T17:27:27.72962Z"}],"ghsa":[{"id":"GHSA-8j4c-6x6g-rq3j","title":"music-metadata: uncatchable process crash parsing a crafted `.dsf` (residual of GHSA-v6c2-xwv6-8xf7)","description":"## Summary\n`DsfParser.parseChunks` skips an unrecognised chunk's payload with an **un-awaited** call:\n```js\nthis.tokenizer.ignore(Number(chunkHeader.size) - ChunkHeader.len);   // lib/dsf/DsfParser.js:51 — no await\n```\n`ChunkHeader.len` is 12. A crafted `.dsf` chunk with `id != 'fmt '` and `size` in `0..11` makes the\nargument negative; strtok3 (≥ 10.3.5) throws `RangeError` on a negative `ignore`. Because the call\nis fire-and-forget, the rejection is **detached from the `parseBuffer()` promise chain** → unhandled\nrejection → Node's default (≥ 15) **crashes the process** — **after** `parseBuffer()` already\nresolved, so a caller's `try/catch` catches nothing and is still taken down.\n\nResidual of GHSA-v6c2-xwv6-8xf7: the ASF site was fixed in 11.12.3 (size validation) and strtok3\nnow throws on negative `ignore`; the DSF site was never validated, and its missing `await`\nescalates that throw into an **uncatchable** crash.\n\n## Root cause (`lib/dsf/DsfParser.js:34-56`)\n```js\nwhile (bytesRemaining \u003e= ChunkHeader.len) {               // ChunkHeader.len = 12\n  const chunkHeader = await this.tokenizer.readToken(ChunkHeader);   // { id, size }\n  switch (chunkHeader.id) {\n    case 'fmt ': { ...; return; }\n    default: this.tokenizer.ignore(Number(chunkHeader.size) - ChunkHeader.len); break;  // size\u003c12 -\u003e negative, no await\n  }\n  bytesRemaining -= chunkHeader.size;\n}\n```\nstrtok3 `AbstractTokenizer.ignore` (L78-79): `if (length \u003c 0) throw new RangeError('ignore length must be ≥ 0 bytes');`\n\n## Steps to reproduce\n`repro/` — public API only, Node's default unhandled-rejection mode, `try/catch` around the parse:\n```\nnpm install \u0026\u0026 node poc.mjs\n```\nConfirmed on 11.14.0:\n```\n[app] parseBuffer() RESOLVED — the caller saw no error to catch.\nRangeError: ignore length must be ≥ 0 bytes\n    at DsfParser.parseChunks (.../lib/dsf/DsfParser.js:51)\n   \u003cprocess exits non-zero — the \"process survived\" line never prints\u003e\n```\n\n## Impact\nDoS: a single crafted `.dsf` (or any file with the `DSD ` magic) crashes the Node process of any\napp parsing untrusted audio with music-metadata (2.2M weekly downloads). The crash bypasses the\ncaller's error handling, so even apps that correctly `try/catch` per-file parsing are killed — one\nmalicious upload can take down a shared server/worker.\n\n## Remediation\nAdd `await` on line 51 (makes the `RangeError` a catchable parse error), and validate\n`chunkHeader.size \u003e= ChunkHeader.len` before the skip (as the ASF fix did; also guards the loop\ncounter). Audit other parsers for un-awaited `tokenizer.ignore()`/`readToken()`.\n\n## Scope / honesty\nRequires the DSF path (a `DSD `-magic file — normal auto-detection). Relies on Node's default\nunhandled-rejection mode (`throw`, default since Node 15); the point is that the standard defensive\nper-parse `try/catch` does not protect against it. Crash (availability), not disclosure/RCE.\nNegatives confirmed alongside: ASF infinite loop fixed; negative-`ignore` infinite-loop class\nclosed at strtok3; unbounded allocation bounded by strtok3's read bound-check.\n\n## Credits\nIssue also reported by @ryu7eroo","status":"published","published":"2026-10-08T19:40:45Z","modified":"2026-10-08T19:40:46Z","cvss":[{"version":"3.1","score":6.2,"vector":"CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H","level":"medium"}],"cwes":["CWE-248","CWE-400"],"updated_at":"2026-11-02T14:41:30.097746Z"}],"nvd":[{"id":"CVE-2026-107392","description":"music-metadata is a metadata parser for audio and video media files. Prior to 11.15.0, the DSF parser handles an unrecognized chunk by calling tokenizer.ignore without awaiting the returned promise and without first rejecting a chunk size smaller than the 12-byte chunk header. A crafted DSF input can produce a negative ignore length; with strtok3 10.3.5 or later, the resulting RangeError is detached from the parseBuffer promise and becomes an unhandled rejection under Node.js default behavior. The parse call can appear to resolve before the process crashes, bypassing per-parse try/catch handling. The demonstrated impact is availability loss only and requires the DSF parsing path. This issue is fixed in version 11.15.0.","status":"published","published":"2026-10-08T20:17:33.707Z","modified":"2026-10-13T19:35:27.147Z","cvss":[{"version":"3.1","score":6.2,"vector":"CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H","level":"medium","assessor":"secondary"},{"version":"3.1","score":7.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H","level":"high","assessor":"primary"}],"cwes":["CWE-248","CWE-400"],"updated_at":"2026-11-02T14:12:55.142936Z"}],"osv":[{"id":"CVE-2026-107392","title":"music-metadata: uncatchable process crash parsing a crafted `.dsf` (residual of CVE-2026-32256)","description":"music-metadata is a metadata parser for audio and video media files. Prior to 11.15.0, the DSF parser handles an unrecognized chunk by calling tokenizer.ignore without awaiting the returned promise and without first rejecting a chunk size smaller than the 12-byte chunk header. A crafted DSF input can produce a negative ignore length; with strtok3 10.3.5 or later, the resulting RangeError is detached from the parseBuffer promise and becomes an unhandled rejection under Node.js default behavior. The parse call can appear to resolve before the process crashes, bypassing per-parse try/catch handling. The demonstrated impact is availability loss only and requires the DSF parsing path. This issue is fixed in version 11.15.0.","status":"published","published":"2026-10-08T19:13:06.392Z","modified":"2026-10-28T02:30:59.840108651Z","cvss":[{"version":"3.1","score":6.2,"vector":"CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H","level":"medium"}],"cwes":["CWE-248","CWE-400"],"updated_at":"2026-11-02T15:44:54.933106Z"},{"id":"GHSA-8j4c-6x6g-rq3j","title":"music-metadata: uncatchable process crash parsing a crafted `.dsf` (residual of GHSA-v6c2-xwv6-8xf7)","description":"## Summary\n`DsfParser.parseChunks` skips an unrecognised chunk's payload with an **un-awaited** call:\n```js\nthis.tokenizer.ignore(Number(chunkHeader.size) - ChunkHeader.len);   // lib/dsf/DsfParser.js:51 — no await\n```\n`ChunkHeader.len` is 12. A crafted `.dsf` chunk with `id != 'fmt '` and `size` in `0..11` makes the\nargument negative; strtok3 (≥ 10.3.5) throws `RangeError` on a negative `ignore`. Because the call\nis fire-and-forget, the rejection is **detached from the `parseBuffer()` promise chain** → unhandled\nrejection → Node's default (≥ 15) **crashes the process** — **after** `parseBuffer()` already\nresolved, so a caller's `try/catch` catches nothing and is still taken down.\n\nResidual of GHSA-v6c2-xwv6-8xf7: the ASF site was fixed in 11.12.3 (size validation) and strtok3\nnow throws on negative `ignore`; the DSF site was never validated, and its missing `await`\nescalates that throw into an **uncatchable** crash.\n\n## Root cause (`lib/dsf/DsfParser.js:34-56`)\n```js\nwhile (bytesRemaining \u003e= ChunkHeader.len) {               // ChunkHeader.len = 12\n  const chunkHeader = await this.tokenizer.readToken(ChunkHeader);   // { id, size }\n  switch (chunkHeader.id) {\n    case 'fmt ': { ...; return; }\n    default: this.tokenizer.ignore(Number(chunkHeader.size) - ChunkHeader.len); break;  // size\u003c12 -\u003e negative, no await\n  }\n  bytesRemaining -= chunkHeader.size;\n}\n```\nstrtok3 `AbstractTokenizer.ignore` (L78-79): `if (length \u003c 0) throw new RangeError('ignore length must be ≥ 0 bytes');`\n\n## Steps to reproduce\n`repro/` — public API only, Node's default unhandled-rejection mode, `try/catch` around the parse:\n```\nnpm install \u0026\u0026 node poc.mjs\n```\nConfirmed on 11.14.0:\n```\n[app] parseBuffer() RESOLVED — the caller saw no error to catch.\nRangeError: ignore length must be ≥ 0 bytes\n    at DsfParser.parseChunks (.../lib/dsf/DsfParser.js:51)\n   \u003cprocess exits non-zero — the \"process survived\" line never prints\u003e\n```\n\n## Impact\nDoS: a single crafted `.dsf` (or any file with the `DSD ` magic) crashes the Node process of any\napp parsing untrusted audio with music-metadata (2.2M weekly downloads). The crash bypasses the\ncaller's error handling, so even apps that correctly `try/catch` per-file parsing are killed — one\nmalicious upload can take down a shared server/worker.\n\n## Remediation\nAdd `await` on line 51 (makes the `RangeError` a catchable parse error), and validate\n`chunkHeader.size \u003e= ChunkHeader.len` before the skip (as the ASF fix did; also guards the loop\ncounter). Audit other parsers for un-awaited `tokenizer.ignore()`/`readToken()`.\n\n## Scope / honesty\nRequires the DSF path (a `DSD `-magic file — normal auto-detection). Relies on Node's default\nunhandled-rejection mode (`throw`, default since Node 15); the point is that the standard defensive\nper-parse `try/catch` does not protect against it. Crash (availability), not disclosure/RCE.\nNegatives confirmed alongside: ASF infinite loop fixed; negative-`ignore` infinite-loop class\nclosed at strtok3; unbounded allocation bounded by strtok3's read bound-check.\n\n## Credits\nIssue also reported by @ryu7eroo","status":"published","published":"2026-10-08T19:40:45Z","modified":"2026-10-08T20:00:05.860263177Z","cvss":[{"version":"3.1","score":6.2,"vector":"CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H","level":"medium"}],"cwes":["CWE-248","CWE-400"],"updated_at":"2026-11-02T15:46:42.437513Z"}]}}
