(baseDir: string, key: string)
| 442 | * @throws {Error} If key is missing/invalid or the resolved path escapes baseDir |
| 443 | */ |
| 444 | export const getSafeFilePath = (baseDir: string, key: string): string => { |
| 445 | if (!key || typeof key !== 'string') { |
| 446 | throw new Error('Invalid file path: key is required and must be a string') |
| 447 | } |
| 448 | |
| 449 | let decodedKey = key |
| 450 | try { |
| 451 | decodedKey = decodeURIComponent(key) |
| 452 | } catch { |
| 453 | // malformed percent-encoding — keep the raw key; resolve/relative handle it safely |
| 454 | } |
| 455 | |
| 456 | if (decodedKey.includes('\0')) { |
| 457 | throw new Error(`Invalid file path: null byte detected in "${key}"`) |
| 458 | } |
| 459 | |
| 460 | const resolvedBase = path.resolve(baseDir) |
| 461 | const resolvedPath = path.resolve(resolvedBase, decodedKey) |
| 462 | |
| 463 | if (process.env.PATH_TRAVERSAL_SAFETY === 'false') { |
| 464 | return resolvedPath |
| 465 | } |
| 466 | |
| 467 | const relative = path.relative(resolvedBase, resolvedPath) |
| 468 | if (relative === '' || relative === '..' || relative.startsWith('..' + path.sep) || path.isAbsolute(relative)) { |
| 469 | throw new Error(`Invalid file path: path traversal attempt detected in "${key}"`) |
| 470 | } |
| 471 | |
| 472 | return resolvedPath |
| 473 | } |
no outgoing calls
no test coverage detected