Phase 3 / Safety & Control / s08

Permission System

用户审批流、自动放行规则与风险分类——在自主与安全之间找平衡

动机:为什么需要 Permission System

上一课的 Sandbox 是二元的——要么允许,要么拒绝。但现实中有大量灰色地带:

OpenClaw 的做法是维护 AllowList 和 DenyList,结合风险分类决定是否需要用户审批。zeroclaw 实现了三级 autonomy:ReadOnly、Supervised、Full——每级对应不同的审批策略。

Ground Truth:真实的 Permission 实现

Permission System——跨代码库对比

审批流和 autonomy 级别在不同代码库中的实现

rustsecurity/policy.rs
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);
14
15 // 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 }
22
23 // 5. 中风险 + Supervised 模式: 需要审批
24 if risk == Medium
25 && self.autonomy == Supervised
26 && self.require_approval_for_medium_risk
27 && !approved
28 {
29 return Err("requires explicit approval");
30 }
31
32 Ok(risk)
33}
rustsecurity/policy.rs (tool gate)
1pub enum ToolOperation { Read, Act }
2
3pub 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}
22
23// 速率限制: 滑动窗口, 每小时 N 次操作
24pub fn record_action(&self) -> bool {
25 let count = self.tracker.record(); // 记录+统计
26 count <= self.max_actions_per_hour as usize
27}

关键观察:

构建: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"

可以尝试的提示:

  1. 设置 auto_approve={"read_file"} 观察不同工具的审批行为
  2. 尝试低风险 (ls)、中风险 (git push)、高风险 (rm -rf) 命令
  3. 对比有无 auto_approve 时的交互差异

距离生产

Read vs Act: 操作类型的根本区分

zeroclaw 定义了 ToolOperation 枚举:ReadAct。读操作(read_filesearchgit status跳过所有 autonomy 检查,因为它们没有副作用——读一个文件不会改变系统状态,所以不需要审批。写操作(write_filebashgit push)才需要经过完整的权限检查流程。这个区分大幅减少了不必要的审批弹窗。

滑动窗口速率限制: ActionTracker

zeroclaw 的 record_action() 使用滑动窗口算法统计每小时的操作次数。如果 agent 陷入死循环(反复执行失败的命令),速率限制会自动触发,阻止进一步操作。这是一种被动安全机制——不需要理解 agent 在做什么,只需要检测异常频率。

其他差距

第一性原理思考

Permission 的本质矛盾是”安全 vs 效率”——每次审批都增加延迟,打断用户的工作流。zeroclaw 的 Supervised 模式是一个务实的折中:低风险(read)自动通过,高风险(rm -rf)直接拒绝,只有中间地带(git pushnpm install)需要人类判断。这三级设计暗示了一个更深的洞察:大多数操作要么明显安全要么明显危险,真正需要人类判断的”灰色地带”其实很窄。好的 permission 系统的目标不是”控制一切”,而是”只在关键时刻打扰用户”。