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
This commit is contained in:
committed by
GitHub
parent
7c8bdcc3e2
commit
31be66d764
@@ -4582,7 +4582,7 @@ function handleSetPermissionMode(
|
|||||||
subtype: 'error',
|
subtype: 'error',
|
||||||
request_id: requestId,
|
request_id: requestId,
|
||||||
error:
|
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
|
return toolPermissionContext
|
||||||
|
|||||||
@@ -434,7 +434,7 @@ export function useReplBridge(messages: Message[], setMessages: (action: React.S
|
|||||||
if (!store.getState().toolPermissionContext.isBypassPermissionsModeAvailable) {
|
if (!store.getState().toolPermissionContext.isBypassPermissionsModeAvailable) {
|
||||||
return {
|
return {
|
||||||
ok: false,
|
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'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import {
|
|||||||
getSettings_DEPRECATED,
|
getSettings_DEPRECATED,
|
||||||
getSettingsFilePathForSource,
|
getSettingsFilePathForSource,
|
||||||
getUseAutoModeDuringPlan,
|
getUseAutoModeDuringPlan,
|
||||||
|
hasAllowBypassPermissionsMode,
|
||||||
hasAutoModeOptIn,
|
hasAutoModeOptIn,
|
||||||
} from '../settings/settings.js'
|
} from '../settings/settings.js'
|
||||||
import {
|
import {
|
||||||
@@ -936,9 +937,11 @@ export async function initializeToolPermissionContext({
|
|||||||
const settings = getSettings_DEPRECATED() || {}
|
const settings = getSettings_DEPRECATED() || {}
|
||||||
const settingsDisableBypassPermissionsMode =
|
const settingsDisableBypassPermissionsMode =
|
||||||
settings.permissions?.disableBypassPermissionsMode === 'disable'
|
settings.permissions?.disableBypassPermissionsMode === 'disable'
|
||||||
|
const settingsAllowBypassPermissionsMode = hasAllowBypassPermissionsMode()
|
||||||
const isBypassPermissionsModeAvailable =
|
const isBypassPermissionsModeAvailable =
|
||||||
(permissionMode === 'bypassPermissions' ||
|
(permissionMode === 'bypassPermissions' ||
|
||||||
allowDangerouslySkipPermissions) &&
|
allowDangerouslySkipPermissions ||
|
||||||
|
settingsAllowBypassPermissionsMode) &&
|
||||||
!growthBookDisableBypassPermissionsMode &&
|
!growthBookDisableBypassPermissionsMode &&
|
||||||
!settingsDisableBypassPermissionsMode
|
!settingsDisableBypassPermissionsMode
|
||||||
|
|
||||||
|
|||||||
27
src/utils/settings/allowBypassPermissionsMode.test.ts
Normal file
27
src/utils/settings/allowBypassPermissionsMode.test.ts
Normal file
@@ -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)
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -574,6 +574,7 @@ export function getManagedSettingsKeysForLogging(
|
|||||||
'ask',
|
'ask',
|
||||||
'defaultMode',
|
'defaultMode',
|
||||||
'disableBypassPermissionsMode',
|
'disableBypassPermissionsMode',
|
||||||
|
'allowBypassPermissionsMode',
|
||||||
...(feature('TRANSCRIPT_CLASSIFIER') ? ['disableAutoMode'] : []),
|
...(feature('TRANSCRIPT_CLASSIFIER') ? ['disableAutoMode'] : []),
|
||||||
'additionalDirectories',
|
'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
|
* Returns true if any trusted settings source has accepted the auto
|
||||||
* mode opt-in dialog. projectSettings is intentionally excluded —
|
* mode opt-in dialog. projectSettings is intentionally excluded —
|
||||||
|
|||||||
@@ -69,6 +69,12 @@ export const PermissionsSchema = lazySchema(() =>
|
|||||||
.enum(['disable'])
|
.enum(['disable'])
|
||||||
.optional()
|
.optional()
|
||||||
.describe('Disable the ability to bypass permission prompts'),
|
.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')
|
...(feature('TRANSCRIPT_CLASSIFIER')
|
||||||
? {
|
? {
|
||||||
disableAutoMode: z
|
disableAutoMode: z
|
||||||
|
|||||||
Reference in New Issue
Block a user