Harness 工程 · 权限机制
学习舱 / Harness 工程 / 权限机制

权限机制:权限模式、规则匹配与多路赛跑

整理中 深度 · 应用 更新 2026-09-22

前置 Harness 概览、工具执行、Hooks

权限机制:权限模式、规则匹配与多路赛跑

一句话定位:权限机制是 Harness 部件 ④执行环境 的审批层——工具执行的流水线上,分类器粗筛之后、工具落地之前,裁决「这一步要不要先问你」。前置:什么是 Harness、工具执行机制(裁决卡在站 3 与站 4 之间)、Hook(裁决的参与者之一,产品行为见彼篇)。分工:产品篇讲六档权限模式「怎么用、怎么切」,本篇讲裁决机器内部「怎么判」——同一个六档,机制视角。

先从一次「谁说了算」的困惑说起

你把 Agent 切到自动批准编辑的模式,它改文件确实不问了,但每次跑命令前还是会停下来等你点头——模式说好的「自动」呢?

更困惑的是装了一个安全 Hook 之后:它对某条命令明明说了「允许」,系统却还是拦下了;另一次它对某操作明明没意见,系统倒还停下来问你。同一个工具调用,Hook、规则、模式、分类器(auto 档背后替你审的那个模型,下文细说)都插得上手——到底谁说了算?

裁决顺序是什么?为什么 Hook 说「允许」还不算数?

答案是:权限是一道多路赛跑的裁决——「赛跑」指各层都有话语权、按固定优先级分胜负,不是同时开跑。Hook 先表态(可拦,但 allow 不终审),规则按 deny → ask → allow 定序(deny 是任何模式都翻不掉的一票否决,ask 是点名必问),模式只定「没人管时问不问」的基线。**allow 从来不是通行证,deny 才是真正的法律。**至于开头的两个困惑——「自动档为什么还问命令」,因为那份「自动」名单里只有编辑类;「Hook 没意见为什么还问」,因为没人放行它就落到基线,基线说问就问。

一次调用的裁决之路:四层漏斗 预检 · 硬底线(横切各层) 用户点名必问的 · 关键路径删除 需人交互的工具 第 1 关 · Hook 可拦(理由回填);说「允许」也要往下走 第 2 关 · 规则匹配 deny 一票否决 → ask 命中必问 第 3 关 · 模式基线 表态问不问(auto 由分类器代审);allow 速通随后 批准 · 直接执行 打断 · 弹窗问人 拒绝 · 理由回填模型 硬底线对任何模式生效(图中前置是为教学分层,官方时序上 Hook 最先跑)

图:权限裁决四层漏斗——预检与三道关卡层层收窄,「第 1 关」到「第 3 关」是本篇编号

渐进拆解

把一次裁决想象成出入境检查,四层各司其职:

  • 预检硬底线 = 没护照寸步难行:某些事(涉及交互的确认、关键路径的删除)无论什么口岸政策都必须本人到场
  • 第 1 关 Hook = 海关抽查员:有权把你拦下;但他挥手放行不等于通关——后面该查的照查
  • 第 2 关规则匹配 = 签证规则:黑名单(deny)上的人一票否决,谁来求情都没用;标了「必须面签」(ask)的当场排窗口谈;白名单(allow)上的人速通
  • 第 3 关模式基线 = 口岸政策:三样都不沾的人,按政策办——政策宽松的直接过(auto 档请分类器代看一眼),政策严格的逐个面谈(default 档全问)

看一次「强推代码」的裁决走查——注意 Hook 的「允许」是怎么被规则层否掉的:

一次 git push --force 的裁决走查 调用进来 git push --force 预检:过 非关键路径删除 第 1 关 Hook 安全 Hook:allow 第 2 关:deny 命中 规则 deny: Bash(git push *) 一票否决 Hook 的 allow 保不了它 拒绝不是终点:拒绝理由会作为工具结果回填,模型可以换条路(比如先建分支再推) 若 deny 未命中:ask 规则命中就问;模式基线表态后仍无着落,才由 allow 速通、canUseTool 兜底 Hook 的 allow 在这里只表达「第 1 关没意见」——裁决继续往下走,这才是「allow 不是通行证」

