10.06概念更新于 2026年9月28日

权限规则与审批模式

allow / ask / deny 规则、权限模式与沙箱如何在 Harness 里配合把关。

10.06 · 课时讲解

权限规则与审批模式

规则决定能不能做,模式决定默认怎么办,沙箱决定放行后最多能碰到什么。

1 / 13

一句话理解:一个工具请求要过三道关:权限规则按 deny → ask → allow 的固定顺序匹配,决定“这类动作能不能做”;权限模式决定“规则没覆盖到的动作默认怎么办”;沙箱决定“放行之后最多能碰到什么”。三者叠加,才既不烦人,也不失控。

几乎每个 Agent 产品都会经历这样一个阶段:一开始每个动作都弹窗确认,用户被问烦了,开始闭着眼睛点“允许”,甚至直接打开“跳过所有检查”;出过一次事故之后,团队又把确认收紧,Agent 重新变得寸步难行。问题不在于该松还是该紧,而在于 只有一个开关。

安全模块已经讨论过哪些操作该自动执行、哪些该让人确认(操作分级、Human-in-the-loop),沙箱的隔离原理见 沙箱。这一课讲 Harness 怎么把这些原则 写成配置、按顺序执行。例子以 Claude Code 和 Codex 的官方文档为准,最后再落到一个自研的客服工单 Agent 上。

三道关,各管一件事

关卡 回答的问题 在什么时候起作用 例子
权限规则 这一类具体动作能不能做? 工具执行前,按调用内容匹配 允许 npm run test,询问 git push,禁止读 .env
权限模式 规则没覆盖的动作,默认问不问人? 规则之后,作为整体基调 自动接受文件编辑;无人值守时一律拒绝
沙箱 放行之后,命令最多能读写哪里、连哪里? 命令运行时,由操作系统强制 只能写项目目录,只能连包管理器

前两关在执行前看“字面上要做什么”,第三关在运行时看“实际做了什么”。上一课 工具运行时 流水线里的“权限判定”,就是前两关;沙箱则包在“执行”这一步外面。

规则怎么写:以 Claude Code 为例

