Yoda
参考Agent 设计指南控制与安全

权限与审批

CC 用「规则引擎(deny→ask→allow)+ 模式档位」,Codex 用「approval_policy × sandbox 联动 + execpolicy 规则」——前者表达力强,后者与 OS 沙箱耦合更深

权限与审批

结论

Claude Code 与 Codex CLI 在「agent 自主度阀门」上走了两条路线。CC 是规则引擎模型Tool(specifier) 形式的 allow/ask/deny 规则按 deny→ask→allow 固定顺序裁决,叠加 7 种 permission mode(default/acceptEdits/plan/auto/dontAsk/bypassPermissions)改变兜底行为 [一手文档]。Codex 是策略组合模型approval_policy(untrusted/on-failure/on-request/never/granular)决定"什么时候问人",与 sandbox_mode 联动决定"问不问得起"——审批的默认触发条件是"文件系统策略受限"而非命令匹配 [一手源码]。对 harness 而言:CC 的审批面向"规则没覆盖到的调用",Codex 的审批面向"想越出沙箱的调用",两者的 PermissionRequest 钩子都允许程序化代答,是嵌入式产品(如 Yoda)做自动审批 UI 的正道。

研究问题

  • CC 的 allow/ask/deny 规则如何解析与匹配?deny 优先级是怎么实现的?
  • CC 各 permission mode 在源码里如何改变裁决路径?
  • Codex AskForApproval 各档位的精确语义?审批请求何时发出?
  • Codex 的 PermissionRequest hook 与 CC 的 PreToolUse/PermissionRequest hook 拦截语义差异?

各 Agent 设计与实现

Claude Code

规则语法与解析。规则形如 ToolTool(specifier)。解析器 permissionRuleValueFromString 处理转义括号,并把 Bash() / Bash(*) 归一化为裸工具名(即整工具规则)(src_2026-03-31/utils/permissions/permissionRuleParser.ts:93-133)[一手源码]。Bash specifier 支持三种匹配:legacy 前缀 npm:*、通配符 npm run ** 可在任意位置、跨参数匹配)、精确串——见 ShellPermissionRule 判别联合(utils/permissions/shellRuleMatching.ts:25-37)与 matchWildcardPattern(同文件 :90-154,含「git * 同时匹配裸 git」的尾通配特判 :143-145)[一手源码]。Read/Edit 规则则用 gitignore 语义,// 开头为绝对路径、/ 开头为项目根相对(docs/permissions.md:197-210)[一手文档]。

裁决顺序。文档明确「deny → ask → allow,首条命中即生效」(docs/permissions.md:29)。源码里 checkRuleBasedPermissions 按编号步骤执行:1a 整工具 deny → 1b 整工具 ask → 1c 调 tool.checkPermissions(命令级规则在工具内部判)→ 1d 工具级 deny → 1f 内容级 ask 规则 → 1g safetyCheck(保护路径,bypass 免疫)(utils/permissions/permissions.ts:1071-1156)[一手源码]。值得注意的两个细节:

  • dontAsk 是末端变换hasPermissionsToUseTool 在所有判定结束后,把 ask 结果统一转成 deny,注释写明"放在最后以免被 early return 绕过"(permissions.ts:505-516)[一手源码]。
  • ask 规则与 safetyCheck 可穿透 bypassPermissions:内容级 ask 规则(步骤 1f)和 safetyCheck(1g)的注释明确 "must be respected even in bypass mode"(permissions.ts:1238-1260)[一手源码]。

模式档位。7 种模式见 docs/permission-modes.md:15-22 的对照表;auto 模式用独立分类器代替人审,进入 auto 时会主动丢弃过宽的 allow 规则(Bash(*)Bash(python*) 等),退出时恢复(permission-modes.md:256-263)[一手文档]。bypassPermissions 在 root/sudo 下拒绝启动(permission-modes.md:303-309)。

命令匹配的工程细节(容易踩坑,全部 [一手文档],docs/permissions.md):

  • 复合命令按 && || ; | 等分割,规则须独立匹配每个子命令(:126)
  • 匹配前剥离固定包装器 timeout/time/nice/nohup/stdbuf 及裸 xargs(:133-135)
  • watch/setsid/ionice/flockfind -exec 永远提示,前缀规则无法放行(:139)
  • 内置只读命令集(ls/cat/grep/...)任何模式下免提示,不可配置(:143)
  • symlink 双路径检查:allow 需两端都命中,deny 任一端命中即拦(:230-235)

