Skip to content

Commit

Permalink
Bugfix: Check for relative path when saving file, to prevent unauthor…
Browse files Browse the repository at this point in the history
…ised writes (#3172)

* Check for relative path when saving file, to prevent unauthorised writes

* preventing relative paths for all modes (s3/local)

* preventing relative paths for all modes (s3/local)

* Update storageUtils.ts

* changing the code to sanitizing filenames.

* fix lock file

---------

Co-authored-by: Henry Heng <henryheng@flowiseai.com>
Co-authored-by: Henry <hzj94@hotmail.com>
  • Loading branch information
3 people authored Sep 14, 2024
1 parent 0420ff2 commit 8bd3de4
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 16 deletions.
1 change: 1 addition & 0 deletions packages/components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@
"pyodide": ">=0.21.0-alpha.2",
"redis": "^4.6.7",
"replicate": "^0.31.1",
"sanitize-filename": "^1.6.3",
"socket.io": "^4.6.1",
"srt-parser-2": "^1.2.3",
"typeorm": "^0.3.6",
Expand Down
53 changes: 37 additions & 16 deletions packages/components/src/storageUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
} from '@aws-sdk/client-s3'
import { Readable } from 'node:stream'
import { getUserHome } from './utils'
import sanitize from 'sanitize-filename'

export const addBase64FilesToStorage = async (fileBase64: string, chatflowid: string, fileNames: string[]) => {
const storageType = getStorageType()
Expand All @@ -21,7 +22,9 @@ export const addBase64FilesToStorage = async (fileBase64: string, chatflowid: st
const bf = Buffer.from(splitDataURI.pop() || '', 'base64')
const mime = splitDataURI[0].split(':')[1].split(';')[0]

const Key = chatflowid + '/' + filename
const sanitizedFilename = _sanitizeFilename(filename)

const Key = chatflowid + '/' + sanitizedFilename
const putObjCmd = new PutObjectCommand({
Bucket,
Key,
Expand All @@ -31,7 +34,7 @@ export const addBase64FilesToStorage = async (fileBase64: string, chatflowid: st
})
await s3Client.send(putObjCmd)

fileNames.push(filename)
fileNames.push(sanitizedFilename)
return 'FILE-STORAGE::' + JSON.stringify(fileNames)
} else {
const dir = path.join(getStoragePath(), chatflowid)
Expand All @@ -42,20 +45,23 @@ export const addBase64FilesToStorage = async (fileBase64: string, chatflowid: st
const splitDataURI = fileBase64.split(',')
const filename = splitDataURI.pop()?.split(':')[1] ?? ''
const bf = Buffer.from(splitDataURI.pop() || '', 'base64')
const sanitizedFilename = _sanitizeFilename(filename)

const filePath = path.join(dir, filename)
const filePath = path.join(dir, sanitizedFilename)
fs.writeFileSync(filePath, bf)
fileNames.push(filename)
fileNames.push(sanitizedFilename)
return 'FILE-STORAGE::' + JSON.stringify(fileNames)
}
}

export const addArrayFilesToStorage = async (mime: string, bf: Buffer, fileName: string, fileNames: string[], ...paths: string[]) => {
const storageType = getStorageType()

const sanitizedFilename = _sanitizeFilename(fileName)
if (storageType === 's3') {
const { s3Client, Bucket } = getS3Config()

let Key = paths.reduce((acc, cur) => acc + '/' + cur, '') + '/' + fileName
let Key = paths.reduce((acc, cur) => acc + '/' + cur, '') + '/' + sanitizedFilename
if (Key.startsWith('/')) {
Key = Key.substring(1)
}
Expand All @@ -68,27 +74,28 @@ export const addArrayFilesToStorage = async (mime: string, bf: Buffer, fileName:
Body: bf
})
await s3Client.send(putObjCmd)
fileNames.push(fileName)
fileNames.push(sanitizedFilename)
return 'FILE-STORAGE::' + JSON.stringify(fileNames)
} else {
const dir = path.join(getStoragePath(), ...paths)
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true })
}

const filePath = path.join(dir, fileName)
const filePath = path.join(dir, sanitizedFilename)
fs.writeFileSync(filePath, bf)
fileNames.push(fileName)
fileNames.push(sanitizedFilename)
return 'FILE-STORAGE::' + JSON.stringify(fileNames)
}
}

export const addSingleFileToStorage = async (mime: string, bf: Buffer, fileName: string, ...paths: string[]) => {
const storageType = getStorageType()
const sanitizedFilename = _sanitizeFilename(fileName)

if (storageType === 's3') {
const { s3Client, Bucket } = getS3Config()

let Key = paths.reduce((acc, cur) => acc + '/' + cur, '') + '/' + fileName
let Key = paths.reduce((acc, cur) => acc + '/' + cur, '') + '/' + sanitizedFilename
if (Key.startsWith('/')) {
Key = Key.substring(1)
}
Expand All @@ -101,16 +108,15 @@ export const addSingleFileToStorage = async (mime: string, bf: Buffer, fileName:
Body: bf
})
await s3Client.send(putObjCmd)
return 'FILE-STORAGE::' + fileName
return 'FILE-STORAGE::' + sanitizedFilename
} else {
const dir = path.join(getStoragePath(), ...paths)
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true })
}

const filePath = path.join(dir, fileName)
const filePath = path.join(dir, sanitizedFilename)
fs.writeFileSync(filePath, bf)
return 'FILE-STORAGE::' + fileName
return 'FILE-STORAGE::' + sanitizedFilename
}
}

Expand Down Expand Up @@ -185,6 +191,11 @@ export const removeSpecificFileFromStorage = async (...paths: string[]) => {
}
await _deleteS3Folder(Key)
} else {
const fileName = paths.pop()
if (fileName) {
const sanitizedFilename = _sanitizeFilename(fileName)
paths.push(sanitizedFilename)
}
const file = path.join(getStoragePath(), ...paths)
fs.unlinkSync(file)
}
Expand Down Expand Up @@ -282,10 +293,11 @@ export const streamStorageFile = async (
fileName: string
): Promise<fs.ReadStream | Buffer | undefined> => {
const storageType = getStorageType()
const sanitizedFilename = sanitize(fileName)
if (storageType === 's3') {
const { s3Client, Bucket } = getS3Config()

const Key = chatflowId + '/' + chatId + '/' + fileName
const Key = chatflowId + '/' + chatId + '/' + sanitizedFilename
const getParams = {
Bucket,
Key
Expand All @@ -297,7 +309,7 @@ export const streamStorageFile = async (
return Buffer.from(blob)
}
} else {
const filePath = path.join(getStoragePath(), chatflowId, chatId, fileName)
const filePath = path.join(getStoragePath(), chatflowId, chatId, sanitizedFilename)
//raise error if file path is not absolute
if (!path.isAbsolute(filePath)) throw new Error(`Invalid file path`)
//raise error if file path contains '..'
Expand Down Expand Up @@ -339,3 +351,12 @@ export const getS3Config = () => {
})
return { s3Client, Bucket }
}

const _sanitizeFilename = (filename: string): string => {
if (filename) {
let sanitizedFilename = sanitize(filename)
// remove all leading .
return sanitizedFilename.replace(/^\.+/, '')
}
return ''
}
22 changes: 22 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 8bd3de4

Please sign in to comment.