summaryrefslogtreecommitdiff
path: root/packages/cli
diff options
context:
space:
mode:
authorAbhi <[email protected]>2025-08-18 10:34:51 -0400
committerGitHub <[email protected]>2025-08-18 14:34:51 +0000
commit065eb7897d89033cfd6602c911ad5b01af1a161e (patch)
treeda6371e3b59f8dd4f864b737ee9ac34ef73fac88 /packages/cli
parent88fc6e5861d2835c9755d9f4d4a52408924b3b51 (diff)
fix(commands): Respect YOLO mode in custom slash commands (#6441)
Diffstat (limited to 'packages/cli')
-rw-r--r--packages/cli/src/services/prompt-processors/shellProcessor.test.ts54
-rw-r--r--packages/cli/src/services/prompt-processors/shellProcessor.ts7
2 files changed, 56 insertions, 5 deletions
diff --git a/packages/cli/src/services/prompt-processors/shellProcessor.test.ts b/packages/cli/src/services/prompt-processors/shellProcessor.test.ts
index fe4ae7a2..49d30799 100644
--- a/packages/cli/src/services/prompt-processors/shellProcessor.test.ts
+++ b/packages/cli/src/services/prompt-processors/shellProcessor.test.ts
@@ -4,11 +4,11 @@
* SPDX-License-Identifier: Apache-2.0
*/
-import { describe, it, expect, beforeEach, vi } from 'vitest';
+import { describe, it, expect, beforeEach, vi, type Mock } from 'vitest';
import { ConfirmationRequiredError, ShellProcessor } from './shellProcessor.js';
import { createMockCommandContext } from '../../test-utils/mockCommandContext.js';
import { CommandContext } from '../../ui/commands/types.js';
-import { Config } from '@google/gemini-cli-core';
+import { ApprovalMode, Config } from '@google/gemini-cli-core';
import os from 'os';
import { quote } from 'shell-quote';
@@ -17,7 +17,7 @@ import { quote } from 'shell-quote';
// our tests robust and platform-agnostic.
function getExpectedEscapedArgForPlatform(arg: string): string {
if (os.platform() === 'win32') {
- const comSpec = (process.env.ComSpec || 'cmd.exe').toLowerCase();
+ const comSpec = (process.env['ComSpec'] || 'cmd.exe').toLowerCase();
const isPowerShell =
comSpec.endsWith('powershell.exe') || comSpec.endsWith('pwsh.exe');
@@ -63,6 +63,7 @@ describe('ShellProcessor', () => {
mockConfig = {
getTargetDir: vi.fn().mockReturnValue('/test/dir'),
+ getApprovalMode: vi.fn().mockReturnValue(ApprovalMode.DEFAULT),
};
context = createMockCommandContext({
@@ -164,7 +165,7 @@ describe('ShellProcessor', () => {
expect(result).toBe('On branch main in /usr/home');
});
- it('should throw ConfirmationRequiredError if a command is not allowed', async () => {
+ it('should throw ConfirmationRequiredError if a command is not allowed in default mode', async () => {
const processor = new ShellProcessor('test-command');
const prompt = 'Do something dangerous: !{rm -rf /}';
mockCheckCommandPermissions.mockReturnValue({
@@ -177,6 +178,51 @@ describe('ShellProcessor', () => {
);
});
+ it('should NOT throw ConfirmationRequiredError if a command is not allowed but approval mode is YOLO', async () => {
+ const processor = new ShellProcessor('test-command');
+ const prompt = 'Do something dangerous: !{rm -rf /}';
+ mockCheckCommandPermissions.mockReturnValue({
+ allAllowed: false,
+ disallowedCommands: ['rm -rf /'],
+ });
+ // Override the approval mode for this test
+ (mockConfig.getApprovalMode as Mock).mockReturnValue(ApprovalMode.YOLO);
+ mockShellExecute.mockReturnValue({
+ result: Promise.resolve({ ...SUCCESS_RESULT, stdout: 'deleted' }),
+ });
+
+ const result = await processor.process(prompt, context);
+
+ // It should proceed with execution
+ expect(mockShellExecute).toHaveBeenCalledWith(
+ 'rm -rf /',
+ expect.any(String),
+ expect.any(Function),
+ expect.any(Object),
+ );
+ expect(result).toBe('Do something dangerous: deleted');
+ });
+
+ it('should still throw an error for a hard-denied command even in YOLO mode', async () => {
+ const processor = new ShellProcessor('test-command');
+ const prompt = 'Do something forbidden: !{reboot}';
+ mockCheckCommandPermissions.mockReturnValue({
+ allAllowed: false,
+ disallowedCommands: ['reboot'],
+ isHardDenial: true, // This is the key difference
+ blockReason: 'System commands are blocked',
+ });
+ // Set approval mode to YOLO
+ (mockConfig.getApprovalMode as Mock).mockReturnValue(ApprovalMode.YOLO);
+
+ await expect(processor.process(prompt, context)).rejects.toThrow(
+ /Blocked command: "reboot". Reason: System commands are blocked/,
+ );
+
+ // Ensure it never tried to execute
+ expect(mockShellExecute).not.toHaveBeenCalled();
+ });
+
it('should throw ConfirmationRequiredError with the correct command', async () => {
const processor = new ShellProcessor('test-command');
const prompt = 'Do something dangerous: !{rm -rf /}';
diff --git a/packages/cli/src/services/prompt-processors/shellProcessor.ts b/packages/cli/src/services/prompt-processors/shellProcessor.ts
index 9531d57e..7acf2415 100644
--- a/packages/cli/src/services/prompt-processors/shellProcessor.ts
+++ b/packages/cli/src/services/prompt-processors/shellProcessor.ts
@@ -5,6 +5,7 @@
*/
import {
+ ApprovalMode,
checkCommandPermissions,
escapeShellArg,
getShellConfiguration,
@@ -107,7 +108,11 @@ export class ShellProcessor implements IPromptProcessor {
`${this.commandName} cannot be run. Blocked command: "${command}". Reason: ${blockReason || 'Blocked by configuration.'}`,
);
}
- disallowedCommands.forEach((uc) => commandsToConfirm.add(uc));
+
+ // If not a hard denial, respect YOLO mode and auto-approve.
+ if (config.getApprovalMode() !== ApprovalMode.YOLO) {
+ disallowedCommands.forEach((uc) => commandsToConfirm.add(uc));
+ }
}
}