Skip to content

Commit 99bcbc5

Browse files
HenryHengZJYshayy
authored andcommitted
Bugfix/arbitrary create attachemnt file upload (FlowiseAI#4171)
fix arbitrary create attachemnt file upload
1 parent c794029 commit 99bcbc5

File tree

5 files changed

+59
-16
lines changed

5 files changed

+59
-16
lines changed

packages/components/nodes/recordmanager/PostgresRecordManager/PostgresRecordManager.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import { ListKeyOptions, RecordManagerInterface, UpdateOptions } from '@langchai
44
import { DataSource } from 'typeorm'
55
import { getHost, getSSL } from '../../vectorstores/Postgres/utils'
66
import { getDatabase, getPort, getTableName } from './utils'
7-
import fs from 'fs'
87

98
const serverCredentialsExists = !!process.env.POSTGRES_RECORDMANAGER_USER && !!process.env.POSTGRES_RECORDMANAGER_PASSWORD
109

packages/components/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ export * from './speechToText'
1010
export * from './storageUtils'
1111
export * from './handler'
1212
export * from './followUpPrompts'
13+
export * from './validator'

packages/components/src/storageUtils.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ export const addArrayFilesToStorage = async (mime: string, bf: Buffer, fileName:
7777
fileNames.push(sanitizedFilename)
7878
return 'FILE-STORAGE::' + JSON.stringify(fileNames)
7979
} else {
80-
const dir = path.join(getStoragePath(), ...paths)
80+
const dir = path.join(getStoragePath(), ...paths.map(_sanitizeFilename))
8181
if (!fs.existsSync(dir)) {
8282
fs.mkdirSync(dir, { recursive: true })
8383
}
@@ -110,7 +110,7 @@ export const addSingleFileToStorage = async (mime: string, bf: Buffer, fileName:
110110
await s3Client.send(putObjCmd)
111111
return 'FILE-STORAGE::' + sanitizedFilename
112112
} else {
113-
const dir = path.join(getStoragePath(), ...paths)
113+
const dir = path.join(getStoragePath(), ...paths.map(_sanitizeFilename))
114114
if (!fs.existsSync(dir)) {
115115
fs.mkdirSync(dir, { recursive: true })
116116
}
@@ -180,7 +180,7 @@ export const getFileFromStorage = async (file: string, ...paths: string[]): Prom
180180
const buffer = Buffer.concat(response.Body.toArray())
181181
return buffer
182182
} else {
183-
const fileInStorage = path.join(getStoragePath(), ...paths, sanitizedFilename)
183+
const fileInStorage = path.join(getStoragePath(), ...paths.map(_sanitizeFilename), sanitizedFilename)
184184
return fs.readFileSync(fileInStorage)
185185
}
186186
}
@@ -209,7 +209,7 @@ export const removeFilesFromStorage = async (...paths: string[]) => {
209209
}
210210
await _deleteS3Folder(Key)
211211
} else {
212-
const directory = path.join(getStoragePath(), ...paths)
212+
const directory = path.join(getStoragePath(), ...paths.map(_sanitizeFilename))
213213
_deleteLocalFolderRecursive(directory)
214214
}
215215
}
@@ -243,7 +243,7 @@ export const removeSpecificFileFromStorage = async (...paths: string[]) => {
243243
const sanitizedFilename = _sanitizeFilename(fileName)
244244
paths.push(sanitizedFilename)
245245
}
246-
const file = path.join(getStoragePath(), ...paths)
246+
const file = path.join(getStoragePath(), ...paths.map(_sanitizeFilename))
247247
fs.unlinkSync(file)
248248
}
249249
}
@@ -258,7 +258,7 @@ export const removeFolderFromStorage = async (...paths: string[]) => {
258258
}
259259
await _deleteS3Folder(Key)
260260
} else {
261-
const directory = path.join(getStoragePath(), ...paths)
261+
const directory = path.join(getStoragePath(), ...paths.map(_sanitizeFilename))
262262
_deleteLocalFolderRecursive(directory, true)
263263
}
264264
}

packages/components/src/validator.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/**
2+
* Validates if a string is a valid UUID v4
3+
* @param {string} uuid The string to validate
4+
* @returns {boolean} True if valid UUID, false otherwise
5+
*/
6+
export const isValidUUID = (uuid: string): boolean => {
7+
// UUID v4 regex pattern
8+
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
9+
return uuidV4Pattern.test(uuid)
10+
}
11+
12+
/**
13+
* Validates if a string contains path traversal attempts
14+
* @param {string} path The string to validate
15+
* @returns {boolean} True if path traversal detected, false otherwise
16+
*/
17+
export const isPathTraversal = (path: string): boolean => {
18+
// Check for common path traversal patterns
19+
const dangerousPatterns = [
20+
'..', // Directory traversal
21+
'/', // Root directory
22+
'\\', // Windows root directory
23+
'%2e', // URL encoded .
24+
'%2f', // URL encoded /
25+
'%5c' // URL encoded \
26+
]
27+
28+
return dangerousPatterns.some((pattern) => path.toLowerCase().includes(pattern))
29+
}

packages/server/src/utils/createAttachment.ts

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,15 @@ import {
66
IDocument,
77
mapExtToInputField,
88
mapMimeTypeToInputField,
9-
removeSpecificFileFromUpload
9+
removeSpecificFileFromUpload,
10+
isValidUUID,
11+
isPathTraversal
1012
} from 'flowise-components'
1113
import { getRunningExpressApp } from './getRunningExpressApp'
1214
import { getErrorMessage } from '../errors/utils'
15+
import { InternalFlowiseError } from '../errors/internalFlowiseError'
16+
import { StatusCodes } from 'http-status-codes'
17+
import { ChatFlow } from '../database/entities/ChatFlow'
1318

1419
/**
1520
* Create attachment
@@ -19,17 +24,26 @@ export const createFileAttachment = async (req: Request) => {
1924
const appServer = getRunningExpressApp()
2025

2126
const chatflowid = req.params.chatflowId
22-
if (!chatflowid) {
23-
throw new Error(
24-
'Params chatflowId is required! Please provide chatflowId and chatId in the URL: /api/v1/attachments/:chatflowId/:chatId'
25-
)
27+
if (!chatflowid || !isValidUUID(chatflowid)) {
28+
throw new InternalFlowiseError(StatusCodes.BAD_REQUEST, 'Invalid chatflowId format - must be a valid UUID')
2629
}
2730

2831
const chatId = req.params.chatId
29-
if (!chatId) {
30-
throw new Error(
31-
'Params chatId is required! Please provide chatflowId and chatId in the URL: /api/v1/attachments/:chatflowId/:chatId'
32-
)
32+
if (!chatId || !isValidUUID(chatId)) {
33+
throw new InternalFlowiseError(StatusCodes.BAD_REQUEST, 'Invalid chatId format - must be a valid UUID')
34+
}
35+
36+
// Check for path traversal attempts
37+
if (isPathTraversal(chatflowid) || isPathTraversal(chatId)) {
38+
throw new InternalFlowiseError(StatusCodes.BAD_REQUEST, 'Invalid path characters detected')
39+
}
40+
41+
// Validate chatflow exists and check API key
42+
const chatflow = await appServer.AppDataSource.getRepository(ChatFlow).findOneBy({
43+
id: chatflowid
44+
})
45+
if (!chatflow) {
46+
throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `Chatflow ${chatflowid} not found`)
3347
}
3448

3549
// Find FileLoader node

0 commit comments

Comments
 (0)