hook 扩展。PreToolUse hook 在权限提示前运行,可 deny/强制提示/放行,但 deny 与 ask 规则不受 hook 的 allow 影响(docs/permissions.md:267-271);PermissionRequest 是独立 hook 事件,在权限对话框出现时触发,可返回 decision.behavior allow/deny(docs/hooks.md:34, :805)[一手文档]。

Codex CLI

AskForApproval 五档codex-rs/protocol/src/protocol.rs:760-790)[一手源码]:

pub enum AskForApproval {
    #[serde(rename = "untrusted")]
    UnlessTrusted,   // 只有 is_safe_command() 认定的只读命令免审,其余全问
    OnFailure,       // DEPRECATED:沙箱内全自动,失败才升级问人
    #[default]
    OnRequest,       // 模型自行决定何时请求审批(默认档)
    Granular(GranularApprovalConfig), // 按审批类别细粒度开关
    Never,           // 永不问人,失败直接回给模型
}

Granular 把审批拆成五类开关:sandbox_approval / rules / skill_approval / request_permissions / mcp_elicitations,关闭的类别自动拒绝而不是弹窗(protocol.rs:792-808)[一手源码]。

审批触发逻辑与沙箱联动default_exec_approval_requirement 是核心裁决函数:Never/OnFailure 不问;OnRequest/Granular 仅当文件系统沙箱策略为 Restricted 时问;UnlessTrusted 永远问(codex-rs/core/src/tools/sandboxing.rs:196-236)[一手源码]。也就是说:在 danger-full-access 下,on-request 实际上不会产生审批——审批的存在意义就是"批准越出沙箱"。sandbox_override_for_first_attempt 进一步规定:execpolicy Allow 可携带 bypass_sandbox=true 直接裸跑首次尝试;但若策略含 denied-read 路径,则禁止任何无沙箱执行以免静默放开读限制(sandboxing.rs:246-291)[一手源码]。

审批的协议形态。审批请求作为 ExecApprovalRequestEvent 事件发出,携带 call_id / command / cwd / reasoncodex-rs/protocol/src/approvals.rs:218-243);用户应答是 ReviewDecisionApproved / ApprovedExecpolicyAmendment(顺手固化为规则)/ ApprovedForSession(会话内缓存)/ Denied / Abort 等(protocol.rs:3660-3692)[一手源码]。会话级缓存由 with_cached_approval 按 approval key 实现(sandboxing.rs:70-110)。

execpolicy 规则层。Codex 没有 CC 式的 Tool(pattern) 用户规则,对应物是 execpolicy:规则裁决为 Allow / Prompt / Forbidden 三值(codex-rs/execpolicy/src/decision.rs:9-23)[一手源码],审批通过时模型可提议 ExecPolicyAmendment 让同类命令以后免审(protocol.rs:3664-3668)。

PermissionRequest hook。Codex 的 hook 事件枚举包含 PreToolUse / PermissionRequest / PostToolUse / ...(protocol.rs:1330-1342);run_permission_request_hooks 在 guardian/用户审批之前运行,hook 返回 Option<PermissionRequestDecision>——返回决定则跳过人审,返回 None 则继续走正常审批流(codex-rs/core/src/hook_runtime.rs:219-253)[一手源码]。这与 CC 语义一致:hook 是审批前的程序化代答点。

一键全开--dangerously-bypass-approvals-and-sandbox 等价于 approval_policy=never + sandbox_mode=danger-full-accesscodex-rs/tui/src/lib.rs:912-921),且与 --approval-policy 互斥(tui/src/cli.rs:136-139)[一手源码]。

差异矩阵

