Skip to content

Validate uploaded audio files using magic bytes instead of Content-Type header - #111

Merged
Itzzavdheshh merged 1 commit into
itzzavdhesh:mainfrom
anshul23102:fix/70-file-type-validation
Aug 14, 2026
Merged

Validate uploaded audio files using magic bytes instead of Content-Type header#111
Itzzavdheshh merged 1 commit into
itzzavdhesh:mainfrom
anshul23102:fix/70-file-type-validation

Conversation

@anshul23102

@anshul23102 anshul23102 commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

Validate uploaded audio files using magic bytes instead of Content-Type header

Implements file type validation using magic byte signatures to prevent spoofed audio uploads that bypass Content-Type checks. Ensures only valid audio formats (WebM, MP3, WAV, OGG) are accepted.

Changes

  • New validation logic in server/middleware/upload.js
  • Magic byte checking for audio files before processing
  • Rejection of files with invalid audio signatures
  • Clear error messages for validation failures

Key Features

  • Magic byte (file signature) validation
  • Support for WebM, MP3, WAV, OGG formats
  • Prevents Content-Type spoofing attacks
  • Detailed error logging

Test Plan

  • Upload valid audio file - should succeed
  • Upload file with spoofed audio Content-Type - should be rejected
  • Upload non-audio file renamed as audio - should be rejected
  • Verify error messages are user-friendly

Fixes #70

🤖 Generated with Claude Code

