Gemini CLI Permissions and Policy Engine
Updated by Alex Sorokoletov
Independent reference, not affiliated with Google. Tables are generated from the Gemini CLI source at v0.62.0 (v0.62.0). Official documentation: geminicli.com.
Gemini CLI decides whether a tool call runs, prompts or fails with a policy engine: TOML rules that pair a tool with a decision (allow, deny or ask_user) and a priority, stacked in five tiers from bundled defaults to admin policies. The approval mode selects which rules apply, and an optional sandbox limits what an approved command can reach.
What approval modes does Gemini CLI have?#
| --approval-mode | modes = [...] in TOML | Behavior | Notes |
|---|---|---|---|
default | default | prompt for approval | — |
auto_edit | autoEdit | auto-approve edit tools | forced to default in untrusted folders |
yolo | yolo | auto-approve all tools | not accepted from general.defaultApprovalMode; --yolo and --approval-mode cannot be combined; forced to default in untrusted folders |
plan | plan | read-only mode | falls back to default when general.plan.enabled is false; forced to default in untrusted folders |
The CLI flag and the policy files spell one mode differently: --approval-mode auto_edit on the command line, modes = ["autoEdit"] in TOML. A rule written with modes = ["auto_edit"] fails schema validation and its whole file is skipped. In a session, Shift+Tab cycles default, auto_edit and plan, and Ctrl+Y toggles YOLO; in an untrusted folder only default and plan can be selected. general.defaultApprovalMode sets the starting mode.
| Flag | Type | Description |
|---|---|---|
--yolo, -y | boolean | Automatically accept all actions (YOLO mode)? |
--approval-mode | default | auto_edit | yolo | plan | Set the approval mode: default (prompt for approval), auto_edit (auto-approve edit tools), yolo (auto-approve all tools), plan (read-only mode) |
--policy | string[] | Additional policy files or directories to load (comma-separated or multiple --policy) |
--admin-policy | string[] | Additional admin policy files or directories to load (comma-separated or multiple --admin-policy) |
--sandbox, -s | boolean | Run in sandbox? |
--skip-trust | boolean | Trust the current workspace for this session. |
--allowed-tools | string[] | [DEPRECATED: Use Policy Engine instead] Tools that are allowed to run without confirmation |
--allowed-mcp-server-names | string[] | Allowed MCP server names |
How do I enable YOLO mode in Gemini CLI?#
Start with gemini --yolo (or -y), or gemini --approval-mode=yolo; the two flags cannot be combined. Ctrl+Y switches YOLO on and off inside a running session. YOLO cannot be the saved default: general.defaultApprovalMode accepts only default, auto_edit and plan, and a yolo value there is ignored. security.disableYoloMode or the admin setting secureModeEnabled turns the flag into a startup error and blocks Ctrl+Y, and in an untrusted folder the mode silently drops back to default.
YOLO is not a bypass of the policy engine. It is a bundled rule, toolName = "*", decision = "allow" at priority 1.998, so every user and admin rule still outranks it: a deny in ~/.gemini/policies/ or in tools.exclude keeps blocking in YOLO mode, and the ask_user tool still asks.
What is the Gemini CLI policy engine?#
Every tool call is checked against all loaded rules from the highest effective priority down; the first rule that matches decides. A rule matches when its tool name, MCP server, approval modes, interactive flag, annotations and argument pattern all fit. With no match, interactive sessions ask and headless runs (-p) deny.
# ~/.gemini/policies/shell.toml
[[rule]]
toolName = "run_shell_command"
commandPrefix = ["git status", "git diff", "npm test"]
decision = "allow"
priority = 100
[[rule]]
toolName = "run_shell_command"
commandRegex = "rm -rf .*"
decision = "deny"
priority = 200
denyMessage = "Recursive deletes are blocked by policy."
| Key | Type | Required | Meaning |
|---|---|---|---|
toolName | string | string[] | yes | Tool name, list of names, or wildcard (`*`, `mcp_<server>_*`). The old `server__tool` MCP form only draws a deprecation warning; use mcpName. |
subagent | string | no | Only matches calls made by this subagent. |
mcpName | string | no | MCP server name; `*` matches any MCP tool. |
argsPattern | string | no | Regex tested against the tool arguments as stable-sorted JSON. |
commandPrefix | string | string[] | no | run_shell_command only: allowed command prefix(es). |
commandRegex | string | no | run_shell_command only: regex over the command. |
decision | allow | deny | ask_user | yes | allow, deny or ask_user. |
priority | integer 0-999 | yes | Effective priority is tier + priority/1000. A missing or out-of-range value fails schema validation and the whole file is skipped. |
modes | string[] | no | Approval modes the rule applies to (`default`, `autoEdit`, `yolo`, `plan`); omitted means all. |
interactive | boolean | no | true: interactive sessions only; false: headless only; omitted: both. |
toolAnnotations | table | no | Match MCP tool annotations, e.g. `{ readOnlyHint = true }`. |
allowRedirection | boolean | no | Keep ALLOW for shell commands with redirection (otherwise downgraded to ask_user). |
denyMessage | string | no | Appended to "Tool execution denied by policy." |
commandPrefix and commandRegex work only with toolName = "run_shell_command" (a single name, not a list) and cannot be combined with each other or with argsPattern. A shell command with output redirection is downgraded from allow to ask unless the rule sets allowRedirection = true.
How do policy priority tiers resolve?#
| Tier | Name | Loaded from |
|---|---|---|
| 1 | default | policies bundled with the CLI (table below) |
| 2 | extension | policies/ directory of each active extension |
| 3 | workspace | <project>/.gemini/policies/*.toml (not loaded in this release: workspace policies are switched off in code) |
| 4 | user | ~/.gemini/policies/*.toml, replaced by policyPaths / --policy when set |
| 5 | admin | /Library/Application Support/GeminiCli/policies (macOS), C:\ProgramData\gemini-cli\policies (Windows), /etc/gemini-cli/policies (Linux), plus adminPolicyPaths / --admin-policy (ignored when the system directory already holds .toml files) |
Each file's priority (0 to 999) is divided by 1000 and added to its tier, so an admin rule at priority 0 (5.000) beats a user rule at 999 (4.999). Project-level .gemini/policies/ files are not loaded in 0.62.0: the workspace-policy switch in the CLI is off by default and nothing turns it on, so project rules have to go in a user or admin directory. The system directory is also skipped with a "Security Warning" if its permissions are not secure.
Several settings keys become rules in the user tier:
| Setting | Decision | Effective priority |
|---|---|---|
"Always allow" answer, user scope | allow | 4.95 |
mcp.excluded | deny | 4.9 |
tools.exclude | deny | 4.4 |
tools.confirmationRequired | ask_user | 4.35 |
tools.allowed / --allowed-tools | allow | 4.3 |
tools.core (everything else denied) | allow | 4.25 |
mcpServers.<name>.trust = true | allow | 4.2 |
mcp.allowed / --allowed-mcp-server-names | allow | 4.1 |
"Always allow" answer, workspace scope | allow | 3.95 |
Narrowed entries behave differently from plain names. "tools.allowed": ["run_shell_command(git status)"] allows git status and adds a deny rule just below it, so every other shell command is denied rather than prompted. tools.core works the same way for the whole tool list: anything not named is denied outside plan mode.
Which policy rules ship by default?#
| File | toolName | Decision | Priority | Modes | Conditions |
|---|---|---|---|---|---|
agents.toml | invoke_agent | allow | 1.050 | default, autoEdit, yolo | none |
discovered.toml | discovered_tool_* | ask_user | 1.010 | all | interactive only |
discovered.toml | discovered_tool_* | deny | 1.010 | all | headless only |
non-interactive.toml | ask_user | deny | 1.999 | all | headless only |
plan.toml | enter_plan_mode | ask_user | 1.050 | all | interactive only |
plan.toml | enter_plan_mode | allow | 1.050 | all | headless only |
plan.toml | enter_plan_mode | deny | 1.070 | plan | none |
plan.toml | exit_plan_mode | ask_user | 1.070 | plan | interactive only |
plan.toml | exit_plan_mode | allow | 1.070 | plan | headless only |
plan.toml | exit_plan_mode | deny | 1.050 | all | none |
plan.toml | * | deny | 1.040 | plan | none |
plan.toml | * | ask_user | 1.050 | plan | mcpName=*; annotations readOnlyHint=true; interactive only |
plan.toml | invoke_agent | allow | 1.050 | plan | argsPattern |
plan.toml | ask_user, web_fetch, activate_skill | ask_user | 1.050 | plan | interactive only |
plan.toml | write_file, replace | allow | 1.070 | plan | argsPattern |
plan.toml | write_file, replace | deny | 1.065 | plan | none |
read-only.toml | glob, grep_search, list_directory, read_file, google_web_search, codebase_investigator, cli_help, get_internal_docs, tracker_create_task, tracker_update_task, tracker_get_task, tracker_list_tasks, tracker_add_dependency, tracker_visualize, update_topic, complete_task, read_mcp_resource, list_mcp_resources | allow | 1.050 | all | none |
write.toml | replace | ask_user | 1.010 | all | interactive only |
write.toml | replace | allow | 1.015 | autoEdit | none |
write.toml | run_shell_command | ask_user | 1.010 | all | interactive only |
write.toml | write_file | ask_user | 1.010 | all | interactive only |
write.toml | activate_skill | ask_user | 1.010 | all | interactive only |
write.toml | write_file | allow | 1.015 | autoEdit | none |
write.toml | web_fetch | allow | 1.015 | autoEdit | none |
write.toml | web_fetch | ask_user | 1.010 | all | interactive only |
write.toml | replace, run_shell_command, write_file, activate_skill, web_fetch | deny | 1.010 | all | headless only |
yolo.toml | ask_user | ask_user | 1.999 | yolo | interactive only |
yolo.toml | enter_plan_mode, exit_plan_mode | deny | 1.999 | yolo | interactive only |
yolo.toml | * | allow | 1.998 | yolo | none |
Read-only tools are allowed, file writes, shell commands, skills and web fetches ask, autoEdit allows replace, write_file and web_fetch, and headless runs deny anything that would have asked. Plan mode denies everything at 1.040 and then carves out read-only tools, the codebase_investigator and cli_help subagents, prompts for ask_user, web_fetch, activate_skill and read-only MCP tools, and allows writing Markdown plan files.
Why does Gemini CLI say "Tool execution denied by policy"?#
| Message | Cause |
|---|---|
Tool execution denied by policy. <denyMessage> | the highest-priority matching rule has decision = "deny"; its denyMessage, if any, is appended |
Tool execution for "<tool>" requires user confirmation, which is not supported in non-interactive mode. | a rule resolved to ask_user in a headless run (-p) |
Tool execution blocked: <reason> | a BeforeTool hook returned decision block or deny (hook, not policy) |
The denial comes from the highest-priority matching deny rule. Common sources: a deny in ~/.gemini/policies/ or an admin policy, tools.exclude or mcp.excluded in settings, a narrowed tools.allowed entry that denies every other command, tools.core denying unlisted tools, plan mode, or a headless run where a rule would have asked. Set GEMINI_DEBUG_LOG_FILE=/tmp/gemini.log before starting to log every check; the deciding rule appears as [PolicyEngine.check] MATCHED rule.
How does the Gemini CLI sandbox work?#
--sandbox (-s), tools.sandbox in settings or the GEMINI_SANDBOX environment variable runs the CLI inside a sandbox. tools.sandbox also takes an object with enabled, command, image, allowedPaths and networkAccess. Policy decides whether a call runs; the sandbox limits what an allowed call can touch.
| Sandbox command | Notes |
|---|---|
docker | container; used for sandbox = true when sandbox-exec is unavailable |
podman | container; used for sandbox = true when neither sandbox-exec nor docker exists |
sandbox-exec | macOS Seatbelt; the pick for sandbox = true on macOS |
runsc | gVisor; Linux only and needs Docker |
lxc | LXC container |
windows-native | Windows only |
On macOS the default is Seatbelt (sandbox-exec) with the permissive-open profile. Choose another with SEATBELT_PROFILE:
| SEATBELT_PROFILE | File reads | Outbound network | Inbound network |
|---|---|---|---|
permissive-open (default) | anywhere | all | *:* |
permissive-closed | no profile file ships in this release | — | — |
permissive-proxied | anywhere | proxy only (localhost:8877) | localhost:9229 |
restrictive-open | anywhere | all | localhost:9229 |
restrictive-closed | no profile file ships in this release | — | — |
restrictive-proxied | anywhere | proxy only (localhost:8877) | localhost:9229 |
strict-open | working directory, system and selected user paths | all | localhost:9229 |
strict-proxied | working directory, system and selected user paths | proxy only (localhost:8877) | localhost:9229 |
What are trusted folders in Gemini CLI?#
Trust decides how much a project may configure Gemini CLI. Until a folder is trusted, its .gemini/settings.json, its hooks and any approval mode other than default are ignored.
| Item | Rule |
|---|---|
| File | ~/.gemini/trustedFolders.json (path override: GEMINI_CLI_TRUSTED_FOLDERS_PATH), a map of path to trust level |
| Default | folder trust is on (security.folderTrust.enabled = true) and a folder with no matching rule counts as untrusted |
| Environment | GEMINI_CLI_TRUST_WORKSPACE=true trusts the workspace and false distrusts it, ahead of the file and the IDE |
| --skip-trust | sets GEMINI_CLI_TRUST_WORKSPACE=true for the session |
| Untrusted folder | workspace .gemini/settings.json is ignored, hooks from settings.json do not run, and the approval mode is forced to default |
| Value in trustedFolders.json | Meaning |
|---|---|
TRUST_FOLDER | the folder and everything under it is trusted |
TRUST_PARENT | the folder's parent and everything under it is trusted |
DO_NOT_TRUST | the folder and everything under it is untrusted |
Security settings#
| Key | Type | Default | Description |
|---|---|---|---|
general.defaultApprovalMode | default | auto_edit | plan | "default" | The default approval mode for tool execution. 'default' prompts for approval, 'auto_edit' auto-approves edit tools, and 'plan' is read-only mode. YOLO mode (auto-approve all actions) can only be enabled via command line (--yolo or --approval-mode=yolo). |
security.toolSandboxing | boolean | false | Tool-level sandboxing. Isolates individual tools instead of the entire CLI process. |
security.disableYoloMode | boolean | false | Disable YOLO mode, even if enabled by a flag. |
security.disableAlwaysAllow | boolean | false | Disable "Always allow" options in tool confirmation dialogs. |
security.enablePermanentToolApproval | boolean | false | Enable the "Allow for all future sessions" option in tool confirmation dialogs. |
security.autoAddToPolicyByDefault | boolean | false | When enabled, the "Allow for all future sessions" option becomes the default choice for low-risk tools in trusted workspaces. |
security.enableConseca | boolean | false | Enable the context-aware security checker. This feature uses an LLM to dynamically generate and enforce security policies for tool use based on your prompt, providing an additional layer of protection against unintended actions. |
admin.secureModeEnabled | boolean | false | If true, disallows YOLO mode and "Always allow" options from being used. |
Frequently asked questions#
What is the Gemini CLI policy engine?#
The component that decides whether each tool call is allowed, denied or needs confirmation. Rules live in TOML files with toolName, decision and priority, plus optional matchers for MCP server, arguments, shell command prefix, approval mode and interactivity. Rules are layered in default, extension, workspace, user and admin tiers, and the highest-priority match wins.
Is there a Gemini CLI equivalent of --dangerously-skip-permissions?#
Yes: gemini --yolo or gemini --approval-mode=yolo. It adds an allow-everything rule near the top of the default tier, so it approves any call your own rules do not deny. It cannot be set as the default in settings.json, is refused when security.disableYoloMode is on, and is dropped to default in untrusted folders.
How do I auto-approve only specific shell commands in Gemini CLI?#
Add a rule in ~/.gemini/policies/ with toolName = "run_shell_command", commandPrefix = ["git status", "npm test"], decision = "allow" and a priority. Other shell commands keep asking. Listing run_shell_command(git status) in tools.allowed also works but denies every other shell command instead of asking, and --allowed-tools is deprecated.
Why does Gemini CLI say "Tool execution denied by policy"?#
A matching rule with decision = "deny" outranked every allow rule for that call, and any denyMessage is appended to the error. Check ~/.gemini/policies/ and admin policies, tools.exclude, mcp.excluded, a narrowed tools.allowed or tools.core list, plan mode, and headless runs, where calls that would ask are denied. GEMINI_DEBUG_LOG_FILE logs the matching rule.
How do Gemini CLI policy priority tiers work?#
Five tiers: default (1), extension (2), workspace (3), user (4) and admin (5). A rule's effective priority is its tier plus priority / 1000, so any admin rule beats any user rule, which beats extension and default rules. Settings such as tools.exclude become user-tier rules; "Always allow" answers land at 4.95 or 3.95.
What approval modes does Gemini CLI have?#
Four: default prompts for writes and shell commands, auto_edit also approves file edits, plan is read-only apart from writing plan files, and yolo approves everything your rules do not deny. Select one with --approval-mode, Shift+Tab or Ctrl+Y. In TOML policy files auto_edit is spelled autoEdit.
Why do my .gemini/policies files in the project do nothing?#
Gemini CLI 0.62.0 does not load workspace policy files. The CLI keeps workspace policies switched off by default and no command or setting turns them on, so rules in a project's .gemini/policies/ are ignored. Put them in ~/.gemini/policies/, pass them with --policy, or list their paths in policyPaths.
Sources
- Gemini CLI official documentation
- Gemini CLI release notes
packages/core/src/policy/types.ts: PolicyDecision, ApprovalMode, PolicyRule (at v0.62.0)packages/cli/src/config/config.ts: CLI flags, approval mode resolution (at v0.62.0)packages/core/src/policy/toml-loader.ts: policy TOML schema, validation, tiers (at v0.62.0)packages/core/src/policy/config.ts: tier constants, policy directories, settings-derived rules (at v0.62.0)packages/core/src/config/storage.ts: policy directory paths (at v0.62.0)packages/cli/src/config/policy.ts: workspace policy loading switch (at v0.62.0)packages/core/src/policy/policies: bundled default policy TOML files (at v0.62.0)packages/core/src/policy/policy-engine.ts: rule matching, default decision (at v0.62.0)packages/core/src/scheduler/policy.ts: denial message, headless ask_user error (at v0.62.0)packages/core/src/scheduler/hook-utils.ts: BeforeTool hook denial (at v0.62.0)packages/cli/src/config/sandboxConfig.ts: sandbox command resolution (at v0.62.0)packages/cli/src/utils/sandboxUtils.ts: built-in Seatbelt profile names (at v0.62.0)packages/cli/src/utils/sandboxBuiltinProfiles.ts: embedded Seatbelt profile contents (at v0.62.0)packages/cli/src/utils/sandbox.ts: SEATBELT_PROFILE default and custom profile lookup (at v0.62.0)packages/core/src/utils/trust.ts: TrustLevel enum, trust resolution order (at v0.62.0)packages/cli/src/config/trustedFolders.ts: folderTrust.enabled default (at v0.62.0)packages/core/src/hooks/hookRegistry.ts: settings hooks skipped in untrusted folders (at v0.62.0)packages/cli/src/config/settings.ts: workspace settings ignored in untrusted folders (at v0.62.0)schemas/settings.schema.json: settings.json schema (security.*) (at v0.62.0)
Release-by-release changes: Gemini CLI version tracker. All Gemini CLI pages: Gemini CLI reference index.