
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 模式的精确边界,附五条可落地的实践建议。
リサーチノート
给 Agent 运行时加一把什么样的锁,直接决定它会做什么、不会做什么,以及出了问题你能不能事后溯源。
Claude Code SDK 的权限系统不是一个开关,而是一条五步评估链——每一步都可能让工具调用被放行或拦截。理解这条链的执行顺序,是构建安全、可审计 Agent 的前提。
五步评估链:顺序就是策略
当 Claude 请求使用某个工具时,SDK 按以下顺序依次评估:1
- Hooks:最先执行。自定义 Hook 可以直接拒绝请求或放行,但 Hook 返回
allow并不跳过后续步骤——Deny 和 Ask 规则仍会继续评估。 - Deny 规则:检查
disallowed_tools和settings.json中的拒绝规则。命中后即拦截,在任何 permission_mode 下都有效,包括bypassPermissions。 - Permission Mode:应用当前激活的权限模式。
bypassPermissions在这一步放行所有到达此处的请求;acceptEdits放行文件操作;其他模式继续向下传递。 - Allow 规则:检查
allowed_tools和settings.json的允许规则。命中后放行。 - canUseTool 回调:若前面所有步骤都未给出决定,调用你注册的回调请求用户或逻辑层决策。
dontAsk模式下,此步直接跳过并拒绝。
理解这条链的关键在于:第 2 步(Deny)永远早于第 3 步(Mode)执行。这意味着
disallowed_tools 规则是真正意义上的全局拦截屏障,不受 mode 影响。Allow 和 Deny 规则的行为差异
allowed_tools 和 disallowed_tools 看起来对称,实际上行为逻辑完全不同。
allowed_tools 与 disallowed_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 模式,结果是所有工具包括 Bash、Write、Edit 依然会被放行——因为这些工具虽然没在 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 使用
bypassPermissions、acceptEdits 或 auto 时,所有子 Agent 强制继承该模式,无法在子 Agent 级别覆盖。子 Agent 通常有更宽泛的系统 prompt 和更少约束,继承 bypassPermissions 意味着它们拿到了完整的系统访问权限,没有任何审批。组合策略:锁定 Agent 工具集
构建无头 Agent(headless agent)时,推荐的最小权限模式是
allowedTools + dontAsk:1from 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 回调:交互审批的实现点
コンテンツカードを読み込んでいます…
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 工具)
- 文件系统命令:
mkdir、touch、rm、rmdir、mv、cp、sed
仍然需要正常权限的操作:
- 不属于文件操作的 Bash 命令(如
curl、npm install、git 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 构建可脚本化的自动化流程,批处理任务调度与错误恢复策略。
このコンテンツについて、さらに観点や背景を補足しましょう。