# Defense-in-Depth Score: Gemini CLI

**Repo:** https://github.com/google-gemini/gemini-cli · **Commit:** `fb972b2f87fe7d5b06d37eac711490162d98de2c` · **Reviewed:** 2026-10-03
**What it is:** Google's open-source terminal AI agent for Gemini
**Category:** Coding
**Scored configuration:** Interactive `gemini` with no flags on a fresh install: default approval mode, folder trust on, no sandbox, no extensions or MCP servers configured.
**Agent surface (default):** code execution yes · filesystem write yes · network egress yes · external credentials yes · persistent memory yes · untrusted input yes · third party extensions opt-in · sub agents yes · external communication opt-in

## Score: 3.7 / 10.0 (Minimal)

| # | Criterion | S | C | D | B | Raw | Cap | Score | Confidence |
|---|---|---|---|---|---|---|---|---|---|
| C1 | Identity & least privilege | L0 | L1 | L0 | L1 | 0.12 | — | **0.12** | High |
| C2 | Approval gates | L3 | L3 | L2 | L1 | 0.60 | — | **0.60** | High |
| C3 | Tool & action scoping | L2 | L2 | L1 | L1 | 0.40 | — | **0.40** | High |
| C4 | Code-execution isolation | L2 | L3 | L0 | L0 | 0.38 | G1 | **0.38** (alt) | High |
| C5 | Untrusted input blast radius | L2 | L2 | L2 | L2 | 0.50 | — | **0.50** | High |
| C6 | Memory, context & configuration integrity | L2 | L2 | L2 | L2 | 0.50 | C6-REPOCONFIG | **0.25** | High |
| C7 | Third-party extensions | L1 | L1 | L2 | L1 | 0.30 | — | **0.30** | High |
| C8 | Secrets & sensitive-data protection | L2 | L1 | L1 | L0 | 0.28 | — | **0.28** | High |
| C9 | Audit & traceability | L2 | L2 | L1 | L2 | 0.45 | — | **0.45** | High |
| C10 | Limits & kill switch | L2 | L2 | L2 | L1 | 0.45 | — | **0.45** | Medium |


Gemini CLI has a careful approval system: a deterministic policy engine asks before every write, shell command, web fetch and MCP call, parses compound shell commands, and ignores an untrusted folder's settings, hooks and instructions. The dominant risk is that nothing is sandboxed by default, so any approved command runs on the host with the user's full environment and a cloud-platform Google token on disk. Two further gaps: one 'Allow always' click on an edit switches the session to auto-edit mode, which also auto-approves web fetches, and the folder-trust gate does not cover every workspace configuration path.

