Skip to contents

语言: English | 简体中文

每个已注册的工具调用在执行前都会经过同一个中央授权门。原生工具、btw 工具、 Format 工具和 MCP 工具均以 bypass 模式构建;安装在 chat$on_tool_request 上的权限门是唯一的权限判定机构。如果该回调无法注册, 权限门安装会以失败关闭(fail-closed)的方式终止。

权限门如何判定

模型请求工具
    |
    v
chat$on_tool_request -> 中央权限门(.tool_gate_fn)
    |
    +- Data Shield 入口扫描(启用 Shield 时;覆盖每个工具)
    |     阻止/错误 -> PermissionDenied -> ellmer::tool_reject()
    |
    +- capability == read、没有逐工具 override、且 Shield 未要求询问?
    |     是 -> 权限门直接 ALLOW(快速路径)
    |
    v  .gate_decide()(优先级从高到低)
  1. settings$tools$overrides[tool]      -> allow / deny / ask
  2. settings$tools$capabilities[class]  -> write|exec|net -> allow/deny/ask
  3. 回退到 check_permission(mode, rules):
       plan          -> 在检查规则前拒绝非读取操作
       用户规则      -> 第一个匹配的 glob 生效
       accept_edits  -> 允许编辑工具
       bypass        -> 允许
       bubble        -> ask(由父代理/宿主处理)
       dont_ask      -> 允许只读,否则拒绝
       auto          -> 快速模型分类为 allow/deny/ask
       default       -> 允许只读和识别出的只读 Bash;否则 ask
    |
    v
  决策 -------+-- deny -> PermissionDenied -> ellmer::tool_reject()
              +-- ask  -> ask_fn():CLI 控制台提示或 Shiny 异步审批条
              |            拒绝/错误/无 ask_fn -> deny
              |            批准 -> 继续
              +-- allow -------------------------> 继续
                                                    |
                                                    v
                         PreToolUse 包装层:拒绝或重写参数;
                         重写后的参数会再次经过权限门/Shield 检查
                                                    |
                                                    v
                         执行工具 -> on_tool_result -> PostToolUse hook

读取快速路径是有意设计:只要工具能力为 read、没有显式逐工具 override,且 Shield 未要求询问,它会在 Shield 扫描后、能力策略/规则/模式回退之前被权限门 允许。若某个读取工具必须询问或拒绝,请使用 settings$tools$overrides。为了兼容, 未知的宿主工具会默认为 read 能力,因此宿主应通过 register_tool_meta() 或安装 权限门时的 tool_meta 声明其真实能力。

settings$tools$sets"A" = codeagent 核心,"B" = btw)控制注册哪些工具集; 这是注册策略,不是逐调用决策。

模式

下表描述回退行为。逐工具 override 或非读取能力策略可以覆盖这些行为。

模式 回退行为
default 允许只读工具和识别出的只读 Bash 命令;其他调用询问
plan 在用户规则前拒绝非读取调用;允许读取工具
accept_edits 允许文件编辑工具;其他非读取调用仍会询问
bypass 允许所有调用(请谨慎使用)
dont_ask 允许只读调用并拒绝非读取调用,适合无人值守运行
auto 由配置的快速模型把调用分类为 allow、deny 或 ask
bubble 返回 ask,让父代理或宿主审批回调决定
client <- codeagent_client(chat, permission_mode = "default")

细粒度规则

PermissionRule 先用 glob 匹配工具名,再选择性匹配相关参数:Bash 使用 command,Read/Write/Edit/MultiEdit 使用 file_path,Glob/Grep 使用 pattern。匹配区分大小写,第一个匹配项生效。直接传给 codeagent_client(rules=) 的规则排在设置文件规则之前。

设置中的数组按 allowdenyask 顺序转换。请避免模式重叠:deny 项不会 自动优先于更早的 allow 项。规则只会在逐工具策略和能力策略之后到达,而且读取 快速路径不会进入这里。

{
  "permissions": {
    "allow": ["Bash(git status)", "Read(*)"],
    "deny":  ["Bash(rm -rf *)"],
    "ask":   ["Write(*)"],
    "defaultMode": "default"
  }
}

公开签名与默认值

PermissionRule(
  tool_name,
  behavior = c("allow", "deny", "ask"),
  source = "session",
  rule_content = NULL
)

check_permission(
  tool_name,
  mode = "default",
  rules = list(),
  tool_input = NULL
)

install_permission_gate(
  chat,
  permission_mode = "default",
  rules = list(),
  tools = list(),
  ask_fn = NULL,
  tool_meta = list()
)

codeagent_client(
  chat = NULL,
  permission_mode = "default",
  rules = list(),
  cwd = getwd(),
  max_turns = 100L,
  btw_groups = NULL,
  worktree_isolation = FALSE,
  verify_fn = NULL,
  mcp_config = NULL,
  register_tools = TRUE,
  data_shield = NULL,
  max_budget_usd = NULL
)

install_permission_gate() 用于把中央权限门附加到现有的 ellmer::Chattoolssettings$tools 一样采用 sets / capabilities / overrides 结构; tool_meta 是带名称的“工具到能力”列表。正常使用 codeagent 时, codeagent_client() 会加载设置并安装权限门。

交互式审批

default 模式下,当调用解析为 ask 时,Shiny 应用会在输入框上方显示 Allow/Deny 审批条;promise 解析后才恢复执行。CLI 使用同步控制台提示。审批 失败、拒绝或缺少 ask_fn 都会导致拒绝。AskUserQuestion 使用独立的异步问题条 暂停并等待澄清答案。

Hook 与参数重写

PreToolUse 不负责权限门的初始决策。它在权限门授权后(因此也在可能的人类审批 之后)、工具执行前,于工具包装层中运行一次。它可以拒绝调用或返回 updatedInput;重写后的参数会再次按照当前权限策略和 Data Shield 检查,此时若 结果为 ask 而没有第二条审批路径,则会拒绝。权限门拒绝时触发 PermissionDenied,执行后由 on_tool_result 触发 PostToolUse

因此,PreToolUse 的否决仍能阻止执行,但不能阻止审批提示先显示。

危险审批与纵深防御

如果操作员批准 Bash: rm -rf ./data,仅凭批准并不能证明操作安全。各否决层的 时机不同:

时机与限制
settings$tools$overrides deny 在提示前拒绝;适合无条件的逐工具策略,包括读取工具
deny PermissionRule 绝对拒绝,先于 allow/ask 规则、逐工具 override、能力 allow、模式快捷路径和读取快速路径
返回 block 的 Data Shield 入口/工具策略 在读取快速路径和提示之前运行
返回 deny 的 PreToolUse 在权限门/人类授权之后、工具执行之前运行

这些控制属于基于语法或策略的纵深防御。例如,Bash(rm -rf *) 无法捕获通过 RunRunlink(..., recursive = TRUE)、其他破坏性命令、运行时字符串拼接, 或“先写脚本再执行”的两个调用所实现的等价行为。

针对操作意图的语义审查器可以提高门槛,但仍依赖模型。采用只读或限定可写挂载 的完整操作系统级沙箱,可以不依赖命令拼写来执行边界;当前可移植的 shield_sandbox() policy 后端并不是这种内核强制适配器。整理跨语言破坏性操作 策略仍是开放设计问题,而不是已实现的安全保证。