Skip to content

Conversation

@jocelynlin-wd
Copy link
Contributor

@jocelynlin-wd jocelynlin-wd commented Dec 18, 2025

Address security vulnerability reported in https://github.com/FlowiseAI/Flowise/security/advisories/GHSA-j8g8-j7fc-43v6 (also in issues #5579)

Add MIME type and extension validation for file uploads:

  • Updated jest.config.js to include the 'src' directory in test roots.
  • Introduced a new validator for checking MIME type and file extension matches, addressing security vulnerabilities.
  • clean up invalid binary file in storage when loader failed (leverage loader to verify content)
  • Implemented validation in multiple server controllers and utilities to prevent MIME type spoofing attacks.
  • Added comprehensive tests for the new validation logic in validator.test.ts.

Unit Tests

 PASS  src/validator.test.ts (12.537 s)
  validateMimeTypeAndExtensionMatch
    valid cases
      ✓ should pass validation for matching MIME type and extension - document.txt with text/plain (3 ms)
      ✓ should pass validation for matching MIME type and extension - page.html with text/html
      ✓ should pass validation for matching MIME type and extension - data.json with application/json
      ✓ should pass validation for matching MIME type and extension - document.pdf with application/pdf (1 ms)
      ✓ should pass validation for matching MIME type and extension - script.js with text/javascript
      ✓ should pass validation for matching MIME type and extension - script.js with application/javascript
      ✓ should pass validation for matching MIME type and extension - readme.md with text/markdown
      ✓ should pass validation for matching MIME type and extension - readme.md with text/x-markdown
      ✓ should pass validation for matching MIME type and extension - DOCUMENT.TXT with text/plain (1 ms)
      ✓ should pass validation for matching MIME type and extension - Document.TxT with text/plain
      ✓ should pass validation for matching MIME type and extension - my.document.txt with text/plain
    invalid filename
      ✓ should throw error for empty filename (12 ms)
      ✓ should throw error for null filename (1 ms)
      ✓ should throw error for undefined filename (5 ms)
      ✓ should throw error for non-string filename (number)
      ✓ should throw error for object filename
    invalid MIME type
      ✓ should throw error for empty MIME type
      ✓ should throw error for null MIME type
      ✓ should throw error for undefined MIME type
      ✓ should throw error for non-string MIME type (number)
    path traversal detection
      ✓ should throw error for filename with ..
      ✓ should throw error for filename with .. in middle
      ✓ should throw error for filename with multle levels of .. (1 ms)
      ✓ should throw error for filename with  ..\..\..
      ✓ should throw error for filename with ....//....//
      ✓ should throw error for filename starting with /
      ✓ should throw error for Windows absolute path
      ✓ should throw error for URL encoded path traversal
      ✓ should throw error for URL encoded path traversal multiple levels
      ✓ should throw error for null byte
    files without extensions
      ✓ should throw error for filename without extension
      ✓ should throw error for filename ending with dot
    unsupported MIME types
      ✓ should throw error for unsupported MIME type application/octet-stream with file.txt (1 ms)
      ✓ should throw error for unsupported MIME type invalid-mime-type with file.txt
      ✓ should throw error for unsupported MIME type application/x-msdownload with malware.exe
      ✓ should throw error for unsupported MIME type application/x-executable with script.exe
      ✓ should throw error for unsupported MIME type application/x-msdownload with program.EXE
      ✓ should throw error for unsupported MIME type application/octet-stream with script.js
    MIME type and extension mismatches
      ✓ should throw error when extension does not match MIME type - file.txt with application/json (1 ms)
      ✓ should throw error when extension does not match MIME type - script.js with application/pdf
      ✓ should throw error when extension does not match MIME type - page.html with text/plain
      ✓ should throw error when extension does not match MIME type - document.pdf with application/json
      ✓ should throw error when extension does not match MIME type - data.json with text/plain
      ✓ should throw error when extension does not match MIME type - malware.exe with text/plain (1 ms)
      ✓ should throw error when extension does not match MIME type - script.js with application/json

Manual tests

----- Test Parameters: ----------------------------------------
  filename:     security-test.js
  mimetype:     application/javascript
  chat_target:  test-js-ext
  curl_cmd:  curl -s -S -X POST http://localhost:3000/api/v1/attachments/a29bbc33-c530-4981-b3ef-58a5aea28769/test-js-ext -F [email protected];type=application/javascript

