diff --git a/docs/extensions/reference.md b/docs/extensions/reference.md index e0d96ea0c2..e78480df2f 100644 --- a/docs/extensions/reference.md +++ b/docs/extensions/reference.md @@ -136,8 +136,6 @@ The manifest file defines the extension's behavior and configuration. also be an array of strings to load multiple context files. - `excludeTools`: An array of tools to block from the model. You can restrict specific arguments, such as `run_shell_command(rm -rf)`. -- `policies`: An optional path to a policy TOML file relative to the extension - root. See [Policy Engine](#policy-engine) for more information. - `themes`: An optional list of themes provided by the extension. See [Themes](../cli/themes.md) for more information. @@ -209,22 +207,16 @@ agent definition files (`.md`) to an `agents/` directory in your extension root. ### Policy Engine Extensions can contribute policy rules and safety checkers to the Gemini CLI -[Policy Engine](../reference/policy-engine.md). These rules are defined in a -TOML file and take effect when the extension is activated. +[Policy Engine](../admin/policy-engine.md). These rules are defined in `.toml` +files and take effect when the extension is activated. -To add policies, specify the file path in your `gemini-extension.json`: +To add policies, create a `policies/` directory in your extension's root and +place your `.toml` policy files inside it. Gemini CLI automatically loads all +`.toml` files from this directory. -```json -{ - "name": "my-secure-extension", - "version": "1.0.0", - "policies": "policies.toml" -} -``` - -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. +Rules contributed by extensions run in the **Workspace Tier** (Tier 2), +alongside workspace-defined policies. This tier has higher priority than the +default rules but lower priority than user or admin policies. > **Warning:** For security, Gemini CLI ignores any `allow` decisions or `yolo` > mode configurations in extension policies. This ensures that an extension diff --git a/packages/a2a-server/src/agent/task.ts b/packages/a2a-server/src/agent/task.ts index b74381714d..7d9ced1f7a 100644 --- a/packages/a2a-server/src/agent/task.ts +++ b/packages/a2a-server/src/agent/task.ts @@ -728,7 +728,7 @@ export class Task { // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion const errorEvent = event as ServerGeminiErrorEvent; // Type assertion const errorMessage = - errorEvent.value?.error.message ?? 'Unknown error from LLM stream'; + errorEvent.value?.error?.message ?? 'Unknown error from LLM stream'; logger.error( '[Task] Received error event from LLM stream:', errorMessage, diff --git a/packages/cli/src/commands/extensions/examples/policies/README.md b/packages/cli/src/commands/extensions/examples/policies/README.md index 19d7bc9726..d1c06de6e3 100644 --- a/packages/cli/src/commands/extensions/examples/policies/README.md +++ b/packages/cli/src/commands/extensions/examples/policies/README.md @@ -5,7 +5,7 @@ to the Gemini CLI Policy Engine. ## Description -The extension uses a `policies.toml` file to define: +The extension uses a `policies/` directory containing `.toml` files to define: - A rule that requires user confirmation for `rm -rf` commands. - A rule that denies searching for sensitive files (like `.env`) using `grep`. @@ -13,8 +13,8 @@ The extension uses a `policies.toml` file to define: ## Structure -- `gemini-extension.json`: The manifest file that points to the policy file. -- `policies.toml`: Contains the policy rules and safety checkers. +- `gemini-extension.json`: The manifest file. +- `policies/`: Contains the `.toml` policy files. ## How to use diff --git a/packages/cli/src/commands/extensions/examples/policies/gemini-extension.json b/packages/cli/src/commands/extensions/examples/policies/gemini-extension.json index b8f858c362..2a2b992532 100644 --- a/packages/cli/src/commands/extensions/examples/policies/gemini-extension.json +++ b/packages/cli/src/commands/extensions/examples/policies/gemini-extension.json @@ -1,6 +1,5 @@ { "name": "policy-example", "version": "1.0.0", - "description": "An example extension demonstrating Policy Engine support.", - "policies": "policies.toml" + "description": "An example extension demonstrating Policy Engine support." } diff --git a/packages/cli/src/commands/extensions/examples/policies/policies.toml b/packages/cli/src/commands/extensions/examples/policies/policies/policies.toml similarity index 100% rename from packages/cli/src/commands/extensions/examples/policies/policies.toml rename to packages/cli/src/commands/extensions/examples/policies/policies/policies.toml diff --git a/packages/cli/src/config/extension-manager.ts b/packages/cli/src/config/extension-manager.ts index fbe595e87a..aeca7b91bb 100644 --- a/packages/cli/src/config/extension-manager.ts +++ b/packages/cli/src/config/extension-manager.ts @@ -51,7 +51,7 @@ import { applyAdminAllowlist, getAdminBlockedMcpServersMessage, CoreToolCallStatus, - EXTENSION_POLICY_TIER, + WORKSPACE_POLICY_TIER, loadPoliciesFromToml, PolicyDecision, ApprovalMode, @@ -761,74 +761,61 @@ Would you like to attempt to install via "git clone" instead?`, let rules: PolicyRule[] | undefined; let checkers: SafetyCheckerRule[] | undefined; - if (config.policies) { - const policyPath = path.join(effectiveExtensionPath, config.policies); - const resolvedPolicyPath = path.resolve(policyPath); - const resolvedExtensionPath = path.resolve(effectiveExtensionPath); + const policyDir = path.join(effectiveExtensionPath, 'policies'); + if (fs.existsSync(policyDir)) { + const result = await loadPoliciesFromToml( + [policyDir], + () => WORKSPACE_POLICY_TIER, + ); + rules = result.rules; + checkers = result.checkers; - if (!resolvedPolicyPath.startsWith(resolvedExtensionPath)) { - debugLogger.warn( - `[ExtensionManager] Extension "${config.name}" attempted to contribute a policy file outside its directory: "${config.policies}". Ignoring for security.`, - ); - } else if (fs.existsSync(policyPath)) { - const result = await loadPoliciesFromToml( - [policyPath], - () => EXTENSION_POLICY_TIER, - ); - rules = result.rules; - checkers = result.checkers; - - // Prefix source with extension name to avoid collisions - if (rules) { - rules = rules.filter((rule) => { - // Security: Extensions are not allowed to automatically approve tool calls. - // We ignore any rule that is ALLOW. - if (rule.decision === PolicyDecision.ALLOW) { - debugLogger.warn( - `[ExtensionManager] Extension "${config.name}" attempted to contribute an ALLOW rule for tool "${rule.toolName}". Ignoring this rule for security.`, - ); - return false; - } - - // Security: Extensions are not allowed to contribute YOLO mode rules. - if (rule.modes?.includes(ApprovalMode.YOLO)) { - debugLogger.warn( - `[ExtensionManager] Extension "${config.name}" attempted to contribute a rule for YOLO mode. Ignoring this rule for security.`, - ); - return false; - } - - rule.source = `Extension (${config.name}): ${rule.source}`; - return true; - }); - } - - if (checkers) { - checkers = checkers.filter((checker) => { - // Security: Extensions are not allowed to contribute YOLO mode checkers. - if (checker.modes?.includes(ApprovalMode.YOLO)) { - debugLogger.warn( - `[ExtensionManager] Extension "${config.name}" attempted to contribute a safety checker for YOLO mode. Ignoring this checker for security.`, - ); - return false; - } - - checker.source = `Extension (${config.name}): ${checker.source}`; - return true; - }); - } - - if (result.errors.length > 0) { - for (const error of result.errors) { + // Prefix source with extension name to avoid collisions + if (rules) { + rules = rules.filter((rule) => { + // Security: Extensions are not allowed to automatically approve tool calls. + // We ignore any rule that is ALLOW. + if (rule.decision === PolicyDecision.ALLOW) { debugLogger.warn( - `[ExtensionManager] Error loading policies from ${config.name}: ${error.message}`, + `[ExtensionManager] Extension "${config.name}" attempted to contribute an ALLOW rule for tool "${rule.toolName}". Ignoring this rule for security.`, ); + return false; } + + // Security: Extensions are not allowed to contribute YOLO mode rules. + if (rule.modes?.includes(ApprovalMode.YOLO)) { + debugLogger.warn( + `[ExtensionManager] Extension "${config.name}" attempted to contribute a rule for YOLO mode. Ignoring this rule for security.`, + ); + return false; + } + + rule.source = `Extension (${config.name}): ${rule.source}`; + return true; + }); + } + + if (checkers) { + checkers = checkers.filter((checker) => { + // Security: Extensions are not allowed to contribute YOLO mode checkers. + if (checker.modes?.includes(ApprovalMode.YOLO)) { + debugLogger.warn( + `[ExtensionManager] Extension "${config.name}" attempted to contribute a safety checker for YOLO mode. Ignoring this checker for security.`, + ); + return false; + } + + checker.source = `Extension (${config.name}): ${checker.source}`; + return true; + }); + } + + if (result.errors.length > 0) { + for (const error of result.errors) { + debugLogger.warn( + `[ExtensionManager] Error loading policies from ${config.name}: ${error.message}`, + ); } - } else { - debugLogger.warn( - `[ExtensionManager] Policy file not found for ${config.name}: ${policyPath}`, - ); } } diff --git a/packages/cli/src/config/extension.ts b/packages/cli/src/config/extension.ts index e9b6e06e0c..815cf23ece 100644 --- a/packages/cli/src/config/extension.ts +++ b/packages/cli/src/config/extension.ts @@ -33,10 +33,6 @@ export interface ExtensionConfig { * These themes will be registered when the extension is activated. */ themes?: CustomTheme[]; - /** - * Path to a policy TOML file relative to the extension root. - */ - policies?: string; } export interface ExtensionUpdateInfo { diff --git a/packages/core/src/policy/config.ts b/packages/core/src/policy/config.ts index d81ceb0928..434611205b 100644 --- a/packages/core/src/policy/config.ts +++ b/packages/core/src/policy/config.ts @@ -41,7 +41,7 @@ export const DEFAULT_CORE_POLICIES_DIR = path.join(__dirname, 'policies'); export const DEFAULT_POLICY_TIER = 1; export const WORKSPACE_POLICY_TIER = 2; export const USER_POLICY_TIER = 3; -export const EXTENSION_POLICY_TIER = 3; +export const EXTENSION_POLICY_TIER = WORKSPACE_POLICY_TIER; export const ADMIN_POLICY_TIER = 4; // Specific priority offsets and derived priorities for dynamic/settings rules.