mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-12 21:03:05 -07:00
feat(windows-sandbox): architectural hardening, spawnAsync refactor, and build fixes
This commit is contained in:
@@ -0,0 +1 @@
|
||||
packages/core/src/services/scripts/*.exe
|
||||
@@ -115,6 +115,8 @@ they appear in the UI.
|
||||
|
||||
| UI Label | Setting | Description | Default |
|
||||
| -------------------------------- | ------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- |
|
||||
| Sandbox Allowed Paths | `tools.sandboxAllowedPaths` | List of additional paths that the sandbox is allowed to access. | `[]` |
|
||||
| Sandbox Network Access | `tools.sandboxNetworkAccess` | Whether the sandbox is allowed to access the network. | `false` |
|
||||
| Enable Interactive Shell | `tools.shell.enableInteractiveShell` | Use node-pty for an interactive shell experience. Fallback to child_process still applies. | `true` |
|
||||
| Show Color | `tools.shell.showColor` | Show color in shell output. | `false` |
|
||||
| Use Ripgrep | `tools.useRipgrep` | Use ripgrep for file content search instead of the fallback implementation. Provides faster search performance. | `true` |
|
||||
|
||||
@@ -26,6 +26,8 @@ export class SandboxedFileSystemService implements FileSystemService {
|
||||
});
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
// Direct spawn is necessary here for streaming large file contents.
|
||||
|
||||
const child = spawn(prepared.program, prepared.args, {
|
||||
cwd: this.cwd,
|
||||
env: prepared.env,
|
||||
@@ -65,6 +67,8 @@ export class SandboxedFileSystemService implements FileSystemService {
|
||||
});
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
// Direct spawn is necessary here for streaming large file contents.
|
||||
|
||||
const child = spawn(prepared.program, prepared.args, {
|
||||
cwd: this.cwd,
|
||||
env: prepared.env,
|
||||
|
||||
@@ -5,19 +5,19 @@
|
||||
*/
|
||||
|
||||
import fs from 'node:fs';
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import {
|
||||
type SandboxManager,
|
||||
type SandboxRequest,
|
||||
type SandboxedCommand,
|
||||
import type {
|
||||
SandboxManager,
|
||||
SandboxRequest,
|
||||
SandboxedCommand,
|
||||
} from './sandboxManager.js';
|
||||
import {
|
||||
sanitizeEnvironment,
|
||||
type EnvironmentSanitizationConfig,
|
||||
} from './environmentSanitization.js';
|
||||
import { debugLogger } from '../utils/debugLogger.js';
|
||||
import { spawnAsync } from '../utils/shell-utils.js';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
@@ -38,7 +38,7 @@ export class WindowsSandboxManager implements SandboxManager {
|
||||
this.helperPath = path.resolve(__dirname, 'scripts', 'GeminiSandbox.exe');
|
||||
}
|
||||
|
||||
private ensureInitialized(): void {
|
||||
private async ensureInitialized(): Promise<void> {
|
||||
if (this.initialized) return;
|
||||
if (this.platform !== 'win32') {
|
||||
this.initialized = true;
|
||||
@@ -67,18 +67,37 @@ export class WindowsSandboxManager implements SandboxManager {
|
||||
'v4.0.30319',
|
||||
'csc.exe',
|
||||
),
|
||||
// Added newer framework paths
|
||||
path.join(
|
||||
systemRoot,
|
||||
'Microsoft.NET',
|
||||
'Framework64',
|
||||
'v4.8',
|
||||
'csc.exe',
|
||||
),
|
||||
path.join(
|
||||
systemRoot,
|
||||
'Microsoft.NET',
|
||||
'Framework',
|
||||
'v4.8',
|
||||
'csc.exe',
|
||||
),
|
||||
path.join(
|
||||
systemRoot,
|
||||
'Microsoft.NET',
|
||||
'Framework64',
|
||||
'v3.5',
|
||||
'csc.exe',
|
||||
),
|
||||
];
|
||||
|
||||
for (const csc of cscPaths) {
|
||||
const result = spawnSync(
|
||||
csc,
|
||||
['/out:' + this.helperPath, sourcePath],
|
||||
{
|
||||
stdio: 'ignore',
|
||||
},
|
||||
);
|
||||
if (result.status === 0) {
|
||||
try {
|
||||
// We use spawnAsync but we don't need to capture output
|
||||
await spawnAsync(csc, ['/out:' + this.helperPath, sourcePath]);
|
||||
break;
|
||||
} catch (_e) {
|
||||
// Try next path
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -97,7 +116,7 @@ export class WindowsSandboxManager implements SandboxManager {
|
||||
* Prepares a command for sandboxed execution on Windows.
|
||||
*/
|
||||
async prepareCommand(req: SandboxRequest): Promise<SandboxedCommand> {
|
||||
this.ensureInitialized();
|
||||
await this.ensureInitialized();
|
||||
|
||||
const sanitizationConfig: EnvironmentSanitizationConfig = {
|
||||
allowedEnvironmentVariables:
|
||||
@@ -113,12 +132,12 @@ export class WindowsSandboxManager implements SandboxManager {
|
||||
|
||||
// 1. Handle filesystem permissions for Low Integrity
|
||||
// Grant "Low Mandatory Level" write access to the CWD.
|
||||
this.grantLowIntegrityAccess(req.cwd);
|
||||
await this.grantLowIntegrityAccess(req.cwd);
|
||||
|
||||
// Grant "Low Mandatory Level" read access to allowedPaths.
|
||||
if (req.config?.allowedPaths) {
|
||||
for (const allowedPath of req.config.allowedPaths) {
|
||||
this.grantLowIntegrityAccess(allowedPath);
|
||||
await this.grantLowIntegrityAccess(allowedPath);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,7 +163,7 @@ export class WindowsSandboxManager implements SandboxManager {
|
||||
/**
|
||||
* Grants "Low Mandatory Level" access to a path using icacls.
|
||||
*/
|
||||
private grantLowIntegrityAccess(targetPath: string): void {
|
||||
private async grantLowIntegrityAccess(targetPath: string): Promise<void> {
|
||||
if (this.platform !== 'win32') {
|
||||
return;
|
||||
}
|
||||
@@ -169,16 +188,8 @@ export class WindowsSandboxManager implements SandboxManager {
|
||||
}
|
||||
|
||||
try {
|
||||
const result = spawnSync(
|
||||
'icacls',
|
||||
[resolvedPath, '/setintegritylevel', 'Low'],
|
||||
{
|
||||
stdio: 'ignore',
|
||||
},
|
||||
);
|
||||
if (result.status === 0) {
|
||||
await spawnAsync('icacls', [resolvedPath, '/setintegritylevel', 'Low']);
|
||||
this.lowIntegrityCache.add(resolvedPath);
|
||||
}
|
||||
} catch (e) {
|
||||
debugLogger.log(
|
||||
'WindowsSandboxManager: icacls failed for',
|
||||
|
||||
@@ -1924,10 +1924,27 @@
|
||||
"properties": {
|
||||
"sandbox": {
|
||||
"title": "Sandbox",
|
||||
"description": "Legacy full-process sandbox execution environment. Set to a boolean to enable or disable the sandbox, provide a string path to a sandbox profile, or specify an explicit sandbox command (e.g., \"docker\", \"podman\", \"lxc\").",
|
||||
"markdownDescription": "Legacy full-process sandbox execution environment. Set to a boolean to enable or disable the sandbox, provide a string path to a sandbox profile, or specify an explicit sandbox command (e.g., \"docker\", \"podman\", \"lxc\").\n\n- Category: `Tools`\n- Requires restart: `yes`",
|
||||
"description": "Legacy full-process sandbox execution environment. Set to a boolean to enable or disable the sandbox, provide a string path to a sandbox profile, or specify an explicit sandbox command (e.g., \"docker\", \"podman\", \"lxc\", \"windows-native\").",
|
||||
"markdownDescription": "Legacy full-process sandbox execution environment. Set to a boolean to enable or disable the sandbox, provide a string path to a sandbox profile, or specify an explicit sandbox command (e.g., \"docker\", \"podman\", \"lxc\", \"windows-native\").\n\n- Category: `Tools`\n- Requires restart: `yes`",
|
||||
"$ref": "#/$defs/BooleanOrStringOrObject"
|
||||
},
|
||||
"sandboxAllowedPaths": {
|
||||
"title": "Sandbox Allowed Paths",
|
||||
"description": "List of additional paths that the sandbox is allowed to access.",
|
||||
"markdownDescription": "List of additional paths that the sandbox is allowed to access.\n\n- Category: `Tools`\n- Requires restart: `yes`\n- Default: `[]`",
|
||||
"default": [],
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"sandboxNetworkAccess": {
|
||||
"title": "Sandbox Network Access",
|
||||
"description": "Whether the sandbox is allowed to access the network.",
|
||||
"markdownDescription": "Whether the sandbox is allowed to access the network.\n\n- Category: `Tools`\n- Requires restart: `yes`\n- Default: `false`",
|
||||
"default": false,
|
||||
"type": "boolean"
|
||||
},
|
||||
"shell": {
|
||||
"title": "Shell",
|
||||
"description": "Settings for shell execution.",
|
||||
|
||||
@@ -26,7 +26,7 @@ import path from 'node:path';
|
||||
const sourceDir = path.join('src');
|
||||
const targetDir = path.join('dist', 'src');
|
||||
|
||||
const extensionsToCopy = ['.md', '.json', '.sb', '.toml'];
|
||||
const extensionsToCopy = ['.md', '.json', '.sb', '.toml', '.cs', '.exe'];
|
||||
|
||||
function copyFilesRecursive(source, target) {
|
||||
if (!fs.existsSync(target)) {
|
||||
|
||||
Reference in New Issue
Block a user