summaryrefslogtreecommitdiff
path: root/packages/cli/src/config/sandboxConfig.ts
blob: 69a5490086a82cee9a13163d5c0b835ed3c0ddfb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
/**
 * @license
 * Copyright 2025 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */

import { SandboxConfig } from '@gemini-cli/core';
import commandExists from 'command-exists';
import * as os from 'node:os';
import { getPackageJson } from '../utils/package.js';
import { Settings } from './settings.js';

// This is a stripped-down version of the CliArgs interface from config.ts
// to avoid circular dependencies.
interface SandboxCliArgs {
  sandbox?: boolean | string;
  'sandbox-image'?: string;
}

const VALID_SANDBOX_COMMANDS: ReadonlyArray<SandboxConfig['command']> = [
  'docker',
  'podman',
  'sandbox-exec',
];

function isSandboxCommand(value: string): value is SandboxConfig['command'] {
  return (VALID_SANDBOX_COMMANDS as readonly string[]).includes(value);
}

function getSandboxCommand(
  sandbox?: boolean | string,
): SandboxConfig['command'] | '' {
  // note environment variable takes precedence over argument (from command line or settings)
  sandbox = process.env.GEMINI_SANDBOX?.toLowerCase().trim() ?? sandbox;
  if (sandbox === '1' || sandbox === 'true') sandbox = true;
  else if (sandbox === '0' || sandbox === 'false') sandbox = false;

  if (sandbox === false) {
    return '';
  }

  if (typeof sandbox === 'string' && sandbox !== '') {
    if (!isSandboxCommand(sandbox)) {
      console.error(
        `ERROR: invalid sandbox command '${sandbox}'. Must be one of ${VALID_SANDBOX_COMMANDS.join(
          ', ',
        )}`,
      );
      process.exit(1);
    }
    // confirm that specfied command exists
    if (commandExists.sync(sandbox)) {
      return sandbox;
    }
    console.error(
      `ERROR: missing sandbox command '${sandbox}' (from GEMINI_SANDBOX)`,
    );
    process.exit(1);
  }

  // look for seatbelt, docker, or podman, in that order
  // for container-based sandboxing, require sandbox to be enabled explicitly
  if (os.platform() === 'darwin' && commandExists.sync('sandbox-exec')) {
    return 'sandbox-exec';
  } else if (commandExists.sync('docker') && sandbox === true) {
    return 'docker';
  } else if (commandExists.sync('podman') && sandbox === true) {
    return 'podman';
  }

  // throw an error if user requested sandbox but no command was found
  if (sandbox === true) {
    console.error(
      'ERROR: GEMINI_SANDBOX is true but failed to determine command for sandbox; ' +
        'install docker or podman or specify command in GEMINI_SANDBOX',
    );
    process.exit(1);
  }

  return '';
}

export async function loadSandboxConfig(
  settings: Settings,
  argv: SandboxCliArgs,
): Promise<SandboxConfig | undefined> {
  const sandboxOption = argv.sandbox ?? settings.sandbox;
  const sandboxCommand = getSandboxCommand(sandboxOption);
  if (!sandboxCommand) {
    return undefined;
  }

  const packageJson = await getPackageJson();
  return {
    command: sandboxCommand,
    image:
      argv['sandbox-image'] ??
      process.env.GEMINI_SANDBOX_IMAGE ??
      packageJson?.config?.sandboxImageUri ??
      'gemini-cli-sandbox',
  };
}