Bugfix/arbitrary create attachemnt file upload (#4171)
fix arbitrary create attachemnt file upload
This commit is contained in:
parent
c6968ff385
commit
c2b830f279
|
|
@ -4,7 +4,6 @@ import { ListKeyOptions, RecordManagerInterface, UpdateOptions } from '@langchai
|
||||||
import { DataSource } from 'typeorm'
|
import { DataSource } from 'typeorm'
|
||||||
import { getHost, getSSL } from '../../vectorstores/Postgres/utils'
|
import { getHost, getSSL } from '../../vectorstores/Postgres/utils'
|
||||||
import { getDatabase, getPort, getTableName } from './utils'
|
import { getDatabase, getPort, getTableName } from './utils'
|
||||||
import fs from 'fs'
|
|
||||||
|
|
||||||
const serverCredentialsExists = !!process.env.POSTGRES_RECORDMANAGER_USER && !!process.env.POSTGRES_RECORDMANAGER_PASSWORD
|
const serverCredentialsExists = !!process.env.POSTGRES_RECORDMANAGER_USER && !!process.env.POSTGRES_RECORDMANAGER_PASSWORD
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,3 +10,4 @@ export * from './speechToText'
|
||||||
export * from './storageUtils'
|
export * from './storageUtils'
|
||||||
export * from './handler'
|
export * from './handler'
|
||||||
export * from './followUpPrompts'
|
export * from './followUpPrompts'
|
||||||
|
export * from './validator'
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,7 @@ export const addArrayFilesToStorage = async (mime: string, bf: Buffer, fileName:
|
||||||
fileNames.push(sanitizedFilename)
|
fileNames.push(sanitizedFilename)
|
||||||
return 'FILE-STORAGE::' + JSON.stringify(fileNames)
|
return 'FILE-STORAGE::' + JSON.stringify(fileNames)
|
||||||
} else {
|
} else {
|
||||||
const dir = path.join(getStoragePath(), ...paths)
|
const dir = path.join(getStoragePath(), ...paths.map(_sanitizeFilename))
|
||||||
if (!fs.existsSync(dir)) {
|
if (!fs.existsSync(dir)) {
|
||||||
fs.mkdirSync(dir, { recursive: true })
|
fs.mkdirSync(dir, { recursive: true })
|
||||||
}
|
}
|
||||||
|
|
@ -110,7 +110,7 @@ export const addSingleFileToStorage = async (mime: string, bf: Buffer, fileName:
|
||||||
await s3Client.send(putObjCmd)
|
await s3Client.send(putObjCmd)
|
||||||
return 'FILE-STORAGE::' + sanitizedFilename
|
return 'FILE-STORAGE::' + sanitizedFilename
|
||||||
} else {
|
} else {
|
||||||
const dir = path.join(getStoragePath(), ...paths)
|
const dir = path.join(getStoragePath(), ...paths.map(_sanitizeFilename))
|
||||||
if (!fs.existsSync(dir)) {
|
if (!fs.existsSync(dir)) {
|
||||||
fs.mkdirSync(dir, { recursive: true })
|
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())
|
const buffer = Buffer.concat(response.Body.toArray())
|
||||||
return buffer
|
return buffer
|
||||||
} else {
|
} else {
|
||||||
const fileInStorage = path.join(getStoragePath(), ...paths, sanitizedFilename)
|
const fileInStorage = path.join(getStoragePath(), ...paths.map(_sanitizeFilename), sanitizedFilename)
|
||||||
return fs.readFileSync(fileInStorage)
|
return fs.readFileSync(fileInStorage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -209,7 +209,7 @@ export const removeFilesFromStorage = async (...paths: string[]) => {
|
||||||
}
|
}
|
||||||
await _deleteS3Folder(Key)
|
await _deleteS3Folder(Key)
|
||||||
} else {
|
} else {
|
||||||
const directory = path.join(getStoragePath(), ...paths)
|
const directory = path.join(getStoragePath(), ...paths.map(_sanitizeFilename))
|
||||||
_deleteLocalFolderRecursive(directory)
|
_deleteLocalFolderRecursive(directory)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -243,7 +243,7 @@ export const removeSpecificFileFromStorage = async (...paths: string[]) => {
|
||||||
const sanitizedFilename = _sanitizeFilename(fileName)
|
const sanitizedFilename = _sanitizeFilename(fileName)
|
||||||
paths.push(sanitizedFilename)
|
paths.push(sanitizedFilename)
|
||||||
}
|
}
|
||||||
const file = path.join(getStoragePath(), ...paths)
|
const file = path.join(getStoragePath(), ...paths.map(_sanitizeFilename))
|
||||||
fs.unlinkSync(file)
|
fs.unlinkSync(file)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -258,7 +258,7 @@ export const removeFolderFromStorage = async (...paths: string[]) => {
|
||||||
}
|
}
|
||||||
await _deleteS3Folder(Key)
|
await _deleteS3Folder(Key)
|
||||||
} else {
|
} else {
|
||||||
const directory = path.join(getStoragePath(), ...paths)
|
const directory = path.join(getStoragePath(), ...paths.map(_sanitizeFilename))
|
||||||
_deleteLocalFolderRecursive(directory, true)
|
_deleteLocalFolderRecursive(directory, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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))
|
||||||
|
}
|
||||||
|
|
@ -6,10 +6,15 @@ import {
|
||||||
IDocument,
|
IDocument,
|
||||||
mapExtToInputField,
|
mapExtToInputField,
|
||||||
mapMimeTypeToInputField,
|
mapMimeTypeToInputField,
|
||||||
removeSpecificFileFromUpload
|
removeSpecificFileFromUpload,
|
||||||
|
isValidUUID,
|
||||||
|
isPathTraversal
|
||||||
} from 'flowise-components'
|
} from 'flowise-components'
|
||||||
import { getRunningExpressApp } from './getRunningExpressApp'
|
import { getRunningExpressApp } from './getRunningExpressApp'
|
||||||
import { getErrorMessage } from '../errors/utils'
|
import { getErrorMessage } from '../errors/utils'
|
||||||
|
import { InternalFlowiseError } from '../errors/internalFlowiseError'
|
||||||
|
import { StatusCodes } from 'http-status-codes'
|
||||||
|
import { ChatFlow } from '../database/entities/ChatFlow'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create attachment
|
* Create attachment
|
||||||
|
|
@ -19,17 +24,26 @@ export const createFileAttachment = async (req: Request) => {
|
||||||
const appServer = getRunningExpressApp()
|
const appServer = getRunningExpressApp()
|
||||||
|
|
||||||
const chatflowid = req.params.chatflowId
|
const chatflowid = req.params.chatflowId
|
||||||
if (!chatflowid) {
|
if (!chatflowid || !isValidUUID(chatflowid)) {
|
||||||
throw new Error(
|
throw new InternalFlowiseError(StatusCodes.BAD_REQUEST, 'Invalid chatflowId format - must be a valid UUID')
|
||||||
'Params chatflowId is required! Please provide chatflowId and chatId in the URL: /api/v1/attachments/:chatflowId/:chatId'
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const chatId = req.params.chatId
|
const chatId = req.params.chatId
|
||||||
if (!chatId) {
|
if (!chatId || !isValidUUID(chatId)) {
|
||||||
throw new Error(
|
throw new InternalFlowiseError(StatusCodes.BAD_REQUEST, 'Invalid chatId format - must be a valid UUID')
|
||||||
'Params chatId is required! Please provide chatflowId and chatId in the URL: /api/v1/attachments/:chatflowId/:chatId'
|
}
|
||||||
)
|
|
||||||
|
// 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
|
// Find FileLoader node
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue