Skip to content

Commit ec767b1

Browse files
authored
fix: validate mime type list when updating chatflow configuration (#5768)
* fix: validate mime type list when updating chatflow configuration * fix: lint issues * fix: code reviews
1 parent 95a26ae commit ec767b1

6 files changed

Lines changed: 93 additions & 11 deletions

File tree

.github/workflows/docker-image-ecr.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ on:
2525
default: 'latest'
2626

2727
permissions:
28-
contents: read # Required for checkout
28+
contents: read # Required for checkout
2929
id-token: write # Required for AWS OIDC
3030

3131
jobs:

SECURITY.md

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
At Flowise, we prioritize security and continuously work to safeguard our systems. However, vulnerabilities can still exist. If you identify a security issue, please report it to us so we can address it promptly. Your cooperation helps us better protect our platform and users.
44

55
### Scope
6+
67
- Flowise Cloud: cloud.flowiseai.com
78
- Public Flowise Repositories
89

@@ -31,7 +32,6 @@ At Flowise, we prioritize security and continuously work to safeguard our system
3132
- Known vulnerabilities in used libraries (unless exploitability can be proven)
3233
- Static application security testing findings
3334

34-
3535
### Reporting Guidelines
3636

3737
- Submit your findings to https://github.com/FlowiseAI/Flowise/security
@@ -46,9 +46,10 @@ At Flowise, we prioritize security and continuously work to safeguard our system
4646

4747
### Disclosure Terms
4848

49-
The Flowise team believes that transparency is important and public bug bounty reports are a valuable source of knowledge for bug bounty researchers. However, the Flowise team may have legitimate reasons not to disclose vulnerabilities.
49+
The Flowise team believes that transparency is important and public bug bounty reports are a valuable source of knowledge for bug bounty researchers. However, the Flowise team may have legitimate reasons not to disclose vulnerabilities.
5050

5151
Do not discuss or disclose vulnerability information without prior written consent. If you plan on presenting your research, please share a draft with us at least 45 days in advance for review. Avoid including:
52+
5253
- Data from any Flowise customer projects
5354
- Flowise user/customer information
5455
- Details about Flowise employees, contractors, or partners
@@ -63,7 +64,7 @@ We will validate submissions within the below timelines.
6364
| Medium | 15 business days |
6465
| Low | 15 business days |
6566

66-
Your report will be kept *confidential*, and your details will not be shared without your consent. The Flowise team will triage and adjust severity or CVSS score if necessary.
67+
Your report will be kept _confidential_, and your details will not be shared without your consent. The Flowise team will triage and adjust severity or CVSS score if necessary.
6768
We appreciate your efforts in helping us maintain a secure platform and look forward to working together to resolve any issues responsibly.
6869

6970
### Remediation
@@ -72,15 +73,16 @@ Once the report has been verified, the Flowise team will plan the remediation st
7273
Below is the estimated time to remediate the triaged security reports.
7374

7475
| Triaged Severity | Estimated Time to Remediate |
75-
| ---------------------- | ---------------- |
76-
| Critical | 30 business days |
77-
| High | 60 business days |
78-
| Medium | 90 business days |
76+
| ---------------- | --------------------------- |
77+
| Critical | 30 business days |
78+
| High | 60 business days |
79+
| Medium | 90 business days |
7980

8081
### Public Disclosure Timeline
8182

8283
Public Disclosure occurs exactly 30 days after the next official release that includes the security patch. This period gives Flowise users a time to adopt the patched version before technical vulnerability details are made public, mitigating the risk of immediate post-disclosure exploitation.
8384

8485
#### Reaching out to the Security team
86+
8587
To report a new vulnerability, please submit a Github security Security Advisory report.
86-
If you have any questions or concerns about the existing Security Advisory, please contact security-team@flowiseai.com.
88+
If you have any questions or concerns about the existing Security Advisory, please contact security-team@flowiseai.com.

packages/components/src/utils.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1198,6 +1198,42 @@ export const mapMimeTypeToExt = (mimeType: string) => {
11981198
}
11991199
}
12001200