维度Claude CodeCodex CLI
用户规则模型Tool(specifier) allow/ask/deny 三表,deny→ask→allow 固定序execpolicy 规则(Allow/Prompt/Forbidden)+ requirements 约束
审批默认触发规则未覆盖且模式要求提示文件系统沙箱受限(Restricted)且 policy 为 on-request/untrusted
模式档位7 种 permission mode(含 auto 分类器档)5 档 approval_policy × 4 种 sandbox_mode 组合
审批与沙箱关系解耦(沙箱另有 autoAllowBashIfSandboxed)强耦合:审批本质是"批准绕过沙箱"
"下次别问"写入 settings 规则(复合命令拆成 ≤5 条子规则)ApprovedForSession(会话缓存)或 ExecpolicyAmendment(持久规则)
程序化代答PreToolUse / PermissionRequest hookPermissionRequest hook(返回 decision 即短路人审)
deny 的强度任何 scope 的 deny 不可被覆盖,ask 规则/safetyCheck 穿透 bypass 模式Granular 关闭类别 = 自动拒绝;Forbidden 规则直接拦
全开逃生门--dangerously-skip-permissions(root 下拒启)--dangerously-bypass-approvals-and-sandbox

最小复现

# CC:验证 deny 优先于 allow(同一 scope 内)
claude -p 'run: git push origin main' \
  --allowedTools 'Bash(git *)' --disallowedTools 'Bash(git push *)'
# 预期:git push 被拒绝(deny 先于 allow 评估)[一手文档 permissions.md:29,360]

# Codex:验证 never + workspace-write 下完全无审批
codex exec --sandbox workspace-write -c approval_policy=never 'touch /tmp/x && echo ok'
# 预期:无审批提示;写 cwd 外失败时直接把错误回给模型,不升级问人
# [一手源码 sandboxing.rs:206 Never=>needs_approval=false]

(本章未在本机实测运行,以上命令的预期行为均由引用的源码/文档行支撑。)

Harness 接入建议(Yoda 实践)

  • 把"自动批准"映射到官方逃生门,不要自己点按钮。Yoda 的 runtime registry 为每个 CLI 配置 autoApproveFlag:Codex 用 --dangerously-bypass-approvals-and-sandbox、CC 用 --dangerously-skip-permissionsyoda/src/shared/runtime-registry.ts:200,224),在 agent-command.ts:136-137 注入。这是正确的下限做法,但粒度太粗——下一步应改用 PermissionRequest hook / SDK canUseTool 做逐请求代答,保留 deny 规则的防线。
  • 可视化生效权限。CC 的 /permissions 列出每条规则及来源文件;harness 应聚合同样的信息(CC:五层 settings 的 allow/ask/deny;Codex:approval_policy + sandbox_mode + execpolicy 规则)做一个"当前 agent 能做什么"面板,避免用户在黑盒下授权。
  • 审批 UI 要区分两种语义:CC 的提示是"这条命令没规则覆盖",Codex 的提示是"这条命令想绕过沙箱"(ExecApprovalRequestEvent.reason)。Yoda 的审批卡片应把 Codex 的 reason 字段透出,并把「Approve for session / 固化为规则」两个按钮分开渲染(对应 ReviewDecision 的不同变体)。
  • 不要在 harness 层伪造 allow:CC 的 deny/ask 规则穿透 hook 的 allow(permissions.md:269),设计上就是防 harness/hook 越权,顺着这个模型走。

失效条件

  • Codex OnFailure 已标记 DEPRECATED(protocol.rs:768-774),后续版本可能移除,untrusted/granular 语义也可能再调整
  • CC auto 模式为 research preview(permission-modes.md:170),其分类器规则、丢弃宽规则列表随版本频繁变动
  • CC 源码为 2026-03-31 重建快照,落后线上约 2 个月;步骤编号(1a-1g)属内部实现,重构即失效
  • Codex Granular(GranularApprovalConfig) 字段集(5 类开关)在 b89ce9a 之后可能扩充

参考资料

  • CC 文档:claude-code-docs/docs/permissions.mddocs/permission-modes.mddocs/hooks.md
  • CC 源码(重建快照 2026-03-31):utils/permissions/permissions.tspermissionRuleParser.tsshellRuleMatching.ts
  • Codex 源码(b89ce9a, 2026-06-06):protocol/src/protocol.rs(AskForApproval/ReviewDecision)、core/src/tools/sandboxing.rscore/src/hook_runtime.rsexecpolicy/src/decision.rs
  • Codex 官方安全文档(外链):https://developers.openai.com/codex/security

On this page