google-gemini / google-gemini/gemini-cli

security(sandbox): host shell injection via BUILD_SANDBOX path interpolation in sandbox.ts

Open
#29,070 1 comment 0 reactions 0 assignees View on GitHub
area/security status/need-triage
Dominant language
TypeScript
Stars
107k
Forks
14.6k
Avg merge
2d 3h
Merged PRs (30d)
45

Description

### What happened?

When `BUILD_SANDBOX=1` is set, `packages/cli/src/utils/sandbox.ts` builds a custom sandbox image. The code interpolates filesystem paths directly into a shell command string executed via `execSync` with `shell: true` (default):

`packages/cli/src/utils/sandbox.ts` (main @ 812f7a2bc) lines 338-366:

```ts
const projectSandboxDockerfile = path.join(GEMINI_DIR, 'sandbox.Dockerfile');
let buildArgs = '';
if (isCustomProjectSandbox) {
buildArgs += `-f ${path.resolve(projectSandboxDockerfile)} -i ${image}`;
}
execSync(`cd ${gcRoot} && node scripts/build_sandbox.js -s ${buildArgs}`, {
stdio: 'inherit',
env: { ...process.env, GEMINI_SANDBOX: command },
});
```

`gcRoot` is derived from `fs.realpathSync(process.argv[1]).split('/packages/')[0]` and `buildArgs` includes `path.resolve(projectSandboxDockerfile)`. Neither is shell-escaped. Both can contain spaces, `$`, `;`, backticks, or other shell metacharacters if the repository is checked out into a directory with those characters (e.g., attacker-controlled ZIP, shared filesystem, or `git clone` into `/tmp/evil; touch /tmp/pwned; echo`).

The same pattern appears at lines 551-564 for network creation:

```ts
execSync(
`${command} network inspect ${SANDBOX_NETWORK_NAME} || ${command} network create ${networkFlags} ${SANDBOX_NETWORK_NAME}`,
{ stdio: 'ignore' },
);
```

While `command` and `image` are validated, file paths are not. An attacker controlling the checkout path can execute arbitrary host commands outside the sandbox with the user's UID before the container even starts.

Note: the `image` variable is validated (`/^[a-zA-Z0-9_.:/-]+$/`), but `gcRoot` and `projectSandboxDockerfile` paths are not.

### What did you expect to happen?

Filesystem paths interpolated into shell strings must be shell-escaped (e.g., via `quote([path])` from `shell-quote`, already imported in this file) or the command should use `execFile`/`spawn` with `shell: false` and explicit argv arrays.

Suggested fix:

```ts
execSync(`cd ${quote([gcRoot])} && node scripts/build_sandbox.js -s ${buildArgs}`, ...)
```

or better, use `spawn` with `shell: false`:
```ts
execFileSync('node', ['scripts/build_sandbox.js', '-s', buildArgs], { cwd: gcRoot })
```

Similarly for network commands, use `spawn`/`execFile` with array args (as done correctly for `LXC proxyCommand` at lines 796-826).

### Client information

- Source-level finding verified against upstream `main` at commit `812f7a2bc`
- Affects all platforms when `BUILD_SANDBOX=1` is set (Linux/macOS container path; Windows not applicable for this code path but same pattern exists)
- File: `packages/cli/src/utils/sandbox.ts:338-366, 551-564`

### Login information

Not applicable.

### Anything else we need to know?

Sources:
- https://github.com/google-gemini/gemini-cli/blob/812f7a2bc/packages/cli/src/utils/sandbox.ts#L338-L366
- https://github.com/google-gemini/gemini-cli/blob/812f7a2bc/packages/cli/src/utils/sandbox.ts#L551-L564
- Related: LXC proxyCommand handling at lines 796-826 correctly uses `spawn(..., {shell: false})` — demonstrating the intended safe pattern

Searched existing issues for "BUILD_SANDBOX", "sandbox shell injection", "sandbox injection" — no open duplicate found (only closed build-failure issues #14506, #3404, etc.).

Contributor guide

Open the contributing guide

Research direction

Start in packages/cli/src/utils/sandbox.ts at lines 338-366 and 551-564, tracing the BUILD_SANDBOX build and network command entry points. Compare them with the safe LXC proxyCommand handling at lines 796-826 and verify behavior with repository paths containing spaces and shell metacharacters. Done means checkout-derived paths cannot be interpreted as shell syntax before the sandbox starts.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
cli, security
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
74/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.