Claude Code 权限与安全系统源码解读
Claude Code 权限与安全系统源码解读
分析版本:Claude Code Main (Transcription Classifier Feature) 源码路径:
/mnt/e/code/cc/claude-code-mainoho 项目路径:/mnt/d/fe/opencode_cli/oho
目录
1. 架构概览
Claude Code 的权限安全系统采用分层过滤架构,核心设计思想:
┌─────────────────────────────────────────────────────────────┐
│ Tool Use Request │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Step 1: Rule-Based Permission Check (checkRuleBasedPermissions) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 1a. 工具级拒绝规则 (alwaysDenyRules) │ │
│ │ 1b. 工具级询问规则 (alwaysAskRules) │ │
│ │ 1c. 工具特定权限检查 (tool.checkPermissions) │ │
│ │ 1d. 工具实现拒绝 │ │
│ │ 1e. 用户交互要求检查 │ │
│ │ 1f. 内容级询问规则 │ │
│ │ 1g. 安全检查 (bypass-immune) │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Step 2: Mode-Based Permission Check │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 2a. bypassPermissions / plan + bypassAvailable │ │
│ │ → 直接 allow │ │
│ │ 2b. alwaysAllowRules 匹配 │ │
│ │ → 直接 allow │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Step 3: Fallback - Ask User │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 3. passthrough → ask │ │
│ │ Auto Mode: 发送至 AI Classifier 判断 │ │
│ │ Default Mode: 弹出用户确认对话框 │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘核心设计原则:
- 纵深防御:每层检查独立,失败不影响其他层
- 显式优先:用户显式规则 > 模式决定 > 默认行为
- 安全免疫:关键安全检查在 bypassPermissions 模式下仍触发确认
- AI 辅助决策:Auto 模式使用分类器判断,降低打扰
2. Permission Mode 实现
2.1 模式定义
源码位置:src/types/permissions.ts (L16-39)
// 用户可寻址模式
const EXTERNAL_PERMISSION_MODES = [
'acceptEdits', // 自动接受文件编辑
'bypassPermissions', // 跳过所有确认
'default', // 默认询问模式
'dontAsk', // 自动拒绝所有
'plan', // 计划模式
] as const
// 内部模式 (ant 用户额外增加 'auto')
type InternalPermissionMode = ExternalPermissionMode | 'auto' | 'bubble'
type PermissionMode = InternalPermissionMode模式配置:src/utils/permissions/PermissionMode.ts (L42-91)
| 模式 | 标题 | 颜色 | 说明 |
|---|---|---|---|
default | Default | text | 正常询问 |
plan | Plan Mode | planMode | 计划模式 (显示暂停图标) |
acceptEdits | Accept edits | autoAccept | 自动接受文件编辑 |
bypassPermissions | Bypass Permissions | error | 危险 - 跳过所有确认 |
dontAsk | Don’t Ask | error | 自动拒绝所有 |
auto | Auto mode | warning | AI 分类器判断 (ANT-only) |
2.2 权限上下文 (ToolPermissionContext)
核心数据结构:src/Tool.ts (L123-138)
type ToolPermissionContext = {
mode: PermissionMode // 当前权限模式
additionalWorkingDirectories: Map<string> // 额外工作目录
alwaysAllowRules: ToolPermissionRulesBySource // 允许规则
alwaysDenyRules: ToolPermissionRulesBySource // 拒绝规则
alwaysAskRules: ToolPermissionRulesBySource // 询问规则
isBypassPermissionsModeAvailable: boolean // bypass 模式是否可用
isAutoModeAvailable?: boolean // auto 模式是否可用
strippedDangerousRules?: ToolPermissionRulesBySource // auto 模式剥离的危险规则
shouldAvoidPermissionPrompts?: boolean // 避免权限提示 (后台代理)
awaitAutomatedChecksBeforeDialog?: boolean // 对话框前等待自动检查
prePlanMode?: PermissionMode // 进入 plan 前的模式
}2.3 权限规则结构
源码位置:src/types/permissions.ts (L67-79)
// 规则值:指定工具和可选内容
type PermissionRuleValue = {
toolName: string // 工具名,如 "Bash", "Read", "Edit"
ruleContent?: string // 内容,如 "rm -rf" 来自规则 "Bash(rm -rf:*)"
}
// 完整规则
type PermissionRule = {
source: PermissionRuleSource // 规则来源
ruleBehavior: PermissionBehavior // 'allow' | 'deny' | 'ask'
ruleValue: PermissionRuleValue
}规则来源优先级:src/utils/permissions/permissions.ts (L109-114)
const PERMISSION_RULE_SOURCES = [
...SETTING_SOURCES, // userSettings, projectSettings, localSettings
'cliArg', // 命令行参数
'command', // 命令
'session', // 会话
] as const2.4 权限检查管道
核心函数:src/utils/permissions/permissions.ts (L1158-1319)
async function hasPermissionsToUseToolInner(
tool: Tool,
input: { [key: string]: unknown },
context: ToolUseContext,
): Promise<PermissionDecision> {
// ========== Step 1: Rule-Based Checks ==========
// 1a. 工具级拒绝规则
const denyRule = getDenyRuleForTool(appState.toolPermissionContext, tool)
if (denyRule) {
return { behavior: 'deny', decisionReason: { type: 'rule', rule: denyRule } }
}
// 1b. 工具级询问规则 (沙箱自动允许除外)
const askRule = getAskRuleForTool(appState.toolPermissionContext, tool)
if (askRule && !canSandboxAutoAllow) {
return { behavior: 'ask', decisionReason: { type: 'rule', rule: askRule } }
}
// 1c. 工具特定权限检查
let toolPermissionResult = await tool.checkPermissions(parsedInput, context)
// 1d. 工具实现拒绝
if (toolPermissionResult?.behavior === 'deny') return toolPermissionResult
// 1e. 工具要求用户交互
if (tool.requiresUserInteraction?.() && toolPermissionResult?.behavior === 'ask') {
return toolPermissionResult
}
// 1f. 内容级询问规则 (优先于 bypass)
if (toolPermissionResult?.behavior === 'ask' &&
toolPermissionResult.decisionReason?.type === 'rule' &&
toolPermissionResult.decisionReason.rule.ruleBehavior === 'ask') {
return toolPermissionResult
}
// 1g. 安全检查 (bypass-immune)
if (toolPermissionResult?.behavior === 'ask' &&
toolPermissionResult.decisionReason?.type === 'safetyCheck') {
return toolPermissionResult
}
// ========== Step 2: Mode-Based Check ==========
const shouldBypassPermissions =
context.mode === 'bypassPermissions' ||
(context.mode === 'plan' && context.isBypassPermissionsModeAvailable)
if (shouldBypassPermissions) {
return { behavior: 'allow', decisionReason: { type: 'mode', mode } }
}
// 2b. 始终允许规则
const alwaysAllowedRule = toolAlwaysAllowedRule(appState.toolPermissionContext, tool)
if (alwaysAllowedRule) {
return { behavior: 'allow', decisionReason: { type: 'rule', rule: alwaysAllowedRule } }
}
// ========== Step 3: Fallback to Ask ==========
return { behavior: 'ask', message: createPermissionRequestMessage(tool.name) }
}2.5 模式切换
模式循环顺序 (Shift+Tab):default → acceptEdits → plan → bypassPermissions → auto → default
切换处理:src/utils/permissions/permissionSetup.ts (L597-646)
export function transitionPermissionMode(
fromMode: string,
toMode: string,
context: ToolPermissionContext,
): ToolPermissionContext {
// plan 模式特殊处理
handlePlanModeTransition(fromMode, toMode)
handleAutoModeTransition(fromMode, toMode)
// 进入 auto 模式:剥离危险权限
if (toMode === 'auto' && fromMode !== 'auto') {
context = stripDangerousPermissionsForAutoMode(context)
}
// 离开 auto 模式:恢复危险权限
if (fromMode === 'auto' && toMode !== 'auto') {
context = restoreDangerousPermissions(context)
}
return context
}3. 命令白名单/黑名单
3.1 Bash 工具规则匹配
源码位置:src/tools/BashTool/bashPermissions.ts
Bash 工具的权限检查采用多阶段匹配:
async function bashToolCheckPermission(
input: BashInput,
context: ToolUseContext,
): Promise<PermissionResult> {
// 1. 精确匹配检查
const exactMatch = bashToolCheckExactMatchPermission(input, context)
// 2. 前缀/通配符匹配
const prefixMatch = matchingRulesForInput(input, context)
// 3. 路径约束检查
const pathCheck = checkPathConstraints(input, context)
// 4. Sed 约束检查
const sedCheck = checkSedConstraints(input, context)
// 5. 模式特定权限
const modeCheck = checkPermissionMode(context)
// 6. 只读命令检查
const readOnlyCheck = checkReadOnlyConstraints(input, context)
}3.2 规则匹配与安全剥离
关键安全机制:src/utils/permissions/permissionSetup.ts (L94-147)
// 检查 Bash 权限规则是否危险 (auto 模式)
export function isDangerousBashPermission(
toolName: string,
ruleContent: string | undefined,
): boolean {
// 工具级允许 (无内容或 Bash(*)) - 允许所有命令
if (ruleContent === undefined || ruleContent === '') return true
// 通配符 * 匹配一切
if (ruleContent === '*') return true
// 检查危险模式
for (const pattern of DANGEROUS_BASH_PATTERNS) {
if (content === pattern) return true
if (content === `${pattern}:*`) return true // 前缀语法
if (content === `${pattern}*`) return true // 通配符
if (content === `${pattern} *`) return true // 带空格
}
return false
}3.3 只读命令白名单
源码位置:src/utils/shell/readOnlyCommandValidation.ts
// Git 只读命令
export const GIT_READ_ONLY_COMMANDS = {
'git diff': { safeFlags: { '--stat': 'none', '-p': 'none', ... } },
'git log': { safeFlags: { '--oneline': 'none', '--graph': 'none', ... } },
'git show': { safeFlags: {...} },
'git status': { safeFlags: {...} },
'git grep': { safeFlags: {...} },
// ...
}
// GitHub CLI 只读命令
export const GH_READ_ONLY_COMMANDS = {
'gh pr view': { safeFlags: {...}, additionalCommandIsDangerousCallback: ghIsDangerousCallback },
'gh pr list': { safeFlags: {...} },
'gh issue view': { safeFlags: {...} },
// ...
}3.4 危险模式列表
源码位置:src/utils/permissions/dangerousPatterns.ts (L44-79)
// 跨平台代码执行入口
const CROSS_PLATFORM_CODE_EXEC = [
'python', 'python3', 'node', 'deno', 'ruby', 'perl', 'php', 'lua',
'npx', 'bunx', 'npm run', 'yarn run', 'pnpm run', 'bun run',
'bash', 'sh', 'ssh',
]
// Bash 危险模式 (ANT 用户额外增加)
const DANGEROUS_BASH_PATTERNS = [
...CROSS_PLATFORM_CODE_EXEC,
'zsh', 'fish', 'eval', 'exec', 'env', 'xargs', 'sudo',
// ANT-only: 内部工具 + 常见过度允许的前缀
...(process.env.USER_TYPE === 'ant' ? [
'fa run', 'coo', 'gh', 'gh api', 'curl', 'wget',
'git', 'kubectl', 'aws', 'gcloud', 'gsutil',
] : []),
]4. Dangerous 工具识别
4.1 工具级危险识别
源码位置:src/utils/permissions/permissionSetup.ts (L157-233)
// PowerShell 危险权限检查
export function isDangerousPowerShellPermission(
toolName: string,
ruleContent: string | undefined,
): boolean {
if (toolName !== 'PowerShell') return false
// 工具级允许 = 危险
if (!ruleContent) return true
const dangerousPatterns = [
...CROSS_PLATFORM_CODE_EXEC,
'pwsh', 'powershell', 'cmd', 'wsl',
// 代码执行
'iex', 'invoke-expression', 'icm', 'invoke-command',
// 进程生成
'start-process', 'saps', 'start', 'start-job',
// 事件/会话代码执行
'register-objectevent', 'register-engineevent',
// .NET 逃逸
'add-type', 'new-object',
]
// ... 匹配逻辑
}
// Agent (子代理) 权限危险检查
export function isDangerousTaskPermission(
toolName: string,
ruleContent: string | undefined,
): boolean {
return toolName === 'Agent' // 任何 Agent 允许规则都危险
}4.2 Bash 安全检查
源码位置:src/tools/BashTool/bashSecurity.ts
该文件包含 23 种安全检查,用于识别潜在危险命令:
const BASH_SECURITY_CHECK_IDS = {
INCOMPLETE_COMMANDS: 1, // 不完整命令
JQ_SYSTEM_FUNCTION: 2, // jq system() 函数
JQ_FILE_ARGUMENTS: 3, // jq 危险标志
OBFUSCATED_FLAGS: 4, // 混淆标志
SHELL_METACHARACTERS: 5, // Shell 元字符
DANGEROUS_VARIABLES: 6, // 危险变量
NEWLINES: 7, // 换行符分隔多命令
COMMAND_SUBSTITUTION: 8, // 命令替换
INPUT_REDIRECTION: 9, // 输入重定向
OUTPUT_REDIRECTION: 10, // 输出重定向
IFS_INJECTION: 11, // IFS 注入
GIT_COMMIT_SUBSTITUTION: 12, // Git 提交替换
PROC_ENVIRON_ACCESS: 13, // /proc 环境访问
MALFORMED_TOKEN_INJECTION: 14, // 畸形标记注入
BACKSLASH_ESCAPED_WHITESPACE: 15, // 反斜杠逃逸
BRACE_EXPANSION: 16, // 大括号扩展
CONTROL_CHARACTERS: 17, // 控制字符
UNICODE_WHITESPACE: 18, // Unicode 空白
MID_WORD_HASH: 19, // 词中哈希
ZSH_DANGEROUS_COMMANDS: 20, // Zsh 危险命令
BACKSLASH_ESCAPED_OPERATORS: 21, // 反斜杠转义操作符
COMMENT_QUOTE_DESYNC: 22, // 注释引号去同步
QUOTED_NEWLINE: 23, // 引号内换行
}4.3 Zsh 危险命令集合
源码位置:src/tools/BashTool/bashSecurity.ts (L45-74)
const ZSH_DANGEROUS_COMMANDS = new Set([
// zmodload 是许多危险模块的入口
'zmodload',
// emulate with -c 是 eval 等价物
'emulate',
// Zsh 模块内置命令
'sysopen', 'sysread', 'syswrite', 'sysseek',
'zpty', // 伪终端命令执行
'ztcp', // TCP 连接 (数据泄露)
'zsocket', // Unix/TCP 套接字
'mapfile', // 关联数组
'zf_rm', 'zf_mv', 'zf_ln', 'zf_chmod', 'zf_chown',
'zf_mkdir', 'zf_rmdir', 'zf_chgrp',
])4.4 危险模式检测回调
Git reflog 写操作检测:src/utils/shell/readOnlyCommandValidation.ts (L278-304)
'git reflog': {
safeFlags: {...},
additionalCommandIsDangerousCallback: (_rawCommand, args) => {
const DANGEROUS_SUBCOMMANDS = new Set(['expire', 'delete', 'exists'])
for (const token of args) {
if (!token || token.startsWith('-')) continue
if (DANGEROUS_SUBCOMMANDS.has(token)) return true
return false // 第一个安全位置后即安全
}
return false
},
}5. 用户确认流程
5.1 权限对话框处理
源码位置:src/components/permissions/FilePermissionDialog/useFilePermissionDialog.ts
export function useFilePermissionDialog<T extends ToolInput>({
filePath,
completionType,
languageName,
toolUseConfirm, // 确认请求
onDone, // 完成回调
onReject, // 拒绝回调
parseInput,
operationType = 'write',
}) {
const toolPermissionContext = useAppState(s => s.toolPermissionContext)
const [acceptFeedback, setAcceptFeedback] = useState('')
const [rejectFeedback, setRejectFeedback] = useState('')
const [focusedOption, setFocusedOption] = useState('yes')
// 根据上下文生成选项
const options = useMemo(() =>
getFilePermissionOptions({
filePath,
toolPermissionContext,
operationType,
// ...
}), [filePath, toolPermissionContext])
// 处理选项选择
const onChange = useCallback((option, input, feedback?) => {
const handler = PERMISSION_HANDLERS[option.type]
handler(params, {
feedback,
scope: option.type === 'accept-session' ? option.scope : undefined,
})
}, [])
}5.2 权限选项类型
可能的权限决策:
| 选项类型 | 行为 | 说明 |
|---|---|---|
accept-once | 当次允许 | 仅本次允许 |
accept-session | 会话允许 | 本次会话允许 |
accept-always | 永久允许 | 写入设置 |
deny-once | 当次拒绝 | 仅本次拒绝 |
deny-session | 会话拒绝 | 本次会话拒绝 |
deny-always | 永久拒绝 | 写入设置 |
5.3 权限确认消息创建
源码位置:src/utils/permissions/permissions.ts (L137-211)
export function createPermissionRequestMessage(
toolName: string,
decisionReason?: PermissionDecisionReason,
): string {
if (decisionReason) {
switch (decisionReason.type) {
case 'classifier':
return `Classifier '${decisionReason.classifier}' requires approval: ${decisionReason.reason}`
case 'hook':
return `Hook '${decisionReason.hookName}' blocked: ${decisionReason.reason}`
case 'rule':
return `Permission rule '${ruleString}' requires approval`
case 'sandboxOverride':
return 'Run outside of the sandbox'
case 'safetyCheck':
return decisionReason.reason
case 'mode':
return `Current mode (${modeTitle}) requires approval`
}
}
return `Claude requested permissions to use ${toolName}`
}5.4 Auto Mode 分类器流程
源码位置:src/utils/permissions/permissions.ts (L688-926)
// Auto 模式使用 AI 分类器判断
if (appState.toolPermissionContext.mode === 'auto') {
// 1. AcceptEdits 快速通道
const acceptEditsResult = await tool.checkPermissions(input, {
...context,
mode: 'acceptEdits',
})
if (acceptEditsResult.behavior === 'allow') {
return { behavior: 'allow', decisionReason: { type: 'mode', mode: 'auto' } }
}
// 2. 安全工具白名单跳过分类器
if (isAutoModeAllowlistedTool(tool.name)) {
return { behavior: 'allow' }
}
// 3. 运行分类器
const classifierResult = await classifyYoloAction(
context.messages,
action,
context.options.tools,
appState.toolPermissionContext,
context.abortController.signal,
)
// 4. 分类器拒绝
if (classifierResult.shouldBlock) {
// 更新拒绝追踪
const newDenialState = recordDenial(denialState)
// 超过限制则回退到询问
if (shouldFallbackToPrompting(denialState)) {
return { behavior: 'ask', ... }
}
return { behavior: 'deny', message: buildYoloRejectionMessage(...) }
}
// 5. 分类器允许
return { behavior: 'allow', decisionReason: { type: 'classifier', ... } }
}5.5 拒绝追踪
源码位置:src/utils/permissions/denialTracking.ts
const DENIAL_LIMITS = {
maxConsecutive: 3, // 连续 3 次拒绝后回退到询问
maxTotal: 10, // 总计 10 次拒绝后回退到询问
}
export function shouldFallbackToPrompting(state: DenialTrackingState): boolean {
return state.consecutiveDenials >= DENIAL_LIMITS.maxConsecutive ||
state.totalDenials >= DENIAL_LIMITS.maxTotal
}6. 安全边界设计
6.1 安全检查的 Bypass 免疫
关键设计原则:某些安全检查在 bypassPermissions 模式下仍然触发确认对话框
源码位置:src/utils/permissions/permissions.ts (L1252-1260)
// 1g. 安全检查 (.git/, .claude/, .vscode/, shell configs)
// bypass-immune — 即使在 bypassPermissions 模式也必须询问
if (
toolPermissionResult?.behavior === 'ask' &&
toolPermissionResult.decisionReason?.type === 'safetyCheck'
) {
return toolPermissionResult
}安全检查路径:src/utils/permissions/pathValidation.ts
// 检查路径安全性
export function checkPathSafetyForAutoEdit(path: string): PermissionResult {
// .git/ 目录 - 始终询问
if (path.includes('.git/')) {
return { behavior: 'ask', decisionReason: { type: 'safetyCheck', ... } }
}
// .claude/ 配置目录 - 始终询问
if (path.includes('.claude/')) {
return { behavior: 'ask', decisionReason: { type: 'safetyCheck', ... } }
}
// Shell 配置文件 - 始终询问
if (isShellConfigFile(path)) {
return { behavior: 'ask', decisionReason: { type: 'safetyCheck', ... } }
}
}6.2 内容级规则优先于模式
设计原则:用户显式配置的内容级询问规则优先于 bypassPermissions 模式
源码位置:src/utils/permissions/permissions.ts (L1238-1250)
// 1f. 内容级询问规则优先于 bypassPermissions
// 当用户显式配置如 Bash(npm publish:*) 的询问规则时
// 即使在 bypassPermissions 模式也必须尊重
if (
toolPermissionResult?.behavior === 'ask' &&
toolPermissionResult.decisionReason?.type === 'rule' &&
toolPermissionResult.decisionReason.rule.ruleBehavior === 'ask'
) {
return toolPermissionResult
}6.3 环境变量剥离
防御机制:防止通过环境变量绕过拒绝规则
源码位置:src/utils/bash/commands.ts
// 拒绝规则剥离所有前导环境变量
export function stripAllLeadingEnvVars(command: string): string {
// 剥离 FOO=bar BAZ=qux from 'FOO=bar rm -rf /'
// 防止通过环境变量绕过 deny 规则
}
// 允许规则保留环境变量以便合法使用
// 例如: FOO=bar ls 是合法的 ls 命令
6.4 复合命令检测
防御机制:防止前缀规则被复合命令绕过
// 示例: 规则 Bash(cd:*) 不应匹配 "cd /path && rm -rf"
// 因为复合命令的第二部分是危险的
export function matchesCompoundCommand(command: string, prefix: string): boolean {
// 检测 &&, ||, ;, | 等操作符
// 如果规则前缀后面跟着操作符,则不匹配
}6.5 沙箱集成
源码位置:src/tools/BashTool/shouldUseSandbox.ts
export function shouldUseSandbox(input: BashInput): boolean {
// 检查是否应该使用沙箱执行
// 某些命令类型默认使用沙箱
if (isReadOnlyCommand(input.command)) return true
// 危险命令可能使用沙箱限制
if (isPotentiallyDangerous(input.command)) {
return SandboxManager.isSandboxingEnabled()
}
return false
}6.6 Statsig 特性门控
动态禁用机制:通过 Statsig 远程禁用 bypassPermissions 模式
源码位置:src/utils/permissions/permissionSetup.ts (L689-811)
export function initialPermissionModeFromCLI({
permissionModeCli,
dangerouslySkipPermissions,
}): { mode: PermissionMode; notification?: string } {
// 检查 GrowthBook Statsig gate
const growthBookDisableBypassPermissionsMode =
checkStatsigFeatureGate_CACHED_MAY_BE_STALE(
'tengu_disable_bypass_permissions_mode',
)
// 检查设置
const settingsDisableBypassPermissionsMode =
settings.permissions?.disableBypassPermissionsMode === 'disable'
// Statsig 优先于设置
const disableBypassPermissionsMode =
growthBookDisableBypassPermissionsMode || settingsDisableBypassPermissionsMode
// 如果禁用,跳过 bypassPermissions 模式
if (mode === 'bypassPermissions' && disableBypassPermissionsMode) {
return { mode: 'default', notification: '...' }
}
}7. oho CLI 权限实现
oho 是 Claude Code 的 CLI 客户端,权限安全决策由服务器处理,CLI 主要提供配置和交互接口。
7.1 AutoApprove 配置
源码位置:oho/internal/types/types.go (L104)
// Config 配置类型
type Config struct {
Providers map[string]interface{} `json:"providers"`
DefaultModel string `json:"defaultModel"`
Theme string `json:"theme"`
Language string `json:"language"`
AutoApprove []string `json:"autoApprove"` // 自动批准的工具列表
MaxTokens int `json:"maxTokens"`
Temperature float64 `json:"temperature"`
}配置命令:oho/cmd/configcmd/config.go (L222)
setCmd.Flags().StringSliceVar(&autoApprove, "auto-approve", nil,
"自动批准的工具列表(注意:可能不被服务器支持)")使用示例:
# 设置自动批准的工具列表
oho config set --auto-approve read,ls,cat,grep7.2 权限响应命令
源码位置:oho/cmd/session/session.go (L811-860)
// permissionsCmd 响应权限请求
var permissionsCmd = &cobra.Command{
Use: "permissions [id] [permissionID]",
Short: "响应权限请求",
Args: cobra.MaximumNArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
// ...
req := map[string]interface{}{
"response": permissionResp, // "allow" 或 "deny"
"remember": rememberPerm, // 是否记住决策
}
resp, err := c.Post(ctx,
fmt.Sprintf("/session/%s/permissions/%s", id, permID), req)
// ...
},
}API 映射:oho/API_MAPPING.md (L61)
| 命令 | API 端点 | 方法 | 描述 |
|---|---|---|---|
oho session permissions <id> <permId> | /session/:id/permissions/:permissionID | POST | 响应权限请求 |
7.3 交互式确认
源码位置:oho/internal/util/output.go (L92-102)
// Confirm 确认操作
func Confirm(prompt string) bool {
if config.Get().JSON {
return true // JSON 模式下自动批准
}
fmt.Printf("%s [y/N]: ", prompt)
var response string
_, _ = fmt.Scanln(&response)
return strings.ToLower(response) == "y" || strings.ToLower(response) == "yes"
}7.4 oho 权限安全特点
| 特性 | 说明 |
|---|---|
| 服务器决策 | 权限判断逻辑在服务器端,CLI 仅传递配置和响应 |
| AutoApprove | 透传工具列表给服务器,由服务器决定是否自动批准 |
| permissions 命令 | 允许用户响应服务器发出的权限请求 |
| Confirm 函数 | CLI 层面的交互式确认,用于需要本地确认的场景 |
| JSON 模式 | --json 模式下自动批准所有操作(无交互) |
8. 核心文件索引
8.1 Claude Code 主源码
类型定义
| 文件 | 说明 |
|---|---|
src/types/permissions.ts | 核心权限类型定义 |
src/utils/permissions/PermissionRule.ts | 权限规则类型 |
src/utils/permissions/PermissionMode.ts | 模式枚举与配置 |
src/utils/permissions/PermissionResult.ts | 决策结果类型 |
核心实现
| 文件 | 说明 |
|---|---|
src/utils/permissions/permissions.ts | 主权限检查管道 |
src/utils/permissions/permissionSetup.ts | 权限上下文初始化与模式切换 |
src/utils/permissions/dangerousPatterns.ts | 危险模式列表 |
src/utils/permissions/denialTracking.ts | 拒绝追踪状态 |
src/utils/permissions/yoloClassifier.ts | Auto 模式 AI 分类器 |
工具实现
| 文件 | 说明 |
|---|---|
src/Tool.ts | 工具接口与 checkPermissions 定义 |
src/tools/BashTool/bashSecurity.ts | Bash 安全检查 (23 种) |
src/tools/BashTool/bashPermissions.ts | Bash 权限规则匹配 |
src/utils/shell/readOnlyCommandValidation.ts | 只读命令白名单 |
UI/UX
| 文件 | 说明 |
|---|---|
src/components/permissions/FilePermissionDialog/useFilePermissionDialog.ts | 文件权限对话框 |
src/components/permissions/hooks.ts | 权限相关 React hooks |
src/hooks/useCanUseTool.tsx | 权限检查 React hook |
集成点
| 文件 | 说明 |
|---|---|
src/services/tools/toolExecution.ts | 工具执行与权限集成 |
src/hooks/toolPermission/handlers/interactiveHandler.ts | 交互式权限处理 |
src/hooks/toolPermission/handlers/coordinatorHandler.ts | 协调器处理 |
src/hooks/toolPermission/handlers/swarmWorkerHandler.ts | 后台代理处理 |
8.2 oho CLI
| 文件 | 说明 |
|---|---|
oho/internal/types/types.go | 配置类型定义(含 AutoApprove) |
oho/cmd/configcmd/config.go | 配置命令(含 –auto-approve) |
oho/cmd/session/session.go | 会话命令(含 permissions 子命令) |
oho/internal/util/output.go | 输出工具(含 Confirm 函数) |
oho/API_MAPPING.md | API 端点映射 |
8.3 OpenClaw 网关
| 文档 | 说明 |
|---|---|
docs/zh-CN/gateway/security/index.md | 安全架构与威胁模型 |
docs/zh-CN/gateway/sandboxing.md | 沙箱隔离机制 |
docs/zh-CN/tools/exec-approvals.md | 执行审批系统 |
附录:权限决策类型
// 允许决策
type PermissionAllowDecision<Input> = {
behavior: 'allow'
updatedInput?: Input
userModified?: boolean
decisionReason?: PermissionDecisionReason
toolUseID?: string
acceptFeedback?: string
}
// 询问决策
type PermissionAskDecision<Input> = {
behavior: 'ask'
message: string
updatedInput?: Input
decisionReason?: PermissionDecisionReason
suggestions?: PermissionUpdate[]
blockedPath?: string
metadata?: PermissionMetadata
pendingClassifierCheck?: PendingClassifierCheck // Auto 模式异步检查
}
// 拒绝决策
type PermissionDenyDecision = {
behavior: 'deny'
message: string
decisionReason: PermissionDecisionReason
toolUseID?: string
}
// 决策原因
type PermissionDecisionReason =
| { type: 'rule', rule: PermissionRule } // 规则匹配
| { type: 'mode', mode: PermissionMode } // 模式决定
| { type: 'subcommandResults', reasons: Map } // 子命令结果
| { type: 'permissionPromptTool', ... } // 权限提示工具
| { type: 'hook', hookName: string } // Hook 拦截
| { type: 'asyncAgent', reason: string } // 异步代理
| { type: 'sandboxOverride', reason: string } // 沙箱覆盖
| { type: 'classifier', classifier: string } // AI 分类器
| { type: 'workingDir', reason: string } // 工作目录
| { type: 'safetyCheck', reason: string, classifierApprovable: boolean } // 安全检查
| { type: 'other', reason: string } // 其他
参考架构图
┌─────────────────────────────────────────────────────────────────┐
│ Claude Code 权限系统 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Claude │ │ oho CLI │ │ OpenClaw │ │
│ │ Code │ │ (客户端) │ │ Gateway │ │
│ │ (主源码) │ │ │ │ (服务端) │ │
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
│ │ │ │ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 权限检查管道 │ │
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
│ │ │ Rule │→ │ Mode │→ │ Auto │→ │ Fallback│ │ │
│ │ │ Based │ │ Based │ │ Classifier│ │ Ask │ │ │
│ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 安全边界 │ │
│ │ • Bypass 免疫 (safetyCheck) │ │
│ │ • 内容级规则优先 │ │
│ │ • 环境变量剥离 │ │
│ │ • 复合命令检测 │ │
│ │ • 沙箱集成 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘文档生成时间:2026-04-03 分析深度:P6 (架构级理解,可完整复现实现)