…r (Issue itzzavdhesh#70)

File uploads are now validated against actual file contents using the file-type package to detect magic bytes. This prevents clients from spoofing the Content-Type header to bypass audio format validation.

Changes:
- Import fileTypeFromBuffer from file-type package
- Add async magic byte validation in Multer fileFilter
- Reject files where detected MIME type does not match audio format
- Provide clear error message when file validation fails

Security prevents:
- Binary files (executables, images, etc.) disguised as audio
- Content-Type header spoofing attacks
- Invalid audio files reaching ElevenLabs API

Fixes itzzavdhesh#70
@vercel

vercel Bot commented Jun 4, 2026

Copy link
Copy Markdown

@anshul23102 is attempting to deploy a commit to the itzzavdhesh's projects Team on Vercel.

A member of the Team first needs to authorize it.

@anshul23102

Copy link
Copy Markdown
Contributor Author

Could the maintainers please add relevant labels? Suggested: type:security, severity:high, area:file-handling, nsoc

@coderabbitai

coderabbitai Bot commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The upload middleware now validates audio files using actual content inspection rather than relying solely on client-supplied MIME types. The fileTypeFromBuffer library detects file types from buffer headers, and the async filter rejects non-audio uploads with appropriate error messages.

Changes

Audio file content validation

Layer / File(s) Summary
Buffer-based audio file validation in Multer filter
server/middleware/upload.js
Imports fileTypeFromBuffer and replaces the synchronous MIME-type-only filter with an async implementation that detects actual file type from the uploaded buffer, rejecting uploads that are not audio-typed and handling detection errors.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related issues

  • #103: Changes to server/middleware/upload.js fileFilter add buffer-based validation using fileTypeFromBuffer, directly addressing concerns about client-controlled Content-Type headers being insufficient for audio file verification.

Poem

🐰 A rabbit hops with glee,
No more files in disguise shall be,
Magic bytes now tell the tale,
Spoofed uploads? They shall fail!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the primary change: using magic bytes instead of the Content-Type header for audio file validation.
Linked Issues check ✅ Passed The changes implement all coding requirements: import fileTypeFromBuffer, make fileFilter async, detect actual MIME from magic bytes, and reject non-audio files with appropriate error handling.
Out of Scope Changes check ✅ Passed All changes to server/middleware/upload.js are directly related to the linked issue #70 objective of implementing magic byte-based file validation.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

github-actions Bot commented Jun 4, 2026

Copy link
Copy Markdown

✍️ DCO Sign-off Needed

Hey @anshul23102! 👋 One or more commits in this PR are missing a Signed-off-by: line.

Warning

How to fix:

For the latest commit:

git commit --amend --signoff
git push --force-with-lease

For multiple commits, replace N with the number to update:

git rebase --signoff HEAD~N
git push --force-with-lease

This comment will update automatically after you push.


🤖 VoiceForge Automation · Updates automatically on edits

@github-actions

github-actions Bot commented Jun 4, 2026

Copy link
Copy Markdown

🛠️ PR Needs Updates

Hey @anshul23102! 👋 A few things need fixing before a mentor can review this PR.

Warning

  • Select a program PR template: GSSoC, NSOC, SSOC, or ELUSOC.
  • Use the mandatory VoiceForge PR template and keep all required sections.
  • Use a clear PR title, for example feat: add voice preview or [feature]: add voice preview.

How to fix:

  • PR template: Use one complete program PR template and keep all required sections.
  • PR title: Use a clear title like fix: update onboarding progress bar or [bug refactor]: replace pending stream cache.
  • Program: Choose exactly one program: GSSoC, NSOC, SSOC, or ELUSOC.

Once fixed, the workflow re-runs automatically and pings the right mentor.


🤖 VoiceForge Automation · Updates automatically on edits

@github-actions github-actions Bot added the bug Something isn't working label Jun 4, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@server/middleware/upload.js`:
- Around line 12-25: The three plain Error instances thrown in the upload
middleware need HTTP status codes so the global error handler returns 4xx, not
500: when creating the "Please upload an audio recording." error set
error.status = 400 before calling callback(error), when creating the "Uploaded
file is not a valid audio recording..." error set error.status = 415
(Unsupported Media Type) before callback(error), and in the catch block wrap or
set error.status = 400 (or 415 if you prefer to treat detection failures as
media errors) before passing it to callback; locate these changes around the
fileTypeFromBuffer usage and the callback(...) calls in the upload middleware.
- Around line 10-19: The Multer fileFilter currently calls
fileTypeFromBuffer(file.buffer) but Multer invokes fileFilter before the file
buffer is populated, causing valid uploads to be rejected; move the magic-byte
validation out of fileFilter into a post-upload middleware that runs after
upload.single(...) (where req.file.buffer is available) or implement a
stream-based detection, and ensure any validation errors created (e.g., when
detectedType is missing or not audio) set err.status = 400 before passing to
next(err); update references: fileFilter, upload.single, req.file.buffer, and
fileTypeFromBuffer to implement this change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 65bfc890-827d-45d7-aea3-0f21caec86c5

📥 Commits

Reviewing files that changed from the base of the PR and between 67066b4 and d26b005.

📒 Files selected for processing (1)
  • server/middleware/upload.js

Comment on lines +10 to +19
fileFilter: async (_request, file, callback) => {
if (!file.mimetype.startsWith("audio/")) {
callback(new Error("Please upload an audio recording."));
return;
}
callback(null, true);

try {
const detectedType = await fileTypeFromBuffer(file.buffer);
if (!detectedType || !detectedType.mime.startsWith("audio/")) {
callback(new Error("Uploaded file is not a valid audio recording. The file contents do not match an audio format."));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical | 🏗️ Heavy lift

🧩 Analysis chain

🌐 Web query:

In Multer v2.0.2, what properties are guaranteed on the fileargument insidefileFilter? Specifically, is file.bufferavailable there, or only onreq.fileaftermemoryStorage has finished processing?

💡 Result:

In Multer v2.0.2, the file argument provided to the fileFilter function does not contain the file.buffer property [1][2]. The fileFilter function is designed to run before the file has been processed or fully read into memory/storage [1], meaning it only has access to the file's metadata, such as fieldname, originalname, encoding, and mimetype [3][4]. The file.buffer property is only available when using memoryStorage and is attached to the file object after the processing has been completed and the file is available in the request (e.g., in subsequent middleware or your route handler) [3][1][4]. Because fileFilter is intended to accept or reject a file based on its metadata before the upload process completes, the file contents are not yet available for inspection [1]. If you need to validate a file based on its contents (such as its magic bytes), you cannot do so directly inside fileFilter [1][2]; common workarounds include checking the file in a later middleware or route handler and deleting the file if it fails your criteria [1].

Citations:


Fix: fileTypeFromBuffer(file.buffer) can’t run in Multer fileFilter (rejects valid audio)

In server/middleware/upload.js (fileFilter, around line 17), Multer v2 calls fileFilter before the file is read into memory; the file argument there does not include file.buffer. Move the magic-byte validation to a middleware that runs after upload.single(...) (where req.file.buffer exists with memoryStorage), or validate via a stream-based approach. Also set err.status = 400 for validation failures so clients don’t get 500s.

Suggested direction
 const upload = multer({
   storage: multer.memoryStorage(),
   limits: {
     fileSize: 12 * 1024 * 1024
   },
-  fileFilter: async (_request, file, callback) => {
+  fileFilter: (_request, file, callback) => {
     if (!file.mimetype.startsWith("audio/")) {
-      callback(new Error("Please upload an audio recording."));
+      const err = new Error("Please upload an audio recording.");
+      err.status = 400;
+      callback(err);
       return;
     }
-
-    try {
-      const detectedType = await fileTypeFromBuffer(file.buffer);
-      if (!detectedType || !detectedType.mime.startsWith("audio/")) {
-        callback(new Error("Uploaded file is not a valid audio recording. The file contents do not match an audio format."));
-        return;
-      }
-      callback(null, true);
-    } catch (error) {
-      callback(new Error("Failed to validate file type: " + error.message));
-    }
+    callback(null, true);
   }
 });
+
+export const validateAudioMagicBytes = async (request, _response, next) => {
+  try {
+    const detectedType = await fileTypeFromBuffer(request.file?.buffer);
+    if (!detectedType?.mime?.startsWith("audio/")) {
+      const err = new Error("Uploaded file is not a valid audio recording.");
+      err.status = 400;
+      return next(err);
+    }
+    return next();
+  } catch {
+    const err = new Error("Failed to validate file type.");
+    err.status = 400;
+    return next(err);
+  }
+};
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
fileFilter: async (_request, file, callback) => {
if (!file.mimetype.startsWith("audio/")) {
callback(new Error("Please upload an audio recording."));
return;
}
callback(null, true);
try {
const detectedType = await fileTypeFromBuffer(file.buffer);
if (!detectedType || !detectedType.mime.startsWith("audio/")) {
callback(new Error("Uploaded file is not a valid audio recording. The file contents do not match an audio format."));
fileFilter: (_request, file, callback) => {
if (!file.mimetype.startsWith("audio/")) {
const err = new Error("Please upload an audio recording.");
err.status = 400;
callback(err);
return;
}
callback(null, true);
}
});
export const validateAudioMagicBytes = async (request, _response, next) => {
try {
const detectedType = await fileTypeFromBuffer(request.file?.buffer);
if (!detectedType?.mime?.startsWith("audio/")) {
const err = new Error("Uploaded file is not a valid audio recording.");
err.status = 400;
return next(err);
}
return next();
} catch {
const err = new Error("Failed to validate file type.");
err.status = 400;
return next(err);
}
};
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/middleware/upload.js` around lines 10 - 19, The Multer fileFilter
currently calls fileTypeFromBuffer(file.buffer) but Multer invokes fileFilter
before the file buffer is populated, causing valid uploads to be rejected; move
the magic-byte validation out of fileFilter into a post-upload middleware that
runs after upload.single(...) (where req.file.buffer is available) or implement
a stream-based detection, and ensure any validation errors created (e.g., when
detectedType is missing or not audio) set err.status = 400 before passing to
next(err); update references: fileFilter, upload.single, req.file.buffer, and
fileTypeFromBuffer to implement this change.

Comment on lines 12 to +25
callback(new Error("Please upload an audio recording."));
return;
}
callback(null, true);

try {
const detectedType = await fileTypeFromBuffer(file.buffer);
if (!detectedType || !detectedType.mime.startsWith("audio/")) {
callback(new Error("Uploaded file is not a valid audio recording. The file contents do not match an audio format."));
return;
}
callback(null, true);
} catch (error) {
callback(new Error("Failed to validate file type: " + error.message));
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Validation failures are returned as 500 instead of 4xx

Lines 12, 19, and 24 create plain Error objects without status; with the global error handler, these become HTTP 500 responses for client-side invalid uploads. Set error.status = 400 (or 415) before passing to callback.

Minimal fix
-      callback(new Error("Please upload an audio recording."));
+      const err = new Error("Please upload an audio recording.");
+      err.status = 400;
+      callback(err);
       return;
@@
-        callback(new Error("Uploaded file is not a valid audio recording. The file contents do not match an audio format."));
+        const err = new Error("Uploaded file is not a valid audio recording. The file contents do not match an audio format.");
+        err.status = 400;
+        callback(err);
         return;
       }
@@
-      callback(new Error("Failed to validate file type: " + error.message));
+      const err = new Error("Failed to validate file type.");
+      err.status = 400;
+      callback(err);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/middleware/upload.js` around lines 12 - 25, The three plain Error
instances thrown in the upload middleware need HTTP status codes so the global
error handler returns 4xx, not 500: when creating the "Please upload an audio
recording." error set error.status = 400 before calling callback(error), when
creating the "Uploaded file is not a valid audio recording..." error set
error.status = 415 (Unsupported Media Type) before callback(error), and in the
catch block wrap or set error.status = 400 (or 415 if you prefer to treat
detection failures as media errors) before passing it to callback; locate these
changes around the fileTypeFromBuffer usage and the callback(...) calls in the
upload middleware.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

3 issues found across 1 file

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="server/middleware/upload.js">

<violation number="1" location="server/middleware/upload.js:3">
P1: New `file-type` import is not declared in dependencies, which will break runtime module resolution.</violation>

<violation number="2" location="server/middleware/upload.js:17">
P1: `fileFilter` tries to read `file.buffer` before Multer has produced it, so uploads fail validation.</violation>

<violation number="3" location="server/middleware/upload.js:19">
P2: Validation errors created here (and at the other `callback(new Error(...))` call sites) lack a `status` property. If the global error handler defaults to 500 for errors without an explicit status, clients will receive 500 Internal Server Error for invalid uploads instead of 400 Bad Request. Set `err.status = 400` before passing to the callback.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Fix all with cubic | Re-trigger cubic

callback(null, true);

try {
const detectedType = await fileTypeFromBuffer(file.buffer);

@cubic-dev-ai cubic-dev-ai Bot Jun 4, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: fileFilter tries to read file.buffer before Multer has produced it, so uploads fail validation.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At server/middleware/upload.js, line 17:

<comment>`fileFilter` tries to read `file.buffer` before Multer has produced it, so uploads fail validation.</comment>

<file context>
@@ -1,17 +1,28 @@
-    callback(null, true);
+
+    try {
+      const detectedType = await fileTypeFromBuffer(file.buffer);
+      if (!detectedType || !detectedType.mime.startsWith("audio/")) {
+        callback(new Error("Uploaded file is not a valid audio recording. The file contents do not match an audio format."));
</file context>
Fix with cubic

@@ -1,17 +1,28 @@
// Configures Multer for in-memory reference audio uploads sent to ElevenLabs.
import multer from "multer";
import { fileTypeFromBuffer } from "file-type";

@cubic-dev-ai cubic-dev-ai Bot Jun 4, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: New file-type import is not declared in dependencies, which will break runtime module resolution.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At server/middleware/upload.js, line 3:

<comment>New `file-type` import is not declared in dependencies, which will break runtime module resolution.</comment>

<file context>
@@ -1,17 +1,28 @@
 // Configures Multer for in-memory reference audio uploads sent to ElevenLabs.
 import multer from "multer";
+import { fileTypeFromBuffer } from "file-type";
 
 const upload = multer({
</file context>
Fix with cubic

try {
const detectedType = await fileTypeFromBuffer(file.buffer);
if (!detectedType || !detectedType.mime.startsWith("audio/")) {
callback(new Error("Uploaded file is not a valid audio recording. The file contents do not match an audio format."));

@cubic-dev-ai cubic-dev-ai Bot Jun 4, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Validation errors created here (and at the other callback(new Error(...)) call sites) lack a status property. If the global error handler defaults to 500 for errors without an explicit status, clients will receive 500 Internal Server Error for invalid uploads instead of 400 Bad Request. Set err.status = 400 before passing to the callback.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At server/middleware/upload.js, line 19:

<comment>Validation errors created here (and at the other `callback(new Error(...))` call sites) lack a `status` property. If the global error handler defaults to 500 for errors without an explicit status, clients will receive 500 Internal Server Error for invalid uploads instead of 400 Bad Request. Set `err.status = 400` before passing to the callback.</comment>

<file context>
@@ -1,17 +1,28 @@
+    try {
+      const detectedType = await fileTypeFromBuffer(file.buffer);
+      if (!detectedType || !detectedType.mime.startsWith("audio/")) {
+        callback(new Error("Uploaded file is not a valid audio recording. The file contents do not match an audio format."));
+        return;
+      }
</file context>
Suggested change
callback(new Error("Uploaded file is not a valid audio recording. The file contents do not match an audio format."));
const err = new Error("Uploaded file is not a valid audio recording. The file contents do not match an audio format.");
err.status = 400;
callback(err);
Fix with cubic

@anshul23102

Copy link
Copy Markdown
Contributor Author

Please add relevant labels:

  • type/security
  • severity/high
  • area/upload
  • nsoc

These help with tracking and prioritization. Thank you!

@anshul23102

Copy link
Copy Markdown
Contributor Author

Closing this as a duplicate of #78, which targets the same issue and already has maintainer review history. Consolidating to a single PR per issue to keep the queue clean. All further work will continue on #78.

@anshul23102 anshul23102 closed this Jun 4, 2026
@Itzzavdheshh Itzzavdheshh added the level:advanced level:advanced label Aug 13, 2026
@Itzzavdheshh Itzzavdheshh reopened this Aug 14, 2026
@Itzzavdheshh
Itzzavdheshh merged commit e804607 into itzzavdhesh:main Aug 14, 2026
11 of 18 checks passed
@github-actions

Copy link
Copy Markdown

❌ Merge Policy Violation

Caution

Unauthorized Merge — Pull request #111 was merged by @Itzzavdheshh (mentor) without any review on record.

VoiceForge guidelines require contributors/mentors to submit at least one review (approval, comment, or changes requested) before merging a pull request to ensure code quality and point-tracking integrity.

📊 Violation Summary

Parameter Details
Merged By @Itzzavdheshh (mentor)
Review Count 0 reviews on record
PR Author @anshul23102
PR Number #111

⚠️ Action Required (@itzzavdhesh):

  • Please review the merged code for quality and scope.
  • Consider reverting the merge if it was done inappropriately.
    • Ensure your repository ruleset or branch protection rules are active.

🤖 VoiceForge Automation

@github-actions

Copy link
Copy Markdown

🎊 PR Merged Successfully

Hey @anshul23102! 👋 Congratulations and thank you for your contribution to VoiceForge!

Note

🔗 Linked issue(s): #70 · ✅ Marked as merged and complete

Maintainers may still handle final cleanup, release notes, or follow-up tracking after the merge.


🤖 VoiceForge Automation · Updates automatically on edits

@Itzzavdheshh Itzzavdheshh added type:accessibility type type:bug type:bug type:feature type:feature type:performance type:performance type:refactor type:refactor level:intermediate level:intermediate VETERAN VETERAN Hard hard and removed level:intermediate level:intermediate labels Aug 14, 2026
@itzzavdhesh itzzavdhesh added mentor:Anushreebasics GSSoC: Mentor-@Anushreebasics mentor:sabeenaviklar GSSoC: Mentor-@sabeenaviklar mentor:itsdakshjain GSSoC: Mentor-@itsdakshjain mentor:Nitya-003 Applied by mentor automation labels Aug 14, 2026
@itzzavdhesh itzzavdhesh added the gssoc26 gssoc26 label Aug 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working completed dco-missing ELUSOC ELUSOC gssoc:approved Gssoc gssoc26 gssoc26 Hard hard level:advanced level:advanced mentor:Anushreebasics GSSoC: Mentor-@Anushreebasics mentor:itsdakshjain GSSoC: Mentor-@itsdakshjain mentor:Nitya-003 Applied by mentor automation mentor:sabeenaviklar GSSoC: Mentor-@sabeenaviklar merged needs-template needs-template needs-title pr-merged quality:clean quality:clean server size/xs SSoC26 SSOC type:accessibility type type:bug type:bug type:feature type:feature type:performance type:performance type:refactor type:refactor VETERAN VETERAN

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] upload.js validates only MIME type for audio files — clients can upload arbitrary data with a spoofed Content-Type

3 participants