mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-30 15:04:16 -07:00
chore(core): add build script for Windows sandbox helper
This commit is contained in:
@@ -46,7 +46,7 @@ export class SandboxedFileSystemService implements FileSystemService {
|
||||
if (code === 0) {
|
||||
resolve(output);
|
||||
} else {
|
||||
reject(new Error(`Failed to read file via sandbox: ${error || 'Unknown error'}`));
|
||||
reject(new Error(`Sandbox Error: Command failed with exit code ${code}. ${error ? 'Details: ' + error : ''}`));
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -78,7 +78,7 @@ export class SandboxedFileSystemService implements FileSystemService {
|
||||
if (code === 0) {
|
||||
resolve();
|
||||
} else {
|
||||
reject(new Error(`Failed to write file via sandbox: ${error || 'Unknown error'}`));
|
||||
reject(new Error(`Sandbox Error: Command failed with exit code ${code}. ${error ? 'Details: ' + error : ''}`));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -162,6 +162,12 @@ public class GeminiSandbox {
|
||||
IntPtr pSidsToDisable = IntPtr.Zero;
|
||||
uint sidCount = 0;
|
||||
|
||||
IntPtr pSidsToRestrict = IntPtr.Zero;
|
||||
uint restrictCount = 0;
|
||||
|
||||
// "networkAccess == false" implies Strict Sandbox Level 1.
|
||||
// In Strict mode, we strip the Network SID and apply the Restricted Code SID.
|
||||
// This blocks network access and restricts file reads, but requires cmd.exe.
|
||||
if (!networkAccess) {
|
||||
IntPtr networkSid;
|
||||
if (ConvertStringSidToSid("S-1-5-2", out networkSid)) {
|
||||
@@ -173,20 +179,21 @@ public class GeminiSandbox {
|
||||
saa.Attributes = 0;
|
||||
Marshal.StructureToPtr(saa, pSidsToDisable, false);
|
||||
}
|
||||
}
|
||||
|
||||
IntPtr pSidsToRestrict = IntPtr.Zero;
|
||||
uint restrictCount = 0;
|
||||
IntPtr restrictedSid;
|
||||
if (ConvertStringSidToSid("S-1-5-12", out restrictedSid)) {
|
||||
restrictCount = 1;
|
||||
int saaSize = Marshal.SizeOf(typeof(SID_AND_ATTRIBUTES));
|
||||
pSidsToRestrict = Marshal.AllocHGlobal(saaSize);
|
||||
SID_AND_ATTRIBUTES saa = new SID_AND_ATTRIBUTES();
|
||||
saa.Sid = restrictedSid;
|
||||
saa.Attributes = 0;
|
||||
Marshal.StructureToPtr(saa, pSidsToRestrict, false);
|
||||
IntPtr restrictedSid;
|
||||
// S-1-5-12 is Restricted Code SID
|
||||
if (ConvertStringSidToSid("S-1-5-12", out restrictedSid)) {
|
||||
restrictCount = 1;
|
||||
int saaSize = Marshal.SizeOf(typeof(SID_AND_ATTRIBUTES));
|
||||
pSidsToRestrict = Marshal.AllocHGlobal(saaSize);
|
||||
SID_AND_ATTRIBUTES saa = new SID_AND_ATTRIBUTES();
|
||||
saa.Sid = restrictedSid;
|
||||
saa.Attributes = 0;
|
||||
Marshal.StructureToPtr(saa, pSidsToRestrict, false);
|
||||
}
|
||||
}
|
||||
// If networkAccess == true, we are in Elevated mode (Level 2).
|
||||
// We only strip privileges (DISABLE_MAX_PRIVILEGE), allowing network and powershell.
|
||||
|
||||
if (!CreateRestrictedToken(hToken, DISABLE_MAX_PRIVILEGE, sidCount, pSidsToDisable, 0, IntPtr.Zero, restrictCount, pSidsToRestrict, out hRestrictedToken)) {
|
||||
Console.Error.WriteLine("Failed to create restricted token");
|
||||
|
||||
@@ -326,9 +326,26 @@ export class ShellExecutionService {
|
||||
try {
|
||||
const isWindows = os.platform() === 'win32';
|
||||
const { executable, argsPrefix, shell } = getShellConfiguration();
|
||||
const guardedCommand = ensurePromptvarsDisabled(commandToExecute, shell);
|
||||
const spawnArgs = [...argsPrefix, guardedCommand];
|
||||
// In Strict Sandbox mode (networkAccess = false), PowerShell will fail to load
|
||||
// its required system DLLs due to the Restricted Code SID.
|
||||
// We must fallback to cmd.exe for strict sandboxing.
|
||||
const isStrictSandbox =
|
||||
shellExecutionConfig.sandboxConfig?.enabled &&
|
||||
shellExecutionConfig.sandboxConfig?.command === 'windows-native' &&
|
||||
!shellExecutionConfig.sandboxConfig?.networkAccess;
|
||||
|
||||
const finalShell = isStrictSandbox ? 'cmd' : shell;
|
||||
let finalArgsPrefix: string[] = [];
|
||||
if (finalShell === 'cmd') {
|
||||
finalArgsPrefix = ['/c'];
|
||||
} else {
|
||||
finalArgsPrefix = argsPrefix;
|
||||
}
|
||||
|
||||
// We still use the original executable logic (e.g. searching for git)
|
||||
// but the guard command formatting is based on the final shell.
|
||||
const guardedCommand = ensurePromptvarsDisabled(commandToExecute, finalShell as ShellType);
|
||||
const spawnArgs = [...finalArgsPrefix, guardedCommand];
|
||||
const env = {
|
||||
...process.env,
|
||||
[GEMINI_CLI_IDENTIFICATION_ENV_VAR]:
|
||||
@@ -456,6 +473,12 @@ export class ShellExecutionService {
|
||||
|
||||
let combinedOutput = state.output;
|
||||
|
||||
const isSandboxError = code === 3221225781 || code === -1073741515; // 0xC0000135
|
||||
if (isSandboxError && shellExecutionConfig.sandboxConfig?.enabled) {
|
||||
const sandboxMessage = `\\n[GEMINI_CLI_SANDBOX_ERROR: Command execution was blocked by the native Windows sandbox. This typically means the command attempted an unauthorized network request or file access.]`;
|
||||
combinedOutput += sandboxMessage;
|
||||
}
|
||||
|
||||
if (state.truncated) {
|
||||
const truncationMessage = `\n[GEMINI_CLI_WARNING: Output truncated. The buffer is limited to ${
|
||||
MAX_CHILD_PROCESS_BUFFER_SIZE / (1024 * 1024)
|
||||
@@ -595,8 +618,21 @@ export class ShellExecutionService {
|
||||
const rows = shellExecutionConfig.terminalHeight ?? 30;
|
||||
const { executable, argsPrefix, shell } = getShellConfiguration();
|
||||
|
||||
const guardedCommand = ensurePromptvarsDisabled(commandToExecute, shell);
|
||||
const args = [...argsPrefix, guardedCommand];
|
||||
const isStrictSandbox =
|
||||
shellExecutionConfig.sandboxConfig?.enabled &&
|
||||
shellExecutionConfig.sandboxConfig?.command === 'windows-native' &&
|
||||
!shellExecutionConfig.sandboxConfig?.networkAccess;
|
||||
|
||||
const finalShell = isStrictSandbox ? 'cmd' : shell;
|
||||
let finalArgsPrefix: string[] = [];
|
||||
if (finalShell === 'cmd') {
|
||||
finalArgsPrefix = ['/c'];
|
||||
} else {
|
||||
finalArgsPrefix = argsPrefix;
|
||||
}
|
||||
|
||||
const guardedCommand = ensurePromptvarsDisabled(commandToExecute, finalShell as ShellType);
|
||||
const args = [...finalArgsPrefix, guardedCommand];
|
||||
|
||||
const env = {
|
||||
...process.env,
|
||||
@@ -846,6 +882,13 @@ export class ShellExecutionService {
|
||||
|
||||
// Store exit info for late subscribers (e.g. backgrounding race condition)
|
||||
this.exitedPtyInfo.set(ptyProcess.pid, { exitCode, signal });
|
||||
|
||||
const isSandboxError = exitCode === 3221225781 || exitCode === -1073741515; // 0xC0000135
|
||||
let finalOutput = getFullBufferText(headlessTerminal);
|
||||
if (isSandboxError && shellExecutionConfig.sandboxConfig?.enabled) {
|
||||
finalOutput += `\n[GEMINI_CLI_SANDBOX_ERROR: Command execution was blocked by the native Windows sandbox. This typically means the command attempted an unauthorized network request or file access.]`;
|
||||
}
|
||||
|
||||
setTimeout(
|
||||
() => {
|
||||
this.exitedPtyInfo.delete(ptyProcess.pid);
|
||||
@@ -869,7 +912,7 @@ export class ShellExecutionService {
|
||||
|
||||
resolve({
|
||||
rawOutput: finalBuffer,
|
||||
output: getFullBufferText(headlessTerminal),
|
||||
output: finalOutput,
|
||||
exitCode,
|
||||
signal: signal ?? null,
|
||||
error,
|
||||
|
||||
Reference in New Issue
Block a user