{"statusCode":500,"success":false,"message":"Error: attachmentService.createAttachment - File type 'application/javascript' is not allowed. Allowed types: text/css, text/csv, text/html, application/json, text/markdown, application/x-yaml, application/pdf, application/sql, text/plain, application/xml, application/msword, application/vnd.openxmlformats-officedocument.wordprocessingml.document, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.openxmlformats-officedocument.presentationml.presentation","stack":{}} 
=> Expected: Validation Error

----- Test Parameters: ----------------------------------------
  filename:     security-test.js
  mimetype:     application/pdf
  chat_target:  test-js-ext
  curl_cmd:  curl -s -S -X POST http://localhost:3000/api/v1/attachments/a29bbc33-c530-4981-b3ef-58a5aea28769/test-js-ext -F [email protected];type=application/pdf

{"statusCode":500,"success":false,"message":"Error: attachmentService.createAttachment - MIME type mismatch: file extension \"js\" does not match declared MIME type \"application/pdf\". Expected: pdf","stack":{}} 
=> Expected: Validation Error

----- Test Parameters: ----------------------------------------
  filename:     security-test-text.txt
  mimetype:     text/plain
  chat_target:  test-good-ext
  curl_cmd:  curl -s -S -X POST http://localhost:3000/api/v1/attachments/a29bbc33-c530-4981-b3ef-58a5aea28769/test-good-ext -F [email protected];type=text/plain

[{"name":"security-test-text.txt","mimeType":"text/plain","size":110,"content":"alert(\"Hello, world! You are hacked\");\nconsole.log(\"🔥🔥🔥 Hello, world! You are hacked 🔥🔥🔥\");\n"}] 
=> Expected: SUCCESS

----- Test Parameters: ----------------------------------------
  filename:     security-test.md
  mimetype:     text/markdown
  chat_target:  test-good-ext
  curl_cmd:  curl -s -S -X POST http://localhost:3000/api/v1/attachments/a29bbc33-c530-4981-b3ef-58a5aea28769/test-good-ext -F [email protected];type=text/markdown

[{"name":"security-test.md","mimeType":"text/markdown","size":111,"content":"alert(\"Hello, world! You are hacked\");\nconsole.log(\"🔥🔥🔥 Hello, world! You are hacked 🔥🔥🔥\");\n\n"}] 
=> Expected: SUCCESS

----- Test Parameters: ----------------------------------------
  filename:     security-test.pdf
  mimetype:     application/pdf
  chat_target:  test-good-ext
  curl_cmd:  curl -s -S -X POST http://localhost:3000/api/v1/attachments/a29bbc33-c530-4981-b3ef-58a5aea28769/test-good-ext -F [email protected];type=application/pdf

[{"name":"security-test.pdf","mimeType":"application/pdf","size":8991,"content":""}] 
=> Expected: Success

----- Test Parameters: ----------------------------------------
  filename:     security-test.png
  mimetype:     image/png
  chat_target:  test-image
  curl_cmd:  curl -s -S -X POST http://localhost:3000/api/v1/attachments/a29bbc33-c530-4981-b3ef-58a5aea28769/test-image -F [email protected];type=image/png

{"statusCode":500,"success":false,"message":"Error: attachmentService.createAttachment - File type 'image/png' is not allowed. Allowed types: text/css, text/csv, text/html, application/json, text/markdown, application/x-yaml, application/pdf, application/sql, text/plain, application/xml, application/msword, application/vnd.openxmlformats-officedocument.wordprocessingml.document, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.openxmlformats-officedocument.presentationml.presentation","stack":{}} 
=> Expected: Unsupported Error

----- Test Parameters: ----------------------------------------
  filename:     security-test-faker.pdf
  mimetype:     application/pdf
  chat_target:  test-fake-ext
  curl_cmd:  curl -s -S -X POST http://localhost:3000/api/v1/attachments/a29bbc33-c530-4981-b3ef-58a5aea28769/test-fake-ext -F [email protected];type=application/pdf

{"statusCode":500,"success":false,"message":"Error: attachmentService.createAttachment - Failed createFileAttachment: security-test-faker.pdf (application/pdf - Invalid PDF structure","stack":{}} 
=> Expected: Invalid File Error

