Download packages/cli/src/config/sandboxConfig.test.ts from SaylorTwift/gemini-cli: direct link, hf CLI and curl.
- Browser
- Download file 16.3 kB
-
https://huggingface.co/SaylorTwift/gemini-cli/resolve/main/packages/cli/src/config/sandboxConfig.test.ts
- Command line
-
hf download hf://SaylorTwift/gemini-cli/packages/cli/src/config/sandboxConfig.test.ts
-
curl -L -o sandboxConfig.test.ts https://huggingface.co/SaylorTwift/gemini-cli/resolve/main/packages/cli/src/config/sandboxConfig.test.ts
16.3 kB
| /** | |
| * @license | |
| * Copyright 2025 Google LLC | |
| * SPDX-License-Identifier: Apache-2.0 | |
| */ | |
| import { getPackageJson } from '@google/gemini-cli-core'; | |
| import commandExists from 'command-exists'; | |
| import * as os from 'node:os'; | |
| import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; | |
| import { loadSandboxConfig } from './sandboxConfig.js'; | |
| // Mock dependencies | |
| vi.mock('@google/gemini-cli-core', async (importOriginal) => { | |
| const actual = await importOriginal(); | |
| return { | |
| ...(actual as object), | |
| getPackageJson: vi.fn(), | |
| FatalSandboxError: class extends Error { | |
| constructor(message: string) { | |
| super(message); | |
| this.name = 'FatalSandboxError'; | |
| } | |
| }, | |
| }; | |
| }); | |
| vi.mock('command-exists', () => { | |
| const sync = vi.fn(); | |
| return { | |
| sync, | |
| default: { | |
| sync, | |
| }, | |
| }; | |
| }); | |
| vi.mock('node:os', async (importOriginal) => { | |
| const actual = await importOriginal(); | |
| return { | |
| ...(actual as object), | |
| platform: vi.fn(), | |
| }; | |
| }); | |
| const mockedGetPackageJson = vi.mocked(getPackageJson); | |
| const mockedCommandExistsSync = vi.mocked(commandExists.sync); | |
| const mockedOsPlatform = vi.mocked(os.platform); | |
| describe('loadSandboxConfig', () => { | |
| const originalEnv = { ...process.env }; | |
| beforeEach(() => { | |
| vi.resetAllMocks(); | |
| process.env = { ...originalEnv }; | |
| delete process.env['SANDBOX']; | |
| delete process.env['GEMINI_SANDBOX']; | |
| mockedGetPackageJson.mockResolvedValue({ | |
| config: { sandboxImageUri: 'default/image' }, | |
| }); | |
| }); | |
| afterEach(() => { | |
| process.env = originalEnv; | |
| }); | |
| it('should return undefined if sandbox is explicitly disabled via argv', async () => { | |
| const config = await loadSandboxConfig({}, { sandbox: false }); | |
| expect(config).toBeUndefined(); | |
| }); | |
| it('should return undefined if sandbox is explicitly disabled via settings', async () => { | |
| const config = await loadSandboxConfig({ tools: { sandbox: false } }, {}); | |
| expect(config).toBeUndefined(); | |
| }); | |
| it('should return undefined if sandbox is not configured', async () => { | |
| const config = await loadSandboxConfig({}, {}); | |
| expect(config).toBeUndefined(); | |
| }); | |
| it('should return undefined if already inside a sandbox (SANDBOX env var is set)', async () => { | |
| process.env['SANDBOX'] = '1'; | |
| const config = await loadSandboxConfig({}, { sandbox: true }); | |
| expect(config).toBeUndefined(); | |
| }); | |
| describe('with GEMINI_SANDBOX environment variable', () => { | |
| it('should use docker if GEMINI_SANDBOX=docker and it exists', async () => { | |
| process.env['GEMINI_SANDBOX'] = 'docker'; | |
| mockedCommandExistsSync.mockReturnValue(true); | |
| const config = await loadSandboxConfig({}, {}); | |
| expect(config).toEqual({ | |
| enabled: true, | |
| allowedPaths: [], | |
| networkAccess: true, | |
| command: 'docker', | |
| image: 'default/image', | |
| }); | |
| expect(mockedCommandExistsSync).toHaveBeenCalledWith('docker'); | |
| }); | |
| it('should throw if GEMINI_SANDBOX is an invalid command', async () => { | |
| process.env['GEMINI_SANDBOX'] = 'invalid-command'; | |
| await expect(loadSandboxConfig({}, {})).rejects.toThrow( | |
| "Invalid sandbox command 'invalid-command'. Must be one of docker, podman, sandbox-exec, runsc, lxc", | |
| ); | |
| }); | |
| it('should throw if GEMINI_SANDBOX command does not exist', async () => { | |
| process.env['GEMINI_SANDBOX'] = 'docker'; | |
| mockedCommandExistsSync.mockReturnValue(false); | |
| await expect(loadSandboxConfig({}, {})).rejects.toThrow( | |
| "Missing sandbox command 'docker' (from GEMINI_SANDBOX)", | |
| ); | |
| }); | |
| it('should use lxc if GEMINI_SANDBOX=lxc and it exists', async () => { | |
| process.env['GEMINI_SANDBOX'] = 'lxc'; | |
| mockedCommandExistsSync.mockReturnValue(true); | |
| const config = await loadSandboxConfig({}, {}); | |
| expect(config).toEqual({ | |
| enabled: true, | |
| allowedPaths: [], | |
| networkAccess: true, | |
| command: 'lxc', | |
| image: 'default/image', | |
| }); | |
| expect(mockedCommandExistsSync).toHaveBeenCalledWith('lxc'); | |
| }); | |
| it('should throw if GEMINI_SANDBOX=lxc but lxc command does not exist', async () => { | |
| process.env['GEMINI_SANDBOX'] = 'lxc'; | |
| mockedCommandExistsSync.mockReturnValue(false); | |
| await expect(loadSandboxConfig({}, {})).rejects.toThrow( | |
| "Missing sandbox command 'lxc' (from GEMINI_SANDBOX)", | |
| ); | |
| }); | |
| }); | |
| describe('with sandbox: true', () => { | |
| it('should use sandbox-exec on darwin if available', async () => { | |
| mockedOsPlatform.mockReturnValue('darwin'); | |
| mockedCommandExistsSync.mockImplementation( | |
| (cmd) => cmd === 'sandbox-exec', | |
| ); | |
| const config = await loadSandboxConfig({}, { sandbox: true }); | |
| expect(config).toEqual({ | |
| enabled: true, | |
| allowedPaths: [], | |
| networkAccess: true, | |
| command: 'sandbox-exec', | |
| image: 'default/image', | |
| }); | |
| }); | |
| it('should prefer sandbox-exec over docker on darwin', async () => { | |
| mockedOsPlatform.mockReturnValue('darwin'); | |
| mockedCommandExistsSync.mockReturnValue(true); // all commands exist | |
| const config = await loadSandboxConfig({}, { sandbox: true }); | |
| expect(config).toEqual({ | |
| enabled: true, | |
| allowedPaths: [], | |
| networkAccess: true, | |
| command: 'sandbox-exec', | |
| image: 'default/image', | |
| }); | |
| }); | |
| it('should use docker if available and sandbox is true', async () => { | |
| mockedOsPlatform.mockReturnValue('linux'); | |
| mockedCommandExistsSync.mockImplementation((cmd) => cmd === 'docker'); | |
| const config = await loadSandboxConfig({ tools: { sandbox: true } }, {}); | |
| expect(config).toEqual({ | |
| enabled: true, | |
| allowedPaths: [], | |
| networkAccess: true, | |
| command: 'docker', | |
| image: 'default/image', | |
| }); | |
| }); | |
| it('should use podman if available and docker is not', async () => { | |
| mockedOsPlatform.mockReturnValue('linux'); | |
| mockedCommandExistsSync.mockImplementation((cmd) => cmd === 'podman'); | |
| const config = await loadSandboxConfig({}, { sandbox: true }); | |
| expect(config).toEqual({ | |
| enabled: true, | |
| allowedPaths: [], | |
| networkAccess: true, | |
| command: 'podman', | |
| image: 'default/image', | |
| }); | |
| }); | |
| it('should throw if sandbox: true but no command is found', async () => { | |
| mockedOsPlatform.mockReturnValue('linux'); | |
| mockedCommandExistsSync.mockReturnValue(false); | |
| await expect(loadSandboxConfig({}, { sandbox: true })).rejects.toThrow( | |
| 'GEMINI_SANDBOX is true but failed to determine command for sandbox; ' + | |
| 'install docker or podman or specify command in GEMINI_SANDBOX', | |
| ); | |
| }); | |
| }); | |
| describe("with sandbox: 'command'", () => { | |
| it('should use the specified command if it exists', async () => { | |
| mockedCommandExistsSync.mockReturnValue(true); | |
| const config = await loadSandboxConfig({}, { sandbox: 'podman' }); | |
| expect(config).toEqual({ | |
| enabled: true, | |
| allowedPaths: [], | |
| networkAccess: true, | |
| command: 'podman', | |
| image: 'default/image', | |
| }); | |
| expect(mockedCommandExistsSync).toHaveBeenCalledWith('podman'); | |
| }); | |
| it('should throw if the specified command does not exist', async () => { | |
| mockedCommandExistsSync.mockReturnValue(false); | |
| await expect( | |
| loadSandboxConfig({}, { sandbox: 'podman' }), | |
| ).rejects.toThrow( | |
| "Missing sandbox command 'podman' (from GEMINI_SANDBOX)", | |
| ); | |
| }); | |
| it('should throw if the specified command is invalid', async () => { | |
| await expect( | |
| loadSandboxConfig({}, { sandbox: 'invalid-command' }), | |
| ).rejects.toThrow( | |
| "Invalid sandbox command 'invalid-command'. Must be one of docker, podman, sandbox-exec, runsc, lxc", | |
| ); | |
| }); | |
| }); | |
| describe('image configuration', () => { | |
| it('should use image from GEMINI_SANDBOX_IMAGE env var if set', async () => { | |
| process.env['GEMINI_SANDBOX_IMAGE'] = 'env/image'; | |
| process.env['GEMINI_SANDBOX'] = 'docker'; | |
| mockedCommandExistsSync.mockReturnValue(true); | |
| const config = await loadSandboxConfig({}, {}); | |
| expect(config).toEqual({ | |
| enabled: true, | |
| allowedPaths: [], | |
| networkAccess: true, | |
| command: 'docker', | |
| image: 'env/image', | |
| }); | |
| }); | |
| it('should use image from package.json if env var is not set', async () => { | |
| process.env['GEMINI_SANDBOX'] = 'docker'; | |
| mockedCommandExistsSync.mockReturnValue(true); | |
| const config = await loadSandboxConfig({}, {}); | |
| expect(config).toEqual({ | |
| enabled: true, | |
| allowedPaths: [], | |
| networkAccess: true, | |
| command: 'docker', | |
| image: 'default/image', | |
| }); | |
| }); | |
| it('should return undefined if command is found but no image is configured', async () => { | |
| mockedGetPackageJson.mockResolvedValue({}); // no sandboxImageUri | |
| process.env['GEMINI_SANDBOX'] = 'docker'; | |
| mockedCommandExistsSync.mockReturnValue(true); | |
| const config = await loadSandboxConfig({}, {}); | |
| expect(config).toBeUndefined(); | |
| }); | |
| }); | |
| describe('truthy/falsy sandbox values', () => { | |
| beforeEach(() => { | |
| mockedOsPlatform.mockReturnValue('linux'); | |
| mockedCommandExistsSync.mockImplementation((cmd) => cmd === 'docker'); | |
| }); | |
| it.each([true, 'true', '1'])( | |
| 'should enable sandbox for value: %s', | |
| async (value) => { | |
| const config = await loadSandboxConfig({}, { sandbox: value }); | |
| expect(config).toEqual({ | |
| enabled: true, | |
| allowedPaths: [], | |
| networkAccess: true, | |
| command: 'docker', | |
| image: 'default/image', | |
| }); | |
| }, | |
| ); | |
| it.each([false, 'false', '0', undefined, null, ''])( | |
| 'should disable sandbox for value: %s', | |
| async (value) => { | |
| // `null` is not a valid type for the arg, but good to test falsiness | |
| const config = await loadSandboxConfig({}, { sandbox: value }); | |
| expect(config).toBeUndefined(); | |
| }, | |
| ); | |
| }); | |
| describe('with SandboxConfig object in settings', () => { | |
| beforeEach(() => { | |
| mockedOsPlatform.mockReturnValue('linux'); | |
| mockedCommandExistsSync.mockImplementation((cmd) => cmd === 'docker'); | |
| }); | |
| it('should support object structure with enabled: true', async () => { | |
| const config = await loadSandboxConfig( | |
| { | |
| tools: { | |
| sandbox: { | |
| enabled: true, | |
| allowedPaths: ['/tmp'], | |
| networkAccess: true, | |
| }, | |
| }, | |
| }, | |
| {}, | |
| ); | |
| expect(config).toEqual({ | |
| enabled: true, | |
| allowedPaths: ['/tmp'], | |
| networkAccess: true, | |
| command: 'docker', | |
| image: 'default/image', | |
| }); | |
| }); | |
| it('should support object structure with explicit command', async () => { | |
| mockedCommandExistsSync.mockImplementation((cmd) => cmd === 'podman'); | |
| const config = await loadSandboxConfig( | |
| { | |
| tools: { | |
| sandbox: { | |
| enabled: true, | |
| command: 'podman', | |
| allowedPaths: [], | |
| networkAccess: true, | |
| }, | |
| }, | |
| }, | |
| {}, | |
| ); | |
| expect(config?.command).toBe('podman'); | |
| }); | |
| it('should support object structure with custom image', async () => { | |
| const config = await loadSandboxConfig( | |
| { | |
| tools: { | |
| sandbox: { | |
| enabled: true, | |
| image: 'custom/image', | |
| allowedPaths: [], | |
| networkAccess: true, | |
| }, | |
| }, | |
| }, | |
| {}, | |
| ); | |
| expect(config?.image).toBe('custom/image'); | |
| }); | |
| it('should return undefined if enabled is false in object', async () => { | |
| const config = await loadSandboxConfig( | |
| { | |
| tools: { | |
| sandbox: { | |
| enabled: false, | |
| allowedPaths: [], | |
| networkAccess: true, | |
| }, | |
| }, | |
| }, | |
| {}, | |
| ); | |
| expect(config).toBeUndefined(); | |
| }); | |
| it('should prioritize CLI flag over settings object', async () => { | |
| const config = await loadSandboxConfig( | |
| { | |
| tools: { | |
| sandbox: { | |
| enabled: true, | |
| allowedPaths: ['/settings-path'], | |
| networkAccess: true, | |
| }, | |
| }, | |
| }, | |
| { sandbox: false }, | |
| ); | |
| expect(config).toBeUndefined(); | |
| }); | |
| }); | |
| describe('with sandbox: runsc (gVisor)', () => { | |
| beforeEach(() => { | |
| mockedOsPlatform.mockReturnValue('linux'); | |
| mockedCommandExistsSync.mockReturnValue(true); | |
| }); | |
| it('should use runsc via CLI argument on Linux', async () => { | |
| const config = await loadSandboxConfig({}, { sandbox: 'runsc' }); | |
| expect(config).toEqual({ | |
| enabled: true, | |
| allowedPaths: [], | |
| networkAccess: true, | |
| command: 'runsc', | |
| image: 'default/image', | |
| }); | |
| expect(mockedCommandExistsSync).toHaveBeenCalledWith('runsc'); | |
| expect(mockedCommandExistsSync).toHaveBeenCalledWith('docker'); | |
| }); | |
| it('should use runsc via GEMINI_SANDBOX environment variable', async () => { | |
| process.env['GEMINI_SANDBOX'] = 'runsc'; | |
| const config = await loadSandboxConfig({}, {}); | |
| expect(config).toEqual({ | |
| enabled: true, | |
| allowedPaths: [], | |
| networkAccess: true, | |
| command: 'runsc', | |
| image: 'default/image', | |
| }); | |
| expect(mockedCommandExistsSync).toHaveBeenCalledWith('runsc'); | |
| expect(mockedCommandExistsSync).toHaveBeenCalledWith('docker'); | |
| }); | |
| it('should use runsc via settings file', async () => { | |
| const config = await loadSandboxConfig( | |
| { tools: { sandbox: 'runsc' } }, | |
| {}, | |
| ); | |
| expect(config).toEqual({ | |
| enabled: true, | |
| allowedPaths: [], | |
| networkAccess: true, | |
| command: 'runsc', | |
| image: 'default/image', | |
| }); | |
| expect(mockedCommandExistsSync).toHaveBeenCalledWith('runsc'); | |
| expect(mockedCommandExistsSync).toHaveBeenCalledWith('docker'); | |
| }); | |
| it('should prioritize GEMINI_SANDBOX over CLI and settings', async () => { | |
| process.env['GEMINI_SANDBOX'] = 'runsc'; | |
| const config = await loadSandboxConfig( | |
| { tools: { sandbox: 'docker' } }, | |
| { sandbox: 'podman' }, | |
| ); | |
| expect(config).toEqual({ | |
| enabled: true, | |
| allowedPaths: [], | |
| networkAccess: true, | |
| command: 'runsc', | |
| image: 'default/image', | |
| }); | |
| }); | |
| it('should reject runsc on macOS (Linux-only)', async () => { | |
| mockedOsPlatform.mockReturnValue('darwin'); | |
| await expect(loadSandboxConfig({}, { sandbox: 'runsc' })).rejects.toThrow( | |
| 'gVisor (runsc) sandboxing is only supported on Linux', | |
| ); | |
| }); | |
| it('should reject runsc on Windows (Linux-only)', async () => { | |
| mockedOsPlatform.mockReturnValue('win32'); | |
| await expect(loadSandboxConfig({}, { sandbox: 'runsc' })).rejects.toThrow( | |
| 'gVisor (runsc) sandboxing is only supported on Linux', | |
| ); | |
| }); | |
| it('should throw if runsc binary not found', async () => { | |
| mockedCommandExistsSync.mockReturnValue(false); | |
| await expect(loadSandboxConfig({}, { sandbox: 'runsc' })).rejects.toThrow( | |
| "Missing sandbox command 'runsc' (from GEMINI_SANDBOX)", | |
| ); | |
| }); | |
| it('should throw if Docker not available (runsc requires Docker)', async () => { | |
| mockedCommandExistsSync.mockImplementation((cmd) => cmd === 'runsc'); | |
| await expect(loadSandboxConfig({}, { sandbox: 'runsc' })).rejects.toThrow( | |
| "runsc (gVisor) requires Docker. Install Docker, or use sandbox: 'docker'.", | |
| ); | |
| }); | |
| it('should NOT auto-detect runsc when both runsc and docker available', async () => { | |
| mockedCommandExistsSync.mockImplementation( | |
| (cmd) => cmd === 'runsc' || cmd === 'docker', | |
| ); | |
| const config = await loadSandboxConfig({}, { sandbox: true }); | |
| expect(config?.command).toBe('docker'); | |
| expect(config?.command).not.toBe('runsc'); | |
| }); | |
| }); | |
| }); | |