(base: string, resolved: string)
| 24 | * from there without a backwards edge. |
| 25 | */ |
| 26 | export function isSafePath(base: string, resolved: string): boolean { |
| 27 | let baseReal: string; |
| 28 | try { |
| 29 | baseReal = realpathSync(resolve(base)); |
| 30 | } catch { |
| 31 | // Base must exist and be resolvable; fail closed if not. |
| 32 | return false; |
| 33 | } |
| 34 | |
| 35 | const target = resolve(resolved); |
| 36 | const trailing: string[] = []; |
| 37 | let probe = target; |
| 38 | |
| 39 | for (;;) { |
| 40 | let ancestorReal: string; |
| 41 | try { |
| 42 | ancestorReal = realpathSync(probe); |
| 43 | } catch { |
| 44 | const parent = dirname(probe); |
| 45 | if (parent === probe) return false; // walked past the filesystem root |
| 46 | trailing.push(basename(probe)); |
| 47 | probe = parent; |
| 48 | continue; |
| 49 | } |
| 50 | |
| 51 | // Copy before reverse(): the array is only consumed once today, but a future |
| 52 | // edit that loops would otherwise silently misorder the rebuilt segments. |
| 53 | const targetReal = trailing.length |
| 54 | ? join(ancestorReal, ...[...trailing].reverse()) |
| 55 | : ancestorReal; |
| 56 | return targetReal === baseReal || targetReal.startsWith(baseReal + sep); |
| 57 | } |
| 58 | } |
| 59 | |
| 60 | /** |
| 61 | * Resolve `relativePath` against `base` and return the absolute path only if it |
no test coverage detected