----- Test Parameters: ----------------------------------------
  filename:     security-test-faker.docx
  mimetype:     application/vnd.openxmlformats-officedocument.wordprocessingml.document
  chat_target:  test-fake-ext
  curl_cmd:  curl -s -S -X POST http://localhost:3000/api/v1/attachments/a29bbc33-c530-4981-b3ef-58a5aea28769/test-fake-ext -F [email protected];type=application/vnd.openxmlformats-officedocument.wordprocessingml.document

{"statusCode":500,"success":false,"message":"Error: attachmentService.createAttachment - Failed createFileAttachment: security-test-faker.docx (application/vnd.openxmlformats-officedocument.wordprocessingml.document - Can't find end of central directory : is this a zip file ? If it is, see https://stuk.github.io/jszip/documentation/howto/read_zip.html","stack":{}} 
=> Expected: Invalid File Error

----- Test Parameters: ----------------------------------------
  filename:     security-test-faker.doc
  mimetype:     application/msword
  chat_target:  test-fake-ext
  curl_cmd:  curl -s -S -X POST http://localhost:3000/api/v1/attachments/a29bbc33-c530-4981-b3ef-58a5aea28769/test-fake-ext -F [email protected];type=application/msword

{"statusCode":500,"success":false,"message":"Error: attachmentService.createAttachment - Failed createFileAttachment: security-test-faker.doc (application/msword - Can't find end of central directory : is this a zip file ? If it is, see https://stuk.github.io/jszip/documentation/howto/read_zip.html","stack":{}} 
=> Expected: Invalid File Error

----- Flowise Storage Structure after running above ----------------------------------------
.
|____a29bbc33-c530-4981-b3ef-58a5aea28769
| |____2e849e0e-721b-4ef8-9e38-cc739ff40928
| | |____security-test-text.txt
| |____test-fake-ext
| |____test-good-ext
| | |____security-test.pdf
| | |____security-test.md
| | |____security-test-text.txt
|____docustore
| |____576cad4a-813f-4af5-bb22-276f04fb1ee7
| | |____2025-1215-security-test.js

- Updated jest.config.js to include the 'src' directory in test roots.
- Introduced a new validator for checking MIME type and file extension matches, addressing security vulnerabilities.
- Implemented validation in multiple server controllers and utilities to prevent MIME type spoofing attacks.
- Added comprehensive tests for the new validation logic in validator.test.ts.
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @jocelynlin-wd, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the security of file uploads by introducing a comprehensive validation mechanism. It directly addresses a reported vulnerability (GHSA-j8g8-j7fc-43v6) by preventing MIME type spoofing and path traversal attacks. The new validation ensures that uploaded files have appropriate extensions matching their declared MIME types and are free from malicious path components. This logic is integrated across several file handling services, and a cleanup process is added to remove invalid files from storage, bolstering the system's integrity against malicious uploads.

Highlights

  • Security Vulnerability Fix: Addresses the reported security vulnerability GHSA-j8g8-j7fc-43v6 by implementing strict file upload validation.
  • MIME Type and Extension Validation: Introduces a new validator to ensure that uploaded file extensions correctly match their declared MIME types, preventing spoofing attacks.
  • Path Traversal Prevention: Includes robust checks within the new validation logic to detect and block path traversal attempts in filenames.
  • Cleanup on Failed Uploads: Adds logic to automatically remove partially uploaded or invalid files from storage if subsequent processing by a loader fails.
  • Extensive Test Coverage: New unit tests (validator.test.ts) have been added to cover a wide range of valid and invalid scenarios for file and MIME type validation.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request effectively addresses a security vulnerability by introducing robust MIME type and file extension validation for file uploads. The new validator is well-implemented and comes with a comprehensive suite of tests, covering many edge cases. The validation has been consistently applied across all relevant parts of the application that handle file uploads. Additionally, the new logic to clean up files from storage when a loader fails is a great improvement for system hygiene. I have a few minor suggestions to improve code consistency and error message accuracy.

jocelynlin-wd and others added 3 commits December 17, 2025 21:15
isUnsafeFilePath function checks for various security risks, including absolute paths, null bytes, and control characters, not just path traversal. A more general error message would be more appropriate to cover all cases.

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
preset: 'ts-jest',
testEnvironment: 'node',
roots: ['<rootDir>/nodes'],
roots: ['<rootDir>/nodes', '<rootDir>/src'],
Copy link
Contributor Author