Claude Code 的规则写在 settings.json 的 permissions 里,分 allow、ask、deny 三个列表,每条规则的格式是 工具名 或 工具名(限定条件):

  • Bash(npm run *):匹配以 npm run 开头的命令,* 代表任意文本。
  • Read(./.env)、Edit(/src/**):文件路径用 gitignore 语法;// 开头是文件系统绝对路径,~/ 是用户目录,单个 / 开头是相对配置来源的路径。
  • WebFetch(domain:example.com):按域名匹配网页抓取。
  • mcp__github__get_*:匹配名为 github 的 MCP Server 中以 get_ 开头的工具。

下面是一个 Coding Agent 项目的配置示例(放在项目的 .claude/settings.json):

{
  "permissions": {
    "defaultMode": "acceptEdits",
    "allow": [
      "Bash(npm run test *)",
      "Bash(npm run lint)",
      "Bash(git diff *)",
      "Bash(git commit *)"
    ],
    "ask": [
      "Bash(git push *)",
      "Bash(npm install *)"
    ],
    "deny": [
      "Bash(git push --force *)",
      "Bash(curl *)",
      "Read(./.env)",
      "Read(./.env.*)",
      "Read(./secrets/**)"
    ]
  },
  "sandbox": {
    "enabled": true,
    "network": {
      "allowedDomains": ["registry.npmjs.org"]
    }
  }
}

匹配顺序:deny 先查,第一个命中说了算

Claude Code 的规则 按 deny → ask → allow 的顺序检查,第一个命中的决定结果,写得多具体都不改变这个顺序。这带来两个反直觉的后果:

  1. allow 不能在 deny 里“开例外”。有了 Bash(aws *) 这条 deny,再写一条更具体的 allow Bash(aws s3 ls) 也没用。
  2. ask 压过 allow。同一个调用同时命中 ask 和 allow,照样要问。

用上面的配置走几个请求:

请求 命中 结果
git push --force origin main deny(也命中 ask,但 deny 先查) 拒绝
git push origin main ask 询问用户
npm run test -- auth allow 直接执行
编辑 src/auth.ts 无规则,由 acceptEdits 模式放行 直接执行
cat .env deny Read(./.env)(对 cat 这类命令同样生效) 拒绝
node scripts/sync.js 无规则;沙箱开启时,沙箱内命令默认自动放行 在沙箱里执行,访问未放行的域名时再询问

多个配置层级之间也是“拒绝优先”:用户、项目、本地、命令行参数和企业托管配置里,任何一层的 deny 都不能被其他层的 allow 推翻,托管配置的优先级最高。另外,项目配置里的 allow 规则会给 Agent 放权,只有在用户信任这个目录之后才生效,而 deny 和 ask 只会收紧权限,不受这个限制。

把规则、模式、询问和沙箱串起来,就是一个工具请求的完整判定流程。Claude Agent SDK 的文档给出了固定顺序:Hooks → deny 规则 → ask 规则 → 权限模式 → allow 规则 → 询问回调:

一个工具请求的判定流程:依次检查 deny 规则、ask 规则、权限模式、allow 规则,仍未决定时询问用户,放行后在沙箱边界内执行,否则拒绝 执行 Shell 命令在沙箱内运行,越界仍会被拦 工具请求 先过 Hooks ① deny 规则 任一命中即拒 ② ask 规则 命中必须询问 ③ 权限模式 按模式放行 ④ allow 规则 命中即放行 ⑤ 询问用户 dontAsk 则拒绝 模式放行 规则放行 用户同意 ask 命中:必须询问 命中 拒绝 拒绝:原因作为工具结果交还模型 deny 在任何模式下都生效,包括 bypassPermissions
图 1 · 一个工具请求的判定流程:规则匹配 → 权限模式 → 询问 → 在沙箱内执行,或拒绝

读图时注意两点。第一,被自动放行的调用不会走到“询问用户”这一步:如果你把自定义审批逻辑写在询问回调里,模式或 allow 规则放行的调用会悄悄绕过它;必须对每个调用生效的检查,要放在最前面的 Hooks 里(下一课展开)。第二,被规则拒绝并不会中断循环,拒绝原因会作为工具结果交给模型,它通常会换一种做法,或如实报告做不了。

权限模式:没被规则覆盖时的默认态度

规则只能覆盖你想得到的动作,其余的交给模式。Claude Code 目前有六种模式,本质上是在选 “规则没覆盖的动作,由谁来审批”:

Claude Code 的权限模式光谱:从人工审批、自动接受编辑、分类器审批、只认规则到跳过检查,打断越来越少 default · plan 人工审批 未允许的动作都问人 acceptEdits 自动接受编辑 编辑放行,其余照问 auto 分类器审批 另一个模型代为判断 dontAsk 只认规则 没预先允许就拒绝 bypassPermissions 跳过检查 仅限容器或虚拟机 打断多:人来把关 打断少:适合无人值守 所有模式下:deny 规则都生效,显式 ask 规则不会被自动放行
图 2 · 权限模式光谱:模式的本质是选择“谁来审批规则没覆盖的动作”
模式 不问就能做的事 适合
default(界面上叫 Manual) 只有读取 敏感工作、陌生代码
acceptEdits 读取、工作目录内的文件编辑和常见文件命令 边改边审的日常开发
plan 读取和只读命令,不改源码 先探索、出方案,再动手
auto 几乎全部,由后台分类器做安全检查 长任务、减少打断
dontAsk 读取和预先允许的工具,其余一律拒绝 CI、脚本等无人值守场景
bypassPermissions 全部 只在隔离的容器或虚拟机里用

几个细节决定了模式能不能放心用:

  • 有些动作任何模式都不自动放行:命中显式 ask 规则的调用、需要用户交互的工具,以及对关键路径的删除。写入 .git、.claude 这类受保护路径,除 bypassPermissions 外也都不会被直接放行(auto 模式下交给分类器判断)。
  • bypassPermissions 下,allow 列表不再限制任何东西。Agent SDK 文档特别提醒:只列了 Read 的 allowed_tools,配上这个模式照样放行所有工具;想禁用某个工具,只能用 deny。
  • 无人值守用 dontAsk,而不是指望“没人点确认”:它把所有“本该询问”的调用直接转成拒绝,工具边界一目了然。
  • 子 Agent 默认继承父会话的模式,父会话开了 bypassPermissions,子 Agent 也一样。

沙箱:放行之后的最后一道墙

权限规则只能看命令的字面。官方文档说得很坦率:Bash(curl *) 这条 deny 拦得住 curl https://...,却拦不住 /usr/bin/curl 或 sh -c 'curl ...'。所以规则 不是安全边界,真正兜底的是沙箱:它由操作系统限制命令及其子进程能读写的路径和能连接的域名,不管模型写成什么形式都成立。

两者在 Claude Code 里这样配合:开启沙箱后,默认 沙箱内的 Bash 命令自动放行,沙箱边界替代了逐条确认;但 deny 规则、Bash(git push *) 这种针对具体命令的 ask 规则照样生效。命令在沙箱里跑不通时,Agent 可以申请脱离沙箱重试,这会重新走正常的权限流程;把 allowUnsandboxedCommands 设为 false 可以关掉这个逃生口。

Codex 走的是另一条路:先划沙箱,越界才审批。

# ~/.codex/config.toml
sandbox_mode    = "workspace-write"   # 另有 read-only 与 danger-full-access
approval_policy = "on-request"        # 沙箱内自动执行,越界(写工作区外、联网)才请示

对于要在沙箱外运行的具体命令,Codex 再用规则文件细分:prefix_rule 按命令前缀匹配,决定是 allow、prompt 还是 forbidden;多条规则同时命中时取最严格的(forbidden 高于 prompt 高于 allow)。

Claude Code 与 Codex 的三层配置对比:Claude Code 先看权限规则和模式、再由沙箱兜底;Codex 先划沙箱边界,越界时再按审批策略和命令规则决定 Claude Code:先看规则 ① 权限规则 allow · ask · deny,deny 最先检查 ② 权限模式 default、acceptEdits、auto 等六种 ③ Bash 沙箱 沙箱内命令可自动放行,越界再审批 Codex:先看沙箱边界 ① 沙箱模式 read-only · workspace-write · 完全访问 ② 审批策略 on-request:越界才问;never:从不问 ③ 命令规则 沙箱外命令:allow · prompt · forbidden 共同点:规则与策略决定问不问,沙箱决定最多能碰到什么;冲突时最严格者胜
图 3 · 两种配合方式:Claude Code 规则先行、沙箱兜底;Codex 沙箱先行、越界才审批

落到自研 Harness:客服工单 Agent

不做 Coding Agent,也可以照搬这套结构。假设客服工单 Agent 有查询工单、搜索知识库、回复客户、退款、删除工单五个工具,先定规则:

列表 规则 理由
allow 查询工单、搜索知识库 只读,无副作用
ask 回复客户、退款 对外发送、涉及资金,需要坐席确认
deny 删除工单 不可逆,Agent 不应该有这个能力

再把判定写成上一课 run_one 里调用的 policy.decide(伪代码,不对应任何特定 SDK):

# 伪代码:与 Claude Code 相同的判定顺序
def decide(tool, args, session):
    call = tool.describe(args)                          # 例如 "refund(order=A1, amount=300)"
    if matches(RULES["deny"], call):                    # ① deny:命中即拒
        return Decision("deny", reason="命中禁止规则:Agent 不能删除工单")
    if matches(RULES["ask"], call):                     # ② ask:命中必须问人
        return ask_or_deny(call, session)
    if session.mode == "readonly" and tool.writes:      # ③ 模式:只读模式下写操作一律问人
        return ask_or_deny(call, session)
    if matches(RULES["allow"], call):                   # ④ allow:命中即放行
        return Decision("allow")
    return ask_or_deny(call, session)                   # ⑤ 兜底:问人


def ask_or_deny(call, session):
    if session.unattended:                              # 夜间批处理没人可问:直接拒绝(类似 dontAsk)
        return Decision("deny", reason="无人值守,未预先允许")
    return session.request_approval(call)               # 推到坐席的审批界面,等待确认

审批界面怎么设计才不让人闭眼点“同意”,见 审批交互;工具凭据和作用域本身的收敛,见 最小权限与凭据管理。

常见误区

  • “再写一条更具体的 allow,就能给 deny 开个口子。” 规则按 deny → ask → allow 检查,具体程度不影响顺序。真需要例外,应该缩小 deny 的范围。
  • “写了 Bash(rm *) 的 deny,就不会删文件了。” 规则只匹配命令字面,换个写法就能绕过。要硬边界,用沙箱限制可写路径。
  • “开了沙箱,就可以放心用跳过检查的模式。” 沙箱只管 Shell 命令的文件和网络边界,管不了文件编辑工具、MCP 工具,也管不了在允许范围内做错事。bypassPermissions 仍然只该出现在一次性的隔离环境里。

产品经理检查清单

  • 能列出这个 Agent 的 deny 清单吗?每条都是“无论如何都不能做”的动作吗?
  • ask 清单里的动作,用户每天会被问多少次?会不会问到麻木?
  • 规则没覆盖的动作,默认模式是什么?交互场景和无人值守场景是否用了不同的模式?
  • 哪些限制只写在规则里、没有沙箱兜底?被绕过的后果是什么?
  • 规则分几层(企业、项目、个人)?谁能改哪一层?改动有没有记录?
  • 被拒绝时,模型收到的原因能帮它换一条合规路径吗?

练习

为一个你熟悉的代码仓库写一份权限配置(约 45 分钟):

  1. 写出 allow、ask、deny 各至少三条规则,选一个默认模式,决定是否开启沙箱以及允许哪些域名。
  2. 列出 8 个 Agent 可能发出的请求(包括 git push -f、/bin/rm -rf build、读取 .env.local、安装新依赖等),先预测每个请求的结果和命中的规则,再说明哪几个是“规则挡不住、要靠沙箱”的。
  3. 如果手边有 Claude Code,把配置放进项目并用 /permissions 查看,逐条验证你的预测。

延伸阅读

下一步

规则和模式是声明式的,写不出“退款金额超过阈值才要主管审批”这种带逻辑的判断。下一课「Hooks 与生命周期」讲如何在工具调用前后插入一段一定会执行的代码,补上这最后一块。

试试:上下文、MCP、沙箱、Prompt 注入、外链