| |
| |
| |
| |
| |
|
|
| import * as fs from 'node:fs'; |
| import * as path from 'node:path'; |
| import { resolveToRealPath, isSubpath } from '@google/gemini-cli-core'; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| export async function validateWorkspacePath( |
| workspacePath?: string, |
| allowedRoot: string = process.cwd(), |
| ): Promise<string> { |
| const trimmedPath = workspacePath?.trim(); |
| if (!trimmedPath) { |
| return resolveToRealPath(allowedRoot); |
| } |
|
|
| if (trimmedPath.includes('\0')) { |
| throw new Error('Security violation: Null byte detected in path.'); |
| } |
|
|
| try { |
| const canonicalAllowedRoot = resolveToRealPath(allowedRoot); |
| const resolvedWorkspacePath = path.resolve( |
| canonicalAllowedRoot, |
| trimmedPath, |
| ); |
| const canonicalWorkspacePath = resolveToRealPath(resolvedWorkspacePath); |
|
|
| |
| if ( |
| canonicalWorkspacePath !== canonicalAllowedRoot && |
| !isSubpath(canonicalAllowedRoot, canonicalWorkspacePath) |
| ) { |
| throw new Error( |
| `Security violation: The path "${trimmedPath}" is outside the allowed root directory.`, |
| ); |
| } |
|
|
| const stats = await fs.promises.stat(canonicalWorkspacePath); |
| if (!stats.isDirectory()) { |
| throw new Error(`The path "${trimmedPath}" is not a directory.`); |
| } |
|
|
| return canonicalWorkspacePath; |
| } catch (e) { |
| if (e instanceof Error && 'code' in e && e.code === 'ENOENT') { |
| throw new Error(`The path "${trimmedPath}" does not exist.`); |
| } |
| throw e; |
| } |
| } |
|
|