(path: string, workingPath: string)
| 707 | } |
| 708 | |
| 709 | export function pathInWorkingPath(path: string, workingPath: string): boolean { |
| 710 | const absolutePath = expandPath(path) |
| 711 | const absoluteWorkingPath = expandPath(workingPath) |
| 712 | |
| 713 | // On macOS, handle common symlink issues: |
| 714 | // - /var -> /private/var |
| 715 | // - /tmp -> /private/tmp |
| 716 | const normalizedPath = absolutePath |
| 717 | .replace(/^\/private\/var\//, '/var/') |
| 718 | .replace(/^\/private\/tmp(\/|$)/, '/tmp$1') |
| 719 | const normalizedWorkingPath = absoluteWorkingPath |
| 720 | .replace(/^\/private\/var\//, '/var/') |
| 721 | .replace(/^\/private\/tmp(\/|$)/, '/tmp$1') |
| 722 | |
| 723 | // Normalize case for case-insensitive comparison to prevent bypassing security |
| 724 | // checks on case-insensitive filesystems (macOS/Windows) like .cLauDe/CoMmAnDs |
| 725 | const caseNormalizedPath = normalizeCaseForComparison(normalizedPath) |
| 726 | const caseNormalizedWorkingPath = normalizeCaseForComparison( |
| 727 | normalizedWorkingPath, |
| 728 | ) |
| 729 | |
| 730 | // Use cross-platform relative path helper |
| 731 | const relative = relativePath(caseNormalizedWorkingPath, caseNormalizedPath) |
| 732 | |
| 733 | // Same path |
| 734 | if (relative === '') { |
| 735 | return true |
| 736 | } |
| 737 | |
| 738 | if (containsPathTraversal(relative)) { |
| 739 | return false |
| 740 | } |
| 741 | |
| 742 | // Path is inside (relative path that doesn't go up) |
| 743 | return !posix.isAbsolute(relative) |
| 744 | } |
| 745 | |
| 746 | function rootPathForSource(source: PermissionRuleSource): string { |
| 747 | switch (source) { |
no test coverage detected