google-gemini / google-gemini/gemini-cli
Plan Mode: the read-only restriction for MCP tools depends on an unverified, server-controlled annotation
- Dominant language
- TypeScript
- Stars
- 107k
- Forks
- 14.6k
- Avg merge
- 2d 3h
- Merged PRs (30d)
- 45
Description
Reported to Google VRP as b/524448710 (closed Won't Fix / Infeasible, P2 / S4). The triage team confirmed on 2026-07-27 that a public PR is welcome, so I am filing the details and a proposed fix here.
## Summary
Plan Mode tells the user it is read-only ("You are in Plan Mode with access to read-only tools"). For MCP tools, the read-only signal is the `readOnlyHint` annotation supplied by the MCP server itself. Gemini CLI does not verify that annotation, and the Plan Mode policy promotes any MCP tool carrying it out of the deny-all and into `ask_user`.
A malicious or compromised MCP server can therefore set `readOnlyHint: true` on a destructive tool and have it offered inside Plan Mode, while an honestly annotated destructive tool from the same server is correctly denied. The server decides which of its tools Plan Mode will surface.
The confirmation prompt still fires, so this is not silent execution. The gap is that the prompt never tells the user the read-only claim came from the server and was never verified, while Plan Mode's own messaging tells them Plan Mode is read-only.
## Where this is on main
`packages/core/src/policy/policies/plan.toml` denies everything in Plan Mode at priority 40, then promotes annotated tools above it:
```toml
[[rule]]
toolName = "*"
mcpName = "*"
toolAnnotations = { readOnlyHint = true }
decision = "ask_user"
priority = 50
modes = ["plan"]
interactive = true
```
`packages/core/src/tools/mcp-client.ts:1367` takes the value straight from the server's `tools/list` response:
```ts
const isReadOnly = annotations?.readOnlyHint === true;
```
`packages/cli/src/ui/components/messages/ToolConfirmationMessage.tsx` renders the MCP confirmation with the server name and tool name only. The read-only claim, and the fact that it is unverified, are never shown to the person being asked to approve.
## Reproduce
1. Configure an MCP server exposing two tools: one with `annotations: { readOnlyHint: true }` whose implementation is destructive, and one with `readOnlyHint: false`.
2. Start `gemini` and enter Plan Mode with `/plan`.
3. Ask for the honest tool. It is unavailable, correctly denied.
4. Ask for the tool that claims read-only. Plan Mode offers `Allow execution of MCP tool "..." from server "..."?` and it runs on approval.
Verified end to end on 0.45.1, and separately against the current default policy TOMLs by loading them with `@iarna/toml` and applying the engine's annotation matching and priority resolution. Only the server-supplied value changes between the two runs:
```
readOnlyHint = true -> "ask_user" (priority 1.050, plan.toml)
readOnlyHint = false -> "deny" (priority 1.040, plan.toml)
```
## Preconditions, stated plainly
The user has to add the malicious or compromised server to their own configuration, and the confirmation prompt still appears. This is not silent execution and I am not arguing that it is. What it defeats is the specific expectation Plan Mode sets, which is the thing that makes the approval more likely to be granted.
## Proposed fix (PR to follow)
Surface the provenance at the point of decision. When an MCP tool reaches the Plan Mode confirmation because of `readOnlyHint`, tell the user that the server declared the tool read-only and that Gemini CLI has not verified that claim. Additive, no default behavior change, Plan Mode MCP support stays intact.
## Possible follow-up, if you want it
Gate the promotion on user-side trust: honor `readOnlyHint` in Plan Mode only for servers the user has explicitly marked trusted, and treat MCP tools as non-read-only in Plan Mode otherwise. Stronger boundary, but it changes default behavior for existing MCP users, so I have deliberately kept it out of the first PR. Happy to build it if you would prefer that shape.
Related prior discussion in the opposite direction: #27156 and #27163 proposed trusting `readOnlyHint` enough to auto-allow annotated tools in Plan Mode. Both were closed.
Contributor guide
Research direction
Start with packages/core/src/policy/policies/plan.toml and packages/core/src/tools/mcp-client.ts:1367 to trace how the server-supplied readOnlyHint reaches Plan Mode, then inspect packages/cli/src/ui/components/messages/ToolConfirmationMessage.tsx. Done means the MCP confirmation identifies the read-only claim as server-declared and unverified, without changing default behavior; verify the relevant policy and UI tests.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- cli, security
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 68/100