Skip to content

Commit e6e5bf2

Browse files
committed
fix: validate mime type list when updating chatflow configuration
1 parent 280e3ef commit e6e5bf2

4 files changed

Lines changed: 80 additions & 2 deletions

File tree

packages/components/src/utils.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1198,6 +1198,41 @@ 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+
*/
1206+
export const ALLOWED_UPLOAD_MIME_TYPES: readonly string[] = [
1207+
'text/css',
1208+
'text/csv',
1209+
'text/html',
1210+
'application/json',
1211+
'text/markdown',
1212+
'application/x-yaml',
1213+
'application/pdf',
1214+
'application/sql',
1215+
'text/plain',
1216+
'application/xml',
1217+
'application/msword',
1218+
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
1219+
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
1220+
'application/vnd.openxmlformats-officedocument.presentationml.presentation'
1221+
]
1222+
1223+
/**
1224+
* Returns true if the MIME type is allowed for file upload config.
1225+
* Must be in ALLOWED_UPLOAD_MIME_TYPES and have a mapping in mapMimeTypeToExt.
1226+
* @param {string} mime
1227+
* @returns {boolean}
1228+
*/
1229+
export const isAllowedUploadMimeType = (mime: string): boolean => {
1230+
if (!mime || typeof mime !== 'string') return false
1231+
const trimmed = mime.trim()
1232+
if (!trimmed) return false
1233+
return ALLOWED_UPLOAD_MIME_TYPES.includes(trimmed) && mapMimeTypeToExt(trimmed) !== ''
1234+
}
1235+
12011236
// remove invalid markdown image pattern: ![<some-string>](<some-string>)
12021237
export const removeInvalidImageMarkdown = (output: string): string => {
12031238
return typeof output === 'string' ? output.replace(/!\[.*?\]\((?!https?:\/\/).*?\)/g, '') : output

packages/components/src/validator.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { mapMimeTypeToExt } from './utils'
1+
import { isAllowedUploadMimeType, mapMimeTypeToExt } from './utils'
22

33
/**
44
* Validates if a string is a valid UUID v4
@@ -147,3 +147,16 @@ export const validateMimeTypeAndExtensionMatch = (filename: string, mimetype: st
147147
)
148148
}
149149
}
150+
151+
/**
152+
* Filters an array of MIME type strings to only those allowed for file upload config.
153+
* Used when sanitizing chatbotConfig.allowedUploadFileTypes to prevent malicious values.
154+
* @param {string[]} mimeTypes Raw MIME types (e.g. from splitting comma-separated config)
155+
* @returns {string[]} Only MIME types that pass isAllowedUploadMimeType
156+
*/
157+
export const filterAllowedUploadMimeTypes = (mimeTypes: string[]): string[] => {
158+
if (!Array.isArray(mimeTypes)) return []
159+
return mimeTypes
160+
.map((m) => (typeof m === 'string' ? m.trim() : ''))
161+
.filter((m) => m !== '' && isAllowedUploadMimeType(m))
162+
}

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(
361+
typeof current === 'string' ? current : String(current ?? '')
362+
)
363+
parsed.fullFileUpload.allowedUploadFileTypes = sanitized
364+
updateChatFlow.chatbotConfig = JSON.stringify(parsed)
365+
}
366+
} catch {
367+
// If parsing fails, leave chatbotConfig unchanged
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: 15 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,17 @@ 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.split(',').map((s) => s.trim()).filter(Boolean)
40+
return filterAllowedUploadMimeTypes(parts).join(',')
41+
}

0 commit comments

Comments
 (0)