Gemini CLI policy engine for QA is where a test team can decide whether an agent tool runs automatically, is blocked, or requires confirmation. A TOML file may look correct and still fail in practice because the rule targets the wrong tool name, its argument pattern misses the serialized payload, a higher-priority rule wins, the approval mode differs, or the run is headless.
This tutorial builds a disposable policy lab with harmless canary actions. You will verify allow, deny, and ask_user decisions; conflicting priorities; interactive and CI behavior; mode-specific rules; MCP and subagent boundaries; Plan Mode transitions; and sandbox containment before any policy protects a real QA workflow.
What the policy engine controls
The current Gemini CLI policy-engine documentation describes rules with conditions, a decision, and a priority. Conditions can match a tool name, serialized arguments, shell-command prefixes or regular expressions, the current approval mode, interactive versus non-interactive execution, an MCP server, tool annotations, or a subagent.
The three decisions are:
- allow: execute without interactive approval.
- deny: block execution. A global deny without an argument pattern can also remove the tool from model context.
- ask_user: request confirmation interactively; in non-interactive mode this is treated as deny.
Google recommends deny rules instead of the deprecated tools.exclude setting. Treat migration as a tested policy change, not a text replacement.
Define the QA contract
Given a frozen Gemini CLI build, policy set, approval mode, execution environment, and harmless canary request, the effective rule must produce the expected decision, leave independently verified evidence, and perform no unapproved action outside a disposable workspace.
Record the trial ID, CLI version, operating system, authentication mode, workspace hash, user and admin policy hashes, rule name, tier, priority, tool name, normalized arguments, command prefix or regex, modes, interactive flag, sandbox state, expected winner, observed prompt or denial, tool event, exit code, canary state, and reviewer disposition.
Step 1: create a harmless canary tool
Use a disposable repository containing fixtures/policy-probe.mjs. The script should accept a case name, print a unique correlation ID, and optionally create one marker only inside tmp/policy-lab/. It must not use credentials, the network, package installation, Git remotes, production paths, or system settings.
Create four cases:
| Case | Expected policy | Independent evidence |
|---|---|---|
| read-only | Allow in reviewed modes | stdout marker; no file change |
| confirm | Ask in interactive mode | prompt plus marker only after approval |
| blocked | Deny everywhere | no process and no marker |
| headless-confirm | Ask interactively, deny headless | no CI marker |
Before every trial, capture Git status, the lab directory inventory, running probe processes, and marker hashes. Repeat those checks afterward. A model statement that a tool was blocked is not sufficient evidence.
Step 2: place policies where they actually load
User policies are loaded from ~/.gemini/policies/*.toml. Admin locations are operating-system specific; on Windows the documented standard directory is C:\ProgramData\gemini-cli\policies. Admin policy testing belongs in a managed disposable machine because ownership and write-permission checks are part of the security boundary.
Current Google documentation labels the workspace policy tier disabled. Do not put a test rule only in .gemini/policies and assume the project is protected. Add a negative test that proves a workspace-only rule has no effect on the installed build, then use a reviewed user or administrator policy for the actual gate.
Capture every loaded source separately. A copied policy in the wrong directory can produce a convincing file review while contributing nothing to effective behavior.
Step 3: write the first three rules
Use exact prefixes for the disposable probe:
[[rule]]
toolName = "run_shell_command"
commandPrefix = "node ./fixtures/policy-probe.mjs --case blocked"
decision = "deny"
priority = 300
denyMessage = "QA canary: blocked by policy"
[[rule]]
toolName = "run_shell_command"
commandPrefix = "node ./fixtures/policy-probe.mjs --case confirm"
decision = "ask_user"
priority = 200
interactive = true
[[rule]]
toolName = "run_shell_command"
commandPrefix = "node ./fixtures/policy-probe.mjs --case read-only"
decision = "allow"
priority = 100
modes = ["default"]
Run one case at a time. Verify the displayed command exactly matches the requested probe and that the canary state matches the decision. Never approve a prompt whose arguments differ from the frozen fixture.
Step 4: test priority conflicts
Within one policy tier, a higher priority wins. Create an intentionally conflicting lower-priority Allow rule for the blocked probe. The Deny rule should still win. Swap priorities in a separate test file and prove the winner changes, then restore the reviewed baseline.
Also test equal-priority ambiguity, multiple matching files, an absent priority, and a value outside the documented range. Fail the rollout if the installed CLI rejects policy parsing, ignores a file, or resolves a conflict differently from the reviewed expectation.
Policy tiers matter more than local numeric priority. Administrator policies override user policies. Do not assume a high number in a user file can defeat a managed administrator restriction. Capture the winning source, not just the winning decision.
Step 5: verify argument matching
For general tools, argsPattern is matched against a stable JSON serialization of the tool arguments. For run_shell_command, Google documents the simpler commandPrefix and commandRegex shorthands.
Exercise exact command, additional safe argument, leading path variation, different quoting, newline, compound command, case change, similar prefix, and an unrelated command. A prefix such as node is usually too broad for automatic approval. Prefer the narrowest stable command and keep a default confirmation or denial around it.
Log the normalized arguments used for the decision, but redact secrets. Regex tests should include adversarial near-matches and catastrophic-input limits before CI adoption.
Step 6: cover interactive and headless behavior
The interactive condition can restrict a rule to a terminal session or a non-interactive run. If omitted, the rule applies to both. Most importantly, ask_user becomes deny when no interactive prompt is possible.
Run the confirm canary interactively and explicitly reject it, then approve it in a fresh trial. Next run the same request with -p and JSON or streaming JSON output. The headless test must not hang and must not create a marker.
Gemini CLI’s headless documentation defines structured output and standard exit codes, but an exit code alone does not prove a specific tool executed or was denied. Parse tool-use and tool-result evidence when available, preserve error fields, and independently inspect the canary.
Step 7: test approval-mode scope
Policy rules can target default, autoEdit, plan, and yolo. The command-line spelling for Auto-Edit is currently auto_edit, while policy examples use autoEdit. Preserve the documented spelling for each surface and add a test that catches an accidental mismatch.
Build a matrix with all four modes. A rule with no modes field should apply everywhere. A rule limited to Default and Auto-Edit should not activate in Plan Mode. Do not use YOLO against anything except the isolated canary lab.
Persistent approvals also flow by mode. Current docs say an approval granted in Plan Mode applies to Plan and more permissive modes, while an approval granted in Default does not flow backward into Plan. Verify the generated policy after choosing any “future sessions” option; the UI action may broaden trust more than a reviewer expects.
Step 8: challenge Plan Mode assumptions
Plan Mode is designed as a read-only research mode with a constrained tool set and plan-file writes. Test that ordinary source edits, arbitrary shell commands, write-capable MCP tools, and non-plan-path writes remain blocked.
There is an important CI boundary in the current Plan Mode documentation: in non-interactive execution, entering and exiting Plan Mode can be auto-approved, and exiting to implement a plan switches to YOLO so the run does not wait for interactive approvals. Do not treat a headless Plan Mode label as a permanent read-only guarantee.
Use an analysis-only prompt, a deny rule for implementation tools, and a canary that attempts to exit the plan. Verify no marker is created. If your workflow needs implementation, make it a separate reviewed job with its own policy and sandbox.
Step 9: test MCP and subagent boundaries
Gemini policies can match an MCP server with mcpName, a simple tool name, tool annotations such as readOnlyHint, or all MCP tools with wildcards. Register a synthetic MCP server with one read-only echo and one marker-writing action. Verify a server-wide Ask rule, a narrow read-only Allow rule, and a high-priority Deny for the writing action.
Do not trust annotations alone. Independently prove the “read-only” tool did not change files, databases, network state, or remote objects.
Subagents are also policy targets. Exercise one read-only investigator and one synthetic action agent. Verify main-agent delegation and every downstream tool call are governed as expected; an approved delegation must not become blanket approval for the child’s actions.
Step 10: layer sandbox tests under policy tests
Policy decides whether a tool may be called. Sandbox controls what an allowed process can reach. Test both layers independently. Enable a disposable sandbox, permit the probe, and verify it cannot access an out-of-workspace canary or unauthorized network endpoint.
Then deny the probe at policy level and confirm the sandbox never receives it. Test a sandbox-expansion request and reject it. The sandbox documentation explicitly says sandboxing reduces but does not eliminate risk, so a passing container test does not justify broad Allow rules.
Step 11: build a CI policy regression report
Run a deterministic matrix whenever the CLI build, policy files, settings, extensions, MCP inventory, or approval mode changes. Report:
- Rules loaded and their hashes
- Expected and observed winner for each canary
- Interactive and headless decisions
- Mode and tier coverage
- Tool events, prompts, denials, and exit classification
- Independent filesystem, process, network, and remote-state evidence
- Unexpected prompts, silent allows, false denials, and indeterminate trials
Gate on semantics, not identical log text. A changed error sentence is low risk; an allowed marker in a denied case is release-blocking.
Step 12: keep policy changes human-owned
The test harness may recommend a narrower rule, but it should not rewrite user or administrator policies automatically. Require review of tool scope, argument scope, modes, interactive condition, priority, tier, owner, expiry, rollback, and the evidence from the canary matrix.
Re-run the full matrix after every approved change. Policy configuration is code with security consequences; treat it like a release artifact.
QA rollout checklist
- CLI build, OS, policy sources, hashes, settings, and sandbox state are frozen
- All actions use disposable canaries with independent side-effect checks
- User and administrator policy locations are verified
- Workspace-only policies are not trusted while the documented tier is disabled
- Allow, Deny, and Ask are tested separately
- Conflicting priorities and tiers identify the actual winner
- Command prefixes, regexes, and serialized argument patterns have near-match tests
- Interactive rejection, approval, and headless Ask-as-Deny behavior are verified
- Default, Auto-Edit, Plan, and isolated YOLO cases are covered
- Persistent approvals are inspected for mode expansion
- Headless Plan Mode cannot silently transition into unguarded implementation
- MCP annotations are backed by independent evidence
- Delegated subagent tools remain governed
- Sandbox containment and expansion denial are tested separately from policy
- Exit code is not used as the only tool-decision evidence
- CI reports semantic regressions without rewriting policy
- Humans approve policy, agent execution, merge, and release
Official sources
- Gemini CLI policy engine
- Gemini CLI Plan Mode
- Gemini CLI headless mode
- Sandboxing in Gemini CLI
- Gemini CLI configuration
A policy file is only a hypothesis until QA proves the effective decision. Use harmless canaries, test every mode and execution environment, identify the real winning rule, inspect side effects outside the transcript, layer sandboxing, and keep changes under human review.