图:裁决走查——第 2 关的 deny 否掉了第 1 关的「允许」,这就是「allow 不是通行证」;未命中时的旁路见图内下两行

大白话:权限系统的设计哲学一句话——谁都可以拦,放行必须无人反对。「放行」永远只表示「这一关没意见」,不表示「这事定了」;Hook、规则、分类器、预检都能拦。想给自己留一道绝对兜底,就写 deny 规则——它比任何模式、任何 Hook 都硬。

模式族谱(基线):六档

六档模式定义「没人管时的默认动作」(行为速览见产品篇的六档表,这里只讲机制含义):

档位没人管时的动作注意点
default(界面显示 Manual)只放行读取,其余先问—
acceptEdits额外自动批准文件编辑与常见文件系统命令名单只此一类:跑命令、碰网络仍要问,且限工作目录之内
plan只读加提议编辑在计划批准前全锁
auto全部放行,由独立分类器模型代审—
dontAsk只跑预批准的东西,其余自动拒绝而非弹窗给 CI 与脚本用
bypassPermissions全放行且关掉安全检查官方要求仅限隔离容器或虚拟机

两句总纲:模式决定「问不问」,沙箱决定「跑起来后碰得到什么」,两者独立、可叠加;模式只是基线,规则叠加在其上。还有一条容易忽略:ask 规则、关键路径删除、需要人交互的工具,任何模式都不自动批准——包括 bypass。

规则匹配(三种规则、两级粒度)

三种规则一句话说清:

  • deny:拉黑名单(一票否决)。
  • ask:点名必问的名单,allow 也保不住。
  • allow:白名单,速通放行。

粒度分两级:整工具级管「这个工具整体放行还是拉黑」(比如允许读、禁止写);内容级管到参数——Bash(git push *) 这种命令模式匹配,允许 git push 但限定范围。规则写在企业、用户、项目多层配置里,而 deny 的效力跨层最高:官方原话,任何一层 deny,其他层都无法放行。

多路赛跑(裁决顺序)

官方 SDK 口径的评估次序是六步:

  1. Hook 先跑。
  2. deny 规则一票否决。
  3. ask 规则命中就问——哪怕更精确的 allow 规则也命中,照样问。
  4. 模式基线表态:auto 交分类器、dontAsk 自动拒、default 弹窗。
  5. allow 规则速通放行。
  6. 最后落到 canUseTool 兜底——那是 Agent SDK 里程序化的裁决回调,无头会话中所有「需要确认」的出口都汇聚到它,ask 命中的调用与 plan 模式的编辑类调用都归它,连 bypass 模式也不例外。

规则之间的相对次序记住一句:deny > ask > allow。

为什么 Hook 说「允许」还要走规则匹配? 官方的理由是保住 deny 优先序(包括企业管理配置里的 deny)——Hook 可能是用户自写的,也可能是第三方装的,判断标准未必与你一致,它的 allow 不该拥有终审效力;它能做的是「拦下并说明理由」。

auto 分类器与兜底

auto 档的「全部放行」不是不审查,而是把审查外包给一个独立的分类器模型(默认跑在固定模型上,不随会话主模型切换)。它偏重拦「越界」而非「危险本身」——动作超出当前任务的请求范围、打向组织没声明过信任的基础设施(这是对官方配置项与社区观察的概括,官方未逐字定义拦截标准)。

误判的兜底是系统性回退(官方口径):连续拦截 3 次或累计拦截 20 次,auto 暂停、恢复逐条提问,你批准某个动作后 auto 再恢复——宁可退化成手动,也不让误判滚雪球。plan 档里分类器也兼职:规划期间的命令由它代审。

与粗筛分类器的分工

工具执行篇那位「裁决之前先粗筛」的命令分类器,与这里的 auto 代审分类器是两道工序——前者在站 3 与站 4 之间给 Bash 命令定风险级(只读/需确认/危险),是所有模式共用的前置过滤;后者只在 auto 档的第 3 关内工作,替人做「要不要放行」的完整裁决。一个管「这条命令像不像危险的」,一个管「这个动作越没越界」。

打断的艺术

