Skip to content

fix(policy): enforce ask_user as DENY in headless/non-interactive mode#20438

Open
Devnil434 wants to merge 4 commits intogoogle-gemini:mainfrom
Devnil434:fix/19773-headless-policy-ignored
Open

fix(policy): enforce ask_user as DENY in headless/non-interactive mode#20438
Devnil434 wants to merge 4 commits intogoogle-gemini:mainfrom
Devnil434:fix/19773-headless-policy-ignored

Conversation

@Devnil434
Copy link

Summary

Fixes a bug where --policy rules are ignored in headless mode (-p/--prompt).
When a policy rule has decision = "ask_user" and the CLI runs non-interactively,
the policy engine should treat that as DENY (since there's no user to ask).
Previously, getExcludedTools() did not apply the non-interactive mode conversion,
so tools like run_shell_command remained available to the LLM even when a policy
required user confirmation.

Details

The fix is in PolicyEngine.getExcludedTools(): it now calls
applyNonInteractiveMode() when computing the effective per-rule decision. This
ensures ask_userDENY in headless mode, excluding those tools from the tool
registry and preventing the LLM from invoking them.

An integration test (integration-tests/policy_repro.test.ts) is added to
reproduce the issue and verify the fix end-to-end.

Related Issues

Fixes #19773

How to Validate

  1. Build the bundle: npm run bundle

  2. Run the integration test:

    npx vitest run integration-tests/policy_repro.test.ts
    

    Expected: 1 passed (1)

  3. Manual reproduction:
    Create a policy file block-shell.toml:

    [[rule]]
    toolName = "run_shell_command"
    decision = "ask_user"
    priority = 999

    Run: gemini -p "run echo hello" --policy block-shell.toml
    Expected: the shell command is NOT executed (tool blocked by policy).

Pre-Merge Checklist

  • Updated relevant documentation and README (if needed)
  • Added/updated tests (if needed)
  • Noted breaking changes (if any)
  • Validated on required platforms/methods:
    • MacOS
      • npm run
      • npx
      • Docker
      • Podman
      • Seatbelt
    • Windows
      • npm run
      • npx
      • Docker
    • Linux
      • npm run
      • npx
      • Docker

In non-interactive (headless) mode, policy rules with decision=ask_user
should be treated as DENY since there is no user to ask. The
getExcludedTools() method in PolicyEngine was computing which tools to hide
from the LLM, but was not applying the nonInteractive mode conversion. This
caused tools like run_shell_command to remain available even when a policy
rule marked them as ask_user.

Fix: apply applyNonInteractiveMode() when determining the effective policy
decision in getExcludedTools(), so ask_user becomes DENY in headless mode.

Also adds an integration test (policy_repro.test.ts) that verifies tools
marked ask_user are blocked (not successfully executed) when the CLI runs
headlessly with -p/--policy flags.

Fixes google-gemini#19773
@Devnil434 Devnil434 requested a review from a team as a code owner February 26, 2026 13:57
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @Devnil434, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request resolves a critical issue in the policy engine where rules configured to ask_user for confirmation were not correctly applied in non-interactive, headless environments. The changes ensure that in such scenarios, these policies are now correctly interpreted as a denial of tool access, thereby upholding the intended security and control measures for tool usage.

Highlights

  • Policy Enforcement Fix: Fixed a bug where ask_user policy decisions were not correctly enforced as DENY in headless or non-interactive modes, allowing tools to be invoked when they should have been blocked.
  • Core Logic Update: Modified the PolicyEngine.getExcludedTools() method to ensure that applyNonInteractiveMode() is consistently called when determining effective policy decisions, correctly converting ask_user to DENY in headless environments.
  • Tool Exclusion: Ensured that tools requiring user confirmation are now properly excluded from the LLM's access when running in a non-interactive environment, enhancing security and adherence to policy.
  • Test Coverage: Added a new integration test (integration-tests/policy_repro.test.ts) to specifically validate this fix and prevent future regressions related to headless policy enforcement.
Changelog
  • integration-tests/policy_repro.responses
    • Added mock API responses for the new integration test.
  • integration-tests/policy_repro.test.ts
    • Added a new integration test to verify that tools with ask_user policies are blocked in headless mode.
  • packages/core/src/policy/policy-engine.ts
    • Modified getExcludedTools to apply applyNonInteractiveMode to both global verdicts and individual rule decisions, ensuring correct policy enforcement in headless mode.
Activity
  • Author added a new integration test (integration-tests/policy_repro.test.ts) to reproduce the issue and verify the fix end-to-end.
  • Author validated the changes on Windows.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request addresses a bug where ask_user policies were not enforced as DENY in non-interactive mode for getExcludedTools. While the change correctly handles tool-specific rules, it misses a critical scenario with global rules. A global ask_user policy will still be incorrectly treated as permissive in non-interactive mode, leading to a security vulnerability where tools that should be blocked are made available to the model. I have added a critical review comment on packages/core/src/policy/policy-engine.ts detailing the vulnerability and the required fix. I also recommend enhancing the new integration test to cover this global rule case.

I am having trouble creating individual review comments. Click here to see my feedback.

packages/core/src/policy/policy-engine.ts (620-622)

critical

This change correctly applies non-interactive mode to the decision for tool-specific rules, but the fix is incomplete and a critical security vulnerability remains with global rules.

If the highest-priority global rule has decision = "ask_user", the logic around lines 577-583 will cause the loop to break prematurely when in non-interactive mode, bypassing this fix entirely.

Vulnerability Details:

  1. A global rule with decision = "ask_user" is processed.
  2. globalVerdict is set to ASK_USER.
  3. The check if (globalVerdict !== PolicyDecision.DENY) at line 578 passes.
  4. The loop breaks.
  5. getExcludedTools() returns an empty set.
  6. In non-interactive mode, this effectively bypasses the policy, as no tools are excluded and they will be presented to the LLM. The expected behavior is for ask_user to be treated as DENY, which should prevent tools from being available.

To properly fix this, the check for the global verdict must also be updated to account for non-interactive mode:

// Around line 577
globalVerdict = rule.decision;
const effectiveGlobalVerdict = this.applyNonInteractiveMode(globalVerdict);
if (effectiveGlobalVerdict !== PolicyDecision.DENY) {
  // Global ALLOW/ASK found.
  // ...
  break;
}

This ensures that a global ask_user rule in non-interactive mode is correctly treated as a DENY and does not cause an early exit.

Additionally, the new integration test in integration-tests/policy_repro.test.ts should be updated to include a case for a global ask_user policy to verify this fix and prevent regressions.

@gemini-cli gemini-cli bot added the area/enterprise Issues related to Telemetry, Policy, Quota / Licensing label Feb 26, 2026
@Devnil434
Copy link
Author

Thanks for the detailed review — you’re absolutely right.

The previous change only applied non-interactive handling to tool-specific rules and did not account for global ask_user verdicts, which could cause premature loop exit and bypass policy enforcement.

Updated the global verdict evaluation to apply applyNonInteractiveMode() before the DENY check and added an integration test covering the global ask_user case to prevent regressions.

All tests passing.

@Devnil434
Copy link
Author

/gemini review

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request addresses an important policy enforcement bug in headless mode, specifically correcting an issue where ASK_USER policy decisions were not being treated as DENY in headless/non-interactive mode within the getExcludedTools function. While the core logic changes are on the right track, the fix is incomplete as it misses several code paths within the getExcludedTools function, potentially exposing tools to the LLM despite policy requirements, which violates the 'fail-closed' security principle. Additionally, there are generated files that should be removed from the commit. Please address these points to finalize the PR and ensure full compliance with the intended security policy.

Comment on lines +1 to +32
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { z } from 'zod';
export declare const settingsZodSchema: z.ZodObject<
Record<string, z.ZodTypeAny>,
z.UnknownKeysParam,
z.ZodTypeAny,
{
[x: string]: any;
},
{
[x: string]: any;
}
>;
/**
* Validates settings data against the Zod schema
*/
export declare function validateSettings(data: unknown): {
success: boolean;
data?: unknown;
error?: z.ZodError;
};
/**
* Format a Zod error into a helpful error message
*/
export declare function formatValidationError(
error: z.ZodError,
filePath: string,
): string;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

This file and numerous other .d.ts and .txt files in this pull request appear to be generated build artifacts. These files should not be committed to the source repository. Please remove them and update your .gitignore file to exclude them in the future.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/enterprise Issues related to Telemetry, Policy, Quota / Licensing

Projects

None yet

Development

Successfully merging this pull request may close these issues.

In headless mode policies/tools are ignored

1 participant