Claude Code SDK #12:权限与安全全解——五步评估链 × 五种模式 × Allow/Deny 规则,精准控制 Agent 的工具访问边界

Claude Code SDK #12:权限与安全全解——五步评估链 × 五种模式 × Allow/Deny 规则,精准控制 Agent 的工具访问边界

权限系统不是一个开关,而是一条五步单向评估链:Hooks → Deny 规则 → Permission Mode → Allow 规则 → canUseTool 回调,每步都可能终止请求。本篇完整拆解 allowed_tools 与 disallowed_tools 的行为差异(后者才是真正的全局拦截)、bypassPermissions 不受 allowed_tools 约束的关键陷阱、五种 mode 的选型逻辑、set_permission_mode 动态切换、canUseTool 回调的 Python 实现要点,以及 acceptEdits 模式的精确边界,附五条可落地的实践建议。

Claude Code SDK 每日技术拆解
June 5, 2026 · 9:12 AM
3 subscriptions · 3 items

Research Brief

给 Agent 运行时加一把什么样的锁,直接决定它会做什么、不会做什么,以及出了问题你能不能事后溯源。
Claude Code SDK 的权限系统不是一个开关,而是一条五步评估链——每一步都可能让工具调用被放行或拦截。理解这条链的执行顺序,是构建安全、可审计 Agent 的前提。

五步评估链:顺序就是策略

当 Claude 请求使用某个工具时,SDK 按以下顺序依次评估:1
  1. Hooks:最先执行。自定义 Hook 可以直接拒绝请求或放行,但 Hook 返回 allow 并不跳过后续步骤——Deny 和 Ask 规则仍会继续评估。
  2. Deny 规则:检查 disallowed_toolssettings.json 中的拒绝规则。命中后即拦截,在任何 permission_mode 下都有效,包括 bypassPermissions
  3. Permission Mode:应用当前激活的权限模式。bypassPermissions 在这一步放行所有到达此处的请求;acceptEdits 放行文件操作;其他模式继续向下传递。
  4. Allow 规则:检查 allowed_toolssettings.json 的允许规则。命中后放行。
  5. canUseTool 回调:若前面所有步骤都未给出决定,调用你注册的回调请求用户或逻辑层决策。dontAsk 模式下,此步直接跳过并拒绝。
理解这条链的关键在于:第 2 步(Deny)永远早于第 3 步(Mode)执行。这意味着 disallowed_tools 规则是真正意义上的全局拦截屏障,不受 mode 影响。
权限评估流程图:五步串联评估链,绿色通道表示放行,红色表示拦截
SDK 官方权限评估流程图:五步串联,左到右依次检查,每步都可能提前终止 1

Allow 和 Deny 规则的行为差异

allowed_toolsdisallowed_tools 看起来对称,实际上行为逻辑完全不同。
allowed_tools 与 disallowed_tools 对比:前者是白名单预授权,未列出工具仍然存在并继续向下流转;后者有两种形式,裸名移除整个工具上下文,模式规则则在运行时按调用内容拦截
allowed_toolsdisallowed_tools 的核心差异:前者只做预授权,后者才是真正的拦截屏障 1
配置写法实际效果
allowed_tools=["Read", "Grep"]Read / Grep 自动放行。未列出的工具仍然存在,继续向下走到 mode 和 canUseTool
disallowed_tools=["Bash"]Bash 工具从 Claude 的上下文中整体移除,Claude 看不到这个工具,也无法调用
disallowed_tools=["Bash(rm *)"]Bash 工具保留,但匹配 rm * 的调用被拦截,在所有模式下均有效,包括 bypassPermissions
最容易踩的坑是 allowed_tools 不约束 bypassPermissions。设置了 allowed_tools=["Read"] 同时又用 bypassPermissions 模式,结果是所有工具包括 BashWriteEdit 依然会被放行——因为这些工具虽然没在 allow list 里,但会流转到第 3 步被 bypassPermissions 直接批准。1
需要在 bypassPermissions 模式下屏蔽某些工具,唯一有效的做法是 disallowed_tools,而不是 allowed_tools

五种 Permission Mode 及选型

SDK 提供六种模式(TypeScript 多一种 auto),覆盖从完全交互到完全自动的全部场景:1
Mode工具行为适用场景
default未命中规则的工具触发 canUseTool 回调通用交互场景、需要用户审批的敏感操作
dontAsk未在 allowed_tools 或规则中的工具直接拒绝,不调用 canUseTool无头 Agent,需要固定工具集且不允许任何动态决策
acceptEdits工作目录内的文件操作(Edit/Write/mkdir/rm/mv 等)自动放行,其他工具照常开发迭代、原型验证、信任 Claude 编辑代码的场景
bypassPermissions到达第 3 步的所有工具全部放行,无任何提示受控隔离环境,极度信任的自动化流水线
plan只允许只读工具,Claude 读取代码库并规划,不执行任何修改代码审查、需要在执行前审批变更的场景
auto(仅 TS)模型分类器逐次判断每个工具调用是否放行详见官方 Auto mode 文档
子 Agent 继承警告:当父 Agent 使用 bypassPermissionsacceptEditsauto 时,所有子 Agent 强制继承该模式,无法在子 Agent 级别覆盖。子 Agent 通常有更宽泛的系统 prompt 和更少约束,继承 bypassPermissions 意味着它们拿到了完整的系统访问权限,没有任何审批。

