google-gemini / google-gemini/gemini-cli
Extension docs recommend an `excludeTools` form that is never matched
- Dominant language
- TypeScript
- Stars
- 107k
- Forks
- 14.6k
- Avg merge
- 2d 3h
- Merged PRs (30d)
- 45
Description
## What happened?
`docs/extensions/best-practices.md` recommends this manifest as the way to keep a
powerful tool in check:
```json
{
"name": "my-safe-extension",
"excludeTools": ["run_shell_command(rm -rf *)"]
}
```
> This ensures the CLI blocks dangerous commands even if the model attempts to
> execute them.
`docs/extensions/reference.md:162-166` gives the same guidance, and the repo
ships an example manifest using the same shape:
```json
// packages/cli/src/commands/extensions/examples/exclude-tools/gemini-extension.json
{
"name": "excludeTools",
"version": "1.0.0",
"excludeTools": ["run_shell_command(rm -rf)"]
}
```
Extension `excludeTools` entries are matched by exact string equality, so an
entry containing `(...)` never matches a tool and nothing is excluded.
`Config.getExcludeTools()` collects the entries into a `Set`
(`packages/core/src/config/config.ts:2431-2439`) and `ToolRegistry` tests
membership directly (`packages/core/src/tools/tool-registry.ts:637`):
```ts
return !possibleNames.some((name) => excludeTools?.has(name));
```
`possibleNames` holds the tool's real names (`run_shell_command`, its class
name, and MCP-qualified variants), none of which equal
`"run_shell_command(rm -rf *)"`.
### How can this be reproduced?
Applying that matching to the documented manifests:
```
excludeTools: ["run_shell_command(rm -rf)"] -> run_shell_command still active
excludeTools: ["run_shell_command(rm -rf *)"] -> run_shell_command still active
excludeTools: ["run_shell_command"] -> run_shell_command excluded
```
Only the bare tool name has any effect. The documented form silently does
nothing — no warning is emitted that the entry was ignored.
### Why this is a docs problem rather than a code one
The parenthesised `toolName(args)` syntax is real, but it belongs to a different
setting. `tools.core` and `tools.allowed` are parsed by `mapToolsToRules`
(`packages/core/src/policy/config.ts:445-476`), which splits the shape into a
tool name plus an args pattern. Extension `excludeTools` does not go through
that path, and `tools.exclude` — the settings-level equivalent — is already
deprecated in favour of the policy engine (#18508), which is documented in
`docs/tools/shell.md:158` and `docs/cli/enterprise.md:267`.
For the "block one specific command" use case the extension docs are describing,
the policy engine already supports it directly, and extensions can ship policies
in a `policies/` directory (`docs/reference/policy-engine.md:143`,
`packages/cli/src/config/extension-manager.ts:932`):
```toml
toolName = "run_shell_command"
commandPrefix = "rm -rf"
decision = "deny"
priority = 100
```
So the extension docs appear to have been missed when that guidance moved to the
policy engine.
---
## What did you expect to happen?
The extension documentation and the shipped example should show a form that
actually takes effect — either the bare tool name, or a policy rule for
command-level blocking — rather than a form that is silently ignored while the
text promises it "blocks dangerous commands".
A documentation PR is linked below.
---
## Client information
Client Information
```console
> /about
About Gemini CLI
CLI Version 0.56.0-nightly.20260806.g761f604c1
Git Commit 5411f113c
Model gemini-3-pro-preview
Sandbox no sandbox
OS darwin
```
Platform: macOS. The matching path is platform independent — it compares
configuration strings only.
---
## Login information
Not applicable. This concerns tool filtering configuration, independent of the
authentication method.
---
## Anything else we need to know?
**Severity.** This is not an exploit path and needs no attacker. A user who
copies the documented snippet ends up without the exclusion they think they
configured; `run_shell_command` then follows the default write policy, which
still prompts for confirmation rather than running silently. The issue is the
gap between what the docs promise and what the configuration does.
**Related.** #17728 reported the same exact-match behaviour for the
settings-level `tools.exclude` and was closed as stale; that setting has since
been deprecated by #18508. Extension `excludeTools` carries no deprecation
marker and is still the documented mechanism, which is why the docs matter here.
Contributor guide
Research direction
Start with docs/extensions/best-practices.md, docs/extensions/reference.md, and packages/cli/src/commands/extensions/examples/exclude-tools/gemini-extension.json, then verify the matching behavior in packages/core/src/config/config.ts and packages/core/src/tools/tool-registry.ts. Update the extension guidance and shipped example so they describe a configuration that takes effect, and ensure the documentation no longer promises command-level blocking through an ignored excludeTools entry.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- cli, documentation
- Issue type
- Documentation
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 76/100