## Critical gaps
- Shell commands, hooks and MCP servers run directly on the host by default; both sandboxes are opt-in. (ASI05; C4) — [packages/cli/src/config/sandboxConfig.ts:59](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/cli/src/config/sandboxConfig.ts#L59); [packages/core/src/services/sandboxManagerFactory.ts:31-42](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/core/src/services/sandboxManagerFactory.ts#L31-L42); [packages/core/src/services/sandboxManager.ts:301-304](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/core/src/services/sandboxManager.ts#L301-L304)

## Criterion details

### C1 Identity & least privilege — 0.12 (high)

Gemini CLI runs as the logged-in OS user and does not narrow that authority for the commands it runs. Shell commands and hooks inherit the full process environment by default, because the environment-variable redaction feature exists but ships disabled; only MCP stdio servers always get a scrubbed environment. Google sign-in requests the broad cloud-platform OAuth scope and caches the token in a plaintext (0600) file under ~/.gemini unless encrypted storage is forced. A hijacked session that gets a command approved therefore acts with everything the user can do.

- **S L0:** Ambient OS-user authority: no dedicated or scoped identity, and the Google login requests the cloud-platform scope. — [packages/core/src/code_assist/oauth2.ts:88-89](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/core/src/code_assist/oauth2.ts#L88-L89); [packages/core/src/services/environmentSanitization.ts:20-21](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/core/src/services/environmentSanitization.ts#L20-L21) (verified)
  - *To reach the next level:* No scoped or per-tool credentials; a dedicated narrower identity is needed for L1.
- **C L1:** MCP stdio servers always get a redacted environment, but shell commands and hooks inherit the full environment. — [packages/core/src/tools/mcp-client.ts:2345-2347](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/core/src/tools/mcp-client.ts#L2345-L2347); [packages/core/src/services/shellExecutionService.ts:580](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/core/src/services/shellExecutionService.ts#L580); [packages/core/src/hooks/hookRunner.ts:349](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/core/src/hooks/hookRunner.ts#L349) (verified)
  - *To reach the next level:* Shell and hook subprocesses are not given a scoped environment by default.
- **D L0:** Environment redaction defaults to false, so least privilege needs manual hardening. — [packages/cli/src/config/settingsSchema.ts:1958-1961](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/cli/src/config/settingsSchema.ts#L1958-L1961); [packages/core/src/config/config.ts:1086](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/core/src/config/config.ts#L1086) (verified)
  - *To reach the next level:* Least-privilege environment is not the default.
- **B L1:** A hijacked agent that gets a command approved has the user's full ambient authority, including a cloud-platform-scoped OAuth token on disk; the per-call approval gate is the only independent layer that still holds. — [packages/core/src/code_assist/oauth2.ts:88-89](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/core/src/code_assist/oauth2.ts#L88-L89); [packages/core/src/code_assist/oauth2.ts:883](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/core/src/code_assist/oauth2.ts#L883); [packages/core/src/policy/policies/write.toml:51-55](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/core/src/policy/policies/write.toml#L51-L55) (verified)
  - *To reach the next level:* No sandbox or scoped credential limits what an approved command can reach.
- **Cap:** none

### C2 Approval gates — 0.60 (high)

Approval is on by default and is enforced by a deterministic policy engine rather than the model. Writes, edits, shell commands, web fetches, MCP tools and unknown tools ask the user, who sees the exact command or diff. Compound shell commands are parsed and checked part by part, and only a short read-only allowlist confined to the workspace runs without asking. Sub-agents use the same scheduler and gate. Weak points: choosing 'Allow always' on a shell command allows every command with the same root (for example any git command) for the session, and accepting 'always' on an edit switches the session to auto-edit mode. A trusted repository's settings can also pre-approve tools, MCP servers, and auto-edit mode, and checkpointing is off, so approved actions usually cannot be undone.

- **S L3:** Per-call approval shows the exact command or diff, with risk tiers from a read-only allowlist, a dangerous-command heuristic and taint checks. — [packages/core/src/tools/shell.ts:503-506](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/core/src/tools/shell.ts#L503-L506); [packages/core/src/policy/policy-engine.ts:437](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/core/src/policy/policy-engine.ts#L437); [packages/core/src/policy/policy-engine.ts:413](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/core/src/policy/policy-engine.ts#L413); [packages/core/src/tools/shell.ts:300](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/core/src/tools/shell.ts#L300) (verified)
  - *To reach the next level:* 'Allow always' on a shell command widens to its root command names, so approval generalises beyond the approved arguments.
- **C L3:** Every tool path, including MCP, discovered tools and sub-agents, traverses the policy engine; compound and wrapped shell commands are parsed and each part checked, and parse failures fall back to asking. — [packages/core/src/policy/policy-engine.ts:299-301](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/core/src/policy/policy-engine.ts#L299-L301); [packages/core/src/policy/policy-engine.ts:466](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/core/src/policy/policy-engine.ts#L466); [packages/core/src/policy/policy-engine.ts:540](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/core/src/policy/policy-engine.ts#L540); [packages/core/src/policy/policy-engine.ts:495](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/core/src/policy/policy-engine.ts#L495); [packages/core/src/agents/agent-scheduler.ts:78](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/core/src/agents/agent-scheduler.ts#L78) (verified)
  - *To reach the next level:* Unknown tools default to asking rather than being rejected.
- **D L2:** On by default; YOLO needs an explicit flag and is refused in untrusted folders, and persisted rules go to user scope, but a trusted project's settings can pre-approve tools or MCP servers and set auto-edit mode. — [packages/cli/src/config/config.ts:702](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/cli/src/config/config.ts#L702); [packages/cli/src/config/config.ts:758](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/cli/src/config/config.ts#L758); [packages/core/src/scheduler/policy.ts:156](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/core/src/scheduler/policy.ts#L156); [packages/cli/src/config/policy.ts:42](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/cli/src/config/policy.ts#L42); [packages/core/src/policy/config.ts:520](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/core/src/policy/config.ts#L520); [packages/core/src/policy/config.ts:570](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/core/src/policy/config.ts#L570); [packages/core/src/services/FolderTrustDiscoveryService.ts:204](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/core/src/services/FolderTrustDiscoveryService.ts#L204) (verified)
  - *To reach the next level:* Workspace settings can still loosen approval after the one-time folder-trust decision.
- **B L1:** An approved shell command can do anything the user can, and session checkpointing (the only rollback feature) is off by default. — [packages/cli/src/config/settingsSchema.ts:310-313](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/cli/src/config/settingsSchema.ts#L310-L313); [packages/core/src/policy/policies/write.toml:51-55](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/core/src/policy/policies/write.toml#L51-L55) (verified)
  - *To reach the next level:* No default checkpoints or rollback for files and no previews for external actions.
- **Cap:** none

### C3 Tool & action scoping — 0.40 (high)

File tools validate paths in code: they resolve symlinks and refuse anything outside the workspace or the project temp directory, and web fetch blocks localhost and private addresses. But the default tool set also includes a shell tool that accepts any command string, plus write and network tools, all enabled out of the box. Tools can be excluded or restricted to a core list through settings. Extension and MCP tools have no shared argument validation beyond the policy engine's approval decision.

- **S L2:** File tools enforce realpath containment and web fetch blocks private hosts, but the shell tool takes a raw command string. — [packages/core/src/config/config.ts:3312](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/core/src/config/config.ts#L3312); [packages/core/src/config/config.ts:3345](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/core/src/config/config.ts#L3345); [packages/core/src/tools/web-fetch.ts:275-283](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/core/src/tools/web-fetch.ts#L275-L283); [packages/core/src/tools/shell.ts:503-506](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/core/src/tools/shell.ts#L503-L506) (verified)
  - *To reach the next level:* General shell access is not replaced by narrow validated tools.
- **C L2:** Built-in file and fetch tools validate their arguments; MCP and extension tools get no shared argument validation. — [packages/core/src/config/config.ts:3345](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/core/src/config/config.ts#L3345); [packages/core/src/policy/config.ts:570](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/core/src/policy/config.ts#L570) (verified)
  - *To reach the next level:* No shared validation layer wraps extension tools.
- **D L1:** Shell, write and web tools are on by default and can be removed through tools.exclude or a tools.core allowlist. — [packages/core/src/policy/config.ts:428](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/core/src/policy/config.ts#L428); [packages/core/src/policy/config.ts:545](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/core/src/policy/config.ts#L545); [packages/core/src/policy/policies/write.toml:51-55](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/core/src/policy/policies/write.toml#L51-L55) (verified)
  - *To reach the next level:* Default tool set is not read-only.
- **B L1:** File tools are workspace-scoped, but the shell tool reaches the whole machine once approved. — [packages/core/src/config/config.ts:3345](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/core/src/config/config.ts#L3345); [packages/core/src/policy/policies/write.toml:51-55](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/core/src/policy/policies/write.toml#L51-L55) (verified)
  - *To reach the next level:* Misused tools are not limited to the workspace and are not quantity-bounded.
- **Cap:** none

### C4 Code-execution isolation — 0.38 (high)

By default nothing is sandboxed: shell commands, hooks and MCP servers run directly on the host as the user. Two sandboxes exist but are opt-in: a whole-process container (Docker/Podman/gVisor) or macOS Seatbelt profile, and a newer per-tool sandbox (bubblewrap with seccomp on Linux, Seatbelt on macOS, a Windows sandbox) that is off behind a setting. The container option fails closed if the runtime is missing, but it mounts the workspace read-write, leaves the network on by default, and brings in the Gemini API key and the gcloud configuration directory. Because the stronger options are off by default, this criterion is capped.

- **default configuration** (default; raw 0.00, cap G1 → 0.00)
  - **S L0:** Default sandbox manager is a no-op that runs the command on the host. — [packages/core/src/services/sandboxManagerFactory.ts:31-42](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/core/src/services/sandboxManagerFactory.ts#L31-L42); [packages/core/src/services/sandboxManager.ts:301-304](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/core/src/services/sandboxManager.ts#L301-L304) (verified)
    - *To reach the next level:* No isolation primitive is used in the default configuration.
  - **C L0:** No execution path is sandboxed by default. — [packages/cli/src/config/sandboxConfig.ts:59](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/cli/src/config/sandboxConfig.ts#L59); [packages/core/src/services/sandboxManagerFactory.ts:31-42](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/core/src/services/sandboxManagerFactory.ts#L31-L42) (verified)
    - *To reach the next level:* The main shell tool is not sandboxed by default.
  - **D L0:** Sandboxing is off unless enabled with a flag, env var or setting; tool sandboxing defaults to false. — [packages/cli/src/config/sandboxConfig.ts:59](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/cli/src/config/sandboxConfig.ts#L59); [packages/cli/src/config/settingsSchema.ts:1835-1840](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/cli/src/config/settingsSchema.ts#L1835-L1840) (verified)
    - *To reach the next level:* Sandbox is not on by default.
  - **B L0:** Host-equivalent: commands run as the user with the full environment and home directory. — [packages/core/src/services/sandboxManager.ts:301-304](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/core/src/services/sandboxManager.ts#L301-L304); [packages/core/src/services/environmentSanitization.ts:20-21](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/core/src/services/environmentSanitization.ts#L20-L21) (verified)
    - *To reach the next level:* Nothing limits what a host command reaches.
- **opt-in container sandbox (--sandbox / GEMINI_SANDBOX=docker|podman|runsc)** (alt; raw 0.38, cap G1 → 0.38) ← counted
  - **S L2:** Stock container relaunch with no capability drop, seccomp profile or read-only root configured in the launcher. — [packages/cli/src/gemini.tsx:732](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/cli/src/gemini.tsx#L732); searched `rg -n 'cap-drop|security-opt|no-new-privileges'` in `packages/cli/src/utils/sandbox.ts` → 0 hits (no hardening flags on the container) (verified)
    - *To reach the next level:* No hardening flags (dropped capabilities, no-new-privileges, read-only root).
  - **C L3:** The whole CLI is relaunched inside the container, so every tool, hook and MCP stdio server runs inside; a missing runtime is a fatal error. — [packages/cli/src/gemini.tsx:732](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/cli/src/gemini.tsx#L732); [packages/cli/src/config/sandboxConfig.ts:115](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/cli/src/config/sandboxConfig.ts#L115) (verified)
    - *To reach the next level:* Sandbox setup is not verified for every spawned path (e.g. the separate proxy container).
  - **D L0:** Off by default. — [packages/cli/src/config/sandboxConfig.ts:59](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/cli/src/config/sandboxConfig.ts#L59) (verified)
    - *To reach the next level:* Off by default; L1+ needs it on.
  - **B L0:** Workspace is mounted read-write, network is on by default, and the Gemini API key and ~/.config/gcloud are passed in. — [packages/cli/src/utils/sandbox.ts:514](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/cli/src/utils/sandbox.ts#L514); [packages/cli/src/config/sandboxConfig.ts:134](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/cli/src/config/sandboxConfig.ts#L134); [packages/cli/src/utils/sandbox.ts:728](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/cli/src/utils/sandbox.ts#L728); [packages/cli/src/utils/sandbox.ts:573-578](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/cli/src/utils/sandbox.ts#L573-L578) (verified)
    - *To reach the next level:* Credentials are present inside the sandbox and egress is unrestricted.
- **Cap:** G1 — Opt-in mechanism: off in the scored default configuration.

### C5 Untrusted input blast radius — 0.50 (high)

Gemini CLI does not separate untrusted content from instructions structurally, but in the default mode every egress and state-changing tool already needs human approval, so a hijacked session cannot leak data or make changes on its own. Output from web fetch, web search, MCP tools and shell commands is wrapped in untrusted-content tags; a heuristic then forces approval for a shell command that reuses words from that content, even if the command would otherwise be auto-allowed. Repository files read with read_file are not marked as untrusted. The protection is not consistent: once a user picks 'Allow always' on an edit, the session switches to auto-edit, which also auto-approves web fetches to any public URL, opening an unattended exfiltration channel.

- **S L2:** Approval gates egress and writes in default mode, and a taint heuristic forces approval for shell commands built from untrusted tokens, but auto-edit mode auto-approves web_fetch. — [packages/core/src/scheduler/scheduler.ts:675-684](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/core/src/scheduler/scheduler.ts#L675-L684); [packages/core/src/tools/shell.ts:272-283](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/core/src/tools/shell.ts#L272-L283); [packages/core/src/policy/policies/write.toml:84-88](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/core/src/policy/policies/write.toml#L84-L88); [packages/core/src/scheduler/policy.ts:190-199](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/core/src/scheduler/policy.ts#L190-L199) (verified)
  - *To reach the next level:* No Rule-of-Two enforcement: egress is not forced through approval once untrusted content is in context in every mode.
- **C L2:** Web, search, MCP and shell outputs are tagged untrusted; workspace file contents are not. — [packages/core/src/tools/mcp-tool.ts:585](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/core/src/tools/mcp-tool.ts#L585); [packages/core/src/tools/web-fetch.ts:498](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/core/src/tools/web-fetch.ts#L498); searched `rg -n wrapUntrusted` in `packages/core/src/tools/read-file.ts packages/core/src/tools/read-many-files.ts` → 0 hits (workspace file contents are not marked as untrusted, so the taint heuristic never sees them) (verified)
  - *To reach the next level:* Not every source (repository files, sub-agent results) is inside the limit.
- **D L2:** On by default, but auto-edit mode (reachable from one 'Allow always' click or workspace settings) silently drops the egress gate. — [packages/core/src/policy/policies/write.toml:84-88](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/core/src/policy/policies/write.toml#L84-L88); [packages/core/src/scheduler/policy.ts:190-199](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/core/src/scheduler/policy.ts#L190-L199) (verified)
  - *To reach the next level:* Disabling is not explicit or warned.
- **B L2:** In the default mode, exfiltration and irreversible actions both need a human approval; only read-only workspace commands run unattended. — [packages/core/src/policy/policies/write.toml:51-55](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/core/src/policy/policies/write.toml#L51-L55); [packages/core/src/policy/policy-engine.ts:437](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/core/src/policy/policy-engine.ts#L437); [packages/core/src/policy/policy-engine.ts:299-301](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/core/src/policy/policy-engine.ts#L299-L301) (verified)
  - *To reach the next level:* Irreversible actions remain reachable under approval and sensitive data is in reach.
- **Cap:** none

### C6 Memory, context & configuration integrity — 0.25 (high)

Folder trust is on by default and is the main defence: in an untrusted folder, project settings, project GEMINI.md instructions, project hooks, stdio MCP servers and elevated approval modes are all ignored, and the trust prompt lists the MCP servers, hook commands, agents and risky settings the project contains. Edits to .gemini configuration always ask, even in auto-edit mode, and model writes to the global memory file need approval. However, the folder-trust gate does not cover every workspace configuration path consistently. After trust, new project hooks only produce a warning before they run, and instruction files load silently.

- **S L2:** Security-relevant project config is gated by folder trust and .gemini edits always ask, but instruction files load silently once trusted and memory has no expiry. — [packages/cli/src/config/settings.ts:262](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/cli/src/config/settings.ts#L262); [packages/core/src/context/memoryContextManager.ts:132](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/core/src/context/memoryContextManager.ts#L132); [packages/core/src/safety/built-in.ts:107](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/core/src/safety/built-in.ts#L107); [packages/core/src/hooks/hookRegistry.ts:154-159](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/core/src/hooks/hookRegistry.ts#L154-L159); searched `rg -n -i 'expir|ttl'` in `packages/core/src/context/memoryContextManager.ts` → 0 hits (no expiry on loaded memory/context files) (verified)
  - *To reach the next level:* No expiry or validation on memory/context; hooks added after trust run after a warning only.
- **C L2:** Settings, hooks, MCP, GEMINI.md and global memory are covered, but the trust gate does not cover every path. — [packages/core/src/config/config.ts:3355-3365](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/core/src/config/config.ts#L3355-L3365) (verified)
  - *To reach the next level:* Not every auto-loaded file is inside the trust gate.
- **D L2:** Single-user local state is namespaced per project hash; the model can write the cross-project global GEMINI.md with approval. — [packages/core/src/services/chatRecordingService.ts:757-759](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/core/src/services/chatRecordingService.ts#L757-L759); [packages/core/src/config/config.ts:3355-3365](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/core/src/config/config.ts#L3355-L3365); [packages/cli/src/config/settingsSchema.ts:1920](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/cli/src/config/settingsSchema.ts#L1920) (verified)
  - *To reach the next level:* The model can still write a cross-project memory namespace.
- **B L2:** Poisoned GEMINI.md persists across the user's sessions but consequential actions remain approval-gated by default; auto memory is off and review-gated. — [packages/core/src/context/memoryContextManager.ts:132](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/core/src/context/memoryContextManager.ts#L132); [packages/cli/src/config/settingsSchema.ts:2432-2437](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/cli/src/config/settingsSchema.ts#L2432-L2437) (verified)
  - *To reach the next level:* Poisoned context persists across sessions without review or rollback.
- **Cap:** C6-REPOCONFIG — Untrusted workspace configuration can influence security-relevant settings before the trust decision.

### C7 Third-party extensions — 0.30 (high)

Nothing third-party is enabled out of the box. Installing an extension shows the MCP server commands it will run, warns about hooks and lists skills, and an update asks again only if that declared list changes. Extensions come from a git URL or GitHub release at whatever ref the user picks; there is no code hash or signature check (the integrity record covers only install metadata). MCP servers run whatever command the user configures, often unpinned. Stdio MCP servers are blocked in untrusted folders and get a scrubbed environment, but extension hooks run with the full environment.

- **S L1:** User-chosen sources with an optional ref pin; integrity checks cover install metadata, not extension code. — [packages/cli/src/config/extension-manager.ts:1439](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/cli/src/config/extension-manager.ts#L1439); [packages/core/src/config/extensions/integrity.ts:253](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/core/src/config/extensions/integrity.ts#L253) (verified)
  - *To reach the next level:* No default version pinning or code integrity check.
- **C L1:** Extensions get consent and metadata integrity; MCP servers configured in settings get neither. — [packages/cli/src/config/extensions/consent.ts:219-224](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/cli/src/config/extensions/consent.ts#L219-L224); [packages/core/src/config/extensions/integrity.ts:253](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/core/src/config/extensions/integrity.ts#L253) (verified)
  - *To reach the next level:* MCP servers and skills are not verified.
- **D L2:** Explicit install with a consent screen showing MCP commands and a hooks warning; a trusted workspace can still add MCP servers that the trust prompt lists only by name. — [packages/cli/src/config/extensions/consent.ts:219-224](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/cli/src/config/extensions/consent.ts#L219-L224); [packages/cli/src/config/extensions/consent.ts:322](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/cli/src/config/extensions/consent.ts#L322); [packages/cli/src/config/extensions/consent.ts:392](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/cli/src/config/extensions/consent.ts#L392); [packages/core/src/tools/mcp-client.ts:2338](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/core/src/tools/mcp-client.ts#L2338) (verified)
  - *To reach the next level:* Workspace scope can add extensions and the trust prompt does not show their commands.
- **B L1:** MCP stdio servers get a scrubbed environment, but extension hooks run as the same user with the full environment. — [packages/core/src/tools/mcp-client.ts:2345-2347](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/core/src/tools/mcp-client.ts#L2345-L2347); [packages/core/src/hooks/hookRunner.ts:349](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/core/src/hooks/hookRunner.ts#L349) (verified)
  - *To reach the next level:* Hooks do not receive a scrubbed environment by default.
- **Cap:** none

### C8 Secrets & sensitive-data protection — 0.28 (high)

The Gemini API key and MCP OAuth tokens are kept in the OS keychain, but the Google sign-in token (cloud-platform scope) is written as plaintext JSON with 0600 permissions unless encrypted file storage is forced by an environment variable. Secret redaction exists only for subprocess environments and is off by default except for MCP servers; tool output, transcripts and model-bound messages are not scanned. Usage statistics are on by default but content-free (prompt length, tool names, decisions).

- **S L2:** Keychain for API keys, plaintext 0600 file for OAuth credentials, and a pattern-based environment redactor. — [packages/core/src/code_assist/oauth2.ts:112-113](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/core/src/code_assist/oauth2.ts#L112-L113); [packages/core/src/code_assist/oauth2.ts:883](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/core/src/code_assist/oauth2.ts#L883); [packages/core/src/services/environmentSanitization.ts:20-21](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/core/src/services/environmentSanitization.ts#L20-L21) (verified)
  - *To reach the next level:* No redaction before logs, transcripts or model-bound messages.
- **C L1:** Only MCP subprocess environments are redacted by default. — [packages/core/src/tools/mcp-client.ts:2345-2347](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/core/src/tools/mcp-client.ts#L2345-L2347); [packages/core/src/services/chatRecordingService.ts:899](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/core/src/services/chatRecordingService.ts#L899) (verified)
  - *To reach the next level:* Shell environments, transcripts and model-bound messages are unprotected.
- **D L1:** Usage statistics are on by default and content-free; redaction is off by default. — [packages/cli/src/config/settingsSchema.ts:970-975](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/cli/src/config/settingsSchema.ts#L970-L975); [packages/core/src/telemetry/clearcut-logger/clearcut-logger.ts:747](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/core/src/telemetry/clearcut-logger/clearcut-logger.ts#L747); [packages/cli/src/config/settingsSchema.ts:1958-1961](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/cli/src/config/settingsSchema.ts#L1958-L1961) (verified)
  - *To reach the next level:* Telemetry is opt-out and redaction is not on by default.
- **B L0:** Long-lived keys (GEMINI_API_KEY and any user secrets in the environment) reach every shell subprocess, and a long-lived cloud-platform refresh token sits on disk. — [packages/core/src/services/environmentSanitization.ts:20-21](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/core/src/services/environmentSanitization.ts#L20-L21); [packages/core/src/code_assist/oauth2.ts:88-89](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/core/src/code_assist/oauth2.ts#L88-L89) (verified)
  - *To reach the next level:* Keys are long-lived and broadly reachable.
- **Cap:** none

### C9 Audit & traceability — 0.45 (high)

Every session is recorded as a JSONL transcript that includes each tool call's name, arguments, result, status and timestamp, with sub-agent sessions saved under the parent session. Records are appended as they happen. The transcripts live in ~/.gemini/tmp/<project>/chats, which is inside the project temp directory the file tools are allowed to write to, and nothing makes them tamper-evident. Approver identity is not recorded, and if the disk is full recording is silently disabled while the agent keeps working.

- **S L2:** Structured per-call record with arguments, result, status and timestamp. — [packages/core/src/services/chatRecordingTypes.ts:54-61](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/core/src/services/chatRecordingTypes.ts#L54-L61); [packages/core/src/scheduler/scheduler.ts:146](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/core/src/scheduler/scheduler.ts#L146) (verified)
  - *To reach the next level:* No actor/approver attribution or correlation IDs in the record.
- **C L2:** All tool calls including MCP and sub-agent sessions are recorded; approval decisions are not recorded explicitly. — [packages/core/src/services/chatRecordingTypes.ts:54-61](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/core/src/services/chatRecordingTypes.ts#L54-L61); [packages/core/src/services/chatRecordingService.ts:757-759](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/core/src/services/chatRecordingService.ts#L757-L759) (verified)
  - *To reach the next level:* Approvals and denials are not recorded.
- **D L1:** Always on, but stored in the project temp directory that the agent's own file tools may write. — [packages/core/src/services/chatRecordingService.ts:757-759](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/core/src/services/chatRecordingService.ts#L757-L759); [packages/core/src/config/config.ts:3351](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/core/src/config/config.ts#L3351); searched `rg -n -i 'hmac|signature|hash chain|appendOnly'` in `packages/core/src/services/chatRecordingService.ts` → 0 hits (no tamper evidence on transcripts) (verified)
  - *To reach the next level:* The record is writable by the agent's own tools.
- **B L2:** Records are appended per action; on ENOSPC recording is disabled with a warning and actions proceed. — [packages/core/src/services/chatRecordingService.ts:899](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/core/src/services/chatRecordingService.ts#L899); [packages/core/src/services/chatRecordingService.ts:901-903](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/core/src/services/chatRecordingService.ts#L901-L903) (verified)
  - *To reach the next level:* No guarantee a record is durable before a high-risk action runs.
- **Cap:** none

### C10 Limits & kill switch — 0.45 (medium)

Each user prompt is capped at 100 model turns by a hard limit the configuration cannot raise, shell commands are killed after 5 minutes without output, and sub-agents stop after 30 turns or 10 minutes. Pressing stop aborts the turn and kills the running command's whole process group. There is no wall-clock or token/cost limit on a session (the session turn limit defaults to unlimited), and each sub-agent starts with its own budget rather than drawing on the parent's. Background shell processes are not killed when a turn is interrupted.

- **S L2:** Iteration cap plus per-command inactivity timeout, and abort kills the process group. — [packages/core/src/core/client.ts:79](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/core/src/core/client.ts#L79); [packages/cli/src/config/settingsSchema.ts:1646-1651](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/cli/src/config/settingsSchema.ts#L1646-L1651); [packages/core/src/services/shellExecutionService.ts:922-928](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/core/src/services/shellExecutionService.ts#L922-L928); searched `rg -n -i 'maxCost|costLimit|maxTokensPerSession|sessionTokenLimit'` in `packages/core/src/core packages/core/src/config` → 0 hits (no token or cost ceiling on the agent loop) (verified)
  - *To reach the next level:* No session wall-clock or token/cost cap.
- **C L2:** Top-level loop plus tool timeouts; sub-agents have separate budgets. — [packages/core/src/core/client.ts:974](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/core/src/core/client.ts#L974); [packages/core/src/agents/types.ts:51-56](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/core/src/agents/types.ts#L51-L56); [packages/core/src/agents/local-executor.ts:593](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/core/src/agents/local-executor.ts#L593) (verified)
  - *To reach the next level:* Sub-agents do not count against the parent's budget.
- **D L2:** Per-prompt cap is hard-coded via Math.min, but session turns default to unlimited and delegation starts a fresh sub-agent budget. — [packages/core/src/core/client.ts:974](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/core/src/core/client.ts#L974); [packages/cli/src/config/settingsSchema.ts:1080-1085](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/cli/src/config/settingsSchema.ts#L1080-L1085); [packages/core/src/agents/local-executor.ts:593](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/core/src/agents/local-executor.ts#L593) (verified)
  - *To reach the next level:* Delegation resets the budget.
- **B L1:** Sessions are unbounded in time and spend, and background processes keep running after a turn is interrupted. — [packages/cli/src/config/settingsSchema.ts:1080-1085](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/cli/src/config/settingsSchema.ts#L1080-L1085); [packages/core/src/services/shellExecutionService.ts:922-928](https://github.com/google-gemini/gemini-cli/blob/fb972b2f87fe7d5b06d37eac711490162d98de2c/packages/core/src/services/shellExecutionService.ts#L922-L928) (inferred)
  - *To reach the next level:* No tight per-run time/cost ceiling and stop does not end background work.
- **Cap:** none

## Rule-of-Two check
[A] untrusted input: web_fetch/web search/MCP/shell output and repository files (packages/core/src/tools/web-fetch.ts:498, packages/core/src/tools/mcp-tool.ts:585) · [B] sensitive data/systems: full environment in shell subprocesses and OAuth token on disk (packages/core/src/services/environmentSanitization.ts:20-21, packages/core/src/code_assist/oauth2.ts:883) · [C] state change / egress: shell, write_file/replace, web_fetch, auto-approved web_fetch in auto-edit (packages/core/src/policy/policies/write.toml:84-88) · Same default session? Yes

## Highest-impact improvements
1. Turn the per-tool sandbox (bubblewrap/Seatbelt, network off) on by default with an explicitly named flag to disable it. — C4 D L0→L3, +0.150 before caps (Playbook 3, step 1)
2. Ensure every workspace-sourced configuration path is covered by the folder-trust gate. — C6 C L2→L3, +0.075 before caps (Playbook 2)
3. Enable environment-variable redaction by default for shell commands and hooks. — C8 C L1→L2, +0.075 before caps (Playbook 4)
4. Keep web_fetch on ask in auto-edit mode, or require approval for any egress once untrusted content is in context. — C5 S L2→L3, +0.075 before caps (Playbook 1)
5. Add a session wall-clock and token ceiling and make sub-agents draw from the parent's budget. — C10 S L2→L3, +0.075 before caps (Playbook 3, step 3)

## Re-audit log
- No changes.

## Limitations
- Static source review of the pinned commit only; nothing was executed, installed, or probed.
- The repository is a large monorepo; review focused on packages/core and packages/cli (policy engine, scheduler, tools, sandbox, settings, trust, extensions, telemetry). packages/a2a-server, packages/sdk, packages/vscode-ide-companion and the IDE integration were not examined in depth.
- Not verified: whether `echo $VAR` is auto-allowed by the known-safe command heuristic depends on how the shell-quote library expands variables, which was not run.
- C10 B is inferred: no code was found that kills backgrounded shell processes when a turn is interrupted.
- The opt-in Conseca LLM safety checker and the Windows/macOS-specific sandboxes were not scored in depth; the container sandbox was scored as the opt-in alternative for C4.
- No text aimed at AI reviewers was found in README, GEMINI.md or SECURITY.md.
