动机:为什么需要 Permission System
上一课的 Sandbox 是二元的——要么允许,要么拒绝。但现实中有大量灰色地带:
git commit不危险,但你可能想确认 commit messagenpm install不危险,但你可能想看看要安装什么write_file到测试文件可以自动放行,但到package.json需要确认
OpenClaw 的做法是维护 AllowList 和 DenyList,结合风险分类决定是否需要用户审批。zeroclaw 实现了三级 autonomy:ReadOnly、Supervised、Full——每级对应不同的审批策略。
Ground Truth:真实的 Permission 实现
Permission System——跨代码库对比
审批流和 autonomy 级别在不同代码库中的实现
1pub fn validate_command_execution(2 &self, command: &str, approved: bool,3) -> Result<CommandRiskLevel, String> {4 // 1. 白名单检查5 if !self.is_command_allowed(command) {6 return Err("Command not allowed");7 }8 // 2. 路径检查9 if let Some(path) = self.forbidden_path_argument(command) {10 return Err(format!("Path blocked: {path}"));11 }12 // 3. 风险分类13 let risk = self.command_risk_level(command);1415 // 4. 高风险: 直接拒绝或需要审批16 if risk == High && self.block_high_risk_commands {17 return Err("high-risk command blocked");18 }19 if risk == High && self.autonomy == Supervised && !approved {20 return Err("requires explicit approval");21 }2223 // 5. 中风险 + Supervised 模式: 需要审批24 if risk == Medium25 && self.autonomy == Supervised26 && self.require_approval_for_medium_risk27 && !approved28 {29 return Err("requires explicit approval");30 }3132 Ok(risk)33}
1pub enum ToolOperation { Read, Act }23pub fn enforce_tool_operation(4 &self, operation: ToolOperation,5 operation_name: &str,6) -> Result<(), String> {7 match operation {8 // 读操作总是允许的9 ToolOperation::Read => Ok(()),10 // 写操作需要检查 autonomy + 速率限制11 ToolOperation::Act => {12 if !self.can_act() {13 return Err("read-only mode");14 }15 if !self.record_action() {16 return Err("rate limit exceeded");17 }18 Ok(())19 }20 }21}2223// 速率限制: 滑动窗口, 每小时 N 次操作24pub fn record_action(&self) -> bool {25 let count = self.tracker.record(); // 记录+统计26 count <= self.max_actions_per_hour as usize27}
关键观察:
- zeroclaw 的验证是严格分层的:白名单 → 路径检查 → 风险分类 → autonomy 检查 → 审批状态
- 读操作(Read)和写操作(Act)有不同的策略——读操作跳过 autonomy 和速率检查
- 速率限制用滑动窗口实现——防止失控循环
- Supervised 模式对 medium-risk 要求
approved=true,Full 模式则跳过
构建:Permission Manager
class PermissionManager:
"""管理工具执行权限"""
def __init__(self, policy: SecurityPolicy, auto_approve: set[str] | None = None):
self.policy = policy
self.auto_approve = auto_approve or set()
self._pending: dict[str, dict] = {}
def check(self, tool_name: str, params: dict) -> tuple[bool, str]:
"""检查是否可以执行, 返回 (allowed, reason)"""
# 自动放行的工具
if tool_name in self.auto_approve:
return True, "auto-approved"
# bash 命令需要额外的安全检查
if tool_name == "bash":
command = params.get("command", "")
allowed, reason = self.policy.is_command_allowed(command)
if not allowed:
return False, reason
risk = self.policy.classify_risk(command)
if risk == RiskLevel.HIGH:
return False, f"高风险命令被阻止: {command}"
if risk == RiskLevel.MEDIUM:
return self._request_approval(tool_name, params)
# 文件操作检查路径
if tool_name in ("read_file", "write_file"):
path = params.get("path", "")
allowed, reason = self.policy.is_path_allowed(path)
if not allowed:
return False, reason
return True, "allowed"
def _request_approval(self, tool_name: str, params: dict) -> tuple[bool, str]:
"""请求用户审批 (简化实现: 直接拒绝)"""
return False, "需要用户审批 (medium-risk)"
测试:分层审批
policy = SecurityPolicy(Path("."))
perms = PermissionManager(policy, auto_approve={"read_file"})
# 自动放行
assert perms.check("read_file", {"path": "main.py"}) == (True, "auto-approved")
# 低风险命令: 允许
assert perms.check("bash", {"command": "git status"})[0] == True
# 中风险命令: 需要审批
assert perms.check("bash", {"command": "git push"})[0] == False
# 高风险命令: 直接拒绝
assert perms.check("bash", {"command": "rm -rf /"})[0] == False
变更内容
| 组件 | 之前 (s07) | 之后 (s08) |
|---|---|---|
| 决策模型 | 二元 (允许/拒绝) | RiskLevel 三级分类 |
| 权限管理 | 无 | PermissionManager |
| 自动放行 | 无 | auto_approve 规则集 |
本课代码: agents/s08_permission_system.py — 303 行 (新增 38 行)
试一试
cd public/code
python agents/s08_permission_system.py "read main.py then git push"
可以尝试的提示:
- 设置
auto_approve={"read_file"}观察不同工具的审批行为 - 尝试低风险 (
ls)、中风险 (git push)、高风险 (rm -rf) 命令 - 对比有无
auto_approve时的交互差异
距离生产
Read vs Act: 操作类型的根本区分
zeroclaw 定义了 ToolOperation 枚举:Read 和 Act。读操作(read_file、search、git status)跳过所有 autonomy 检查,因为它们没有副作用——读一个文件不会改变系统状态,所以不需要审批。写操作(write_file、bash、git push)才需要经过完整的权限检查流程。这个区分大幅减少了不必要的审批弹窗。
滑动窗口速率限制: ActionTracker
zeroclaw 的 record_action() 使用滑动窗口算法统计每小时的操作次数。如果 agent 陷入死循环(反复执行失败的命令),速率限制会自动触发,阻止进一步操作。这是一种被动安全机制——不需要理解 agent 在做什么,只需要检测异常频率。
其他差距
- 无交互式审批 UI——生产系统有弹窗让用户实时审批,我们直接拒绝中风险操作
- 无持久化审批记录——生产需要审计日志追溯每个操作的审批链
第一性原理思考
Permission 的本质矛盾是”安全 vs 效率”——每次审批都增加延迟,打断用户的工作流。zeroclaw 的 Supervised 模式是一个务实的折中:低风险(read)自动通过,高风险(rm -rf)直接拒绝,只有中间地带(git push、npm install)需要人类判断。这三级设计暗示了一个更深的洞察:大多数操作要么明显安全要么明显危险,真正需要人类判断的”灰色地带”其实很窄。好的 permission 系统的目标不是”控制一切”,而是”只在关键时刻打扰用户”。