google-gemini / google-gemini/gemini-cli

Extension docs recommend an `excludeTools` form that is never matched

Open
#28,962 3 comments 0 reactions 0 assignees View on GitHub
area/agent kind/bug priority/p2 status/bot-triaged
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.