Download packages/core/src/sandbox/windows/commandSafety.ts from SaylorTwift/gemini-cli: direct link, hf CLI and curl.
- Browser
- Download file 8.65 kB
-
https://huggingface.co/SaylorTwift/gemini-cli/resolve/main/packages/core/src/sandbox/windows/commandSafety.ts
- Command line
-
hf download hf://SaylorTwift/gemini-cli/packages/core/src/sandbox/windows/commandSafety.ts
-
curl -L -o commandSafety.ts https://huggingface.co/SaylorTwift/gemini-cli/resolve/main/packages/core/src/sandbox/windows/commandSafety.ts
8.65 kB
| /** | |
| * @license | |
| * Copyright 2026 Google LLC | |
| * SPDX-License-Identifier: Apache-2.0 | |
| */ | |
| import fs from 'node:fs'; | |
| import os from 'node:os'; | |
| import path from 'node:path'; | |
| import { parse as shellParse } from 'shell-quote'; | |
| import { | |
| extractStringFromParseEntry, | |
| initializeShellParsers, | |
| splitCommands, | |
| stripShellWrapper, | |
| } from '../../utils/shell-utils.js'; | |
| import { isSubpath, resolveToRealPath } from '../../utils/paths.js'; | |
| /** | |
| * Determines if a command is strictly approved for execution on Windows. | |
| * A command is approved if it's composed entirely of tools explicitly listed in `approvedTools` | |
| * OR if it's composed of known safe, read-only Windows commands. | |
| * | |
| * @param command - The full command string to execute. | |
| * @param args - The arguments for the command. | |
| * @param approvedTools - A list of explicitly approved tool names (e.g., ['npm', 'git']). | |
| * @param cwd - Optional working directory | |
| * @param workspaceRoot - Optional workspace root directory | |
| * @returns true if the command is strictly approved, false otherwise. | |
| */ | |
| export async function isStrictlyApproved( | |
| command: string, | |
| args: string[], | |
| approvedTools?: string[], | |
| cwd?: string, | |
| workspaceRoot?: string, | |
| ): Promise<boolean> { | |
| const tools = approvedTools ?? []; | |
| await initializeShellParsers(); | |
| const fullCmd = [command, ...args].join(' '); | |
| const stripped = stripShellWrapper(fullCmd); | |
| const pipelineCommands = splitCommands(stripped); | |
| // Fallback for simple commands or parsing failures | |
| if (pipelineCommands.length === 0) { | |
| return ( | |
| tools.includes(command) || | |
| isKnownSafeCommand([command, ...args], cwd, workspaceRoot) | |
| ); | |
| } | |
| // Check every segment of the pipeline | |
| return pipelineCommands.every((cmdString) => { | |
| const trimmed = cmdString.trim(); | |
| if (!trimmed) return true; | |
| const parsedArgs = shellParse(trimmed).map(extractStringFromParseEntry); | |
| if (parsedArgs.length === 0) return true; | |
| let root = parsedArgs[0].toLowerCase(); | |
| if (root.endsWith('.exe')) { | |
| root = root.slice(0, -4); | |
| } | |
| // The segment is approved if the root tool is in the allowlist OR if the whole segment is safe. | |
| return ( | |
| tools.some((t) => t.toLowerCase() === root) || | |
| isKnownSafeCommand(parsedArgs, cwd, workspaceRoot) | |
| ); | |
| }); | |
| } | |
| function isPathEscapingWorkspace( | |
| arg: string, | |
| workspaceRoot: string, | |
| cwd: string, | |
| ): boolean { | |
| if (!arg || typeof arg !== 'string') return false; | |
| if (arg.includes('$') || arg.includes('`') || arg.includes('%')) { | |
| return true; | |
| } | |
| let target: string; | |
| if (arg === '~' || arg.startsWith('~/') || arg.startsWith('~\\')) { | |
| const homeDir = os.homedir(); | |
| target = path.win32.resolve(homeDir, arg.slice(2)); | |
| if (!isSubpath(workspaceRoot, target)) { | |
| return true; | |
| } | |
| } else if (arg.startsWith('~')) { | |
| return true; | |
| } else if (path.win32.isAbsolute(arg) || path.isAbsolute(arg)) { | |
| target = path.win32.resolve(arg); | |
| if (!isSubpath(workspaceRoot, target)) { | |
| return true; | |
| } | |
| } else { | |
| target = path.win32.resolve(cwd, arg); | |
| if (!isSubpath(workspaceRoot, target)) { | |
| return true; | |
| } | |
| } | |
| let curr = target; | |
| while ( | |
| curr && | |
| isSubpath(workspaceRoot, curr) && | |
| curr !== path.win32.dirname(curr) | |
| ) { | |
| try { | |
| const stat = fs.lstatSync(curr, { throwIfNoEntry: false }); | |
| if (stat?.isSymbolicLink()) { | |
| const real = fs.realpathSync(curr); | |
| if (!isSubpath(workspaceRoot, real)) { | |
| return true; | |
| } | |
| } | |
| } catch { | |
| // ignore | |
| } | |
| curr = path.win32.dirname(curr); | |
| } | |
| return false; | |
| } | |
| /** | |
| * Checks if a Windows command is known to be safe (read-only). | |
| */ | |
| export function isKnownSafeCommand( | |
| args: string[], | |
| cwd?: string, | |
| workspaceRoot?: string, | |
| ): boolean { | |
| if (!args || args.length === 0) return false; | |
| let cmd = args[0].toLowerCase(); | |
| if (cmd.endsWith('.exe')) { | |
| cmd = cmd.slice(0, -4); | |
| } | |
| let effectiveWorkspace = workspaceRoot | |
| ? path.win32.isAbsolute(workspaceRoot) | |
| ? path.win32.resolve(workspaceRoot) | |
| : path.resolve(workspaceRoot) | |
| : cwd | |
| ? path.win32.isAbsolute(cwd) | |
| ? path.win32.resolve(cwd) | |
| : path.resolve(cwd) | |
| : process.cwd(); | |
| try { | |
| effectiveWorkspace = resolveToRealPath(effectiveWorkspace); | |
| } catch { | |
| // Keep resolved path on failure | |
| } | |
| let effectiveCwd = cwd | |
| ? path.win32.isAbsolute(cwd) | |
| ? path.win32.resolve(cwd) | |
| : path.resolve(cwd) | |
| : effectiveWorkspace; | |
| try { | |
| effectiveCwd = resolveToRealPath(effectiveCwd); | |
| } catch { | |
| // Keep resolved path on failure | |
| } | |
| if ( | |
| effectiveCwd !== effectiveWorkspace && | |
| !isSubpath(effectiveWorkspace, effectiveCwd) | |
| ) { | |
| return false; | |
| } | |
| // Native Windows/PowerShell safe commands | |
| const safeCommands = new Set([ | |
| '__read', | |
| '__write', | |
| 'dir', | |
| 'type', | |
| 'echo', | |
| 'cd', | |
| 'pwd', | |
| 'whoami', | |
| 'hostname', | |
| 'ver', | |
| 'vol', | |
| 'systeminfo', | |
| 'attrib', | |
| 'findstr', | |
| 'where', | |
| 'sort', | |
| 'more', | |
| 'get-childitem', | |
| 'get-content', | |
| 'get-location', | |
| 'get-help', | |
| 'get-process', | |
| 'get-service', | |
| 'get-eventlog', | |
| 'select-string', | |
| ]); | |
| if (safeCommands.has(cmd)) { | |
| if (cmd === 'cd') { | |
| let hasPath = false; | |
| for (let i = 1; i < args.length; i++) { | |
| const arg = args[i]; | |
| const isSwitch = | |
| arg.startsWith('-') || | |
| (arg.startsWith('/') && /^\/[a-zA-Z0-9?]{1,4}(?::.*)?$/.test(arg)); | |
| if (isSwitch) { | |
| continue; | |
| } | |
| hasPath = true; | |
| if (isPathEscapingWorkspace(arg, effectiveWorkspace, effectiveCwd)) { | |
| return false; | |
| } | |
| } | |
| if (!hasPath) { | |
| if (isPathEscapingWorkspace('~', effectiveWorkspace, effectiveCwd)) { | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| const fileReadingCommands = new Set([ | |
| 'dir', | |
| 'type', | |
| 'attrib', | |
| 'more', | |
| 'findstr', | |
| 'get-childitem', | |
| 'get-content', | |
| 'select-string', | |
| '__read', | |
| 'sort', | |
| ]); | |
| if (fileReadingCommands.has(cmd)) { | |
| let passedDoubleDash = false; | |
| for (let i = 1; i < args.length; i++) { | |
| const arg = args[i]; | |
| if (!passedDoubleDash) { | |
| if (arg === '--') { | |
| passedDoubleDash = true; | |
| continue; | |
| } | |
| const isSwitch = | |
| arg.startsWith('-') || | |
| (arg.startsWith('/') && /^\/[a-zA-Z0-9?]{1,4}(?::.*)?$/.test(arg)); | |
| if (isSwitch) { | |
| const sepIdx = | |
| arg.indexOf('=') !== -1 ? arg.indexOf('=') : arg.indexOf(':'); | |
| if (sepIdx !== -1) { | |
| const val = arg.slice(sepIdx + 1); | |
| if ( | |
| isPathEscapingWorkspace(val, effectiveWorkspace, effectiveCwd) | |
| ) { | |
| return false; | |
| } | |
| continue; | |
| } | |
| if (arg.startsWith('-')) { | |
| try { | |
| const stat = fs.lstatSync( | |
| path.win32.resolve(effectiveCwd, arg), | |
| { | |
| throwIfNoEntry: false, | |
| }, | |
| ); | |
| if (!stat) { | |
| continue; | |
| } | |
| } catch { | |
| continue; | |
| } | |
| } else { | |
| continue; | |
| } | |
| } | |
| } | |
| if (isPathEscapingWorkspace(arg, effectiveWorkspace, effectiveCwd)) { | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| return true; | |
| } | |
| // We allow git on Windows if it's read-only, using the same logic as POSIX | |
| if (cmd === 'git') { | |
| // For simplicity in this branch, we'll allow standard git read operations | |
| // In a full implementation, we'd port the sub-command validation too. | |
| const sub = args[1]?.toLowerCase(); | |
| return ['status', 'log', 'diff', 'show', 'branch'].includes(sub); | |
| } | |
| return false; | |
| } | |
| /** | |
| * Checks if a Windows command is explicitly dangerous. | |
| */ | |
| export function isDangerousCommand( | |
| args: string[], | |
| _cwd?: string, | |
| _workspaceRoot?: string, | |
| ): boolean { | |
| if (!args || args.length === 0) return false; | |
| let cmd = args[0].toLowerCase(); | |
| if (cmd.endsWith('.exe')) { | |
| cmd = cmd.slice(0, -4); | |
| } | |
| const dangerous = new Set([ | |
| 'del', | |
| 'erase', | |
| 'rd', | |
| 'rmdir', | |
| 'net', | |
| 'reg', | |
| 'sc', | |
| 'format', | |
| 'mklink', | |
| 'takeown', | |
| 'icacls', | |
| 'powershell', // prevent shell escapes | |
| 'pwsh', | |
| 'cmd', | |
| 'remove-item', | |
| 'stop-process', | |
| 'stop-service', | |
| 'set-item', | |
| 'new-item', | |
| ]); | |
| return dangerous.has(cmd); | |
| } | |