裁决机器还有一半在「人」这边——何时打断、打断什么。设计取向(工程通则):不可逆的动作(删除、强推、对外发布)、越界的动作(碰工作目录之外)、陌生的目标(没见过的基础设施)值得打断;高频低危的动作打断只会制造「批准疲劳」——人被问烦了就会无脑点允许,权限系统形同虚设。所以好系统的打断是稀缺的:把问题留给真正的问题。

动手:写一个裁决器

伪代码按官方 SDK 口径的六步实现(注释里的关卡号呼应上图;硬底线是横切各层的约束,图中前置为教学分层):

def decide(call, mode, rules, hooks, can_use_tool):
    if hard_guarantee_block(call):           # 横切硬底线:关键路径 rm 等
        return ASK                           # 任何模式(含 bypass)不豁免
    for h in hooks.pre_tool_use:             # 第 1 关:Hook 可拦可放
        v = h(call)
        if v is DENY:
            return DENY(v.reason)            # 拦下:理由稍后回填模型
    if rules.deny.match(call):               # 第 2 关①:deny 一票否决
        return DENY(rules.deny.reason)       # 任何层的 deny 都翻不了案
    if rules.ask.match(call):                # 第 2 关②:ask 命中必问
        return ASK                           # allow 也保不住它
    action = mode.baseline(call)             # 第 3 关:模式基线表态
    if action is DENY:                       # dontAsk:会触发提示的一律拒
        return DENY("mode: dontAsk")
    if action is ALLOW:                      # auto 批准 / bypass / acceptEdits
        return ALLOW
    if rules.allow.match(call):              # 模式表态后 · allow 白名单速通(第 3 关之后)
        return ALLOW
    # default 基线不表态:弹窗出口由 canUseTool 兜底承接
    return can_use_tool(call)                # SDK 兜底:确认出口最后汇聚于此

手写 Harness 时的工程化建议:把高危拦截做成中间件链而不是散落在主循环里的 if——每条中间件只管一类风险(命令黑名单、路径越界、网络目标),裁决器按链依次过;新风险加一个链节,主循环一行不动。审批的出口统一走「打断问人」,问完的答案与拒绝理由都从回填口进上下文——权限机制与工具执行的回填铁律在这里会师。

同一概念的叫法对照:

本篇用词文献中的其他叫法深挖去处
裁决四层漏斗审核流程 · 多路赛跑(固定优先级,非同时跑)本篇
模式基线权限模式 · PERMISSION_MODESClaude Code 总览
内容级规则命令模式匹配 · Bash(git push *) 规则本篇
分类器代审Auto 模式审查(与命令粗筛分类器是两道工序)工具执行
投机预执行Speculation(对提示建议的投机预执行)· 预执行本篇
提示建议Prompt Suggestion本篇

四档模式在放权主轴上,plan 与 dontAsk 是挂在轴外的两个特殊档(与产品篇同一画法),「没人匹配时问不问」一眼看清:

模式光谱:主轴四档放权递增,两个特殊档挂轴外 Manual 默认全问 只放读取 敏感工作 acceptEdits 编辑与常见 文件命令放行 边改边审 auto 分类器代审 拦越界动作 长任务减负 bypass 全放行 硬底线不豁免 仅限隔离容器 plan(轴外) 只读提议 · 编辑全锁 dontAsk(轴外) 不问直接拒 · CI 与脚本 放权递增 → 模式决定「问不问」,沙箱决定「跑起来后碰得到什么」;轴外档满足特定场景,不参与放权排序

图:模式光谱——主轴四档按「没人匹配时的放权程度」递增;plan 与 dontAsk 是轴外特殊档,画法与产品篇一致

边界与取舍:打断是稀缺资源

最严的权限系统往往最不安全

什么都问 = 批准疲劳 = 人无脑点允许——形式上的审批,实质上的全放行。auto 档存在的理由(官方口径)就是减少这种疲劳:把高频低危的裁决交给分类器,把人的注意力省给真正的高危时刻。配权限时的自查标准不是「我拦了多少」,而是「被打断的人每次是否真的在看」。

deny 是底线不是日常

