Bugfix/arbitrary create attachemnt file upload (#4171)

fix arbitrary create attachemnt file upload
This commit is contained in:
Henry Heng 2025-03-13 19:46:27 +00:00 committed by GitHub
parent c6968ff385
commit c2b830f279
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 59 additions and 16 deletions

View File

@ -4,7 +4,6 @@ import { ListKeyOptions, RecordManagerInterface, UpdateOptions } from '@langchai
import { DataSource } from 'typeorm'
import { getHost, getSSL } from '../../vectorstores/Postgres/utils'
import { getDatabase, getPort, getTableName } from './utils'
import fs from 'fs'
const serverCredentialsExists = !!process.env.POSTGRES_RECORDMANAGER_USER && !!process.env.POSTGRES_RECORDMANAGER_PASSWORD

View File

@ -10,3 +10,4 @@ export * from './speechToText'
export * from './storageUtils'
export * from './handler'
export * from './followUpPrompts'
export * from './validator'

View File

@ -77,7 +77,7 @@ export const addArrayFilesToStorage = async (mime: string, bf: Buffer, fileName:
fileNames.push(sanitizedFilename)
return 'FILE-STORAGE::' + JSON.stringify(fileNames)
} else {
const dir = path.join(getStoragePath(), ...paths)
const dir = path.join(getStoragePath(), ...paths.map(_sanitizeFilename))
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true })
}
@ -110,7 +110,7 @@ export const addSingleFileToStorage = async (mime: string, bf: Buffer, fileName:
await s3Client.send(putObjCmd)
return 'FILE-STORAGE::' + sanitizedFilename
} else {
const dir = path.join(getStoragePath(), ...paths)
const dir = path.join(getStoragePath(), ...paths.map(_sanitizeFilename))
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true })
}
@ -180,7 +180,7 @@ export const getFileFromStorage = async (file: string, ...paths: string[]): Prom
const buffer = Buffer.concat(response.Body.toArray())
return buffer
} else {
const fileInStorage = path.join(getStoragePath(), ...paths, sanitizedFilename)
const fileInStorage = path.join(getStoragePath(), ...paths.map(_sanitizeFilename), sanitizedFilename)
return fs.readFileSync(fileInStorage)
}
}
@ -209,7 +209,7 @@ export const removeFilesFromStorage = async (...paths: string[]) => {
}
await _deleteS3Folder(Key)
} else {
const directory = path.join(getStoragePath(), ...paths)
const directory = path.join(getStoragePath(), ...paths.map(_sanitizeFilename))
_deleteLocalFolderRecursive(directory)
}
}
@ -243,7 +243,7 @@ export const removeSpecificFileFromStorage = async (...paths: string[]) => {
const sanitizedFilename = _sanitizeFilename(fileName)
paths.push(sanitizedFilename)
}
const file = path.join(getStoragePath(), ...paths)
const file = path.join(getStoragePath(), ...paths.map(_sanitizeFilename))
fs.unlinkSync(file)
}
}
@ -258,7 +258,7 @@ export const removeFolderFromStorage = async (...paths: string[]) => {
}
await _deleteS3Folder(Key)
} else {
const directory = path.join(getStoragePath(), ...paths)
const directory = path.join(getStoragePath(), ...paths.map(_sanitizeFilename))
_deleteLocalFolderRecursive(directory, true)
}
}

View File

@ -0,0 +1,29 @@
/**
* Validates if a string is a valid UUID v4
* @param {string} uuid The string to validate
* @returns {boolean} True if valid UUID, false otherwise
*/
export const isValidUUID = (uuid: string): boolean => {
// UUID v4 regex pattern
const uuidV4Pattern = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i
return uuidV4Pattern.test(uuid)
}
/**
* Validates if a string contains path traversal attempts
* @param {string} path The string to validate
* @returns {boolean} True if path traversal detected, false otherwise
*/
export const isPathTraversal = (path: string): boolean => {
// Check for common path traversal patterns
const dangerousPatterns = [
'..', // Directory traversal
'/', // Root directory
'\\', // Windows root directory
'%2e', // URL encoded .
'%2f', // URL encoded /
'%5c' // URL encoded \
]
return dangerousPatterns.some((pattern) => path.toLowerCase().includes(pattern))
}

View File

@ -6,10 +6,15 @@ import {
IDocument,
mapExtToInputField,
mapMimeTypeToInputField,
removeSpecificFileFromUpload
removeSpecificFileFromUpload,
isValidUUID,
isPathTraversal
} from 'flowise-components'
import { getRunningExpressApp } from './getRunningExpressApp'
import { getErrorMessage } from '../errors/utils'
import { InternalFlowiseError } from '../errors/internalFlowiseError'
import { StatusCodes } from 'http-status-codes'
import { ChatFlow } from '../database/entities/ChatFlow'
/**
* Create attachment
@ -19,17 +24,26 @@ export const createFileAttachment = async (req: Request) => {
const appServer = getRunningExpressApp()
const chatflowid = req.params.chatflowId
if (!chatflowid) {
throw new Error(
'Params chatflowId is required! Please provide chatflowId and chatId in the URL: /api/v1/attachments/:chatflowId/:chatId'
)
if (!chatflowid || !isValidUUID(chatflowid)) {
throw new InternalFlowiseError(StatusCodes.BAD_REQUEST, 'Invalid chatflowId format - must be a valid UUID')
}
const chatId = req.params.chatId
if (!chatId) {
throw new Error(
'Params chatId is required! Please provide chatflowId and chatId in the URL: /api/v1/attachments/:chatflowId/:chatId'
)
if (!chatId || !isValidUUID(chatId)) {
throw new InternalFlowiseError(StatusCodes.BAD_REQUEST, 'Invalid chatId format - must be a valid UUID')
}
// Check for path traversal attempts
if (isPathTraversal(chatflowid) || isPathTraversal(chatId)) {
throw new InternalFlowiseError(StatusCodes.BAD_REQUEST, 'Invalid path characters detected')
}
// Validate chatflow exists and check API key
const chatflow = await appServer.AppDataSource.getRepository(ChatFlow).findOneBy({
id: chatflowid
})
if (!chatflow) {
throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `Chatflow ${chatflowid} not found`)
}
// Find FileLoader node