Skip to content

Bugfix/arbitrary create attachemnt file upload #4171

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions packages/components/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ export * from './speechToText'
export * from './storageUtils'
export * from './handler'
export * from './followUpPrompts'
export * from './validator'
12 changes: 6 additions & 6 deletions packages/components/src/storageUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 })
}
Expand Down Expand Up @@ -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 })
}
Expand Down Expand Up @@ -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)
}
}
Expand Down Expand Up @@ -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)
}
}
Expand Down Expand Up @@ -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)
}
}
Expand All @@ -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)
}
}
Expand Down
29 changes: 29 additions & 0 deletions packages/components/src/validator.ts
Original file line number Diff line number Diff line change
@@ -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))
}
32 changes: 23 additions & 9 deletions packages/server/src/utils/createAttachment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down