mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-06-25 02:37:53 -07:00
fix(cli): allow disabling hostname in sandbox for rootless containers
This PR introduces a new `setHostname` configuration option (and `GEMINI_CLI_SANDBOX_SET_HOSTNAME` environment variable) to allow disabling the `--hostname` argument when starting Docker or Podman sandboxes. In rootless nested container environments, attempting to set the hostname can fail if `CAP_SYS_ADMIN` is not available in the ambient capability set. By setting `setHostname: false`, users can bypass this requirement and successfully run the sandbox in such environments. The setting defaults to `true` to maintain existing behavior and descriptive hostnames in the CLI footer. Fixes: #26880 cc @danielweis @tommasosciortino
This commit is contained in:
@@ -131,6 +131,7 @@ they appear in the UI.
|
||||
|
||||
| UI Label | Setting | Description | Default |
|
||||
| -------------------------------- | ------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- |
|
||||
| Sandbox Set Hostname | `tools.sandboxSetHostname` | Whether to set the hostname in the sandbox container. Set to false to avoid Mutating UTS namespace, which is required for some rootless container environments. | `true` |
|
||||
| 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` |
|
||||
|
||||
@@ -1553,6 +1553,13 @@ their corresponding top-level category object in your `settings.json` file.
|
||||
- **Default:** `undefined`
|
||||
- **Requires restart:** Yes
|
||||
|
||||
- **`tools.sandboxSetHostname`** (boolean):
|
||||
- **Description:** Whether to set the hostname in the sandbox container. Set
|
||||
to false to avoid Mutating UTS namespace, which is required for some
|
||||
rootless container environments.
|
||||
- **Default:** `true`
|
||||
- **Requires restart:** Yes
|
||||
|
||||
- **`tools.sandboxAllowedPaths`** (array):
|
||||
- **Description:** List of additional paths that the sandbox is allowed to
|
||||
access.
|
||||
|
||||
@@ -132,6 +132,16 @@ export async function loadSandboxConfig(
|
||||
let sandboxValue: boolean | string | null | undefined;
|
||||
let allowedPaths: string[] = [];
|
||||
let networkAccess = true;
|
||||
let setHostname = settings.tools?.sandboxSetHostname ?? true;
|
||||
|
||||
// Environment variable override
|
||||
const envSetHostname = process.env['GEMINI_CLI_SANDBOX_SET_HOSTNAME']?.toLowerCase().trim();
|
||||
if (envSetHostname === 'false') {
|
||||
setHostname = false;
|
||||
} else if (envSetHostname === 'true') {
|
||||
setHostname = true;
|
||||
}
|
||||
|
||||
let customImage: string | undefined;
|
||||
|
||||
if (
|
||||
@@ -143,6 +153,9 @@ export async function loadSandboxConfig(
|
||||
sandboxValue = config.enabled ? (config.command ?? true) : false;
|
||||
allowedPaths = config.allowedPaths ?? [];
|
||||
networkAccess = config.networkAccess ?? true;
|
||||
if (config.setHostname !== undefined) {
|
||||
setHostname = config.setHostname;
|
||||
}
|
||||
customImage = config.image;
|
||||
} else if (typeof sandboxOption !== 'object' || sandboxOption === null) {
|
||||
sandboxValue = sandboxOption;
|
||||
@@ -163,6 +176,13 @@ export async function loadSandboxConfig(
|
||||
command === 'lxc';
|
||||
|
||||
return command && (image || isNative)
|
||||
? { enabled: true, allowedPaths, networkAccess, command, image }
|
||||
? {
|
||||
enabled: true,
|
||||
allowedPaths,
|
||||
networkAccess,
|
||||
command,
|
||||
image,
|
||||
setHostname,
|
||||
}
|
||||
: undefined;
|
||||
}
|
||||
|
||||
@@ -1552,6 +1552,18 @@ const SETTINGS_SCHEMA = {
|
||||
`,
|
||||
showInDialog: false,
|
||||
},
|
||||
sandboxSetHostname: {
|
||||
type: 'boolean',
|
||||
label: 'Sandbox Set Hostname',
|
||||
category: 'Tools',
|
||||
requiresRestart: true,
|
||||
default: true,
|
||||
description: oneLine`
|
||||
Whether to set the hostname in the sandbox container.
|
||||
Set to false to avoid Mutating UTS namespace, which is required for some rootless container environments.
|
||||
`,
|
||||
showInDialog: true,
|
||||
},
|
||||
sandboxAllowedPaths: {
|
||||
type: 'array',
|
||||
label: 'Sandbox Allowed Paths',
|
||||
|
||||
@@ -419,6 +419,56 @@ describe('sandbox', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should skip setting hostname if setHostname is false', async () => {
|
||||
const config: SandboxConfig = createMockSandboxConfig({
|
||||
command: 'docker',
|
||||
image: 'gemini-cli-sandbox',
|
||||
setHostname: false,
|
||||
});
|
||||
|
||||
interface MockProcessWithStdout extends EventEmitter {
|
||||
stdout: EventEmitter;
|
||||
}
|
||||
const mockImageCheckProcess = new EventEmitter() as MockProcessWithStdout;
|
||||
mockImageCheckProcess.stdout = new EventEmitter();
|
||||
vi.mocked(spawn).mockImplementationOnce(() => {
|
||||
setTimeout(() => {
|
||||
mockImageCheckProcess.stdout.emit('data', Buffer.from('image-id'));
|
||||
mockImageCheckProcess.emit('close', 0);
|
||||
}, 1);
|
||||
return mockImageCheckProcess as unknown as ReturnType<typeof spawn>;
|
||||
});
|
||||
|
||||
const mockSpawnProcess = new EventEmitter() as unknown as ReturnType<
|
||||
typeof spawn
|
||||
>;
|
||||
mockSpawnProcess.on = vi.fn().mockImplementation((event, cb) => {
|
||||
if (event === 'close') {
|
||||
setTimeout(() => cb(0), 10);
|
||||
}
|
||||
return mockSpawnProcess;
|
||||
});
|
||||
vi.mocked(spawn).mockImplementationOnce(() => mockSpawnProcess);
|
||||
|
||||
await expect(
|
||||
start_sandbox(config, [], undefined, ['arg1']),
|
||||
).resolves.toBe(0);
|
||||
|
||||
const containerName = 'gemini-cli-sandbox-a1b2c3d4e5f6';
|
||||
expect(spawn).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
'docker',
|
||||
expect.not.arrayContaining(['--hostname']),
|
||||
expect.objectContaining({ stdio: 'inherit' }),
|
||||
);
|
||||
expect(spawn).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
'docker',
|
||||
expect.arrayContaining(['--name', containerName]),
|
||||
expect.anything(),
|
||||
);
|
||||
});
|
||||
|
||||
it('should pull image if missing', async () => {
|
||||
const config: SandboxConfig = createMockSandboxConfig({
|
||||
command: 'docker',
|
||||
|
||||
@@ -509,7 +509,10 @@ export async function start_sandbox(
|
||||
'hex',
|
||||
)}`;
|
||||
debugLogger.log(`ContainerName: ${containerName}`);
|
||||
args.push('--name', containerName, '--hostname', containerName);
|
||||
args.push('--name', containerName);
|
||||
if (config.setHostname !== false) {
|
||||
args.push('--hostname', containerName);
|
||||
}
|
||||
|
||||
// copy GEMINI_CLI_TEST_VAR for integration tests
|
||||
if (process.env['GEMINI_CLI_TEST_VAR']) {
|
||||
|
||||
@@ -523,6 +523,7 @@ export interface SandboxConfig {
|
||||
allowedPaths?: string[];
|
||||
includeDirectories?: string[];
|
||||
networkAccess?: boolean;
|
||||
setHostname?: boolean;
|
||||
command?:
|
||||
| 'docker'
|
||||
| 'podman'
|
||||
@@ -540,6 +541,7 @@ export const ConfigSchema = z.object({
|
||||
allowedPaths: z.array(z.string()).default([]),
|
||||
includeDirectories: z.array(z.string()).default([]),
|
||||
networkAccess: z.boolean().default(false),
|
||||
setHostname: z.boolean().default(true),
|
||||
command: z
|
||||
.enum([
|
||||
'docker',
|
||||
|
||||
@@ -2730,6 +2730,13 @@
|
||||
"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"
|
||||
},
|
||||
"sandboxSetHostname": {
|
||||
"title": "Sandbox Set Hostname",
|
||||
"description": "Whether to set the hostname in the sandbox container. Set to false to avoid Mutating UTS namespace, which is required for some rootless container environments.",
|
||||
"markdownDescription": "Whether to set the hostname in the sandbox container. Set to false to avoid Mutating UTS namespace, which is required for some rootless container environments.\n\n- Category: `Tools`\n- Requires restart: `yes`\n- Default: `true`",
|
||||
"default": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
"sandboxAllowedPaths": {
|
||||
"title": "Sandbox Allowed Paths",
|
||||
"description": "List of additional paths that the sandbox is allowed to access.",
|
||||
|
||||
Reference in New Issue
Block a user