规则系统的正确用法:高频动作用 allow 规则省掉确认(比如永远允许读项目文件),危险动作用 deny 规则焊死底线(比如永远禁止改持续集成配置),中间地带留给模式基线与人的判断。把大量「允许」写进 deny 的反面、试图用规则穷举一切,规则表本身会变成没人读得懂的负债。

Hook 是扩充点不是替代品

Hook 能实现自定义拦截(安全扫描、私有合规检查),但它的 allow 不终审——想让某类操作「无条件下」,正确位置是 allow 规则;想「有条件下」,写 Hook 判断后放行并接受规则层复检。把 Hook 当成绕过权限系统的后门,既违背设计(不生效),也违背安全常识(Hook 的退出码语义里有 fail-open 的坑,见 Hook 的产品行为篇的教训)。

坏味道预警

如果你的 Agent 「每次都在同一类操作上打断你」,缺的不是更严的规则,而是打断的分级——该沉淀成 allow 规则的高频动作没沉淀;反过来「从来不打断」,先查是不是有人开了 bypass 还以为自己在 auto 档。模式与规则谁在生效,用一次高危操作实测就知道。

自测

合上文章先自己答,再展开对照:

hook 返回 allow 之后为什么还要走规则匹配?

因为 Hook 与规则的话语权不同:Hook 可能是用户自写的,也可能是第三方装的,判断标准未必与你一致。官方的理由是「保住 deny 优先序」——包括企业管理配置里设的 deny:Hook 的合法权力是「拦下并说明理由」或「表示没意见」,allow 只意味着「这一关没意见」,裁决继续往下走:deny 规则命中照样一票否决(官方原话,任何一层 deny 其他层都无法放行),ask 规则命中照样问。让 Hook 的 allow 拥有终审效力,等于把安全底线外包给最不可靠的一环。

auto 模式分类器误判时的兜底设计是什么?

三层兜底。其一,分类器的审查对象偏重「越界」而非「危险本身」——拦动作超出当前任务请求范围、打向组织未声明信任的基础设施的情况,本分内的正常操作不误伤(此为对官方配置与社区观察的概括);其二,ask 规则与硬底线(关键路径删除、需人交互的工具)在 auto 档依然生效,分类器无权翻案;其三,系统性回退(官方口径):连续拦截 3 次或累计拦截 20 次,auto 暂停、恢复逐条提问,你批准某个动作后 auto 再恢复——分类器状态可疑时,宁可退化成手动确认,也不让误判滚雪球。三层合起来:分类器只在其统计优势可靠的区间工作,出了区间就交还给人。

参考与延伸

  • Claude Code 官方文档:权限模式(六档行为与沙箱关系) — code.claude.com/docs/en/permission-modes
  • Claude Code 官方文档:权限配置与规则(hook 决策不越过规则匹配) — code.claude.com/docs/en/permissions
  • Claude Code 官方文档:Agent SDK 权限(评估顺序与 canUseTool) — code.claude.com/docs/en/agent-sdk/permissions
  • Anthropic《Building Effective Agents》(2024-12):人机协同与审批节点的设计原则 — anthropic.com/engineering/building-effective-agents
  • Pi 的 YOLO 路线一手材料(2026-10-24 汇总):wordman 播客「YOLO 是逼用户思考权限的 forcing function,CC/Codex 内置多是安全剧场」+ HN 定调文反方(chr15m「非只读工具调用应强制审批」)+ 容器化三方案(Gondolin/Docker/OpenShell)——三方对照完整,见「读 Pi 学 harness 26 零权限」与 W43 扫荡报告

本系列的后续学习路线(推荐顺序):记忆机制 · Skill 机制 → 状态机与 Checkpoint → 多 Agent 编排。

本节点大纲与自测题(6 个子点 · 2 问)

大纲

  • 权限模式族谱:default / acceptEdits / bypassPermissions / auto
  • 整工具级 vs 内容级规则匹配流程
  • 多路赛跑:canUseTool / PreToolUse hook / 规则的裁决顺序
  • Prompt Suggestion 与 Speculation 机制
  • 高危命令拦截 Middleware 与人工审批
  • 人工确认机制设计:何时打断、打断什么

自测——答不上来就是缺口

  • hook 返回 allow 之后为什么还要走规则匹配?
  • auto 模式分类器误判时的兜底设计是什么?