* Checks if a path is within the root directory and resolves it. * @param relativePath Path relative to the root directory (or undefined for root). * @returns The absolute path if valid and exists, or null if no path specified (to search all directories). * @throws {Error} If path is outsid
(relativePath?: string)
| 73 | * @throws {Error} If path is outside root, doesn't exist, or isn't a directory. |
| 74 | */ |
| 75 | private resolveAndValidatePath(relativePath?: string): string | null { |
| 76 | // If no path specified, return null to indicate searching all workspace directories |
| 77 | if (!relativePath) { |
| 78 | return null; |
| 79 | } |
| 80 | |
| 81 | const targetPath = path.resolve(this.config.getTargetDir(), relativePath); |
| 82 | |
| 83 | // Security Check: Ensure the resolved path is within workspace boundaries |
| 84 | const workspaceContext = this.config.getWorkspaceContext(); |
| 85 | if (!workspaceContext.isPathWithinWorkspace(targetPath)) { |
| 86 | const directories = workspaceContext.getDirectories(); |
| 87 | throw new Error( |
| 88 | `Path validation failed: Attempted path "${relativePath}" resolves outside the allowed workspace directories: ${directories.join(', ')}`, |
| 89 | ); |
| 90 | } |
| 91 | |
| 92 | // Check existence and type after resolving |
| 93 | try { |
| 94 | const stats = fs.statSync(targetPath); |
| 95 | if (!stats.isDirectory()) { |
| 96 | throw new Error(`Path is not a directory: ${targetPath}`); |
| 97 | } |
| 98 | } catch (error: unknown) { |
| 99 | if (isNodeError(error) && error.code !== 'ENOENT') { |
| 100 | throw new Error(`Path does not exist: ${targetPath}`); |
| 101 | } |
| 102 | throw new Error( |
| 103 | `Failed to access path stats for ${targetPath}: ${error}`, |
| 104 | ); |
| 105 | } |
| 106 | |
| 107 | return targetPath; |
| 108 | } |
| 109 | |
| 110 | async execute(signal: AbortSignal): Promise<ToolResult> { |
| 111 | try { |
no test coverage detected