From 31be66d7645ea3473334c9ce89ea1a5095b8df6e Mon Sep 17 00:00:00 2001 From: Nourrisse Florian <3023852+Flo5k5@users.noreply.github.com> Date: Mon, 13 Apr 2026 14:05:21 +0200 Subject: [PATCH] feat: add allowBypassPermissionsMode setting (#658) * feat: add allowBypassPermissionsMode setting Allow bypass permissions mode to appear in the mode list via settings.json without requiring the --allow-dangerously-skip-permissions CLI flag. The disableBypassPermissionsMode setting retains priority. * fix: address Copilot review feedback on allowBypassPermissionsMode - Security: read allowBypassPermissionsMode only from trusted settings sources (user/local/flag/policy), excluding projectSettings to prevent a malicious repo from enabling bypass mode - UX: update error messages to reference the correct CLI flag (--allow-dangerously-skip-permissions) and the new settings option - Tests: add schema validation tests for the new field --- src/cli/print.ts | 2 +- src/hooks/useReplBridge.tsx | 2 +- src/utils/permissions/permissionSetup.ts | 5 +++- .../allowBypassPermissionsMode.test.ts | 27 +++++++++++++++++++ src/utils/settings/settings.ts | 19 +++++++++++++ src/utils/settings/types.ts | 6 +++++ 6 files changed, 58 insertions(+), 3 deletions(-) create mode 100644 src/utils/settings/allowBypassPermissionsMode.test.ts diff --git a/src/cli/print.ts b/src/cli/print.ts index 4e1c7d9a..00b5c834 100644 --- a/src/cli/print.ts +++ b/src/cli/print.ts @@ -4582,7 +4582,7 @@ function handleSetPermissionMode( subtype: 'error', request_id: requestId, error: - 'Cannot set permission mode to bypassPermissions because the session was not launched with --dangerously-skip-permissions', + 'Cannot set permission mode to bypassPermissions. Enable it with --allow-dangerously-skip-permissions or set permissions.allowBypassPermissionsMode in settings.json', }, }) return toolPermissionContext diff --git a/src/hooks/useReplBridge.tsx b/src/hooks/useReplBridge.tsx index 86cad57f..bd8d0887 100644 --- a/src/hooks/useReplBridge.tsx +++ b/src/hooks/useReplBridge.tsx @@ -434,7 +434,7 @@ export function useReplBridge(messages: Message[], setMessages: (action: React.S if (!store.getState().toolPermissionContext.isBypassPermissionsModeAvailable) { return { ok: false, - error: 'Cannot set permission mode to bypassPermissions because the session was not launched with --dangerously-skip-permissions' + error: 'Cannot set permission mode to bypassPermissions. Enable it with --allow-dangerously-skip-permissions or set permissions.allowBypassPermissionsMode in settings.json' }; } } diff --git a/src/utils/permissions/permissionSetup.ts b/src/utils/permissions/permissionSetup.ts index 3d9143b0..afd422e6 100644 --- a/src/utils/permissions/permissionSetup.ts +++ b/src/utils/permissions/permissionSetup.ts @@ -19,6 +19,7 @@ import { getSettings_DEPRECATED, getSettingsFilePathForSource, getUseAutoModeDuringPlan, + hasAllowBypassPermissionsMode, hasAutoModeOptIn, } from '../settings/settings.js' import { @@ -936,9 +937,11 @@ export async function initializeToolPermissionContext({ const settings = getSettings_DEPRECATED() || {} const settingsDisableBypassPermissionsMode = settings.permissions?.disableBypassPermissionsMode === 'disable' + const settingsAllowBypassPermissionsMode = hasAllowBypassPermissionsMode() const isBypassPermissionsModeAvailable = (permissionMode === 'bypassPermissions' || - allowDangerouslySkipPermissions) && + allowDangerouslySkipPermissions || + settingsAllowBypassPermissionsMode) && !growthBookDisableBypassPermissionsMode && !settingsDisableBypassPermissionsMode diff --git a/src/utils/settings/allowBypassPermissionsMode.test.ts b/src/utils/settings/allowBypassPermissionsMode.test.ts new file mode 100644 index 00000000..8d1e66f3 --- /dev/null +++ b/src/utils/settings/allowBypassPermissionsMode.test.ts @@ -0,0 +1,27 @@ +import { describe, test, expect } from 'bun:test' + +describe('SettingsSchema allowBypassPermissionsMode', () => { + test('accepts allowBypassPermissionsMode: true', async () => { + const { SettingsSchema } = await import('./types.js') + const result = SettingsSchema().safeParse({ + permissions: { allowBypassPermissionsMode: true }, + }) + expect(result.success).toBe(true) + }) + + test('accepts allowBypassPermissionsMode: false', async () => { + const { SettingsSchema } = await import('./types.js') + const result = SettingsSchema().safeParse({ + permissions: { allowBypassPermissionsMode: false }, + }) + expect(result.success).toBe(true) + }) + + test('rejects non-boolean allowBypassPermissionsMode', async () => { + const { SettingsSchema } = await import('./types.js') + const result = SettingsSchema().safeParse({ + permissions: { allowBypassPermissionsMode: 'yes' }, + }) + expect(result.success).toBe(false) + }) +}) diff --git a/src/utils/settings/settings.ts b/src/utils/settings/settings.ts index c7ddd1b8..6d3da9ae 100644 --- a/src/utils/settings/settings.ts +++ b/src/utils/settings/settings.ts @@ -574,6 +574,7 @@ export function getManagedSettingsKeysForLogging( 'ask', 'defaultMode', 'disableBypassPermissionsMode', + 'allowBypassPermissionsMode', ...(feature('TRANSCRIPT_CLASSIFIER') ? ['disableAutoMode'] : []), 'additionalDirectories', ]), @@ -888,6 +889,24 @@ export function hasSkipDangerousModePermissionPrompt(): boolean { ) } +/** + * Returns true if any trusted settings source has enabled bypass permissions + * mode availability. projectSettings is intentionally excluded — a malicious + * project could otherwise enable bypass mode (security risk). + */ +export function hasAllowBypassPermissionsMode(): boolean { + return !!( + getSettingsForSource('userSettings')?.permissions + ?.allowBypassPermissionsMode || + getSettingsForSource('localSettings')?.permissions + ?.allowBypassPermissionsMode || + getSettingsForSource('flagSettings')?.permissions + ?.allowBypassPermissionsMode || + getSettingsForSource('policySettings')?.permissions + ?.allowBypassPermissionsMode + ) +} + /** * Returns true if any trusted settings source has accepted the auto * mode opt-in dialog. projectSettings is intentionally excluded — diff --git a/src/utils/settings/types.ts b/src/utils/settings/types.ts index 57e757d7..ac27fcee 100644 --- a/src/utils/settings/types.ts +++ b/src/utils/settings/types.ts @@ -69,6 +69,12 @@ export const PermissionsSchema = lazySchema(() => .enum(['disable']) .optional() .describe('Disable the ability to bypass permission prompts'), + allowBypassPermissionsMode: z + .boolean() + .optional() + .describe( + 'Allow bypass permissions mode to appear in the mode list without requiring the CLI flag', + ), ...(feature('TRANSCRIPT_CLASSIFIER') ? { disableAutoMode: z