(userProvidedPath: string | undefined)
| 210 | * @throws {Error} If path validation fails or path is outside allowed directories |
| 211 | */ |
| 212 | export const validateVectorStorePath = (userProvidedPath: string | undefined): string => { |
| 213 | if (process.env.PATH_TRAVERSAL_SAFETY === 'false') { |
| 214 | if (!userProvidedPath || userProvidedPath.trim() === '') { |
| 215 | return path.join(getUserHome(), '.flowise', 'vectorstore') |
| 216 | } |
| 217 | const bypassPath = userProvidedPath.trim() |
| 218 | return path.isAbsolute(bypassPath) ? bypassPath : path.resolve(path.join(getUserHome(), '.flowise', bypassPath)) |
| 219 | } |
| 220 | |
| 221 | // If no path provided, use default secure location |
| 222 | if (!userProvidedPath || userProvidedPath.trim() === '') { |
| 223 | return path.join(getUserHome(), '.flowise', 'vectorstore') |
| 224 | } |
| 225 | |
| 226 | const basePath = userProvidedPath.trim() |
| 227 | |
| 228 | // Check for explicit path traversal patterns (..) |
| 229 | if (basePath.includes('..')) { |
| 230 | throw new Error('Invalid path: path traversal attempt detected') |
| 231 | } |
| 232 | |
| 233 | // Check for URL-encoded path traversal |
| 234 | if (basePath.toLowerCase().includes('%2e') || basePath.toLowerCase().includes('%2f') || basePath.toLowerCase().includes('%5c')) { |
| 235 | throw new Error('Invalid path: encoded path traversal attempt detected') |
| 236 | } |
| 237 | |
| 238 | // Check for null bytes and control characters |
| 239 | if (/\0/.test(basePath) || /[\x00-\x1f]/.test(basePath)) { |
| 240 | throw new Error('Invalid path: null bytes or control characters detected') |
| 241 | } |
| 242 | |
| 243 | // Check for Windows-specific absolute paths and UNC paths (even on Unix systems) |
| 244 | // This prevents cross-platform attack vectors |
| 245 | if (/^[a-zA-Z]:\\/.test(basePath)) { |
| 246 | throw new Error('Invalid path: Windows absolute paths are not allowed') |
| 247 | } |
| 248 | if (/^\\\\[^\\]/.test(basePath)) { |
| 249 | throw new Error('Invalid path: UNC paths are not allowed') |
| 250 | } |
| 251 | if (/^\\\\\?\\/.test(basePath)) { |
| 252 | throw new Error('Invalid path: Extended-length paths are not allowed') |
| 253 | } |
| 254 | |
| 255 | // Resolve to absolute path |
| 256 | // If path is relative, resolve it relative to the .flowise directory (safe default) |
| 257 | // If path is already absolute, keep it as-is |
| 258 | let resolvedPath: string |
| 259 | if (path.isAbsolute(basePath)) { |
| 260 | resolvedPath = path.resolve(basePath) |
| 261 | } else { |
| 262 | // Relative paths are resolved within the .flowise directory for safety |
| 263 | resolvedPath = path.resolve(path.join(getUserHome(), '.flowise', basePath)) |
| 264 | } |
| 265 | |
| 266 | // Verify the resolved path doesn't contain '..' after resolution |
| 267 | if (resolvedPath.includes('..')) { |
| 268 | throw new Error('Invalid path: path traversal detected in resolved path') |
| 269 | } |
no test coverage detected