1201+
/**
1202+
* MIME types allowed for full file upload (chatflow config).
1203+
* Server validates stored allowedUploadFileTypes against this list to prevent
1204+
* malicious clients from allowing executables or other dangerous types.
1205+
* Uses a Set for O(1) lookups and to make the unique allowed set explicit.
1206+
*/
1207+
export const ALLOWED_UPLOAD_MIME_TYPES: ReadonlySet<string> = new Set([
1208+
'text/css',
1209+
'text/csv',
1210+
'text/html',
1211+
'application/json',
1212+
'text/markdown',
1213+
'application/x-yaml',
1214+
'application/pdf',
1215+
'application/sql',
1216+
'text/plain',
1217+
'application/xml',
1218+
'application/msword',
1219+
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
1220+
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
1221+
'application/vnd.openxmlformats-officedocument.presentationml.presentation'
1222+
])
1223+
1224+
/**
1225+
* Returns true if the MIME type is allowed for file upload config.
1226+
* Must be in ALLOWED_UPLOAD_MIME_TYPES and have a mapping in mapMimeTypeToExt.
1227+
* @param {string} mime
1228+
* @returns {boolean}
1229+
*/
1230+
export const isAllowedUploadMimeType = (mime: string): boolean => {
1231+
if (!mime || typeof mime !== 'string') return false
1232+
const trimmed = mime.trim()
1233+
if (!trimmed) return false
1234+
return ALLOWED_UPLOAD_MIME_TYPES.has(trimmed) && mapMimeTypeToExt(trimmed) !== ''
1235+
}
1236+
12011237
// remove invalid markdown image pattern: ![<some-string>](<some-string>)
12021238
export const removeInvalidImageMarkdown = (output: string): string => {
12031239
return typeof output === 'string' ? output.replace(/!\[.*?\]\((?!https?:\/\/).*?\)/g, '') : output

packages/components/src/validator.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import path from 'path'
2-
import { mapMimeTypeToExt, getUserHome } from './utils'
2+
import { getUserHome, isAllowedUploadMimeType, mapMimeTypeToExt } from './utils'
33

44
/**
55
* Validates if a string is a valid UUID v4
@@ -149,6 +149,17 @@ export const validateMimeTypeAndExtensionMatch = (filename: string, mimetype: st
149149
}
150150
}
151151

152+
/**
153+
* Filters an array of MIME type strings to only those allowed for file upload config.
154+
* Used when sanitizing chatbotConfig.allowedUploadFileTypes to prevent malicious values.
155+
* @param {string[]} mimeTypes Raw MIME types (e.g. from splitting comma-separated config)
156+
* @returns {string[]} Only MIME types that pass isAllowedUploadMimeType
157+
*/
158+
export const filterAllowedUploadMimeTypes = (mimeTypes: string[]): string[] => {
159+
if (!Array.isArray(mimeTypes)) return []
160+
return mimeTypes.map((m) => (typeof m === 'string' ? m.trim() : '')).filter((m) => m !== '' && isAllowedUploadMimeType(m))
161+
}
162+
152163
/**
153164
* Get allowed base directories for vector store operations
154165
* @returns {string[]} Array of allowed base directory paths

packages/server/src/services/chatflows/index.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { InternalFlowiseError } from '../../errors/internalFlowiseError'
1515
import { getErrorMessage } from '../../errors/utils'
1616
import documentStoreService from '../../services/documentstore'
1717
import { constructGraphs, getAppVersion, getEndingNodes, getTelemetryFlowObj, isFlowValidForStream } from '../../utils'
18+
import { sanitizeAllowedUploadMimeTypesFromConfig } from '../../utils/fileValidation'
1819
import { containsBase64File, updateFlowDataWithFilePaths } from '../../utils/fileRepository'
1920
import { getRunningExpressApp } from '../../utils/getRunningExpressApp'
2021
import { utilGetUploadsConfig } from '../../utils/getUploadsConfig'
@@ -351,6 +352,21 @@ const updateChatflow = async (
351352
} else {
352353
updateChatFlow.type = chatflow.type
353354
}
355+
if (updateChatFlow.chatbotConfig) {
356+
try {
357+
const parsed = JSON.parse(updateChatFlow.chatbotConfig) as ICommonObject
358+
if (parsed?.fullFileUpload?.allowedUploadFileTypes !== undefined) {
359+
const current = parsed.fullFileUpload.allowedUploadFileTypes
360+
const sanitized = sanitizeAllowedUploadMimeTypesFromConfig(typeof current === 'string' ? current : String(current ?? ''))
361+
parsed.fullFileUpload.allowedUploadFileTypes = sanitized
362+
updateChatFlow.chatbotConfig = JSON.stringify(parsed)
363+
}
364+
} catch (error) {
365+
const message = getErrorMessage(error)
366+
logger.error(`[server]: Invalid chatbotConfig JSON in updateChatflow: ${message}`)
367+
throw new InternalFlowiseError(StatusCodes.BAD_REQUEST, `Invalid chatbotConfig: ${message}`)
368+
}
369+
}
354370
const newDbChatflow = appServer.AppDataSource.getRepository(ChatFlow).merge(chatflow, updateChatFlow)
355371
await _checkAndUpdateDocumentStoreUsage(newDbChatflow, chatflow.workspaceId)
356372
const dbResponse = await appServer.AppDataSource.getRepository(ChatFlow).save(newDbChatflow)

packages/server/src/utils/fileValidation.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { validateMimeTypeAndExtensionMatch } from 'flowise-components'
1+
import { filterAllowedUploadMimeTypes, validateMimeTypeAndExtensionMatch } from 'flowise-components'
22
import { InternalFlowiseError } from '../errors/internalFlowiseError'
33
import { StatusCodes } from 'http-status-codes'
44
import { getErrorMessage } from '../errors/utils'
@@ -25,3 +25,20 @@ export function validateFileMimeTypeAndExtensionMatch(filename: string, mimetype
2525
throw new InternalFlowiseError(StatusCodes.BAD_REQUEST, getErrorMessage(error))
2626
}
2727
}
28+
29+
/**
30+
* Sanitizes the allowedUploadFileTypes string from chatbotConfig by keeping only
31+
* MIME types that are in the server allow list. Removes any type not in the list
32+
* (e.g. executables) to prevent malicious clients from persisting dangerous types.
33+
*
34+
* @param {string} allowedTypesString Comma-separated MIME types from config
35+
* @returns {string} Comma-separated string of allowed MIME types only
36+
*/
37+
export function sanitizeAllowedUploadMimeTypesFromConfig(allowedTypesString: string): string {
38+
if (typeof allowedTypesString !== 'string') return ''
39+
const parts = allowedTypesString
40+
.split(',')
41+
.map((s) => s.trim())
42+
.filter(Boolean)
43+
return filterAllowedUploadMimeTypes(parts).join(',')
44+
}

0 commit comments

Comments
 (0)