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

View File

@@ -273,7 +273,12 @@ public class GeminiSandbox {
si.hStdOutput = GetStdHandle(-11);
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;
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());
@@ -299,6 +304,14 @@ public class GeminiSandbox {
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) {
using (WindowsIdentity.Impersonate(hToken)) {
return action();

View File

@@ -279,6 +279,14 @@ export class ShellExecutionService {
): Promise<ShellExecutionHandle> {
const sandboxManager =
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({
command: commandToExecute,
args: [],
@@ -302,6 +310,7 @@ export class ShellExecutionService {
shellExecutionConfig,
ptyInfo,
sanitizedEnv,
isStrictSandbox,
);
} catch (_e) {
// Fallback to child_process
@@ -316,6 +325,7 @@ export class ShellExecutionService {
abortSignal,
shellExecutionConfig.sanitizationConfig,
shouldUseNodePty,
isStrictSandbox,
);
}
@@ -356,10 +366,18 @@ export class ShellExecutionService {
abortSignal: AbortSignal,
sanitizationConfig: EnvironmentSanitizationConfig,
isInteractive: boolean,
isStrictSandbox?: boolean,
): ShellExecutionHandle {
try {
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 spawnArgs = [...argsPrefix, guardedCommand];
@@ -690,6 +708,7 @@ export class ShellExecutionService {
shellExecutionConfig: ShellExecutionConfig,
ptyInfo: PtyImplementation,
sanitizedEnv: Record<string, string | undefined>,
isStrictSandbox?: boolean,
): Promise<ShellExecutionHandle> {
if (!ptyInfo) {
// This should not happen, but as a safeguard...
@@ -700,7 +719,13 @@ export class ShellExecutionService {
try {
const cols = shellExecutionConfig.terminalWidth ?? 80;
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);
if (!resolvedExecutable) {