(
tool: Tool,
input: { [key: string]: unknown },
toolPermissionContext: ToolPermissionContext,
)
| 1028 | * Permission result for read permission for the specified tool & tool input |
| 1029 | */ |
| 1030 | export function checkReadPermissionForTool( |
| 1031 | tool: Tool, |
| 1032 | input: { [key: string]: unknown }, |
| 1033 | toolPermissionContext: ToolPermissionContext, |
| 1034 | ): PermissionDecision { |
| 1035 | if (typeof tool.getPath !== 'function') { |
| 1036 | return { |
| 1037 | behavior: 'ask', |
| 1038 | message: `Claude requested permissions to use ${tool.name}, but you haven't granted it yet.`, |
| 1039 | } |
| 1040 | } |
| 1041 | const path = tool.getPath(input) |
| 1042 | |
| 1043 | // Get paths to check (includes both original and resolved symlinks). |
| 1044 | // Computed once here and threaded through checkWritePermissionForTool → |
| 1045 | // checkPathSafetyForAutoEdit → pathInAllowedWorkingPath to avoid redundant |
| 1046 | // existsSync/lstatSync/realpathSync syscalls on the same path (previously |
| 1047 | // 6× = 30 syscalls per Read permission check). |
| 1048 | const pathsToCheck = getPathsForPermissionCheck(path) |
| 1049 | |
| 1050 | // 1. Defense-in-depth: Block UNC paths early (before other checks) |
| 1051 | // This catches paths starting with \\ or // that could access network resources |
| 1052 | // This may catch some UNC patterns not detected by containsVulnerableUncPath |
| 1053 | for (const pathToCheck of pathsToCheck) { |
| 1054 | if (pathToCheck.startsWith('\\\\') || pathToCheck.startsWith('//')) { |
| 1055 | return { |
| 1056 | behavior: 'ask', |
| 1057 | message: `Claude requested permissions to read from ${path}, which appears to be a UNC path that could access network resources.`, |
| 1058 | decisionReason: { |
| 1059 | type: 'other', |
| 1060 | reason: 'UNC path detected (defense-in-depth check)', |
| 1061 | }, |
| 1062 | } |
| 1063 | } |
| 1064 | } |
| 1065 | |
| 1066 | // 2. Check for suspicious Windows path patterns (defense in depth) |
| 1067 | for (const pathToCheck of pathsToCheck) { |
| 1068 | if (hasSuspiciousWindowsPathPattern(pathToCheck)) { |
| 1069 | return { |
| 1070 | behavior: 'ask', |
| 1071 | message: `Claude requested permissions to read from ${path}, which contains a suspicious Windows path pattern that requires manual approval.`, |
| 1072 | decisionReason: { |
| 1073 | type: 'other', |
| 1074 | reason: |
| 1075 | 'Path contains suspicious Windows-specific patterns (alternate data streams, short names, long path prefixes, or three or more consecutive dots) that require manual verification', |
| 1076 | }, |
| 1077 | } |
| 1078 | } |
| 1079 | } |
| 1080 | |
| 1081 | // 3. Check for READ-SPECIFIC deny rules first - check both the original path and resolved symlink path |
| 1082 | // SECURITY: This must come before any allow checks (including "edit access implies read access") |
| 1083 | // to prevent bypassing explicit read deny rules |
| 1084 | for (const pathToCheck of pathsToCheck) { |
| 1085 | const denyRule = matchingRuleForInput( |
| 1086 | pathToCheck, |
| 1087 | toolPermissionContext, |
no test coverage detected