feat(core): address review comments and fix strict sandbox shell selection

This commit is contained in:
mkorwel
2026-03-13 18:28:15 +00:00
parent 7814427a1e
commit d7fb4bf464
2 changed files with 41 additions and 3 deletions
@@ -273,7 +273,12 @@ public class GeminiSandbox {
si.hStdOutput = GetStdHandle(-11); si.hStdOutput = GetStdHandle(-11);
si.hStdError = GetStdHandle(-12); si.hStdError = GetStdHandle(-12);
string commandLine = string.Join(" ", args, 2, args.Length - 2); List<string> quotedArgs = new List<string>();
for (int i = 2; i < args.Length; i++) {
quotedArgs.Add(QuoteArgument(args[i]));
}
string commandLine = string.Join(" ", quotedArgs.ToArray());
PROCESS_INFORMATION pi; PROCESS_INFORMATION pi;
if (!CreateProcessAsUser(hRestrictedToken, null, commandLine, IntPtr.Zero, IntPtr.Zero, true, CREATE_SUSPENDED | CREATE_UNICODE_ENVIRONMENT, IntPtr.Zero, cwd, ref si, out pi)) { if (!CreateProcessAsUser(hRestrictedToken, null, commandLine, IntPtr.Zero, IntPtr.Zero, true, CREATE_SUSPENDED | CREATE_UNICODE_ENVIRONMENT, IntPtr.Zero, cwd, ref si, out pi)) {
Console.Error.WriteLine("Failed to create process. Error: " + Marshal.GetLastWin32Error()); Console.Error.WriteLine("Failed to create process. Error: " + Marshal.GetLastWin32Error());
@@ -299,6 +304,14 @@ public class GeminiSandbox {
return (int)exitCode; return (int)exitCode;
} }
private static string QuoteArgument(string arg) {
if (string.IsNullOrEmpty(arg)) return "\"\"";
if (arg.IndexOfAny(new char[] { ' ', '\t', '\n', '\v', '\"' }) == -1) return arg;
string escaped = arg.Replace("\"", "\\\"");
return "\"" + escaped + "\"";
}
private static int RunInImpersonation(IntPtr hToken, Func<int> action) { private static int RunInImpersonation(IntPtr hToken, Func<int> action) {
using (WindowsIdentity.Impersonate(hToken)) { using (WindowsIdentity.Impersonate(hToken)) {
return action(); return action();
@@ -279,6 +279,14 @@ export class ShellExecutionService {
): Promise<ShellExecutionHandle> { ): Promise<ShellExecutionHandle> {
const sandboxManager = const sandboxManager =
shellExecutionConfig.sandboxManager ?? new NoopSandboxManager(); shellExecutionConfig.sandboxManager ?? new NoopSandboxManager();
// Strict sandbox on Windows (network disabled) requires cmd.exe
const isStrictSandbox =
os.platform() === 'win32' &&
shellExecutionConfig.sandboxConfig?.enabled &&
shellExecutionConfig.sandboxConfig?.command === 'windows-native' &&
!shellExecutionConfig.sandboxConfig?.networkAccess;
const { env: sanitizedEnv } = await sandboxManager.prepareCommand({ const { env: sanitizedEnv } = await sandboxManager.prepareCommand({
command: commandToExecute, command: commandToExecute,
args: [], args: [],
@@ -302,6 +310,7 @@ export class ShellExecutionService {
shellExecutionConfig, shellExecutionConfig,
ptyInfo, ptyInfo,
sanitizedEnv, sanitizedEnv,
isStrictSandbox,
); );
} catch (_e) { } catch (_e) {
// Fallback to child_process // Fallback to child_process
@@ -316,6 +325,7 @@ export class ShellExecutionService {
abortSignal, abortSignal,
shellExecutionConfig.sanitizationConfig, shellExecutionConfig.sanitizationConfig,
shouldUseNodePty, shouldUseNodePty,
isStrictSandbox,
); );
} }
@@ -356,10 +366,18 @@ export class ShellExecutionService {
abortSignal: AbortSignal, abortSignal: AbortSignal,
sanitizationConfig: EnvironmentSanitizationConfig, sanitizationConfig: EnvironmentSanitizationConfig,
isInteractive: boolean, isInteractive: boolean,
isStrictSandbox?: boolean,
): ShellExecutionHandle { ): ShellExecutionHandle {
try { try {
const isWindows = os.platform() === 'win32'; const isWindows = os.platform() === 'win32';
const { executable, argsPrefix, shell } = getShellConfiguration(); let { executable, argsPrefix, shell } = getShellConfiguration();
if (isStrictSandbox) {
shell = 'cmd';
argsPrefix = ['/c'];
executable = 'cmd.exe';
}
const guardedCommand = ensurePromptvarsDisabled(commandToExecute, shell); const guardedCommand = ensurePromptvarsDisabled(commandToExecute, shell);
const spawnArgs = [...argsPrefix, guardedCommand]; const spawnArgs = [...argsPrefix, guardedCommand];
@@ -690,6 +708,7 @@ export class ShellExecutionService {
shellExecutionConfig: ShellExecutionConfig, shellExecutionConfig: ShellExecutionConfig,
ptyInfo: PtyImplementation, ptyInfo: PtyImplementation,
sanitizedEnv: Record<string, string | undefined>, sanitizedEnv: Record<string, string | undefined>,
isStrictSandbox?: boolean,
): Promise<ShellExecutionHandle> { ): Promise<ShellExecutionHandle> {
if (!ptyInfo) { if (!ptyInfo) {
// This should not happen, but as a safeguard... // This should not happen, but as a safeguard...
@@ -700,7 +719,13 @@ export class ShellExecutionService {
try { try {
const cols = shellExecutionConfig.terminalWidth ?? 80; const cols = shellExecutionConfig.terminalWidth ?? 80;
const rows = shellExecutionConfig.terminalHeight ?? 30; const rows = shellExecutionConfig.terminalHeight ?? 30;
const { executable, argsPrefix, shell } = getShellConfiguration(); let { executable, argsPrefix, shell } = getShellConfiguration();
if (isStrictSandbox) {
shell = 'cmd';
argsPrefix = ['/c'];
executable = 'cmd.exe';
}
const resolvedExecutable = await resolveExecutable(executable); const resolvedExecutable = await resolveExecutable(executable);
if (!resolvedExecutable) { if (!resolvedExecutable) {