SOURCE NOTE · PERMISSIONS
Lesson 4: Permissions 子系统
工具执行前的统一闸门:先硬拦敏感路径与 deny 列表,再按模式决定放行、弹确认还是直接拒绝。
上一课跟着模型返回的 tool_use 走完了 Tools 子系统,知道了 is_read_only 是工具递给权限系统的一条信号。这一课拆开闸门本身:工具执行前,权限系统按什么顺序裁决?三种模式分别拦什么?硬拦截和「需要确认」有何不同?魔改时该改配置还是改 checker.py?
一句话概括
PermissionChecker.evaluate() 是工具执行前的统一闸门:先硬拦敏感路径与 deny 列表,再按模式决定「放行 / 弹确认 / 直接拒绝」。不要在每个工具里散落 if——策略集中在一处。
PRE_TOOL_USE hook
│
▼
registry.get + model_validate
│
▼
抽取 file_path / command
│
▼
PermissionChecker.evaluate(...)
│
┌────┼────────┐
│ │ │
放行 确认 硬拦
│ │ │
▼ ▼ ▼
execute prompt 错误 ToolResult
│
用户同意? → execute / 拒绝
下面这个交互演示把九步决策瀑布跑一遍:选一个场景,看裁决发生在第几步、出口是哪一个。
INTERACTIVE TRACE · DECISION WATERFALL
9 CHECKSread_file 读普通文件:前 6 步全部未命中,第 7 步 is_read_only 放行,不弹确认。
- 01排队
SENSITIVE_PATH_PATTERNS
命中内置敏感路径
- 02排队
denied_tools
工具名被全局禁用
- 03排队
allowed_tools
工具名被显式放行
- 04排队
path_rules · allow=false
路径规则拒绝
- 05排队
denied_commands
命令命中禁用模式
- 06排队
mode == full_auto
全自动模式放行剩余
- 07排队
is_read_only == True
只读工具免确认
- 08排队
mode == plan
规划模式拦截写操作
- 09排队
mode == default
写操作需人工确认
核心文件
permissions/modes.py:三种模式枚举:default/plan/full_auto。permissions/checker.py:PermissionChecker、PermissionDecision、敏感路径硬拦。config/settings.py:PermissionSettings:mode、deny/allow 列表、路径与命令规则。engine/query.py:_execute_tool_call里调用 evaluate + 处理确认弹窗。commands/registry.py:/permissions、/plan切换模式并重建 checker。
三种模式
default:只读工具通常直接允许;写 / 执行类工具需要确认(requires_confirmation)。对应日常交互开发。plan:只读工具允许;写 / 执行类工具直接拦截(不弹确认)。只规划不改盘。full_auto:只读工具允许;写 / 执行类工具直接允许。对应自动化 / 信任环境。
切换方式:TUI 里 /permissions default|plan|full_auto,或 /plan on|off;CLI 可用 --permission-mode。
PermissionDecision:三种出口
@dataclass(frozen=True)
class PermissionDecision:
allowed: bool
requires_confirmation: bool = False
reason: str = ""allowed=True:引擎直接execute。allowed=False + requires_confirmation=True:调permission_prompt;同意则执行,拒绝则错误返回。allowed=False且无确认:硬拦,直接错误ToolResult,不弹窗。
evaluate() 决策瀑布:顺序即安全策略
顺序写死在 checker.py 的 evaluate()。越靠前优先级越高:
- 敏感路径
SENSITIVE_PATH_PATTERNS→ 硬拦(全模式) - tool 在
denied_tools→ 硬拦 - tool 在
allowed_tools→ 放行(仍躲不过第 1 步) path_rules匹配且allow=False→ 硬拦- command 匹配
denied_commands→ 硬拦 mode == full_auto→ 放行剩余is_read_only == True→ 放行mode == plan→ 硬拦 mutatingmode == default→ 需确认(mutating)
每一步的职责:
- 敏感路径:
*/.ssh/*、*/.aws/credentials、*/.openharness/credentials.json等;配置与模式都无法覆盖。防 prompt injection 读密钥。 - denied_tools:按工具名全局禁。
- allowed_tools:按名放行;但敏感路径已在前面拦死。
- path_rules:fnmatch 路径规则;当前实现主要执行
allow=False的 deny。 - denied_commands:对 bash 类命令做 fnmatch(如拦危险 shell)。
- 模式分支:
full_auto/ 只读 /plan/default确认。
引擎如何消费决策
_execute_tool_call(engine/query.py)在 validate 之后:
_file_path = _resolve_permission_file_path(cwd, tool_input, parsed)
_command = _extract_permission_command(tool_input, parsed)
decision = permission_checker.evaluate(
tool_name,
is_read_only=tool.is_read_only(parsed),
file_path=_file_path,
command=_command,
)
if not decision.allowed:
if decision.requires_confirmation and permission_prompt:
confirmed = await permission_prompt(tool_name, decision.reason)
if not confirmed:
return ToolResultBlock(..., is_error=True)
else:
return ToolResultBlock(..., is_error=True) # 硬拦
# 通过 → execute(...)路径抽取会统一 file_path / path / glob·grep 的目录根,保证 path_rules 对内置工具一致生效。
配置面:PermissionSettings
class PermissionSettings(BaseModel):
mode: PermissionMode = PermissionMode.DEFAULT
allowed_tools: list[str] = []
denied_tools: list[str] = []
path_rules: list[PathRuleConfig] = [] # pattern + allow
denied_commands: list[str] = []挂在全局 settings 的 permission 字段。会话启动时 ui/runtime.py 用它构造 PermissionChecker;用户改模式后会 set_permission_checker 换新实例。
常见诉求对应的优先手段:
- 会话里少点确认 →
/permissions full_auto(仍过硬拦) - 只规划不改文件 →
/plan on或mode=plan - 禁用某个危险工具 → 配置
denied_tools: ["bash"] - 禁止某类 shell →
denied_commands写 fnmatch 模式 - 禁止碰某目录 →
path_rules: [{pattern, allow: false}] - 永不让读密钥 → 已内置敏感路径;可扩
SENSITIVE_PATH_PATTERNS
与 is_read_only 的契约
工具自己声明「这次调用是否只读」:
True→default/plan下通常免确认(仍受敏感路径 / deny 约束)False→default要确认;plan硬拦;full_auto直接过
魔改关键点
要加一条「永远不能做」的规则 → 插在瀑布靠前(或扩展 SENSITIVE_PATH_PATTERNS)。要改「日常是否弹窗」→ 改 mode 或 is_read_only,别到处写 if。
源码精读
src/openharness/permissions/checker.py:evaluate()全文,顺序即策略。src/openharness/permissions/modes.py:三种模式枚举。src/openharness/config/settings.py的PermissionSettings。src/openharness/engine/query.py的_execute_tool_call()中 evaluate 与确认弹窗段。