mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-25 20:44:46 -07:00
feat(policy): map --yolo to allowedTools wildcard policy
This PR maps the `--yolo` flag natively into a wildcard policy array (`allowedTools: ["*"]`) and removes the concept of `ApprovalMode.YOLO` as a distinct state in the application, fulfilling issue #11303. This removes the hardcoded `ApprovalMode.YOLO` state and its associated UI/bypasses. The `PolicyEngine` now evaluates YOLO purely via data-driven rules. - Removes `ApprovalMode.YOLO` - Removes UI toggle (`Ctrl+Y`) and indicators for YOLO - Removes `yolo.toml` - Updates A2A server and CLI config logic to translate YOLO into a wildcard tool - Rewrites policy engine tests to evaluate the wildcard - Enforces enterprise `disableYoloMode` and `secureModeEnabled` controls by actively preventing manual `--allowed-tools=*` bypasses. Fixes #11303
This commit is contained in:
@@ -9,7 +9,6 @@ import {
|
||||
type Config,
|
||||
MessageBusType,
|
||||
ToolConfirmationOutcome,
|
||||
ApprovalMode,
|
||||
Scheduler,
|
||||
type MessageBus,
|
||||
} from '@google/gemini-cli-core';
|
||||
@@ -358,7 +357,7 @@ describe('Task Event-Driven Scheduler', () => {
|
||||
// Enable YOLO mode
|
||||
const yoloConfig = createMockConfig({
|
||||
isEventDrivenSchedulerEnabled: () => true,
|
||||
getApprovalMode: () => ApprovalMode.YOLO,
|
||||
getAllowedTools: () => ['*'],
|
||||
}) as Config;
|
||||
const yoloMessageBus = yoloConfig.messageBus;
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
type GeminiClient,
|
||||
GeminiEventType,
|
||||
ToolConfirmationOutcome,
|
||||
ApprovalMode,
|
||||
getAllMCPServerStatuses,
|
||||
MCPServerStatus,
|
||||
isNodeError,
|
||||
@@ -89,7 +88,8 @@ export class Task {
|
||||
autoExecute: boolean;
|
||||
private get isYoloMatch(): boolean {
|
||||
return (
|
||||
this.autoExecute || this.config.getApprovalMode() === ApprovalMode.YOLO
|
||||
this.autoExecute ||
|
||||
(this.config.getAllowedTools()?.includes('*') ?? false)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -880,9 +880,7 @@ export class Task {
|
||||
if (
|
||||
part.kind !== 'data' ||
|
||||
!part.data ||
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
typeof part.data['callId'] !== 'string' ||
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
typeof part.data['outcome'] !== 'string'
|
||||
) {
|
||||
return false;
|
||||
|
||||
@@ -19,8 +19,6 @@ import {
|
||||
AuthType,
|
||||
isHeadlessMode,
|
||||
FatalAuthenticationError,
|
||||
PolicyDecision,
|
||||
PRIORITY_YOLO_ALLOW_ALL,
|
||||
} from '@google/gemini-cli-core';
|
||||
|
||||
// Mock dependencies
|
||||
@@ -375,35 +373,24 @@ describe('loadConfig', () => {
|
||||
});
|
||||
|
||||
describe('YOLO mode', () => {
|
||||
it('should enable YOLO mode and add policy rule when GEMINI_YOLO_MODE is true', async () => {
|
||||
it('should enable wildcard allowedTools when GEMINI_YOLO_MODE is true', async () => {
|
||||
vi.stubEnv('GEMINI_YOLO_MODE', 'true');
|
||||
await loadConfig(mockSettings, mockExtensionLoader, taskId);
|
||||
expect(Config).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
approvalMode: 'yolo',
|
||||
policyEngineConfig: expect.objectContaining({
|
||||
rules: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
decision: PolicyDecision.ALLOW,
|
||||
priority: PRIORITY_YOLO_ALLOW_ALL,
|
||||
modes: ['yolo'],
|
||||
allowRedirection: true,
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
approvalMode: 'default',
|
||||
allowedTools: expect.arrayContaining(['*']),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should use default approval mode and empty rules when GEMINI_YOLO_MODE is not true', async () => {
|
||||
it('should use default approval mode and undefined allowedTools when GEMINI_YOLO_MODE is not true', async () => {
|
||||
vi.stubEnv('GEMINI_YOLO_MODE', 'false');
|
||||
await loadConfig(mockSettings, mockExtensionLoader, taskId);
|
||||
expect(Config).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
approvalMode: 'default',
|
||||
policyEngineConfig: expect.objectContaining({
|
||||
rules: [],
|
||||
}),
|
||||
allowedTools: undefined,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -26,8 +26,6 @@ import {
|
||||
isHeadlessMode,
|
||||
FatalAuthenticationError,
|
||||
isCloudShell,
|
||||
PolicyDecision,
|
||||
PRIORITY_YOLO_ALLOW_ALL,
|
||||
type TelemetryTarget,
|
||||
type ConfigParameters,
|
||||
type ExtensionLoader,
|
||||
@@ -62,11 +60,6 @@ export async function loadConfig(
|
||||
}
|
||||
}
|
||||
|
||||
const approvalMode =
|
||||
process.env['GEMINI_YOLO_MODE'] === 'true'
|
||||
? ApprovalMode.YOLO
|
||||
: ApprovalMode.DEFAULT;
|
||||
|
||||
const configParams: ConfigParameters = {
|
||||
sessionId: taskId,
|
||||
clientName: 'a2a-server',
|
||||
@@ -79,22 +72,12 @@ export async function loadConfig(
|
||||
|
||||
coreTools: settings.coreTools || settings.tools?.core || undefined,
|
||||
excludeTools: settings.excludeTools || settings.tools?.exclude || undefined,
|
||||
allowedTools: settings.allowedTools || settings.tools?.allowed || undefined,
|
||||
allowedTools:
|
||||
process.env['GEMINI_YOLO_MODE'] === 'true'
|
||||
? [...(settings.allowedTools || settings.tools?.allowed || []), '*']
|
||||
: settings.allowedTools || settings.tools?.allowed || undefined,
|
||||
showMemoryUsage: settings.showMemoryUsage || false,
|
||||
approvalMode,
|
||||
policyEngineConfig: {
|
||||
rules:
|
||||
approvalMode === ApprovalMode.YOLO
|
||||
? [
|
||||
{
|
||||
decision: PolicyDecision.ALLOW,
|
||||
priority: PRIORITY_YOLO_ALLOW_ALL,
|
||||
modes: [ApprovalMode.YOLO],
|
||||
allowRedirection: true,
|
||||
},
|
||||
]
|
||||
: [],
|
||||
},
|
||||
approvalMode: ApprovalMode.DEFAULT,
|
||||
mcpServers: settings.mcpServers,
|
||||
cwd: workspaceDir,
|
||||
telemetry: {
|
||||
|
||||
@@ -72,6 +72,7 @@ const getToolRegistrySpy = vi.fn().mockReturnValue({
|
||||
getToolsByServer: vi.fn().mockReturnValue([]),
|
||||
});
|
||||
const getApprovalModeSpy = vi.fn();
|
||||
const getAllowedToolsSpy = vi.fn();
|
||||
const getShellExecutionConfigSpy = vi.fn();
|
||||
const getExtensionsSpy = vi.fn();
|
||||
|
||||
@@ -83,6 +84,7 @@ vi.mock('../config/config.js', async () => {
|
||||
const mockConfig = createMockConfig({
|
||||
getToolRegistry: getToolRegistrySpy,
|
||||
getApprovalMode: getApprovalModeSpy,
|
||||
getAllowedTools: getAllowedToolsSpy,
|
||||
getShellExecutionConfig: getShellExecutionConfigSpy,
|
||||
getExtensions: getExtensionsSpy,
|
||||
});
|
||||
@@ -118,6 +120,7 @@ describe('E2E Tests', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
getApprovalModeSpy.mockReturnValue(ApprovalMode.DEFAULT);
|
||||
getAllowedToolsSpy.mockReturnValue([]);
|
||||
});
|
||||
|
||||
afterAll(
|
||||
@@ -406,7 +409,7 @@ describe('E2E Tests', () => {
|
||||
|
||||
it('should handle multiple tool calls sequentially in YOLO mode', async () => {
|
||||
// Set YOLO mode to auto-approve tools and test sequential execution.
|
||||
getApprovalModeSpy.mockReturnValue(ApprovalMode.YOLO);
|
||||
getAllowedToolsSpy.mockReturnValue(['*']);
|
||||
|
||||
// First call yields the tool request
|
||||
sendMessageStreamSpy.mockImplementationOnce(async function* () {
|
||||
@@ -697,7 +700,7 @@ describe('E2E Tests', () => {
|
||||
});
|
||||
|
||||
// Set approval mode to yolo
|
||||
getApprovalModeSpy.mockReturnValue(ApprovalMode.YOLO);
|
||||
getAllowedToolsSpy.mockReturnValue(['*']);
|
||||
|
||||
const mockTool = new MockTool({
|
||||
name: 'test-tool-yolo',
|
||||
|
||||
@@ -126,8 +126,8 @@ export function createMockConfig(
|
||||
|
||||
mockConfig.getPolicyEngine = vi.fn().mockReturnValue({
|
||||
check: async () => {
|
||||
const mode = mockConfig.getApprovalMode();
|
||||
if (mode === ApprovalMode.YOLO) {
|
||||
const allowed = mockConfig.getAllowedTools?.() || [];
|
||||
if (allowed.includes('*')) {
|
||||
return { decision: PolicyDecision.ALLOW };
|
||||
}
|
||||
return { decision: PolicyDecision.ASK_USER };
|
||||
|
||||
Reference in New Issue
Block a user