| import type { ToolPermissionContext } from '../../Tool.js' |
| import { BASH_TOOL_NAME } from '../../tools/BashTool/toolName.js' |
| import type { PermissionRule, PermissionRuleSource } from './PermissionRule.js' |
| import { |
| getAllowRules, |
| getAskRules, |
| getDenyRules, |
| permissionRuleSourceDisplayString, |
| } from './permissions.js' |
|
|
| |
| |
| |
| export type ShadowType = 'ask' | 'deny' |
|
|
| |
| |
| |
| export type UnreachableRule = { |
| rule: PermissionRule |
| reason: string |
| shadowedBy: PermissionRule |
| shadowType: ShadowType |
| fix: string |
| } |
|
|
| |
| |
| |
| export type DetectUnreachableRulesOptions = { |
| |
| |
| |
| |
| |
| sandboxAutoAllowEnabled: boolean |
| } |
|
|
| |
| |
| |
| |
| type ShadowResult = |
| | { shadowed: false } |
| | { shadowed: true; shadowedBy: PermissionRule; shadowType: ShadowType } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| export function isSharedSettingSource(source: PermissionRuleSource): boolean { |
| return ( |
| source === 'projectSettings' || |
| source === 'policySettings' || |
| source === 'command' |
| ) |
| } |
|
|
| |
| |
| |
| function formatSource(source: PermissionRuleSource): string { |
| return permissionRuleSourceDisplayString(source) |
| } |
|
|
| |
| |
| |
| function generateFixSuggestion( |
| shadowType: ShadowType, |
| shadowingRule: PermissionRule, |
| shadowedRule: PermissionRule, |
| ): string { |
| const shadowingSource = formatSource(shadowingRule.source) |
| const shadowedSource = formatSource(shadowedRule.source) |
| const toolName = shadowingRule.ruleValue.toolName |
|
|
| if (shadowType === 'deny') { |
| return `Remove the "${toolName}" deny rule from ${shadowingSource}, or remove the specific allow rule from ${shadowedSource}` |
| } |
| return `Remove the "${toolName}" ask rule from ${shadowingSource}, or remove the specific allow rule from ${shadowedSource}` |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| function isAllowRuleShadowedByAskRule( |
| allowRule: PermissionRule, |
| askRules: PermissionRule[], |
| options: DetectUnreachableRulesOptions, |
| ): ShadowResult { |
| const { toolName, ruleContent } = allowRule.ruleValue |
|
|
| |
| |
| if (ruleContent === undefined) { |
| return { shadowed: false } |
| } |
|
|
| |
| const shadowingAskRule = askRules.find( |
| askRule => |
| askRule.ruleValue.toolName === toolName && |
| askRule.ruleValue.ruleContent === undefined, |
| ) |
|
|
| if (!shadowingAskRule) { |
| return { shadowed: false } |
| } |
|
|
| |
| |
| |
| |
| if (toolName === BASH_TOOL_NAME && options.sandboxAutoAllowEnabled) { |
| if (!isSharedSettingSource(shadowingAskRule.source)) { |
| return { shadowed: false } |
| } |
| |
| } |
|
|
| return { shadowed: true, shadowedBy: shadowingAskRule, shadowType: 'ask' } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| function isAllowRuleShadowedByDenyRule( |
| allowRule: PermissionRule, |
| denyRules: PermissionRule[], |
| ): ShadowResult { |
| const { toolName, ruleContent } = allowRule.ruleValue |
|
|
| |
| |
| if (ruleContent === undefined) { |
| return { shadowed: false } |
| } |
|
|
| |
| const shadowingDenyRule = denyRules.find( |
| denyRule => |
| denyRule.ruleValue.toolName === toolName && |
| denyRule.ruleValue.ruleContent === undefined, |
| ) |
|
|
| if (!shadowingDenyRule) { |
| return { shadowed: false } |
| } |
|
|
| return { shadowed: true, shadowedBy: shadowingDenyRule, shadowType: 'deny' } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| export function detectUnreachableRules( |
| context: ToolPermissionContext, |
| options: DetectUnreachableRulesOptions, |
| ): UnreachableRule[] { |
| const unreachable: UnreachableRule[] = [] |
|
|
| const allowRules = getAllowRules(context) |
| const askRules = getAskRules(context) |
| const denyRules = getDenyRules(context) |
|
|
| |
| for (const allowRule of allowRules) { |
| |
| const denyResult = isAllowRuleShadowedByDenyRule(allowRule, denyRules) |
| if (denyResult.shadowed) { |
| const shadowSource = formatSource(denyResult.shadowedBy.source) |
| unreachable.push({ |
| rule: allowRule, |
| reason: `Blocked by "${denyResult.shadowedBy.ruleValue.toolName}" deny rule (from ${shadowSource})`, |
| shadowedBy: denyResult.shadowedBy, |
| shadowType: 'deny', |
| fix: generateFixSuggestion('deny', denyResult.shadowedBy, allowRule), |
| }) |
| continue |
| } |
|
|
| |
| const askResult = isAllowRuleShadowedByAskRule(allowRule, askRules, options) |
| if (askResult.shadowed) { |
| const shadowSource = formatSource(askResult.shadowedBy.source) |
| unreachable.push({ |
| rule: allowRule, |
| reason: `Shadowed by "${askResult.shadowedBy.ruleValue.toolName}" ask rule (from ${shadowSource})`, |
| shadowedBy: askResult.shadowedBy, |
| shadowType: 'ask', |
| fix: generateFixSuggestion('ask', askResult.shadowedBy, allowRule), |
| }) |
| } |
| } |
|
|
| return unreachable |
| } |
|
|