mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-07-16 21:10:40 -07:00
chore: remove reverse engineering of arg pattern
This commit is contained in:
@@ -20,6 +20,7 @@ function makeRule(
|
||||
priority: 0,
|
||||
source: undefined,
|
||||
argsPattern: undefined,
|
||||
constraintDisplay: undefined,
|
||||
...overrides,
|
||||
} as PolicyRule;
|
||||
}
|
||||
@@ -32,21 +33,21 @@ const ALLOW_RULES: PolicyRule[] = [
|
||||
toolName: 'run_shell_command',
|
||||
priority: 4.1,
|
||||
source: 'User: allowed-tools.toml',
|
||||
argsPattern: new RegExp('"command":"git\\ show(?:[\\s"]|\\\\")'),
|
||||
constraintDisplay: 'git show*',
|
||||
}),
|
||||
makeRule({
|
||||
decision: PolicyDecision.ALLOW,
|
||||
toolName: 'run_shell_command',
|
||||
priority: 4.1,
|
||||
source: 'User: allowed-tools.toml',
|
||||
argsPattern: new RegExp('"command":"git\\ diff(?:[\\s"]|\\\\")'),
|
||||
constraintDisplay: 'git diff*',
|
||||
}),
|
||||
makeRule({
|
||||
decision: PolicyDecision.ALLOW,
|
||||
toolName: 'run_shell_command',
|
||||
priority: 4.0,
|
||||
source: 'Workspace: .gemini/settings.json',
|
||||
argsPattern: new RegExp('"command":"npm\\ test(?:[\\s"]|\\\\")'),
|
||||
constraintDisplay: 'npm test*',
|
||||
}),
|
||||
];
|
||||
|
||||
@@ -71,7 +72,7 @@ const DENY_RULES: PolicyRule[] = [
|
||||
toolName: 'run_shell_command',
|
||||
priority: 10,
|
||||
source: 'Admin: admin-policies.toml',
|
||||
argsPattern: new RegExp('"command":"rm\\ -rf(?:[\\s"]|\\\\")'),
|
||||
constraintDisplay: 'rm -rf*',
|
||||
}),
|
||||
];
|
||||
|
||||
|
||||
@@ -5,71 +5,9 @@
|
||||
*/
|
||||
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { formatArgsPattern, buildPolicyListItems } from './policyUtils.js';
|
||||
import { buildPolicyListItems } from './policyUtils.js';
|
||||
import { PolicyDecision } from '@google/gemini-cli-core';
|
||||
|
||||
describe('formatArgsPattern', () => {
|
||||
it('should return undefined for undefined argsPattern', () => {
|
||||
expect(formatArgsPattern(undefined)).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should parse commandPrefix pattern (git diff)', () => {
|
||||
// buildArgsPatterns produces this for commandPrefix: "git diff"
|
||||
const pattern = new RegExp('"command":"git\\ diff(?:[\\s"]|\\\\")');
|
||||
expect(formatArgsPattern(pattern)).toBe('git diff*');
|
||||
});
|
||||
|
||||
it('should parse commandPrefix with escaped quotes (git show)', () => {
|
||||
// buildArgsPatterns uses escapeRegex() which escapes quotes to \"
|
||||
const pattern = new RegExp('\\"command\\":\\"git\\ show(?:[\\s"]|\\\\")');
|
||||
expect(formatArgsPattern(pattern)).toBe('git show*');
|
||||
});
|
||||
|
||||
it('should parse commandPrefix with special chars (npm run test:ci)', () => {
|
||||
// escapeRegex escapes the colon
|
||||
const pattern = new RegExp(
|
||||
'"command":"npm\\ run\\ test\\:ci(?:[\\s"]|\\\\")',
|
||||
);
|
||||
expect(formatArgsPattern(pattern)).toBe('npm run test:ci*');
|
||||
});
|
||||
|
||||
it('should parse commandPrefix with single word (ls)', () => {
|
||||
const pattern = new RegExp('"command":"ls(?:[\\s"]|\\\\")');
|
||||
expect(formatArgsPattern(pattern)).toBe('ls*');
|
||||
});
|
||||
|
||||
it('should parse commandRegex pattern', () => {
|
||||
const pattern = new RegExp('"command":"npm run .*');
|
||||
expect(formatArgsPattern(pattern)).toBe('npm run .*');
|
||||
});
|
||||
|
||||
it('should parse commandRegex with escaped quotes', () => {
|
||||
const pattern = new RegExp('\\"command\\":\\"npm run .*');
|
||||
expect(formatArgsPattern(pattern)).toBe('npm run .*');
|
||||
});
|
||||
|
||||
it('should parse file_path pattern', () => {
|
||||
const pattern = new RegExp('"file_path":".*\\/plans\\/.*"');
|
||||
expect(formatArgsPattern(pattern)).toBe('path: .*\\/plans\\/.*');
|
||||
});
|
||||
|
||||
it('should return file_path fallback for unrecognized file_path shape', () => {
|
||||
const pattern = new RegExp('"file_path"');
|
||||
expect(formatArgsPattern(pattern)).toBe('path: ...');
|
||||
});
|
||||
|
||||
it('should return raw source for unknown short regex', () => {
|
||||
const pattern = new RegExp('something_unknown');
|
||||
expect(formatArgsPattern(pattern)).toBe('something_unknown');
|
||||
});
|
||||
|
||||
it('should truncate long unknown patterns', () => {
|
||||
const longPattern = new RegExp('a'.repeat(50));
|
||||
const result = formatArgsPattern(longPattern);
|
||||
expect(result).toBe('a'.repeat(40) + '...');
|
||||
});
|
||||
});
|
||||
|
||||
describe('buildPolicyListItems', () => {
|
||||
const toolDisplayNames = new Map([
|
||||
['run_shell_command', 'Shell'],
|
||||
@@ -145,7 +83,7 @@ describe('buildPolicyListItems', () => {
|
||||
{
|
||||
decision: PolicyDecision.ALLOW,
|
||||
toolName: 'run_shell_command',
|
||||
argsPattern: new RegExp('"command":"git\\ diff(?:[\\s"]|\\\\")'),
|
||||
constraintDisplay: 'git diff*',
|
||||
},
|
||||
];
|
||||
|
||||
@@ -175,12 +113,12 @@ describe('buildPolicyListItems', () => {
|
||||
expect(items[0].searchText).toContain('run_shell_command');
|
||||
});
|
||||
|
||||
it('should format constraint from argsPattern', () => {
|
||||
it('should take constraint from constraintDisplay', () => {
|
||||
const rules = [
|
||||
{
|
||||
decision: PolicyDecision.ALLOW,
|
||||
toolName: 'run_shell_command',
|
||||
argsPattern: new RegExp('"command":"git\\ show(?:[\\s"]|\\\\")'),
|
||||
constraintDisplay: 'git show*',
|
||||
},
|
||||
];
|
||||
|
||||
@@ -192,7 +130,7 @@ describe('buildPolicyListItems', () => {
|
||||
expect(items[0].constraint).toBe('git show*');
|
||||
});
|
||||
|
||||
it('should have undefined constraint when no argsPattern', () => {
|
||||
it('should have undefined constraint when no constraintDisplay', () => {
|
||||
const rules = [
|
||||
{ decision: PolicyDecision.ALLOW, toolName: 'glob', priority: 5 },
|
||||
];
|
||||
|
||||
@@ -28,80 +28,6 @@ export interface PolicyListItem {
|
||||
searchText: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Un-escapes a regex string that was escaped by `escapeRegex()` from
|
||||
* packages/core/src/policy/utils.ts.
|
||||
*
|
||||
* escapeRegex escapes: [-[\]{}()*+?.,\\^$|#\s"]
|
||||
* by prefixing each with a backslash.
|
||||
*/
|
||||
function unescapeRegex(escaped: string): string {
|
||||
return escaped.replace(/\\(.)/g, '$1');
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats an argsPattern regex into a human-readable constraint string.
|
||||
*
|
||||
* The policy engine compiles TOML rule fields (commandPrefix, commandRegex,
|
||||
* argsPattern) into RegExp objects via buildArgsPatterns() in
|
||||
* packages/core/src/policy/utils.ts. This function reverses that compilation
|
||||
* back into readable text for display.
|
||||
*
|
||||
* Pattern formats (checked in order):
|
||||
*
|
||||
* 1. commandPrefix — `"command":"<escaped>(?:[\s"]|\\")` → `<prefix>*`
|
||||
* 2. commandRegex — starts with `"command":"` → raw regex after the prefix
|
||||
* 3. file_path — contains `"file_path":"<path>"` → `path: <path>`
|
||||
* 4. Fallback — truncated raw regex source
|
||||
*
|
||||
* Returns undefined if there is no constraint (tool-only or wildcard rules).
|
||||
*/
|
||||
export function formatArgsPattern(
|
||||
argsPattern: RegExp | undefined,
|
||||
): string | undefined {
|
||||
if (!argsPattern) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const source = argsPattern.source;
|
||||
|
||||
// buildArgsPatterns uses escapeRegex() which escapes quotes to \", so the
|
||||
// regex source contains escaped quotes. We support both escaped (\") and
|
||||
// unescaped (") formats for robustness.
|
||||
const cmdPrefix = /^\\?"command\\?":\\?"(.+?)\(\?:\[\\s"]\|\\\\"?\)$/;
|
||||
|
||||
// 1. commandPrefix: \"command\":\"<escaped-prefix>(?:[\s"]|\\")"
|
||||
// The lookahead ensures the prefix is word-bounded in JSON.
|
||||
const prefixMatch = source.match(cmdPrefix);
|
||||
if (prefixMatch) {
|
||||
return unescapeRegex(prefixMatch[1]) + '*';
|
||||
}
|
||||
|
||||
// 2. commandRegex: starts with "command":" or \"command\":\"
|
||||
const cmdRegexPrefix = /^\\?"command\\?":\\?"/;
|
||||
const cmdRegexMatch = source.match(cmdRegexPrefix);
|
||||
if (cmdRegexMatch) {
|
||||
const regex = source.slice(cmdRegexMatch[0].length);
|
||||
return regex;
|
||||
}
|
||||
|
||||
// 3. file_path pattern
|
||||
if (source.includes('"file_path"')) {
|
||||
const pathMatch = source.match(/"file_path":"(.+?)"/);
|
||||
if (pathMatch) {
|
||||
return `path: ${pathMatch[1]}`;
|
||||
}
|
||||
return 'path: ...';
|
||||
}
|
||||
|
||||
// 4. Fallback: truncated raw regex
|
||||
const maxLen = 40;
|
||||
if (source.length > maxLen) {
|
||||
return source.substring(0, maxLen) + '...';
|
||||
}
|
||||
return source;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a list of PolicyListItems from rules, filtered by decision and sorted
|
||||
* by priority descending.
|
||||
@@ -119,7 +45,7 @@ export function buildPolicyListItems(
|
||||
const toolDisplayName = rule.toolName
|
||||
? (toolDisplayNames.get(rule.toolName) ?? rule.toolName)
|
||||
: 'all tools';
|
||||
const constraint = formatArgsPattern(rule.argsPattern);
|
||||
const constraint = rule.constraintDisplay;
|
||||
const priority = String(rule.priority ?? 0);
|
||||
const source = rule.source ?? '';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user