mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-07-14 12:00:18 -07:00
fix(core): prioritize conditional policy rules and harden Plan Mode (#18882)
This commit is contained in:
@@ -2046,33 +2046,91 @@ describe('PolicyEngine', () => {
|
|||||||
rules: [],
|
rules: [],
|
||||||
expected: [],
|
expected: [],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'should apply rules without explicit modes to all modes',
|
||||||
|
rules: [{ toolName: 'tool1', decision: PolicyDecision.DENY }],
|
||||||
|
expected: ['tool1'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'should NOT exclude tool if higher priority argsPattern rule exists',
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
toolName: 'tool1',
|
||||||
|
decision: PolicyDecision.ALLOW,
|
||||||
|
argsPattern: /safe/,
|
||||||
|
priority: 100,
|
||||||
|
modes: [ApprovalMode.DEFAULT],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
toolName: 'tool1',
|
||||||
|
decision: PolicyDecision.DENY,
|
||||||
|
priority: 10,
|
||||||
|
modes: [ApprovalMode.DEFAULT],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
expected: [],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'should include tools with DENY decision',
|
name: 'should include tools with DENY decision',
|
||||||
rules: [
|
rules: [
|
||||||
{ toolName: 'tool1', decision: PolicyDecision.DENY },
|
{
|
||||||
{ toolName: 'tool2', decision: PolicyDecision.ALLOW },
|
toolName: 'tool1',
|
||||||
|
decision: PolicyDecision.DENY,
|
||||||
|
modes: [ApprovalMode.DEFAULT],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
toolName: 'tool2',
|
||||||
|
decision: PolicyDecision.ALLOW,
|
||||||
|
modes: [ApprovalMode.DEFAULT],
|
||||||
|
},
|
||||||
],
|
],
|
||||||
expected: ['tool1'],
|
expected: ['tool1'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'should respect priority and ignore lower priority rules (DENY wins)',
|
name: 'should respect priority and ignore lower priority rules (DENY wins)',
|
||||||
rules: [
|
rules: [
|
||||||
{ toolName: 'tool1', decision: PolicyDecision.DENY, priority: 100 },
|
{
|
||||||
{ toolName: 'tool1', decision: PolicyDecision.ALLOW, priority: 10 },
|
toolName: 'tool1',
|
||||||
|
decision: PolicyDecision.DENY,
|
||||||
|
priority: 100,
|
||||||
|
modes: [ApprovalMode.DEFAULT],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
toolName: 'tool1',
|
||||||
|
decision: PolicyDecision.ALLOW,
|
||||||
|
priority: 10,
|
||||||
|
modes: [ApprovalMode.DEFAULT],
|
||||||
|
},
|
||||||
],
|
],
|
||||||
expected: ['tool1'],
|
expected: ['tool1'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'should respect priority and ignore lower priority rules (ALLOW wins)',
|
name: 'should respect priority and ignore lower priority rules (ALLOW wins)',
|
||||||
rules: [
|
rules: [
|
||||||
{ toolName: 'tool1', decision: PolicyDecision.ALLOW, priority: 100 },
|
{
|
||||||
{ toolName: 'tool1', decision: PolicyDecision.DENY, priority: 10 },
|
toolName: 'tool1',
|
||||||
|
decision: PolicyDecision.ALLOW,
|
||||||
|
priority: 100,
|
||||||
|
modes: [ApprovalMode.DEFAULT],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
toolName: 'tool1',
|
||||||
|
decision: PolicyDecision.DENY,
|
||||||
|
priority: 10,
|
||||||
|
modes: [ApprovalMode.DEFAULT],
|
||||||
|
},
|
||||||
],
|
],
|
||||||
expected: [],
|
expected: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'should NOT include ASK_USER tools even in non-interactive mode',
|
name: 'should NOT include ASK_USER tools even in non-interactive mode',
|
||||||
rules: [{ toolName: 'tool1', decision: PolicyDecision.ASK_USER }],
|
rules: [
|
||||||
|
{
|
||||||
|
toolName: 'tool1',
|
||||||
|
decision: PolicyDecision.ASK_USER,
|
||||||
|
modes: [ApprovalMode.DEFAULT],
|
||||||
|
},
|
||||||
|
],
|
||||||
nonInteractive: true,
|
nonInteractive: true,
|
||||||
expected: [],
|
expected: [],
|
||||||
},
|
},
|
||||||
@@ -2083,6 +2141,7 @@ describe('PolicyEngine', () => {
|
|||||||
toolName: 'tool1',
|
toolName: 'tool1',
|
||||||
decision: PolicyDecision.DENY,
|
decision: PolicyDecision.DENY,
|
||||||
argsPattern: /something/,
|
argsPattern: /something/,
|
||||||
|
modes: [ApprovalMode.DEFAULT],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
expected: [],
|
expected: [],
|
||||||
@@ -2123,6 +2182,7 @@ describe('PolicyEngine', () => {
|
|||||||
toolName: 'dangerous-tool',
|
toolName: 'dangerous-tool',
|
||||||
decision: PolicyDecision.DENY,
|
decision: PolicyDecision.DENY,
|
||||||
priority: 10,
|
priority: 10,
|
||||||
|
modes: [ApprovalMode.YOLO],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
approvalMode: ApprovalMode.YOLO,
|
approvalMode: ApprovalMode.YOLO,
|
||||||
@@ -2130,7 +2190,13 @@ describe('PolicyEngine', () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'should respect server wildcard DENY',
|
name: 'should respect server wildcard DENY',
|
||||||
rules: [{ toolName: 'server__*', decision: PolicyDecision.DENY }],
|
rules: [
|
||||||
|
{
|
||||||
|
toolName: 'server__*',
|
||||||
|
decision: PolicyDecision.DENY,
|
||||||
|
modes: [ApprovalMode.DEFAULT],
|
||||||
|
},
|
||||||
|
],
|
||||||
expected: ['server__*'],
|
expected: ['server__*'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -2140,15 +2206,44 @@ describe('PolicyEngine', () => {
|
|||||||
toolName: 'server__*',
|
toolName: 'server__*',
|
||||||
decision: PolicyDecision.DENY,
|
decision: PolicyDecision.DENY,
|
||||||
priority: 100,
|
priority: 100,
|
||||||
|
modes: [ApprovalMode.DEFAULT],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
toolName: 'server__tool1',
|
toolName: 'server__tool1',
|
||||||
decision: PolicyDecision.DENY,
|
decision: PolicyDecision.DENY,
|
||||||
priority: 10,
|
priority: 10,
|
||||||
|
modes: [ApprovalMode.DEFAULT],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
expected: ['server__*', 'server__tool1'],
|
expected: ['server__*', 'server__tool1'],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'should exclude run_shell_command but NOT write_file in simulated Plan Mode',
|
||||||
|
approvalMode: ApprovalMode.PLAN,
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
// Simulates the high-priority allow for plans directory
|
||||||
|
toolName: 'write_file',
|
||||||
|
decision: PolicyDecision.ALLOW,
|
||||||
|
priority: 70,
|
||||||
|
argsPattern: /plans/,
|
||||||
|
modes: [ApprovalMode.PLAN],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Simulates the global deny in Plan Mode
|
||||||
|
decision: PolicyDecision.DENY,
|
||||||
|
priority: 60,
|
||||||
|
modes: [ApprovalMode.PLAN],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Simulates a tool from another policy (e.g. write.toml)
|
||||||
|
toolName: 'run_shell_command',
|
||||||
|
decision: PolicyDecision.ASK_USER,
|
||||||
|
priority: 10,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
expected: ['run_shell_command'],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'should NOT exclude tool if covered by a higher priority wildcard ALLOW',
|
name: 'should NOT exclude tool if covered by a higher priority wildcard ALLOW',
|
||||||
rules: [
|
rules: [
|
||||||
@@ -2156,11 +2251,13 @@ describe('PolicyEngine', () => {
|
|||||||
toolName: 'server__*',
|
toolName: 'server__*',
|
||||||
decision: PolicyDecision.ALLOW,
|
decision: PolicyDecision.ALLOW,
|
||||||
priority: 100,
|
priority: 100,
|
||||||
|
modes: [ApprovalMode.DEFAULT],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
toolName: 'server__tool1',
|
toolName: 'server__tool1',
|
||||||
decision: PolicyDecision.DENY,
|
decision: PolicyDecision.DENY,
|
||||||
priority: 10,
|
priority: 10,
|
||||||
|
modes: [ApprovalMode.DEFAULT],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
expected: [],
|
expected: [],
|
||||||
|
|||||||
@@ -538,8 +538,10 @@ export class PolicyEngine {
|
|||||||
let globalVerdict: PolicyDecision | undefined;
|
let globalVerdict: PolicyDecision | undefined;
|
||||||
|
|
||||||
for (const rule of this.rules) {
|
for (const rule of this.rules) {
|
||||||
// We only care about rules without args pattern for exclusion from the model
|
|
||||||
if (rule.argsPattern) {
|
if (rule.argsPattern) {
|
||||||
|
if (rule.toolName && rule.decision !== PolicyDecision.DENY) {
|
||||||
|
processedTools.add(rule.toolName);
|
||||||
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user