Skip to content

Plan Mode: the read-only restriction for MCP tools depends on an unverified, server-controlled annotation #28548

Description

@cybrdude

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:

[[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:

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area/securityIssues related to securitystatus/need-triageIssues that need to be triaged by the triage automation.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions