权限与审批模型对照:预设怎么定、什么时候弹、拒绝之后怎么走
先把问题定死,不然对照会变成参数罗列:模型要执行一条会写工作区之外文件的命令,谁来拦、拦在第几层、拦下来之后这次工具调用变成什么东西回到模型手里。四家的差别几乎全在这三问上。
需要先说清楚的限定:DeepSeek Harness 仓库 README 自述处于开发者预览阶段,并明写会有破坏兼容性的变更,下面提到的事件名、配置字段与默认值随时可能变。Claude Code 是闭源产品,只能引它的官方文档,不推断实现。Codex 这个快照里 docs/execpolicy.md、docs/sandbox.md、docs/config.md 都只有一两行指向外部网址的正文,本文不联网,因此改用仓库内的 Rust 源码与 codex-rs/execpolicy/README.md 说话。
DeepSeek Harness:两个旋钮,预设只是把它们捆在一起
dsh 这一侧最反直觉的一点是:它没有「权限规则表」这种东西。真正决定行为的只有两个互相独立的旋钮。
一个是沙箱模式。packages/sandbox/sandbox-policy/src/session-mode.ts 里 SANDBOX_MODES 是三个值:read-only、workspace-write、danger-full-access。docs/subsystems/sandbox.md 写明它只管文件效果,网络与进程可见性不在这套词汇里;danger-full-access 直接绕开约束,源码注释写明这一档的消费者根本不调用 ctx.sandbox。
另一个是审批策略。packages/interaction/user-approval/src/index.ts 里 APPROVAL_POLICIES 只有两个值:ask 与 never。ask 把问题交给已组合的应答者链,链上没人应答就落到 unavailable;never 是不问任何人,每次询问确定性地返回 rejected。
所谓 preset,就是把这两个旋钮打成一包。packages/interaction/permission-presets/src/index.ts 的 static Config 里默认表只有两项:workspace-write(workspace-write + ask)和 danger-full-access(danger-full-access + never)。这一点值得盯一眼——切到第二个预设,你不只是开了全盘文件访问,同时把审批策略写成了 never。反过来,如果你只想关掉提示但不想放开文件写入,两个旋钮得分开设,那么这个组合在表里匹配不上任何一项。
匹配逻辑在同文件的 derive() 里:先看日志里记录的 preset 是否仍然与当前两个旋钮匹配,再按表的声明顺序找第一个匹配项,都不中就返回 CUSTOM_PRESET,值是字符串 custom。custom 是纯派生态,文档写明客户端可以把它当作当前值呈现,但它永远不是切换目标;表里如果真有一项叫 custom,服务在构造时就抛错。包 README 把「无法把某个自定义组合存成命名预设」列为已知限制。
顺带记一处口径差:packages/interaction/permission-presets/README.md 写的是 permissionPresets/preset 事件和 /permissionPresets 命令,而 src/index.ts 里声明的事件类型是 permission/preset、注册的命令名是 permission、设置命名空间取的是 settingsNamespace('permission'),docs/subsystems/permission-presets.md 与源码一致。两处不一致,以实读的源码为准,就这样。
什么时候真的会弹一次
dsh 里一次审批询问只有两个来源。
一个是 hook。packages/hooks/hooks-claude-code/src/index.ts 在 tools/pre-execute 上把 PreToolUse 的 ask 决策翻译成 { kind: 'ask' },然后 packages/core/tools/src/index.ts 的 serviceAsk 才去调 ctx.approval.request。
另一个是沙箱提权。packages/sandbox/sandbox/src/escalation.ts 定义了 WIDER_MODES:read-only 只能升到 workspace-write 或 danger-full-access,workspace-write 只能升到 danger-full-access,read-only 是地板、没有东西能升到它。模型要提权,必须在同一次重试里同时给出 sandbox_permissions 和 justification 两个参数,缺一个直接抛「invalid escalation」;不满足严格加宽的请求根本不会惊动人类。被拒的调用会带回一行 [sandbox: file access denied under <mode> mode] 标记,组合里通告了提权字段时还会附一行提示,让模型知道可以带着理由重试一次。
审批服务本身有两个硬约束值得记住。第一,request() 要求会话处于打开的 turn 内,否则直接抛错——源码注释自述的理由是审计对必须被日志的提交/重放边界包住,turn 之间的裸事件重载时和崩溃残尾无法区分。第二,never 的判定被放在服务自己的请求路径里、在 waterfall 派发之前,源码注释自述的理由是:用 prepend 注册的监听器会排在任何「监听器形态的闸门」前面,所以只有服务自身才能守住「无论注册顺序如何都确定性拒绝」这个承诺。
拒绝之后,这次调用变成什么
ApprovalOutcome 是四值闭集:allowed-once、rejected、cancelled、unavailable,而且只有第一个是放行。剩下三个在 serviceAsk 里被映射成三条不同的拒绝理由文本,模型能据此分辨「人说了不」「问题被撤回」和「压根没有审批通道」。fail closed 做得挺彻底:没有应答者、应答者抛异常、应答者返回词表之外的值,统统归一成 unavailable,而不是把门打开。提权路径同理:allowed-once 返回被批准的那个模式,另外三种结局各抛一条措辞不同的错误,源码注释写明由工具注册表把这个 throw 转成这次调用的 isError 结果,此时什么都还没执行。
Claude Code:判定顺序是六步,模式只是其中一步
Claude Code 官方文档把顺序写得很死。agent-sdk/permissions 那页列的是六步:hooks → deny 规则 → ask 规则 → 权限模式 → allow 规则 → canUseTool 回调。文档写明 hook 返回 allow 并不能跳过后面的 deny 与 ask;bare-name 的 deny 规则(例如 Bash)会在这套判定开始之前就把工具从模型上下文里摘掉。permissions 那页对 CLI 侧的表述是规则按 deny、ask、allow 顺序求值,先匹配者胜,规则的具体程度不改变这个顺序。
模式一共六个:default(CLI 里标为 Manual)、acceptEdits、plan、auto、dontAsk、bypassPermissions。真正能落地去查的一处是「受保护路径」表:default 与 acceptEdits 下对受保护路径的写入是提示,auto 走分类器,dontAsk 直接拒,bypassPermissions 才放行;文档还明说设置文件里的 permissions.allow 规则不能预批受保护路径的写入。受保护目录清单里 .claude 在列,但 .claude/worktrees 是明写的例外。
拒绝之后怎么走,这页也写了具体数字:auto 模式下分类器连续阻断 3 次或累计阻断 20 次,自动模式暂停、退回提示,文档写明这两个阈值不可配置;而在没有 --permission-prompt-tool 的 -p 非交互运行里没有可回落的提示,动作不执行、Claude 继续干活。
Codex:策略是四支,命令级判定另有一层
Codex 的审批策略在 codex-rs/protocol/src/protocol.rs 的 AskForApproval 里,四支:untrusted(仅自动批准判定为「已知安全且只读」的命令)、OnRequest(默认档,由模型决定何时问,serde 上还认 on-failure 这个别名)、Granular、Never(不问,失败直接回给模型,绝不升级给用户)。Granular 带一个五字段结构体 GranularApprovalConfig:sandbox_approval、rules、skill_approval、request_permissions、mcp_elicitations,注释写明字段为 false 时该类请求被自动拒绝,而不是展示给用户——这是四家里唯一把「哪一类弹窗允许出现」拆成独立开关的。
沙箱策略是另一个枚举 SandboxPolicy,四支:danger-full-access、read-only、external-sandbox、workspace-write;后三支各自带网络访问字段,workspace-write 还带 writable_roots、exclude_tmpdir_env_var、exclude_slash_tmp。
命令级的判定在 codex-rs/execpolicy。它的 README 写明规则用 Starlark 的 prefix_rule(...) 写,decision 三个合法值 allow、prompt、forbidden,省略时默认 allow;多条规则同时命中时,生效判定取所有命中里最严的那一档(forbidden > prompt > allow)。src/decision.rs 里 Prompt 那一支的注释写明:在 approval_policy="never" 下运行时,它会被直接拒绝而不是弹出。README 末尾自述 execpolicy 命令仍处于 preview,API 未来可能有破坏性变更。
Pi:它明说自己没有这一层
Pi 的立场最省事也最需要说清楚。packages/coding-agent/docs/usage.md 的设计原则段写明它有意不包含内建的 MCP、子代理、权限弹窗、plan 模式、待办和后台 bash,这些要靠扩展或外部工具(容器、tmux)自己搭。docs/security.md 写明 Pi 不含内建沙箱,内建工具以 pi 进程的权限读写文件、跑 shell 命令,扩展是同权限运行的 TypeScript 模块。
它唯一的门是项目信任,而且 security.md 自己把话说死了:项目信任只是一个输入加载的守卫,不是沙箱,不限制你开始工作之后模型能让工具做什么。设置项 defaultProjectTrust 三个值 ask(默认)、always、never,决定存在 ~/.pi/agent/trust.json,按规范化目录保存,当前目录或父目录上最近的一条已保存决定优先于全局默认。非交互模式(-p、--mode json、--mode rpc)不弹信任提示,可以用 --approve/-a 或 --no-approve/-na 单次覆盖。文档给的隔离建议是把整个进程放进容器或虚拟机,见仓库里的 containerization.md。
需要提醒的是:这四个项目都会在你本机执行工具、跑 shell、起子进程,有沙箱档位不等于安全,Pi 更是直说没有沙箱。
那么该怎么选
从你的处境倒推:
你要的是「某类命令永远不许跑」。 只有 Claude Code 官方文档写明了 deny 规则表(含 bare-name 摘除工具与 Tool(specifier) 两种形态),Codex 有 execpolicy 的 forbidden 档。dsh 这边我们在源码里没有找到内建的、按命令模式匹配的 allow/deny 规则表——它的 deny 只能来自 hook 返回的决策;Pi 文档明说没有权限弹窗这一层。
你要的是无人值守时的确定性。 三家都有对应档位,但语义不一样,别当成同一个东西:dsh 的 never 是每次询问都确定性 rejected;Codex 的 Never 是不问、失败直接回给模型;Claude Code 的 dontAsk 是预批范围内照跑、其余一律拒。第一个和第三个的差别在于「预批」这个概念在 dsh 里不存在。
你要的是让模型自己申请更宽的权限。 dsh 有明确的 sandbox_permissions + justification 严格加宽路径,Codex 的 GranularApprovalConfig 里有 request_permissions 与 sandbox_approval 两个开关对应这类请求。Claude Code 文档里我们没有找到对应的「模型主动申请提权并走同一套审批」的机制表述,这一点不比。
最容易咬到人的地方,是 dsh 的预设捆绑:默认表里 danger-full-access 这一项同时把审批策略写成 never。你以为只是放宽了文件写入,实际上连提权询问的通道都一并关掉了——never 是在服务内部、派发之前判定的,后挂的任何应答者都拦不住。想要「全盘访问但仍然逐条确认」这种组合,就得绕开预设分别设两个旋钮,代价是这个会话在 current() 里被派生成 custom,而 custom 存不回命名预设。这不是 bug,包 README 自己把它列在已知限制里了。
本文依据 DeepSeek Harness 官方仓库(github.com/deepseek-ai/deepseek-harness)的 README、docs/ 下的
架构与子系统文档、以及 packages/ 下的源码整理,核对日 2026-08-17,对应仓库快照 47f9438(版本 0.1.0-rc.5)。
本文内容为仓库源码与文档口径,我们没有安装、也没有运行过这个项目,
因此不涉及界面外观、操作手感与运行速度的任何描述。
该仓库 README 自述处于开发者预览阶段并明确说明未来会有破坏兼容性的变更,
文中出现的命令、配置与默认值随时可能变动,请以仓库最新内容为准。
文中涉及的 Claude Code 内容依据其官方文档(code.claude.com/docs)整理,该产品闭源,本文不推断其实现;
Codex 依据 github.com/openai/codex 快照 c6058cc、Pi 依据 github.com/earendil-works/pi 快照 027a5847 整理。
本文只对照各方公开写明的机制,不对三者做优劣排名。
安全与合规相关做法请结合自身环境评估,本文不构成安全方案建议。