mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-25 12:34:38 -07:00
feat(cli): deprecate --allowed-tools and excludeTools in favor of policy engine (#18508)
This commit is contained in:
+23
-23
@@ -27,29 +27,29 @@ and parameters.
|
|||||||
|
|
||||||
## CLI Options
|
## CLI Options
|
||||||
|
|
||||||
| Option | Alias | Type | Default | Description |
|
| Option | Alias | Type | Default | Description |
|
||||||
| -------------------------------- | ----- | ------- | --------- | ---------------------------------------------------------------------------------------------------------- |
|
| -------------------------------- | ----- | ------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| `--debug` | `-d` | boolean | `false` | Run in debug mode with verbose logging |
|
| `--debug` | `-d` | boolean | `false` | Run in debug mode with verbose logging |
|
||||||
| `--version` | `-v` | - | - | Show CLI version number and exit |
|
| `--version` | `-v` | - | - | Show CLI version number and exit |
|
||||||
| `--help` | `-h` | - | - | Show help information |
|
| `--help` | `-h` | - | - | Show help information |
|
||||||
| `--model` | `-m` | string | `auto` | Model to use. See [Model Selection](#model-selection) for available values. |
|
| `--model` | `-m` | string | `auto` | Model to use. See [Model Selection](#model-selection) for available values. |
|
||||||
| `--prompt` | `-p` | string | - | Prompt text. Appended to stdin input if provided. **Deprecated:** Use positional arguments instead. |
|
| `--prompt` | `-p` | string | - | Prompt text. Appended to stdin input if provided. **Deprecated:** Use positional arguments instead. |
|
||||||
| `--prompt-interactive` | `-i` | string | - | Execute prompt and continue in interactive mode |
|
| `--prompt-interactive` | `-i` | string | - | Execute prompt and continue in interactive mode |
|
||||||
| `--sandbox` | `-s` | boolean | `false` | Run in a sandboxed environment for safer execution |
|
| `--sandbox` | `-s` | boolean | `false` | Run in a sandboxed environment for safer execution |
|
||||||
| `--approval-mode` | - | string | `default` | Approval mode for tool execution. Choices: `default`, `auto_edit`, `yolo` |
|
| `--approval-mode` | - | string | `default` | Approval mode for tool execution. Choices: `default`, `auto_edit`, `yolo` |
|
||||||
| `--yolo` | `-y` | boolean | `false` | **Deprecated.** Auto-approve all actions. Use `--approval-mode=yolo` instead. |
|
| `--yolo` | `-y` | boolean | `false` | **Deprecated.** Auto-approve all actions. Use `--approval-mode=yolo` instead. |
|
||||||
| `--experimental-acp` | - | boolean | - | Start in ACP (Agent Code Pilot) mode. **Experimental feature.** |
|
| `--experimental-acp` | - | boolean | - | Start in ACP (Agent Code Pilot) mode. **Experimental feature.** |
|
||||||
| `--experimental-zed-integration` | - | boolean | - | Run in Zed editor integration mode. **Experimental feature.** |
|
| `--experimental-zed-integration` | - | boolean | - | Run in Zed editor integration mode. **Experimental feature.** |
|
||||||
| `--allowed-mcp-server-names` | - | array | - | Allowed MCP server names (comma-separated or multiple flags) |
|
| `--allowed-mcp-server-names` | - | array | - | Allowed MCP server names (comma-separated or multiple flags) |
|
||||||
| `--allowed-tools` | - | array | - | Tools that are allowed to run without confirmation (comma-separated or multiple flags) |
|
| `--allowed-tools` | - | array | - | **Deprecated.** Use the [Policy Engine](../core/policy-engine.md) instead. Tools that are allowed to run without confirmation (comma-separated or multiple flags) |
|
||||||
| `--extensions` | `-e` | array | - | List of extensions to use. If not provided, all extensions are enabled (comma-separated or multiple flags) |
|
| `--extensions` | `-e` | array | - | List of extensions to use. If not provided, all extensions are enabled (comma-separated or multiple flags) |
|
||||||
| `--list-extensions` | `-l` | boolean | - | List all available extensions and exit |
|
| `--list-extensions` | `-l` | boolean | - | List all available extensions and exit |
|
||||||
| `--resume` | `-r` | string | - | Resume a previous session. Use `"latest"` for most recent or index number (e.g. `--resume 5`) |
|
| `--resume` | `-r` | string | - | Resume a previous session. Use `"latest"` for most recent or index number (e.g. `--resume 5`) |
|
||||||
| `--list-sessions` | - | boolean | - | List available sessions for the current project and exit |
|
| `--list-sessions` | - | boolean | - | List available sessions for the current project and exit |
|
||||||
| `--delete-session` | - | string | - | Delete a session by index number (use `--list-sessions` to see available sessions) |
|
| `--delete-session` | - | string | - | Delete a session by index number (use `--list-sessions` to see available sessions) |
|
||||||
| `--include-directories` | - | array | - | Additional directories to include in the workspace (comma-separated or multiple flags) |
|
| `--include-directories` | - | array | - | Additional directories to include in the workspace (comma-separated or multiple flags) |
|
||||||
| `--screen-reader` | - | boolean | - | Enable screen reader mode for accessibility |
|
| `--screen-reader` | - | boolean | - | Enable screen reader mode for accessibility |
|
||||||
| `--output-format` | `-o` | string | `text` | The format of the CLI output. Choices: `text`, `json`, `stream-json` |
|
| `--output-format` | `-o` | string | `text` | The format of the CLI output. Choices: `text`, `json`, `stream-json` |
|
||||||
|
|
||||||
## Model selection
|
## Model selection
|
||||||
|
|
||||||
|
|||||||
@@ -223,9 +223,9 @@ gemini
|
|||||||
## Restricting tool access
|
## Restricting tool access
|
||||||
|
|
||||||
You can significantly enhance security by controlling which tools the Gemini
|
You can significantly enhance security by controlling which tools the Gemini
|
||||||
model can use. This is achieved through the `tools.core` and `tools.exclude`
|
model can use. This is achieved through the `tools.core` setting and the
|
||||||
settings. For a list of available tools, see the
|
[Policy Engine](../core/policy-engine.md). For a list of available tools, see
|
||||||
[Tools documentation](../tools/index.md).
|
the [Tools documentation](../tools/index.md).
|
||||||
|
|
||||||
### Allowlisting with `coreTools`
|
### Allowlisting with `coreTools`
|
||||||
|
|
||||||
@@ -243,7 +243,10 @@ on the approved list.
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Blocklisting with `excludeTools`
|
### Blocklisting with `excludeTools` (Deprecated)
|
||||||
|
|
||||||
|
> **Deprecated:** Use the [Policy Engine](../core/policy-engine.md) for more
|
||||||
|
> robust control.
|
||||||
|
|
||||||
Alternatively, you can add specific tools that are considered dangerous in your
|
Alternatively, you can add specific tools that are considered dangerous in your
|
||||||
environment to a blocklist.
|
environment to a blocklist.
|
||||||
|
|||||||
@@ -166,19 +166,21 @@ a few things you can try in order of recommendation:
|
|||||||
- **Default:** All tools available for use by the Gemini model.
|
- **Default:** All tools available for use by the Gemini model.
|
||||||
- **Example:** `"coreTools": ["ReadFileTool", "GlobTool", "ShellTool(ls)"]`.
|
- **Example:** `"coreTools": ["ReadFileTool", "GlobTool", "ShellTool(ls)"]`.
|
||||||
|
|
||||||
- **`allowedTools`** (array of strings):
|
- **`allowedTools`** (array of strings) [DEPRECATED]:
|
||||||
- **Default:** `undefined`
|
- **Default:** `undefined`
|
||||||
- **Description:** A list of tool names that will bypass the confirmation
|
- **Description:** A list of tool names that will bypass the confirmation
|
||||||
dialog. This is useful for tools that you trust and use frequently. The
|
dialog. This is useful for tools that you trust and use frequently. The
|
||||||
match semantics are the same as `coreTools`.
|
match semantics are the same as `coreTools`. **Deprecated**: Use the
|
||||||
|
[Policy Engine](../core/policy-engine.md) instead.
|
||||||
- **Example:** `"allowedTools": ["ShellTool(git status)"]`.
|
- **Example:** `"allowedTools": ["ShellTool(git status)"]`.
|
||||||
|
|
||||||
- **`excludeTools`** (array of strings):
|
- **`excludeTools`** (array of strings) [DEPRECATED]:
|
||||||
- **Description:** Allows you to specify a list of core tool names that should
|
- **Description:** Allows you to specify a list of core tool names that should
|
||||||
be excluded from the model. A tool listed in both `excludeTools` and
|
be excluded from the model. A tool listed in both `excludeTools` and
|
||||||
`coreTools` is excluded. You can also specify command-specific restrictions
|
`coreTools` is excluded. You can also specify command-specific restrictions
|
||||||
for tools that support it, like the `ShellTool`. For example,
|
for tools that support it, like the `ShellTool`. For example,
|
||||||
`"excludeTools": ["ShellTool(rm -rf)"]` will block the `rm -rf` command.
|
`"excludeTools": ["ShellTool(rm -rf)"]` will block the `rm -rf` command.
|
||||||
|
**Deprecated**: Use the [Policy Engine](../core/policy-engine.md) instead.
|
||||||
- **Default**: No tools excluded.
|
- **Default**: No tools excluded.
|
||||||
- **Example:** `"excludeTools": ["run_shell_command", "findFiles"]`.
|
- **Example:** `"excludeTools": ["run_shell_command", "findFiles"]`.
|
||||||
- **Security Note:** Command-specific restrictions in `excludeTools` for
|
- **Security Note:** Command-specific restrictions in `excludeTools` for
|
||||||
|
|||||||
+5
-4
@@ -167,10 +167,11 @@ configuration file.
|
|||||||
`"tools": {"core": ["run_shell_command(git)"]}` will only allow `git`
|
`"tools": {"core": ["run_shell_command(git)"]}` will only allow `git`
|
||||||
commands. Including the generic `run_shell_command` acts as a wildcard,
|
commands. Including the generic `run_shell_command` acts as a wildcard,
|
||||||
allowing any command not explicitly blocked.
|
allowing any command not explicitly blocked.
|
||||||
- `tools.exclude`: To block specific commands, add entries to the `exclude` list
|
- `tools.exclude` [DEPRECATED]: To block specific commands, use the
|
||||||
under the `tools` category in the format `run_shell_command(<command>)`. For
|
[Policy Engine](../core/policy-engine.md). Historically, this setting allowed
|
||||||
example, `"tools": {"exclude": ["run_shell_command(rm)"]}` will block `rm`
|
adding entries to the `exclude` list under the `tools` category in the format
|
||||||
commands.
|
`run_shell_command(<command>)`. For example,
|
||||||
|
`"tools": {"exclude": ["run_shell_command(rm)"]}` will block `rm` commands.
|
||||||
|
|
||||||
The validation logic is designed to be secure and flexible:
|
The validation logic is designed to be secure and flexible:
|
||||||
|
|
||||||
|
|||||||
@@ -177,7 +177,8 @@ export async function parseArguments(
|
|||||||
type: 'array',
|
type: 'array',
|
||||||
string: true,
|
string: true,
|
||||||
nargs: 1,
|
nargs: 1,
|
||||||
description: 'Tools that are allowed to run without confirmation',
|
description:
|
||||||
|
'[DEPRECATED: Use Policy Engine instead See https://geminicli.com/docs/core/policy-engine] Tools that are allowed to run without confirmation',
|
||||||
coerce: (tools: string[]) =>
|
coerce: (tools: string[]) =>
|
||||||
// Handle comma-separated values
|
// Handle comma-separated values
|
||||||
tools.flatMap((tool) => tool.split(',').map((t) => t.trim())),
|
tools.flatMap((tool) => tool.split(',').map((t) => t.trim())),
|
||||||
|
|||||||
@@ -361,6 +361,26 @@ export async function main() {
|
|||||||
const argv = await parseArguments(settings.merged);
|
const argv = await parseArguments(settings.merged);
|
||||||
parseArgsHandle?.end();
|
parseArgsHandle?.end();
|
||||||
|
|
||||||
|
if (
|
||||||
|
(argv.allowedTools && argv.allowedTools.length > 0) ||
|
||||||
|
(settings.merged.tools?.allowed && settings.merged.tools.allowed.length > 0)
|
||||||
|
) {
|
||||||
|
coreEvents.emitFeedback(
|
||||||
|
'warning',
|
||||||
|
'Warning: --allowed-tools cli argument and tools.allowed in settings.json are deprecated and will be removed in 1.0: Migrate to Policy Engine: https://geminicli.com/docs/core/policy-engine/',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
settings.merged.tools?.exclude &&
|
||||||
|
settings.merged.tools.exclude.length > 0
|
||||||
|
) {
|
||||||
|
coreEvents.emitFeedback(
|
||||||
|
'warning',
|
||||||
|
'Warning: tools.exclude in settings.json is deprecated and will be removed in 1.0. Migrate to Policy Engine: https://geminicli.com/docs/core/policy-engine/',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (argv.startupMessages) {
|
if (argv.startupMessages) {
|
||||||
argv.startupMessages.forEach((msg) => {
|
argv.startupMessages.forEach((msg) => {
|
||||||
coreEvents.emitFeedback('info', msg);
|
coreEvents.emitFeedback('info', msg);
|
||||||
|
|||||||
@@ -383,7 +383,9 @@ export interface ConfigParameters {
|
|||||||
question?: string;
|
question?: string;
|
||||||
|
|
||||||
coreTools?: string[];
|
coreTools?: string[];
|
||||||
|
/** @deprecated Use Policy Engine instead */
|
||||||
allowedTools?: string[];
|
allowedTools?: string[];
|
||||||
|
/** @deprecated Use Policy Engine instead */
|
||||||
excludeTools?: string[];
|
excludeTools?: string[];
|
||||||
toolDiscoveryCommand?: string;
|
toolDiscoveryCommand?: string;
|
||||||
toolCallCommand?: string;
|
toolCallCommand?: string;
|
||||||
@@ -516,7 +518,9 @@ export class Config {
|
|||||||
private readonly question: string | undefined;
|
private readonly question: string | undefined;
|
||||||
|
|
||||||
private readonly coreTools: string[] | undefined;
|
private readonly coreTools: string[] | undefined;
|
||||||
|
/** @deprecated Use Policy Engine instead */
|
||||||
private readonly allowedTools: string[] | undefined;
|
private readonly allowedTools: string[] | undefined;
|
||||||
|
/** @deprecated Use Policy Engine instead */
|
||||||
private readonly excludeTools: string[] | undefined;
|
private readonly excludeTools: string[] | undefined;
|
||||||
private readonly toolDiscoveryCommand: string | undefined;
|
private readonly toolDiscoveryCommand: string | undefined;
|
||||||
private readonly toolCallCommand: string | undefined;
|
private readonly toolCallCommand: string | undefined;
|
||||||
@@ -1487,11 +1491,12 @@ export class Config {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* All the excluded tools from static configuration, loaded extensions, or
|
* All the excluded tools from static configuration, loaded extensions, or
|
||||||
* other sources.
|
* other sources (like the Policy Engine).
|
||||||
*
|
*
|
||||||
* May change over time.
|
* May change over time.
|
||||||
*/
|
*/
|
||||||
getExcludeTools(): Set<string> | undefined {
|
getExcludeTools(): Set<string> | undefined {
|
||||||
|
// Right now this is present for backward compatibility with settings.json exclude
|
||||||
const excludeToolsSet = new Set([...(this.excludeTools ?? [])]);
|
const excludeToolsSet = new Set([...(this.excludeTools ?? [])]);
|
||||||
for (const extension of this.getExtensionLoader().getExtensions()) {
|
for (const extension of this.getExtensionLoader().getExtensions()) {
|
||||||
if (!extension.isActive) {
|
if (!extension.isActive) {
|
||||||
@@ -1501,6 +1506,12 @@ export class Config {
|
|||||||
excludeToolsSet.add(tool);
|
excludeToolsSet.add(tool);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const policyExclusions = this.policyEngine.getExcludedTools();
|
||||||
|
for (const tool of policyExclusions) {
|
||||||
|
excludeToolsSet.add(tool);
|
||||||
|
}
|
||||||
|
|
||||||
return excludeToolsSet;
|
return excludeToolsSet;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2031,6 +2031,156 @@ describe('PolicyEngine', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('getExcludedTools', () => {
|
||||||
|
interface TestCase {
|
||||||
|
name: string;
|
||||||
|
rules: PolicyRule[];
|
||||||
|
approvalMode?: ApprovalMode;
|
||||||
|
nonInteractive?: boolean;
|
||||||
|
expected: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const testCases: TestCase[] = [
|
||||||
|
{
|
||||||
|
name: 'should return empty set when no rules provided',
|
||||||
|
rules: [],
|
||||||
|
expected: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'should include tools with DENY decision',
|
||||||
|
rules: [
|
||||||
|
{ toolName: 'tool1', decision: PolicyDecision.DENY },
|
||||||
|
{ toolName: 'tool2', decision: PolicyDecision.ALLOW },
|
||||||
|
],
|
||||||
|
expected: ['tool1'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'should respect priority and ignore lower priority rules (DENY wins)',
|
||||||
|
rules: [
|
||||||
|
{ toolName: 'tool1', decision: PolicyDecision.DENY, priority: 100 },
|
||||||
|
{ toolName: 'tool1', decision: PolicyDecision.ALLOW, priority: 10 },
|
||||||
|
],
|
||||||
|
expected: ['tool1'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'should respect priority and ignore lower priority rules (ALLOW wins)',
|
||||||
|
rules: [
|
||||||
|
{ toolName: 'tool1', decision: PolicyDecision.ALLOW, priority: 100 },
|
||||||
|
{ toolName: 'tool1', decision: PolicyDecision.DENY, priority: 10 },
|
||||||
|
],
|
||||||
|
expected: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'should NOT include ASK_USER tools even in non-interactive mode',
|
||||||
|
rules: [{ toolName: 'tool1', decision: PolicyDecision.ASK_USER }],
|
||||||
|
nonInteractive: true,
|
||||||
|
expected: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'should ignore rules with argsPattern',
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
toolName: 'tool1',
|
||||||
|
decision: PolicyDecision.DENY,
|
||||||
|
argsPattern: /something/,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
expected: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'should respect approval mode (PLAN mode)',
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
toolName: 'tool1',
|
||||||
|
decision: PolicyDecision.DENY,
|
||||||
|
modes: [ApprovalMode.PLAN],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
approvalMode: ApprovalMode.PLAN,
|
||||||
|
expected: ['tool1'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'should respect approval mode (DEFAULT mode)',
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
toolName: 'tool1',
|
||||||
|
decision: PolicyDecision.DENY,
|
||||||
|
modes: [ApprovalMode.PLAN],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
approvalMode: ApprovalMode.DEFAULT,
|
||||||
|
expected: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'should respect wildcard ALLOW rules (e.g. YOLO mode)',
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
decision: PolicyDecision.ALLOW,
|
||||||
|
priority: 999,
|
||||||
|
modes: [ApprovalMode.YOLO],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
toolName: 'dangerous-tool',
|
||||||
|
decision: PolicyDecision.DENY,
|
||||||
|
priority: 10,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
approvalMode: ApprovalMode.YOLO,
|
||||||
|
expected: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'should respect server wildcard DENY',
|
||||||
|
rules: [{ toolName: 'server__*', decision: PolicyDecision.DENY }],
|
||||||
|
expected: ['server__*'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'should expand server wildcard for specific tools if already processed',
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
toolName: 'server__*',
|
||||||
|
decision: PolicyDecision.DENY,
|
||||||
|
priority: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
toolName: 'server__tool1',
|
||||||
|
decision: PolicyDecision.DENY,
|
||||||
|
priority: 10,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
expected: ['server__*', 'server__tool1'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'should NOT exclude tool if covered by a higher priority wildcard ALLOW',
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
toolName: 'server__*',
|
||||||
|
decision: PolicyDecision.ALLOW,
|
||||||
|
priority: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
toolName: 'server__tool1',
|
||||||
|
decision: PolicyDecision.DENY,
|
||||||
|
priority: 10,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
expected: [],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
it.each(testCases)(
|
||||||
|
'$name',
|
||||||
|
({ rules, approvalMode, nonInteractive, expected }) => {
|
||||||
|
engine = new PolicyEngine({
|
||||||
|
rules,
|
||||||
|
approvalMode: approvalMode ?? ApprovalMode.DEFAULT,
|
||||||
|
nonInteractive: nonInteractive ?? false,
|
||||||
|
});
|
||||||
|
const excluded = engine.getExcludedTools();
|
||||||
|
expect(Array.from(excluded).sort()).toEqual(expected.sort());
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
describe('YOLO mode with ask_user tool', () => {
|
describe('YOLO mode with ask_user tool', () => {
|
||||||
it('should return ASK_USER for ask_user tool even in YOLO mode', async () => {
|
it('should return ASK_USER for ask_user tool even in YOLO mode', async () => {
|
||||||
const rules: PolicyRule[] = [
|
const rules: PolicyRule[] = [
|
||||||
|
|||||||
@@ -26,6 +26,22 @@ import {
|
|||||||
} from '../utils/shell-utils.js';
|
} from '../utils/shell-utils.js';
|
||||||
import { getToolAliases } from '../tools/tool-names.js';
|
import { getToolAliases } from '../tools/tool-names.js';
|
||||||
|
|
||||||
|
function isWildcardPattern(name: string): boolean {
|
||||||
|
return name.endsWith('__*');
|
||||||
|
}
|
||||||
|
|
||||||
|
function getWildcardPrefix(pattern: string): string {
|
||||||
|
return pattern.slice(0, -3);
|
||||||
|
}
|
||||||
|
|
||||||
|
function matchesWildcard(pattern: string, toolName: string): boolean {
|
||||||
|
if (!isWildcardPattern(pattern)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const prefix = getWildcardPrefix(pattern);
|
||||||
|
return toolName.startsWith(prefix + '__');
|
||||||
|
}
|
||||||
|
|
||||||
function ruleMatches(
|
function ruleMatches(
|
||||||
rule: PolicyRule | SafetyCheckerRule,
|
rule: PolicyRule | SafetyCheckerRule,
|
||||||
toolCall: FunctionCall,
|
toolCall: FunctionCall,
|
||||||
@@ -43,8 +59,8 @@ function ruleMatches(
|
|||||||
// Check tool name if specified
|
// Check tool name if specified
|
||||||
if (rule.toolName) {
|
if (rule.toolName) {
|
||||||
// Support wildcard patterns: "serverName__*" matches "serverName__anyTool"
|
// Support wildcard patterns: "serverName__*" matches "serverName__anyTool"
|
||||||
if (rule.toolName.endsWith('__*')) {
|
if (isWildcardPattern(rule.toolName)) {
|
||||||
const prefix = rule.toolName.slice(0, -3); // Remove "__*"
|
const prefix = getWildcardPrefix(rule.toolName);
|
||||||
if (serverName !== undefined) {
|
if (serverName !== undefined) {
|
||||||
// Robust check: if serverName is provided, it MUST match the prefix exactly.
|
// Robust check: if serverName is provided, it MUST match the prefix exactly.
|
||||||
// This prevents "malicious-server" from spoofing "trusted-server" by naming itself "trusted-server__malicious".
|
// This prevents "malicious-server" from spoofing "trusted-server" by naming itself "trusted-server__malicious".
|
||||||
@@ -53,7 +69,7 @@ function ruleMatches(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Always verify the prefix, even if serverName matched
|
// Always verify the prefix, even if serverName matched
|
||||||
if (!toolCall.name || !toolCall.name.startsWith(prefix + '__')) {
|
if (!toolCall.name || !matchesWildcard(rule.toolName, toolCall.name)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else if (toolCall.name !== rule.toolName) {
|
} else if (toolCall.name !== rule.toolName) {
|
||||||
@@ -509,6 +525,90 @@ export class PolicyEngine {
|
|||||||
return this.hookCheckers;
|
return this.hookCheckers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get tools that are effectively denied by the current rules.
|
||||||
|
* This takes into account:
|
||||||
|
* 1. Global rules (no argsPattern)
|
||||||
|
* 2. Priority order (higher priority wins)
|
||||||
|
* 3. Non-interactive mode (ASK_USER becomes DENY)
|
||||||
|
*/
|
||||||
|
getExcludedTools(): Set<string> {
|
||||||
|
const excludedTools = new Set<string>();
|
||||||
|
const processedTools = new Set<string>();
|
||||||
|
let globalVerdict: PolicyDecision | undefined;
|
||||||
|
|
||||||
|
for (const rule of this.rules) {
|
||||||
|
// We only care about rules without args pattern for exclusion from the model
|
||||||
|
if (rule.argsPattern) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if rule applies to current approval mode
|
||||||
|
if (rule.modes && rule.modes.length > 0) {
|
||||||
|
if (!rule.modes.includes(this.approvalMode)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle Global Rules
|
||||||
|
if (!rule.toolName) {
|
||||||
|
if (globalVerdict === undefined) {
|
||||||
|
globalVerdict = rule.decision;
|
||||||
|
if (globalVerdict !== PolicyDecision.DENY) {
|
||||||
|
// Global ALLOW/ASK found.
|
||||||
|
// Since rules are sorted by priority, this overrides any lower-priority rules.
|
||||||
|
// We can stop processing because nothing else will be excluded.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// If Global DENY, we continue to find specific tools to add to excluded set
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const toolName = rule.toolName;
|
||||||
|
|
||||||
|
// Check if already processed (exact match)
|
||||||
|
if (processedTools.has(toolName)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if covered by a processed wildcard
|
||||||
|
let coveredByWildcard = false;
|
||||||
|
for (const processed of processedTools) {
|
||||||
|
if (
|
||||||
|
isWildcardPattern(processed) &&
|
||||||
|
matchesWildcard(processed, toolName)
|
||||||
|
) {
|
||||||
|
// It's covered by a higher-priority wildcard rule.
|
||||||
|
// If that wildcard rule resulted in exclusion, this tool should also be excluded.
|
||||||
|
if (excludedTools.has(processed)) {
|
||||||
|
excludedTools.add(toolName);
|
||||||
|
}
|
||||||
|
coveredByWildcard = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (coveredByWildcard) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
processedTools.add(toolName);
|
||||||
|
|
||||||
|
// Determine decision
|
||||||
|
let decision: PolicyDecision;
|
||||||
|
if (globalVerdict !== undefined) {
|
||||||
|
decision = globalVerdict;
|
||||||
|
} else {
|
||||||
|
decision = rule.decision;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (decision === PolicyDecision.DENY) {
|
||||||
|
excludedTools.add(toolName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return excludedTools;
|
||||||
|
}
|
||||||
|
|
||||||
private applyNonInteractiveMode(decision: PolicyDecision): PolicyDecision {
|
private applyNonInteractiveMode(decision: PolicyDecision): PolicyDecision {
|
||||||
// In non-interactive mode, ASK_USER becomes DENY
|
// In non-interactive mode, ASK_USER becomes DENY
|
||||||
if (this.nonInteractive && decision === PolicyDecision.ASK_USER) {
|
if (this.nonInteractive && decision === PolicyDecision.ASK_USER) {
|
||||||
|
|||||||
Reference in New Issue
Block a user