wip maybe remove

This commit is contained in:
mkorwel
2026-03-11 17:23:51 -07:00
parent 12a2a9725b
commit 13877f6ac6
8 changed files with 132 additions and 68 deletions

View File

@@ -1 +1 @@
../CONTRIBUTING.md
../CONTRIBUTING.md

View File

@@ -216,7 +216,7 @@ export async function start_sandbox(
// process.argv is [node, script, ...args]
// We want to skip the first element (node) when calling spawn(process.execPath, ...)
const finalArgv = cliArgs.slice(1);
const child = spawn(process.execPath, finalArgv, {
stdio: 'inherit',
env: {

View File

@@ -22,9 +22,18 @@ function compileWindowsSandbox() {
return;
}
const srcHelperPath = path.resolve(__dirname, '../src/services/scripts/GeminiSandbox.exe');
const distHelperPath = path.resolve(__dirname, '../dist/src/services/scripts/GeminiSandbox.exe');
const sourcePath = path.resolve(__dirname, '../src/services/scripts/GeminiSandbox.cs');
const srcHelperPath = path.resolve(
__dirname,
'../src/services/scripts/GeminiSandbox.exe',
);
const distHelperPath = path.resolve(
__dirname,
'../dist/src/services/scripts/GeminiSandbox.exe',
);
const sourcePath = path.resolve(
__dirname,
'../src/services/scripts/GeminiSandbox.cs',
);
if (!fs.existsSync(sourcePath)) {
console.error(`Sandbox source not found at ${sourcePath}`);
@@ -32,7 +41,7 @@ function compileWindowsSandbox() {
}
// Ensure directories exist
[srcHelperPath, distHelperPath].forEach(p => {
[srcHelperPath, distHelperPath].forEach((p) => {
const dir = path.dirname(p);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
@@ -42,34 +51,52 @@ function compileWindowsSandbox() {
// Find csc.exe (C# Compiler) which is built into Windows .NET Framework
const systemRoot = process.env['SystemRoot'] || 'C:\\Windows';
const cscPaths = [
path.join(systemRoot, 'Microsoft.NET', 'Framework64', 'v4.0.30319', 'csc.exe'),
path.join(systemRoot, 'Microsoft.NET', 'Framework', 'v4.0.30319', 'csc.exe'),
path.join(
systemRoot,
'Microsoft.NET',
'Framework64',
'v4.0.30319',
'csc.exe',
),
path.join(
systemRoot,
'Microsoft.NET',
'Framework',
'v4.0.30319',
'csc.exe',
),
];
const csc = cscPaths.find(p => fs.existsSync(p));
const csc = cscPaths.find((p) => fs.existsSync(p));
if (!csc) {
console.warn('Windows C# compiler (csc.exe) not found. Native sandboxing will attempt to compile on first run.');
console.warn(
'Windows C# compiler (csc.exe) not found. Native sandboxing will attempt to compile on first run.',
);
return;
}
console.log(`Compiling native Windows sandbox helper...`);
// Compile to src
let result = spawnSync(csc, [`/out:${srcHelperPath}`, '/optimize', sourcePath], {
stdio: 'inherit',
});
let result = spawnSync(
csc,
[`/out:${srcHelperPath}`, '/optimize', sourcePath],
{
stdio: 'inherit',
},
);
if (result.status === 0) {
console.log('Successfully compiled GeminiSandbox.exe to src');
// Copy to dist if dist exists
const distDir = path.resolve(__dirname, '../dist');
if (fs.existsSync(distDir)) {
const distScriptsDir = path.dirname(distHelperPath);
if (!fs.existsSync(distScriptsDir)) {
fs.mkdirSync(distScriptsDir, { recursive: true });
}
fs.copyFileSync(srcHelperPath, distHelperPath);
console.log('Successfully copied GeminiSandbox.exe to dist');
const distScriptsDir = path.dirname(distHelperPath);
if (!fs.existsSync(distScriptsDir)) {
fs.mkdirSync(distScriptsDir, { recursive: true });
}
fs.copyFileSync(srcHelperPath, distHelperPath);
console.log('Successfully copied GeminiSandbox.exe to dist');
}
} else {
console.error('Failed to compile Windows sandbox helper.');

View File

@@ -461,7 +461,13 @@ export interface SandboxConfig {
enabled: boolean;
allowedPaths: string[];
networkAccess: boolean;
command?: 'docker' | 'podman' | 'sandbox-exec' | 'runsc' | 'lxc' | 'windows-native';
command?:
| 'docker'
| 'podman'
| 'sandbox-exec'
| 'runsc'
| 'lxc'
| 'windows-native';
image?: string;
}
@@ -472,7 +478,14 @@ export const ConfigSchema = z.object({
allowedPaths: z.array(z.string()).default([]),
networkAccess: z.boolean().default(false),
command: z
.enum(['docker', 'podman', 'sandbox-exec', 'runsc', 'lxc', 'windows-native'])
.enum([
'docker',
'podman',
'sandbox-exec',
'runsc',
'lxc',
'windows-native',
])
.optional(),
image: z.string().optional(),
})

View File

@@ -46,7 +46,11 @@ export class SandboxedFileSystemService implements FileSystemService {
if (code === 0) {
resolve(output);
} else {
reject(new Error(`Sandbox Error: Command failed with exit code ${code}. ${error ? 'Details: ' + error : ''}`));
reject(
new Error(
`Sandbox Error: Command failed with exit code ${code}. ${error ? 'Details: ' + error : ''}`,
),
);
}
});
});
@@ -78,7 +82,11 @@ export class SandboxedFileSystemService implements FileSystemService {
if (code === 0) {
resolve();
} else {
reject(new Error(`Sandbox Error: Command failed with exit code ${code}. ${error ? 'Details: ' + error : ''}`));
reject(
new Error(
`Sandbox Error: Command failed with exit code ${code}. ${error ? 'Details: ' + error : ''}`,
),
);
}
});
});

View File

@@ -337,14 +337,17 @@ export class ShellExecutionService {
const finalShell = isStrictSandbox ? 'cmd' : shell;
let finalArgsPrefix: string[] = [];
if (finalShell === 'cmd') {
finalArgsPrefix = ['/c'];
finalArgsPrefix = ['/c'];
} else {
finalArgsPrefix = argsPrefix;
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 guardedCommand = ensurePromptvarsDisabled(
commandToExecute,
finalShell as ShellType,
);
const spawnArgs = [...finalArgsPrefix, guardedCommand];
const env = {
...process.env,
@@ -475,8 +478,8 @@ export class ShellExecutionService {
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;
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) {
@@ -628,12 +631,15 @@ export class ShellExecutionService {
const finalShell = isStrictSandbox ? 'cmd' : shell;
let finalArgsPrefix: string[] = [];
if (finalShell === 'cmd') {
finalArgsPrefix = ['/c'];
finalArgsPrefix = ['/c'];
} else {
finalArgsPrefix = argsPrefix;
finalArgsPrefix = argsPrefix;
}
const guardedCommand = ensurePromptvarsDisabled(commandToExecute, finalShell as ShellType);
const guardedCommand = ensurePromptvarsDisabled(
commandToExecute,
finalShell as ShellType,
);
const args = [...finalArgsPrefix, guardedCommand];
const env = {
@@ -887,10 +893,14 @@ 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
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.]`;
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(

View File

@@ -12,35 +12,43 @@ import * as os from 'node:os';
describe('WindowsSandboxManager', () => {
const manager = new WindowsSandboxManager();
it.skipIf(os.platform() !== 'win32')('should prepare a GeminiSandbox.exe command', async () => {
const req: SandboxRequest = {
command: 'whoami',
args: ['/groups'],
cwd: process.cwd(),
env: { TEST_VAR: 'test_value' },
config: {
networkAccess: false
}
};
it.skipIf(os.platform() !== 'win32')(
'should prepare a GeminiSandbox.exe command',
async () => {
const req: SandboxRequest = {
command: 'whoami',
args: ['/groups'],
cwd: process.cwd(),
env: { TEST_VAR: 'test_value' },
config: {
networkAccess: false,
},
};
const result = await manager.prepareCommand(req);
const result = await manager.prepareCommand(req);
expect(result.program).toContain('GeminiSandbox.exe');
expect(result.args).toEqual(expect.arrayContaining(['0', process.cwd(), 'whoami', '/groups']));
});
expect(result.program).toContain('GeminiSandbox.exe');
expect(result.args).toEqual(
expect.arrayContaining(['0', process.cwd(), 'whoami', '/groups']),
);
},
);
it.skipIf(os.platform() !== 'win32')('should handle networkAccess from config', async () => {
const req: SandboxRequest = {
command: 'whoami',
args: [],
cwd: process.cwd(),
env: {},
config: {
networkAccess: true
}
};
it.skipIf(os.platform() !== 'win32')(
'should handle networkAccess from config',
async () => {
const req: SandboxRequest = {
command: 'whoami',
args: [],
cwd: process.cwd(),
env: {},
config: {
networkAccess: true,
},
};
const result = await manager.prepareCommand(req);
expect(result.args[0]).toBe('1');
});
const result = await manager.prepareCommand(req);
expect(result.args[0]).toBe('1');
},
);
});

View File

@@ -31,11 +31,7 @@ export class WindowsSandboxManager implements SandboxManager {
private initialized = false;
constructor() {
this.helperPath = path.resolve(
__dirname,
'scripts',
'GeminiSandbox.exe',
);
this.helperPath = path.resolve(__dirname, 'scripts', 'GeminiSandbox.exe');
}
private ensureInitialized(): void {
@@ -53,7 +49,9 @@ export class WindowsSandboxManager implements SandboxManager {
'csc.exe',
);
if (fs.existsSync(csc)) {
spawnSync(csc, ['/out:' + this.helperPath, sourcePath], { stdio: 'ignore' });
spawnSync(csc, ['/out:' + this.helperPath, sourcePath], {
stdio: 'ignore',
});
}
}
}
@@ -107,7 +105,7 @@ export class WindowsSandboxManager implements SandboxManager {
// 2. Construct the helper command
// GeminiSandbox.exe <network:0|1> <cwd> <command> [args...]
const program = this.helperPath;
// If the command starts with __, it's an internal command for the sandbox helper itself.
const args = [
req.config?.networkAccess ? '1' : '0',