-
Notifications
You must be signed in to change notification settings - Fork 95
fix(upload): validate audio magic bytes in addition to MIME type #78
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
c809fb7
a6a7d41
72d3dae
c6ee85b
bc1f683
a1c54d7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -10,6 +10,39 @@ const ALLOWED_MIME_TYPES = [ | |||||||||||||||||||||
| "audio/flac" | ||||||||||||||||||||||
| ]; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // Known magic-byte signatures for audio formats accepted by ElevenLabs. | ||||||||||||||||||||||
| // Each entry is { offset, bytes } where bytes is a Buffer to match at that | ||||||||||||||||||||||
| // position in the uploaded file. | ||||||||||||||||||||||
| const AUDIO_SIGNATURES = [ | ||||||||||||||||||||||
| // WebM / Matroska (EBML header: 0x1A 0x45 0xDF 0xA3) | ||||||||||||||||||||||
| { offset: 0, bytes: Buffer.from([0x1a, 0x45, 0xdf, 0xa3]) }, | ||||||||||||||||||||||
| // WAV (RIFF....WAVE) | ||||||||||||||||||||||
| { offset: 0, bytes: Buffer.from("RIFF", "ascii") }, | ||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P1: Container checks are too broad; matching Prompt for AI agents |
||||||||||||||||||||||
| // MP3 with ID3 tag | ||||||||||||||||||||||
| { offset: 0, bytes: Buffer.from("ID3", "ascii") }, | ||||||||||||||||||||||
| // MP3 sync word (0xFF 0xFB / 0xFF 0xFA / 0xFF 0xF3 and similar) | ||||||||||||||||||||||
| { offset: 0, bytes: Buffer.from([0xff, 0xfb]) }, | ||||||||||||||||||||||
| { offset: 0, bytes: Buffer.from([0xff, 0xfa]) }, | ||||||||||||||||||||||
| { offset: 0, bytes: Buffer.from([0xff, 0xf3]) }, | ||||||||||||||||||||||
| { offset: 0, bytes: Buffer.from([0xff, 0xe3]) }, | ||||||||||||||||||||||
|
Comment on lines
+24
to
+27
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: MP3 signature list is incomplete; valid Prompt for AI agents
Suggested change
|
||||||||||||||||||||||
| // OGG (OggS) | ||||||||||||||||||||||
| { offset: 0, bytes: Buffer.from("OggS", "ascii") }, | ||||||||||||||||||||||
| // FLAC | ||||||||||||||||||||||
| { offset: 0, bytes: Buffer.from("fLaC", "ascii") }, | ||||||||||||||||||||||
| // AIFF | ||||||||||||||||||||||
| { offset: 0, bytes: Buffer.from("FORM", "ascii") }, | ||||||||||||||||||||||
| // MP4 / M4A ftyp box (bytes 4-7 are "ftyp") | ||||||||||||||||||||||
| { offset: 4, bytes: Buffer.from("ftyp", "ascii") }, | ||||||||||||||||||||||
| ]; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| export function isValidAudioBuffer(buf) { | ||||||||||||||||||||||
| if (!buf || buf.length < 12) return false; | ||||||||||||||||||||||
| return AUDIO_SIGNATURES.some(({ offset, bytes }) => { | ||||||||||||||||||||||
| if (buf.length < offset + bytes.length) return false; | ||||||||||||||||||||||
| return buf.slice(offset, offset + bytes.length).equals(bytes); | ||||||||||||||||||||||
| }); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| const upload = multer({ | ||||||||||||||||||||||
| storage: multer.memoryStorage(), | ||||||||||||||||||||||
| limits: { | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
RIFF/FORMalone are generic container markers; verify the audio sub-type.RIFF(Line 11) also identifies AVI and WebP, andFORM(Line 24) is the generic IFF chunk used by many non-audio formats. Matching only these 4 bytes at offset 0 lets non-audio containers pass the check, which undercuts the goal of this PR. WAV requiresWAVEand AIFF requiresAIFFat offset 8. The current single{offset, bytes}+.somemodel can't enforce a compound match, so the table needs a small reshape to require both parts.π‘οΈ Proposed compound-signature check
And update the matcher accordingly:
export function isValidAudioBuffer(buf) { if (!buf || buf.length < 12) return false; - return AUDIO_SIGNATURES.some(({ offset, bytes }) => { - if (buf.length < offset + bytes.length) return false; - return buf.slice(offset, offset + bytes.length).equals(bytes); - }); + return AUDIO_SIGNATURES.some(({ parts }) => + parts.every(({ offset, bytes }) => + buf.length >= offset + bytes.length && + buf.subarray(offset, offset + bytes.length).equals(bytes) + ) + ); }Also applies to: 24-24
π€ Prompt for AI Agents