组合策略:锁定 Agent 工具集

构建无头 Agent(headless agent)时,推荐的最小权限模式是 allowedTools + dontAsk1
from claude_agent_sdk import query, ClaudeAgentOptions

async for message in query(
    prompt="Analyze the codebase structure",
    options=ClaudeAgentOptions(
        allowed_tools=["Read", "Glob", "Grep"],  # 白名单工具自动放行
        permission_mode="dontAsk",               # 白名单外一律拒绝,不触发回调
    ),
):
    ...
列表内的工具自动放行;列表外的工具被 dontAsk 直接拒绝,而不是弹出 canUseTool 等待决策。这种组合实现了显式工具表面——Claude 只能用你指定的工具,其他都是硬拒绝。
动态调整模式的场景也很常见:先用 default 观察 Claude 的操作意图,确认没问题后再切换到 acceptEdits
import asyncio
from claude_agent_sdk import ClaudeSDKClient, ClaudeAgentOptions

async def main():
    async with ClaudeSDKClient(
        options=ClaudeAgentOptions(
            permission_mode="default",  # 开始时要求逐次审批
        )
    ) as client:
        await client.query("Help me refactor this module")

# 确认方向后,切换到自动接受文件编辑
        await client.set_permission_mode("acceptEdits")

async for message in client.receive_response():
            if hasattr(message, "result"):
                print(message.result)

asyncio.run(main())
set_permission_mode() 的新模式立即生效,后续所有工具请求都按新模式评估。

canUseTool 回调:交互审批的实现点

canUseTool(Python 里叫 can_use_tool)是权限链的最后一道闸——当工具调用走到第 5 步还没被决策时才触发。它的签名很简单:接收工具名、参数和上下文,返回放行或拒绝。2
Loading content card…
from claude_agent_sdk import ClaudeAgentOptions, query
from claude_agent_sdk.types import PermissionResultAllow, PermissionResultDeny, ToolPermissionContext

async def can_use_tool(
    tool_name: str, input_data: dict, context: ToolPermissionContext
) -> PermissionResultAllow | PermissionResultDeny:
    if tool_name == "Bash":
        cmd = input_data.get("command", "")
        print(f"Claude 要执行:{cmd}")
        answer = input("允许?(y/n): ")
        if answer.lower() == "y":
            return PermissionResultAllow(updated_input=input_data)
        return PermissionResultDeny(message="用户拒绝了该操作")
    return PermissionResultAllow(updated_input=input_data)
Python 侧的一个硬性要求can_use_tool 必须在流式模式(streaming mode)下使用,且需要同时注册一个 PreToolUse Hook 返回 {"continue_": True}。没有这个 Hook,流会在回调被触发前关闭,回调永远不会执行。2
拒绝时的 message 字段会直接传给 Claude——Claude 读到后会调整策略,可以利用这个机制主动引导行为,比如写「用户不想删文件,请改成压缩归档」。
canUseTool 也负责处理 Claude 的澄清问题(AskUserQuestion 工具),判断方式是检查 tool_name == "AskUserQuestion",然后把问题结构体展示给用户并返回选择结果。

Accepte Edits 模式的边界

acceptEdits 是开发场景里最常用的模式,但它的自动放行有明确边界:1
自动放行的操作
  • 文件编辑(Edit、Write 工具)
  • 文件系统命令:mkdirtouchrmrmdirmvcpsed
仍然需要正常权限的操作
  • 不属于文件操作的 Bash 命令(如 curlnpm installgit push
  • 工作目录和 additionalDirectories 之外的路径
  • 被写保护路径的写操作
所以 acceptEdits 不是「信任 Claude 做任何事」,是「信任 Claude 在这个目录里改代码」。对 Bash 命令里混入的网络请求、包安装等仍会触发 canUseTool

五条实践建议

1. 无头 Agent 用 allowed_tools + dontAsk 锁死工具集,不要依赖 canUseTool 缺席来等效 dontAsk——显式配置比隐式行为更可靠。
2. 需要在 bypassPermissions 下屏蔽工具,只能用 disallowed_tools,不要用 allowed_tools。前者是从 Claude 的上下文里拔掉工具定义;后者只是预授权,不能限制 bypassPermissions 的放行范围。
3. 用 Bash(rm *) 而不是 Bash 做 Deny 规则,当你需要保留 Bash 能力但禁止特定危险命令时。裸名 Bash 会把整个工具从上下文移除,模式规则 Bash(rm *) 则是在运行时按调用内容拦截。
4. 多 Agent 系统谨慎继承 bypassPermissions。父 Agent 的 mode 会强制下传给所有子 Agent。如果父 Agent 需要 bypassPermissions 而子 Agent 不应该,目前唯一的防护手段是 disallowed_tools + Deny 规则。
5. 用 plan 模式做代码审查前的探索阶段plan 模式允许读取整个代码库、执行只读命令,Claude 可以用 AskUserQuestion 澄清需求,然后给出计划——你审批后再切换模式执行。这是人机协作流程里最安全的起步方式。

下期 #13 预告:自动化脚本(Automation Scripting)——如何用 Claude Code SDK 构建可脚本化的自动化流程,批处理任务调度与错误恢复策略。

Add more perspectives or context around this Post.

  • Sign in to comment.