Choose a reason for hiding this comment

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

tests in components/src was never run, need to expose the directory as roots

jest.mock('@opentelemetry/exporter-trace-otlp-proto', () => {
return {
ProtoOTLPTraceExporter: jest.fn().mockImplementation((args) => {
OTLPTraceExporter: jest.fn().mockImplementation((args) => {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

the exported function from the library is OTLPTraceExporter not the renamed version import { OTLPTraceExporter as ProtoOTLPTraceExporter }.

}
})

import { OTLPTraceExporter as ProtoOTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto'
Copy link
Contributor Author

Choose a reason for hiding this comment

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

move import to the top of the file

@jocelynlin-wd
Copy link
Contributor Author

/gemini review

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request effectively addresses a security vulnerability by introducing MIME type and file extension validation for file uploads. The new validation logic in packages/components/src/validator.ts is robust and is accompanied by a comprehensive set of unit tests in validator.test.ts, covering numerous edge cases. The validation has been correctly integrated into all relevant file upload points across the application. Additionally, the new functionality to clean up failed uploads from storage is a great improvement for system stability and resource management. I have one suggestion to improve maintainability by reducing code duplication.

@yau-wd
Copy link
Contributor

yau-wd commented Dec 22, 2025

Staging Tested

Conversation Chain Upload Conversational Retrieval QA Chain Upsert Document Store Upsert
image imageimage imageimage

['object filename', {}]
])('should throw error for %s', (_description, filename) => {
expect(() => {
validateMimeTypeAndExtensionMatch(filename as any, 'text/plain')

Choose a reason for hiding this comment

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

nit: should it be as string instead of as any?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ideally yes but we are doing tests and purposefully passing in non-string values (null, undefined, number) and can't cast it to string directly or else will have compile error in typescript. I can change it to first cast as unknown and then to string so that it's less confusing.

['non-string MIME type (number)', 123]
])('should throw error for %s', (_description, mimetype) => {
expect(() => {
validateMimeTypeAndExtensionMatch('file.txt', mimetype as any)

Choose a reason for hiding this comment

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

same concern as above

}

// Get the expected extension from mapMimeTypeToExt (returns extension without dot)
const expectedExt = mapMimeTypeToExt(mimetype)

Choose a reason for hiding this comment

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

I'm still learning through the mapMimeTypeToExt function. Should it support image or media types? Ignore this comment if it's not related to this PR.

Copy link
Contributor Author

@jocelynlin-wd jocelynlin-wd Dec 22, 2025

Choose a reason for hiding this comment

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

We should support image/media types since this validation function is reused in other insert/upload features not just chatflow attachments. I have updated the function to address this. Thanks for pointing this out

Screen.Recording.2025-12-22.at.1.21.41.PM.mov

Choose a reason for hiding this comment

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

Looks good to me! Thanks for your effort adding the new types,

Copy link
Contributor

@yau-wd yau-wd Dec 23, 2025

Choose a reason for hiding this comment

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

We need to support image type upload, one of our enterprise customers is doing OCR.
image

@jocelynlin-wd
Copy link
Contributor Author

/gemini review

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a new file validation mechanism to prevent MIME type spoofing, addressing CVE-2025-61687. Key changes include expanding the mapMimeTypeToExt utility in packages/components/src/utils.ts to support a wider range of YAML, SQL, RTF, image, audio, and video MIME types. A new validateMimeTypeAndExtensionMatch function, along with helper functions for filename validation and extension normalization, has been added to packages/components/src/validator.ts, accompanied by comprehensive unit tests. This validation is integrated into various file upload endpoints across the packages/server module, including OpenAI assistants, document store, chatflow execution, and attachment creation, via a new validateFileMimeTypeAndExtensionMatch wrapper in packages/server/src/utils/fileValidation.ts. The createAttachment utility also gains logic to clean up storage if file processing fails post-validation. Review comments suggest refactoring the extension normalization logic in validator.ts to use a map for better scalability and readability, and replacing console.info/console.error with the logger utility in createAttachment.ts for consistent and robust logging.

@jocelynlin-wd jocelynlin-wd merged commit e47d946 into main Dec 23, 2025
5 checks passed
@yau-wd yau-wd deleted the fix/file-upload-mime-spoofing branch December 23, 2025 19:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants