mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-06-10 11:12:35 -07:00
Update extension tier
This commit is contained in:
@@ -222,9 +222,9 @@ To add policies, specify the file path in your `gemini-extension.json`:
|
||||
}
|
||||
```
|
||||
|
||||
Rules contributed by extensions run in the **Extension Tier** (Tier 2). This
|
||||
tier has higher priority than the default rules but lower priority than
|
||||
workspace-specific or user-defined policies.
|
||||
Rules contributed by extensions run in the **User Tier** (Tier 3), alongside
|
||||
user-defined policies. This tier has higher priority than the default or
|
||||
workspace-specific rules but lower priority than admin policies.
|
||||
|
||||
> **Warning:** For security, Gemini CLI ignores any `allow` decisions or `yolo`
|
||||
> mode configurations in extension policies. This ensures that an extension
|
||||
|
||||
@@ -810,11 +810,6 @@ Would you like to attempt to install via "git clone" instead?`,
|
||||
return true;
|
||||
});
|
||||
}
|
||||
if (checkers) {
|
||||
for (const checker of checkers) {
|
||||
checker.source = `Extension (${config.name}): ${checker.source}`;
|
||||
}
|
||||
}
|
||||
|
||||
if (result.errors.length > 0) {
|
||||
for (const error of result.errors) {
|
||||
|
||||
@@ -412,25 +412,25 @@ describe('Policy Engine Integration Tests', () => {
|
||||
|
||||
// Find rules and verify their priorities
|
||||
const blockedToolRule = rules.find((r) => r.toolName === 'blocked-tool');
|
||||
expect(blockedToolRule?.priority).toBe(4.4); // Command line exclude
|
||||
expect(blockedToolRule?.priority).toBe(3.4); // Command line exclude
|
||||
|
||||
const blockedServerRule = rules.find(
|
||||
(r) => r.toolName === 'blocked-server__*',
|
||||
);
|
||||
expect(blockedServerRule?.priority).toBe(4.9); // MCP server exclude
|
||||
expect(blockedServerRule?.priority).toBe(3.9); // MCP server exclude
|
||||
|
||||
const specificToolRule = rules.find(
|
||||
(r) => r.toolName === 'specific-tool',
|
||||
);
|
||||
expect(specificToolRule?.priority).toBe(4.3); // Command line allow
|
||||
expect(specificToolRule?.priority).toBe(3.3); // Command line allow
|
||||
|
||||
const trustedServerRule = rules.find(
|
||||
(r) => r.toolName === 'trusted-server__*',
|
||||
);
|
||||
expect(trustedServerRule?.priority).toBe(4.2); // MCP trusted server
|
||||
expect(trustedServerRule?.priority).toBe(3.2); // MCP trusted server
|
||||
|
||||
const mcpServerRule = rules.find((r) => r.toolName === 'mcp-server__*');
|
||||
expect(mcpServerRule?.priority).toBe(4.1); // MCP allowed server
|
||||
expect(mcpServerRule?.priority).toBe(3.1); // MCP allowed server
|
||||
|
||||
const readOnlyToolRule = rules.find((r) => r.toolName === 'glob');
|
||||
// Priority 70 in default tier → 1.07 (Overriding Plan Mode Deny)
|
||||
@@ -577,16 +577,16 @@ describe('Policy Engine Integration Tests', () => {
|
||||
|
||||
// Verify each rule has the expected priority
|
||||
const tool3Rule = rules.find((r) => r.toolName === 'tool3');
|
||||
expect(tool3Rule?.priority).toBe(4.4); // Excluded tools (user tier)
|
||||
expect(tool3Rule?.priority).toBe(3.4); // Excluded tools (user tier)
|
||||
|
||||
const server2Rule = rules.find((r) => r.toolName === 'server2__*');
|
||||
expect(server2Rule?.priority).toBe(4.9); // Excluded servers (user tier)
|
||||
expect(server2Rule?.priority).toBe(3.9); // Excluded servers (user tier)
|
||||
|
||||
const tool1Rule = rules.find((r) => r.toolName === 'tool1');
|
||||
expect(tool1Rule?.priority).toBe(4.3); // Allowed tools (user tier)
|
||||
expect(tool1Rule?.priority).toBe(3.3); // Allowed tools (user tier)
|
||||
|
||||
const server1Rule = rules.find((r) => r.toolName === 'server1__*');
|
||||
expect(server1Rule?.priority).toBe(4.1); // Allowed servers (user tier)
|
||||
expect(server1Rule?.priority).toBe(3.1); // Allowed servers (user tier)
|
||||
|
||||
const globRule = rules.find((r) => r.toolName === 'glob');
|
||||
// Priority 70 in default tier → 1.07
|
||||
|
||||
@@ -169,7 +169,7 @@ describe('createPolicyEngineConfig', () => {
|
||||
r.decision === PolicyDecision.ALLOW,
|
||||
);
|
||||
expect(rule).toBeDefined();
|
||||
expect(rule?.priority).toBeCloseTo(4.3, 5); // Command line allow
|
||||
expect(rule?.priority).toBeCloseTo(3.3, 5); // Command line allow
|
||||
});
|
||||
|
||||
it('should deny tools in tools.exclude', async () => {
|
||||
@@ -188,7 +188,7 @@ describe('createPolicyEngineConfig', () => {
|
||||
r.decision === PolicyDecision.DENY,
|
||||
);
|
||||
expect(rule).toBeDefined();
|
||||
expect(rule?.priority).toBeCloseTo(4.4, 5); // Command line exclude
|
||||
expect(rule?.priority).toBeCloseTo(3.4, 5); // Command line exclude
|
||||
});
|
||||
|
||||
it('should allow tools from allowed MCP servers', async () => {
|
||||
@@ -206,7 +206,7 @@ describe('createPolicyEngineConfig', () => {
|
||||
r.toolName === 'my-server__*' && r.decision === PolicyDecision.ALLOW,
|
||||
);
|
||||
expect(rule).toBeDefined();
|
||||
expect(rule?.priority).toBe(4.1); // MCP allowed server
|
||||
expect(rule?.priority).toBe(3.1); // MCP allowed server
|
||||
});
|
||||
|
||||
it('should deny tools from excluded MCP servers', async () => {
|
||||
@@ -224,7 +224,7 @@ describe('createPolicyEngineConfig', () => {
|
||||
r.toolName === 'my-server__*' && r.decision === PolicyDecision.DENY,
|
||||
);
|
||||
expect(rule).toBeDefined();
|
||||
expect(rule?.priority).toBe(4.9); // MCP excluded server
|
||||
expect(rule?.priority).toBe(3.9); // MCP excluded server
|
||||
});
|
||||
|
||||
it('should allow tools from trusted MCP servers', async () => {
|
||||
@@ -251,7 +251,7 @@ describe('createPolicyEngineConfig', () => {
|
||||
r.decision === PolicyDecision.ALLOW,
|
||||
);
|
||||
expect(trustedRule).toBeDefined();
|
||||
expect(trustedRule?.priority).toBe(4.2); // MCP trusted server
|
||||
expect(trustedRule?.priority).toBe(3.2); // MCP trusted server
|
||||
|
||||
// Untrusted server should not have an allow rule
|
||||
const untrustedRule = config.rules?.find(
|
||||
@@ -288,7 +288,7 @@ describe('createPolicyEngineConfig', () => {
|
||||
r.decision === PolicyDecision.ALLOW,
|
||||
);
|
||||
expect(allowedRule).toBeDefined();
|
||||
expect(allowedRule?.priority).toBe(4.1); // MCP allowed server
|
||||
expect(allowedRule?.priority).toBe(3.1); // MCP allowed server
|
||||
|
||||
// Check trusted server
|
||||
const trustedRule = config.rules?.find(
|
||||
@@ -297,7 +297,7 @@ describe('createPolicyEngineConfig', () => {
|
||||
r.decision === PolicyDecision.ALLOW,
|
||||
);
|
||||
expect(trustedRule).toBeDefined();
|
||||
expect(trustedRule?.priority).toBe(4.2); // MCP trusted server
|
||||
expect(trustedRule?.priority).toBe(3.2); // MCP trusted server
|
||||
|
||||
// Check excluded server
|
||||
const excludedRule = config.rules?.find(
|
||||
@@ -306,7 +306,7 @@ describe('createPolicyEngineConfig', () => {
|
||||
r.decision === PolicyDecision.DENY,
|
||||
);
|
||||
expect(excludedRule).toBeDefined();
|
||||
expect(excludedRule?.priority).toBe(4.9); // MCP excluded server
|
||||
expect(excludedRule?.priority).toBe(3.9); // MCP excluded server
|
||||
});
|
||||
|
||||
it('should allow all tools in YOLO mode', async () => {
|
||||
@@ -387,11 +387,11 @@ describe('createPolicyEngineConfig', () => {
|
||||
);
|
||||
|
||||
expect(serverDenyRule).toBeDefined();
|
||||
expect(serverDenyRule?.priority).toBe(4.9); // MCP excluded server
|
||||
expect(serverDenyRule?.priority).toBe(3.9); // MCP excluded server
|
||||
expect(toolAllowRule).toBeDefined();
|
||||
expect(toolAllowRule?.priority).toBeCloseTo(4.3, 5); // Command line allow
|
||||
expect(toolAllowRule?.priority).toBeCloseTo(3.3, 5); // Command line allow
|
||||
|
||||
// Server deny (4.9) has higher priority than tool allow (4.3),
|
||||
// Server deny (3.9) has higher priority than tool allow (3.3),
|
||||
// so server deny wins (this is expected behavior - server-level blocks are security critical)
|
||||
});
|
||||
|
||||
@@ -424,7 +424,7 @@ describe('createPolicyEngineConfig', () => {
|
||||
|
||||
expect(serverAllowRule).toBeDefined();
|
||||
expect(toolDenyRule).toBeDefined();
|
||||
// Command line exclude (4.4) has higher priority than MCP server trust (4.2)
|
||||
// Command line exclude (3.4) has higher priority than MCP server trust (3.2)
|
||||
// This is the correct behavior - specific exclusions should beat general server trust
|
||||
expect(toolDenyRule!.priority).toBeGreaterThan(serverAllowRule!.priority!);
|
||||
});
|
||||
@@ -432,16 +432,16 @@ describe('createPolicyEngineConfig', () => {
|
||||
it('should handle complex priority scenarios correctly', async () => {
|
||||
const settings: PolicySettings = {
|
||||
tools: {
|
||||
allowed: ['my-server__tool1', 'other-tool'], // Priority 4.3
|
||||
exclude: ['my-server__tool2', 'glob'], // Priority 4.4
|
||||
allowed: ['my-server__tool1', 'other-tool'], // Priority 3.3
|
||||
exclude: ['my-server__tool2', 'glob'], // Priority 3.4
|
||||
},
|
||||
mcp: {
|
||||
allowed: ['allowed-server'], // Priority 4.1
|
||||
excluded: ['excluded-server'], // Priority 4.9
|
||||
allowed: ['allowed-server'], // Priority 3.1
|
||||
excluded: ['excluded-server'], // Priority 3.9
|
||||
},
|
||||
mcpServers: {
|
||||
'trusted-server': {
|
||||
trust: true, // Priority 90 -> 4.2
|
||||
trust: true, // Priority 90 -> 3.2
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -517,7 +517,7 @@ describe('createPolicyEngineConfig', () => {
|
||||
expect(globDenyRule).toBeDefined();
|
||||
expect(globAllowRule).toBeDefined();
|
||||
// Deny from settings (user tier)
|
||||
expect(globDenyRule!.priority).toBeCloseTo(4.4, 5); // Command line exclude
|
||||
expect(globDenyRule!.priority).toBeCloseTo(3.4, 5); // Command line exclude
|
||||
// Allow from default TOML: 1 + 50/1000 = 1.05
|
||||
expect(globAllowRule!.priority).toBeCloseTo(1.05, 5);
|
||||
|
||||
@@ -530,11 +530,11 @@ describe('createPolicyEngineConfig', () => {
|
||||
}))
|
||||
.sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
|
||||
|
||||
// Check that the highest priority items are the excludes (user tier: 4.4 and 4.9)
|
||||
// Check that the highest priority items are the excludes (user tier: 3.4 and 3.9)
|
||||
const highestPriorityExcludes = priorities?.filter(
|
||||
(p) =>
|
||||
Math.abs(p.priority! - 4.4) < 0.01 ||
|
||||
Math.abs(p.priority! - 4.9) < 0.01,
|
||||
Math.abs(p.priority! - 3.4) < 0.01 ||
|
||||
Math.abs(p.priority! - 3.9) < 0.01,
|
||||
);
|
||||
expect(
|
||||
highestPriorityExcludes?.every((p) => p.decision === PolicyDecision.DENY),
|
||||
@@ -626,7 +626,7 @@ describe('createPolicyEngineConfig', () => {
|
||||
r.toolName === 'dangerous-tool' && r.decision === PolicyDecision.DENY,
|
||||
);
|
||||
expect(excludeRule).toBeDefined();
|
||||
expect(excludeRule?.priority).toBeCloseTo(4.4, 5); // Command line exclude
|
||||
expect(excludeRule?.priority).toBeCloseTo(3.4, 5); // Command line exclude
|
||||
});
|
||||
|
||||
it('should support argsPattern in policy rules', async () => {
|
||||
@@ -733,8 +733,8 @@ priority = 150
|
||||
r.decision === PolicyDecision.ALLOW,
|
||||
);
|
||||
expect(rule).toBeDefined();
|
||||
// Priority 150 in user tier → 4.150
|
||||
expect(rule?.priority).toBeCloseTo(4.15, 5);
|
||||
// Priority 150 in user tier → 3.150
|
||||
expect(rule?.priority).toBeCloseTo(3.15, 5);
|
||||
expect(rule?.argsPattern).toBeInstanceOf(RegExp);
|
||||
expect(rule?.argsPattern?.test('{"command":"git status"}')).toBe(true);
|
||||
expect(rule?.argsPattern?.test('{"command":"git diff"}')).toBe(true);
|
||||
|
||||
@@ -39,15 +39,15 @@ export const DEFAULT_CORE_POLICIES_DIR = path.join(__dirname, 'policies');
|
||||
|
||||
// Policy tier constants for priority calculation
|
||||
export const DEFAULT_POLICY_TIER = 1;
|
||||
export const EXTENSION_POLICY_TIER = 2;
|
||||
export const WORKSPACE_POLICY_TIER = 3;
|
||||
export const USER_POLICY_TIER = 4;
|
||||
export const ADMIN_POLICY_TIER = 5;
|
||||
export const WORKSPACE_POLICY_TIER = 2;
|
||||
export const USER_POLICY_TIER = 3;
|
||||
export const EXTENSION_POLICY_TIER = 3;
|
||||
export const ADMIN_POLICY_TIER = 4;
|
||||
|
||||
// Specific priority offsets and derived priorities for dynamic/settings rules.
|
||||
// These are added to the tier base (e.g., USER_POLICY_TIER).
|
||||
|
||||
// Workspace tier (3) + high priority (950/1000) = ALWAYS_ALLOW_PRIORITY
|
||||
// Workspace tier (2) + high priority (950/1000) = ALWAYS_ALLOW_PRIORITY
|
||||
// This ensures user "always allow" selections are high priority
|
||||
// within the workspace tier but still lose to user/admin policies.
|
||||
export const ALWAYS_ALLOW_PRIORITY = WORKSPACE_POLICY_TIER + 0.95;
|
||||
|
||||
@@ -234,11 +234,11 @@ modes = ["autoEdit"]
|
||||
expect(result3.rules).toHaveLength(1);
|
||||
expect(result3.rules[0].toolName).toBe('tier3-tool');
|
||||
expect(result3.rules[0].modes).toEqual(['autoEdit']);
|
||||
expect(result3.rules[0].source).toBe('Workspace: tier3.toml');
|
||||
expect(result3.rules[0].source).toBe('User: tier3.toml');
|
||||
|
||||
const getPolicyTier4 = (_dir: string) => 4; // Tier 4 (User)
|
||||
const getPolicyTier4 = (_dir: string) => 4; // Tier 4 (Admin)
|
||||
const result4 = await loadPoliciesFromToml([tempDir], getPolicyTier4);
|
||||
expect(result4.rules[0].source).toBe('User: tier3.toml');
|
||||
expect(result4.rules[0].source).toBe('Admin: tier3.toml');
|
||||
expect(result4.errors).toHaveLength(0);
|
||||
});
|
||||
|
||||
|
||||
@@ -106,7 +106,7 @@ export type PolicyFileErrorType =
|
||||
export interface PolicyFileError {
|
||||
filePath: string;
|
||||
fileName: string;
|
||||
tier: 'default' | 'extension' | 'user' | 'workspace' | 'admin';
|
||||
tier: 'default' | 'user' | 'workspace' | 'admin';
|
||||
ruleIndex?: number;
|
||||
errorType: PolicyFileErrorType;
|
||||
message: string;
|
||||
@@ -171,14 +171,11 @@ export async function readPolicyFiles(
|
||||
/**
|
||||
* Converts a tier number to a human-readable tier name.
|
||||
*/
|
||||
function getTierName(
|
||||
tier: number,
|
||||
): 'default' | 'extension' | 'user' | 'workspace' | 'admin' {
|
||||
function getTierName(tier: number): 'default' | 'user' | 'workspace' | 'admin' {
|
||||
if (tier === 1) return 'default';
|
||||
if (tier === 2) return 'extension';
|
||||
if (tier === 3) return 'workspace';
|
||||
if (tier === 4) return 'user';
|
||||
if (tier === 5) return 'admin';
|
||||
if (tier === 2) return 'workspace';
|
||||
if (tier === 3) return 'user';
|
||||
if (tier === 4) return 'admin';
|
||||
return 'default';
|
||||
}
|
||||
|
||||
|
||||
@@ -98,21 +98,21 @@ priority = 10
|
||||
toolName = "test_tool"
|
||||
decision = "deny"
|
||||
priority = 10
|
||||
`; // Tier 4 -> 4.010
|
||||
`; // Tier 3 -> 3.010
|
||||
}
|
||||
if (path.includes('workspace.toml')) {
|
||||
return `[[rule]]
|
||||
toolName = "test_tool"
|
||||
decision = "allow"
|
||||
priority = 10
|
||||
`; // Tier 3 -> 3.010
|
||||
`; // Tier 2 -> 2.010
|
||||
}
|
||||
if (path.includes('admin.toml')) {
|
||||
return `[[rule]]
|
||||
toolName = "test_tool"
|
||||
decision = "deny"
|
||||
priority = 10
|
||||
`; // Tier 5 -> 5.010
|
||||
`; // Tier 4 -> 4.010
|
||||
}
|
||||
return '';
|
||||
});
|
||||
@@ -144,9 +144,9 @@ priority = 10
|
||||
|
||||
// Check for all 4 rules
|
||||
const defaultRule = rules?.find((r) => r.priority === 1.01);
|
||||
const workspaceRule = rules?.find((r) => r.priority === 3.01);
|
||||
const userRule = rules?.find((r) => r.priority === 4.01);
|
||||
const adminRule = rules?.find((r) => r.priority === 5.01);
|
||||
const workspaceRule = rules?.find((r) => r.priority === 2.01);
|
||||
const userRule = rules?.find((r) => r.priority === 3.01);
|
||||
const adminRule = rules?.find((r) => r.priority === 4.01);
|
||||
|
||||
expect(defaultRule).toBeDefined();
|
||||
expect(userRule).toBeDefined();
|
||||
@@ -224,7 +224,7 @@ priority=10`,
|
||||
expect(rules![0].priority).toBe(1.01);
|
||||
});
|
||||
|
||||
it('should load workspace policies and correctly transform to Tier 3', async () => {
|
||||
it('should load workspace policies and correctly transform to Tier 2', async () => {
|
||||
const workspacePoliciesDir = '/mock/workspace/policies';
|
||||
|
||||
// Mock FS
|
||||
@@ -284,7 +284,7 @@ priority=500`,
|
||||
|
||||
const rule = config.rules?.find((r) => r.toolName === 'p_tool');
|
||||
expect(rule).toBeDefined();
|
||||
// Workspace Tier (3) + 500/1000 = 3.5
|
||||
expect(rule?.priority).toBe(3.5);
|
||||
// Workspace Tier (2) + 500/1000 = 2.5
|
||||
expect(rule?.priority).toBe(2.5);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user