(baseDir: string, key: string)
| 402 | * @throws {Error} If key is missing/invalid or the resolved path escapes baseDir |
| 403 | */ |
| 404 | export const getSafeFilePath = (baseDir: string, key: string): string => { |
| 405 | if (!key || typeof key !== 'string') { |
| 406 | throw new Error('Invalid file path: key is required and must be a string') |
| 407 | } |
| 408 | |
| 409 | let decodedKey = key |
| 410 | try { |
| 411 | decodedKey = decodeURIComponent(key) |
| 412 | } catch { |
| 413 | // malformed percent-encoding — keep the raw key; resolve/relative handle it safely |
| 414 | } |
| 415 | |
| 416 | if (decodedKey.includes('\0')) { |
| 417 | throw new Error(`Invalid file path: null byte detected in "${key}"`) |
| 418 | } |
| 419 | |
| 420 | const resolvedBase = path.resolve(baseDir) |
| 421 | const resolvedPath = path.resolve(resolvedBase, decodedKey) |
| 422 | |
| 423 | if (process.env.PATH_TRAVERSAL_SAFETY === 'false') { |
| 424 | return resolvedPath |
| 425 | } |
| 426 | |
| 427 | const relative = path.relative(resolvedBase, resolvedPath) |
| 428 | if (relative === '' || relative === '..' || relative.startsWith('..' + path.sep) || path.isAbsolute(relative)) { |
| 429 | throw new Error(`Invalid file path: path traversal attempt detected in "${key}"`) |
| 430 | } |
| 431 | |
| 432 | return resolvedPath |
| 433 | } |
no outgoing calls
no test coverage detected