> For the complete documentation index, see [llms.txt](https://yeasy.gitbook.io/harness_engineering_guide/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://yeasy.gitbook.io/harness_engineering_guide/di-si-bu-fen-an-quan-ping-gu-yu-yan-jin/12_security/12.2_permission_sandbox.md).

# 12.2 权限系统与沙箱设计

本节首先对比 Claude Code 和 OpenClaw 的权限框架设计，再介绍通用的权限框架与沙箱隔离策略，最后讨论两者的协同机制，涵盖从权限定义到执行的完整防护链条。

## 12.2.1 权限框架对比

### Claude Code 权限框架

Claude Code 当前 `defaultMode` 支持 `default`、`acceptEdits`、`plan`、`auto`、`dontAsk` 和 `bypassPermissions`。其中 `default` 主要自动允许读取并在编辑或执行时提示，`acceptEdits` 可自动批准文件编辑和部分工作区内文件系统操作，`plan` 用于只读研究和计划，`auto` 在项目或本地设置中会被忽略，`bypassPermissions` 会跳过权限层，官方建议仅在隔离环境中使用；组织也可通过 `disableBypassPermissionsMode` 禁用该模式。以下将其扩展为更细粒度的六级模型，便于理解权限决策的设计空间：

```typescript
type PermissionMode =
  | "manual_only"     // 完全人工操作
  | "approve_always"  // 每步审批
  | "approve_once"    // 一次审批
  | "ask_first"       // 事前询问
  | "auto_notification"  // 自动+通知
  | "auto_trusted"    // 完全自动

// 应用于每个工具调用
interface ToolCall {
  toolName: string;
  args: ToolArgs;
  permissionMode: PermissionMode;  // 基于风险动态决定
  confidenceScore: number;         // 模型对决策的置信度
}
```

**风险评估与权限模式映射**：

Claude Code 通过公开权限模式、allow/deny 规则和组织策略控制工具调用边界。以下代码是通用风险评估伪代码，用于说明 Harness 可怎样组织权限决策，不代表 Claude Code 的真实内部 SDK 或实现：

```python
# 特征向量 (简化)
features = {
    "tool_name": "bash",
    "arg_length": len(command),
    "contains_dangerous_pattern": has_rm_rf_pattern,
    "execution_count_today": 45,
    "user_approval_rate": 0.92,
    "similar_calls_approved": 23,
}

risk_score = risk_classifier.predict_proba(features)
# risk_score: 0.0 (低风险) -> 1.0 (高风险)

if risk_score > 0.8:
    mode = "manual_only"  # 高风险,完全人工
elif risk_score > 0.6:
    mode = "approve_always"  # 中高风险,每步审批
elif risk_score > 0.4:
    mode = "approve_once"  # 中等风险,任务开始审批
elif risk_score > 0.2:
    mode = "ask_first"  # 低中风险,事前询问
else:
    mode = "auto_notification"  # 低风险,自动+通知
```

**六模式的实际应用**：

| 模式                 | 触发条件                    | 行为        | 风险控制  |
| ------------------ | ----------------------- | --------- | ----- |
| manual\_only       | 极高风险操作(rm -rf /)        | 每步人工批准    | 完全控制  |
| approve\_always    | 高风险操作(delete, transfer) | 每个操作人工批准  | 逐步控制  |
| approve\_once      | 中高风险（任务批量操作）            | 任务开始批准一次  | 计划控制  |
| ask\_first         | 中等风险（关键文件读取）            | 首次询问，记住选择 | 选择性控制 |
| auto\_notification | 低风险（日常读取、列表）            | 自动执行+通知   | 事后可知  |
| auto\_trusted      | 极低风险（查询、计算）             | 完全自动，无通知  | 最小开销  |

### OpenClaw 权限系统

OpenClaw 实际采用三级权限系统(deny/allowlist/full)，配合三种审批选项(Allow once/Always allow/Deny)和三种提示模式(off/on-miss/always)。以下将其扩展为六个权限等级，展示完整的权限梯度设计：

```mermaid
flowchart TD
    A["<b>Manual Only</b><br/>完全人工"] -->|每一步都需要批准| B["<b>Approve Always</b><br/>每步审批"]
    B -->|每个操作都需要批准| C["<b>Approve Once</b><br/>一次审批"]
    C -->|任务开始时批准一次| D["<b>Ask First</b><br/>事前询问"]
    D -->|首次询问,后续自动| E["<b>Auto with Notification</b><br/>自动+通知"]
    E -->|自动执行并发送通知| F["<b>Full Trust</b><br/>完全信任"]

    style A fill:#ffebee,stroke:#c62828
    style B fill:#fff3e0,stroke:#ffb74d
    style C fill:#fff9c4,stroke:#ffb74d
    style D fill:#e8f5e9,stroke:#388e3c
    style E fill:#e3f2fd,stroke:#1565c0
    style F fill:#f3e5f5,stroke:#7b1fa2
```

图 12-4：六级权限信任模型

**工作机制**：

```python
class ToolPermission(Enum):
    """工具权限等级定义(6级信任模型)"""
    MANUAL_ONLY = "manual_only"              # 完全人工操作
    APPROVE_ALWAYS = "approve_always"        # 每步都需要审批
    APPROVE_ONCE = "approve_once"            # 整个流程开始前审批一次
    ASK_FIRST = "ask_first"                  # 首次询问,后续自动
    AUTO_WITH_NOTIFICATION = "auto_with_notification"  # 自动执行+通知
    FULL_TRUST = "full_trust"                # 完全信任

# 使用示例
@tool(permission=ToolPermission.ASK_FIRST)
def read_file(path: str) -> str:
    """读取文件"""
    return open(path).read()

# 执行流程
if tool.permission == ASK_FIRST:
    canonical_path = canonicalize_path(path)
    approval_scope = (user_id, tool.name, canonical_path, tool.risk_level)

    if approval_scope not in approved_cache:
        approval = await ask_user_permission(f"读取文件 {canonical_path}?")
        if approval:
            approved_cache.add(approval_scope)
        else:
            raise PermissionDenied()
execute_tool(tool, args)
```

**设计特点**：

* 粒度：工具 + 规范化参数 / 资源 + 风险等级
* 状态维护：用户×工具×资源×风险的审批范围；敏感路径和高风险参数不进入长期缓存
* 适用场景：自驱型Agent，需要用户交互反馈

**6级模型的完整支持**：

* Manual Only: 完全人工操作，每次都需要确认
* Approve Always: 每个操作都需要人工批准
* Approve Once: 任务开始时批准一次，执行过程中不再询问
* Ask First: 首次询问，后续记住用户决择，24小时有效
* Auto with Notification: 自动执行并发送通知
* Full Trust: 完全信任，无需额外验证

## 12.2.2 通用权限框架设计

综合Claude Code和OpenClaw的优点，设计一个通用框架：

### 1. 权限定义层

权限层的基础定义：

```python
from enum import Enum
from typing import Callable, List
from dataclasses import dataclass

# 权限等级
class PermissionLevel(Enum):
    """权限等级定义(6级信任模型)"""
    DENY = 0                           # 拒绝
    MANUAL_ONLY = 1                    # 完全人工操作
    APPROVE_ALWAYS = 2                 # 每步审批
    APPROVE_ONCE = 3                   # 一次审批
    ASK_FIRST = 4                      # 事前询问
    AUTO_WITH_NOTIFICATION = 5         # 自动+通知
    FULL_TRUST = 6                     # 完全信任
    OVERRIDE = 7                       # 强制执行(仅管理员)

# 权限条件
@dataclass
class PermissionCondition:
    """权限条件：基于参数、用户、上下文的细粒度控制"""

    # 参数约束 (如路径必须在/data目录内)
    param_validator: Callable[[dict], bool]

    # 用户约束
    allowed_users: List[str] = None  # None表示所有用户

    # 时间约束
    time_window: tuple = None  # (start_hour, end_hour)

    # 频率约束
    max_calls_per_hour: int = None

    # 资源约束
    max_cpu_percent: int = 80
    max_memory_mb: int = 512
    timeout_seconds: int = 30

# 工具权限定义
@dataclass
class ToolPermissionPolicy:
    tool_name: str
    default_level: PermissionLevel
    conditions: List[PermissionCondition]

    def evaluate(self, call: "ToolCall", context: "ExecutionContext") -> PermissionLevel:
        """评估实际权限等级"""
        for condition in self.conditions:
            if condition.matches(call, context):
                return condition.permission_level
        return self.default_level
```

**决策权归属：风险分级之上的类别化前置门**

前述风险分数到权限模式的映射是一条连续梯度，回答的是该操作需要多少人工摩擦，却回答不了该操作是否应当由智能体代为决策。一次金额很小、历史上高频批准的转账，其 `risk_score` 可能趋近 0，若仅由风险分类器裁决就会被路由到 `FULL_TRUST` 而自动放行——这并非风险估计的误差，而是决策权属性问题：某些操作无论风险高低，都不应由智能体自主拍板。

参考三部门 2026 年 5 月印发的[《智能体规范应用与创新发展实施意见》](https://www.cac.gov.cn/2026-05/08/c_1779979789472520.htm)所厘清的三类决策边界，可在风险梯度之上叠加一条正交的决策权归属轴，其类别裁决优先于风险分数、且不可被风险分类器下调：

* 仅限用户本人决策（`USER_ONLY`）：转账、授权变更、不可逆删除等，须由用户本人执行或逐次确认，即便 `risk_score` 趋近 0 也不自动放行。
* 需用户授权决策（`USER_AUTHORIZED`）：在用户明确授权的范围内可代为执行，但不得超出授权边界，越界即回落为用户本人决策。
* 智能体自主决策（`AGENT_AUTONOMOUS`）：只读查询、信息汇总等可自主执行，用户对结果仍保留知情权与最终决策权。

该轴与风险分数互补而非替代：决策权归属先做类别化裁决，把 `USER_ONLY` 操作钉死在人工审批端；只有落入 `AGENT_AUTONOMOUS` 的操作，才继续沿风险分数梯度决定具体的摩擦程度。

```python
class DecisionAuthority(Enum):
    """决策权归属：位于风险分级之上的类别化前置门，不可被风险分类器覆盖"""
    USER_ONLY = "user_only"                # 仅限用户本人决策
    USER_AUTHORIZED = "user_authorized"    # 需用户授权，且不得超出授权范围
    AGENT_AUTONOMOUS = "agent_autonomous"  # 可自主决策，用户保留知情权与最终决策权

def gate(call: "ToolCall", risk_score: float) -> PermissionLevel:
    """先按决策权归属做类别化裁决，再决定是否落入风险梯度"""
    authority = classify_authority(call)         # 只看操作类别，与 risk_score 无关
    if authority == DecisionAuthority.USER_ONLY:
        return PermissionLevel.MANUAL_ONLY       # 转账即使 risk_score≈0 也不放行
    if authority == DecisionAuthority.USER_AUTHORIZED and not within_user_grant(call):
        return PermissionLevel.MANUAL_ONLY       # 越权回落为用户本人决策
    return risk_to_level(risk_score)             # 仅自主类操作才走原有风险梯度
```

### 2. 权限决策引擎

权限决策引擎的完整实现：

```python
from enum import Enum
from typing import Optional
import logging

logger = logging.getLogger(__name__)

class PermissionDecision(Enum):
    ALLOW = "allow"
    DENY = "deny"
    ASK_USER = "ask_user"

class PermissionDecisionEngine:
    def __init__(self, policy_store: dict, audit_log: "AuditLog"):
        self.policies = policy_store  # tool_name -> ToolPermissionPolicy
        self.audit_log = audit_log
        self.user_approvals = {}  # (user_id, tool_name) -> set of approved_params

    async def decide(self,
                    tool_call: "ToolCall",
                    user_id: str,
                    context: "ExecutionContext") -> PermissionDecision:
        """
        决策流程：
        1. 参数验证
        2. 权限等级评估
        3. 用户历史记录查询
        4. 最终决策
        """

        # 步骤1: 参数Schema验证
        if not self._validate_schema(tool_call):
            logger.warning(f"Schema验证失败: {tool_call}")
            self.audit_log.log("schema_validation_failed", tool_call, user_id)
            return PermissionDecision.DENY

        # 步骤2: 获取工具权限策略
        policy = self.policies.get(tool_call.tool_name)
        if not policy:
            logger.error(f"未知工具: {tool_call.tool_name}")
            return PermissionDecision.DENY

        # 步骤3: 评估权限等级
        permission_level = policy.evaluate(tool_call, context)

        # 步骤4: 基于等级决策
        if permission_level == PermissionLevel.DENY:
            logger.warning(f"权限拒绝: {tool_call.tool_name}")
            self.audit_log.log("permission_denied", tool_call, user_id)
            return PermissionDecision.DENY

        elif permission_level == PermissionLevel.OVERRIDE:
            logger.info(f"管理员覆盖: {tool_call.tool_name}")
            self.audit_log.log("override_allowed", tool_call, user_id)
            return PermissionDecision.ALLOW

        elif permission_level == PermissionLevel.FULL_TRUST:
            logger.info(f"完全信任,自动批准: {tool_call.tool_name}")
            self.audit_log.log("full_trust_approved", tool_call, user_id)
            return PermissionDecision.ALLOW

        elif permission_level == PermissionLevel.AUTO_WITH_NOTIFICATION:
            logger.info(f"自动执行并通知: {tool_call.tool_name}")
            self.audit_log.log("auto_with_notification", tool_call, user_id)
            return PermissionDecision.ALLOW

        elif permission_level == PermissionLevel.ASK_FIRST:
            # 检查用户历史批准记录
            approval_key = (user_id, tool_call.tool_name)
            if self._is_approved_before(approval_key, tool_call.args):
                logger.info(f"自动批准 (历史): {tool_call.tool_name}")
                self.audit_log.log("ask_first_cached", tool_call, user_id)
                return PermissionDecision.ALLOW
            else:
                logger.info(f"首次调用需询问: {tool_call.tool_name}")
                return PermissionDecision.ASK_USER

        else:  # APPROVE_ONCE, APPROVE_ALWAYS, MANUAL_ONLY
            return PermissionDecision.ASK_USER

    def _validate_schema(self, tool_call: "ToolCall") -> bool:
        """验证工具调用的参数Schema"""
        # 实现略
        return True

    def _is_approved_before(self, key: tuple, args: dict) -> bool:
        """检查用户是否曾批准过类似调用"""
        # 简化实现：仅检查完全相同的调用
        if key not in self.user_approvals:
            return False
        return str(args) in self.user_approvals[key]

    def record_approval(self, user_id: str, tool_name: str, args: dict):
        """记录用户批准"""
        key = (user_id, tool_name)
        if key not in self.user_approvals:
            self.user_approvals[key] = set()
        self.user_approvals[key].add(str(args))
```

### 3. 异步权限决策

在并发Agent系统中，权限决策需支持异步操作。现代Agent系统几乎全部采用异步架构，PolicyEngine.evaluate应提供async版本以支持并发工具调用、数据库查询等I/O操作。以下为异步版 `PolicyEngine.evaluate` 示例：

```python
class AsyncPolicyEngine:
    """异步权限策略引擎"""

    async def evaluate(self, tool_call: "ToolCall", user_id: str) -> PermissionLevel:
        """异步评估权限等级(支持数据库查询、API调用)"""
        # 步骤1:异步查询用户权限缓存
        cached_level = await self._get_cached_permission(user_id, tool_call.tool_name)
        if cached_level:
            return cached_level

        # 步骤2:异步查询策略库(可能涉及数据库)
        policy = await self._fetch_policy(tool_call.tool_name)

        # 步骤3:评估并缓存结果
        level = policy.evaluate(tool_call, user_id)
        await self._cache_permission(user_id, tool_call.tool_name, level, ttl=300)
        return level
```

### 4. 子智能体权限继承

当智能体创建子智能体时，权限继承需特殊处理。Claude Code 的子智能体通常继承父会话的权限模式，并通过前台/后台执行、工具 allowlist 和用户确认边界控制风险，而不是使用独立的子智能体权限模式：

```python
class SubAgentPermissionInheritance:
    """子智能体权限继承管理"""

    def create_sub_agent(self,
                        parent_context: "ExecutionContext",
                        sub_agent_config: dict) -> "Agent":
        """
        创建子智能体时,若子智能体需要权限提升,
        请求冒泡到父Agent或用户
        """
        sub_agent = Agent(config=sub_agent_config)
        sub_agent.parent_context = parent_context
        sub_agent.permission_mode = parent_context.permission_mode
        return sub_agent

    async def execute_with_inherited_permissions(self,
                                                sub_agent: "Agent",
                                                tool_call: "ToolCall") -> "ToolResult":
        """
        执行子智能体工具调用,通过父上下文继承权限
        """
        decision = await self.decide(tool_call, sub_agent.context)

        if decision == PermissionDecision.ASK_USER:
            # 子智能体没有独立提权，向父上下文或用户请求批准
            if sub_agent.permission_mode == sub_agent.parent_context.permission_mode:
                parent_context = sub_agent.parent_context
                # 向用户或父Agent请求批准
                approval = await self._request_parent_approval(
                    parent_context,
                    tool_call,
                    reason="Sub-agent permission requirement"
                )
                if not approval:
                    raise PermissionDenied(f"父级拒绝了权限提升")
            else:
                # 直接询问用户
                approval = await self._request_user_approval(tool_call)
                if not approval:
                    raise PermissionDenied("用户拒绝")

        return await sub_agent.execute_tool(tool_call)
```

## 12.2.3 沙箱隔离策略

沙箱的目标是 **限制突破**：即使智能体执行恶意操作，破坏范围也受限。

### 1. 进程级隔离

**机制**：使用操作系统原语限制单个进程的资源访问。

```python
import resource
import os

class ProcessSandbox:
    """进程级隔离：通过setrlimit限制资源"""

    def setup(self):
        # CPU时间限制(秒)
        resource.setrlimit(resource.RLIMIT_CPU, (30, 30))

        # 内存限制(字节)
        resource.setrlimit(resource.RLIMIT_AS, (512 * 1024 * 1024, 512 * 1024 * 1024))

        # 文件大小限制(字节)
        resource.setrlimit(resource.RLIMIT_FSIZE, (1024 * 1024 * 1024, 1024 * 1024 * 1024))

        # 打开文件数限制
        resource.setrlimit(resource.RLIMIT_NOFILE, (256, 256))

        # 子进程数限制
        resource.setrlimit(resource.RLIMIT_NPROC, (32, 32))

        # 禁止生成core dump
        resource.setrlimit(resource.RLIMIT_CORE, (0, 0))

def run_in_process_sandbox(tool_func, *args, **kwargs):
    """在沙箱内执行工具函数"""
    import json
    import subprocess

    code = """
import importlib
import json
import sys

try:
    request = json.loads(sys.stdin.read())
    module_name = request["module"]
    func_name = request["function"]
    args = request.get("args", [])
    kwargs = request.get("kwargs", {})

    if not module_name.startswith("trusted_tools."):
        raise ValueError("only trusted_tools modules are allowed")

    tool_func = getattr(importlib.import_module(module_name), func_name)
    output = tool_func(*args, **kwargs)
    print(json.dumps({"ok": True, "output": output}))
except Exception as exc:
    print(json.dumps({"ok": False, "error": str(exc)}), file=sys.stderr)
    sys.exit(1)
"""

    proc = subprocess.Popen(
        ["python", "-c", code],
        stdin=subprocess.PIPE,
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
        preexec_fn=ProcessSandbox().setup
    )

    stdout, stderr = proc.communicate(
        json.dumps({
            "module": tool_func.__module__,
            "function": tool_func.__name__,
            "args": args,
            "kwargs": kwargs,
        }).encode(),
        timeout=30
    )
    if proc.returncode != 0:
        message = json.loads(stderr.decode())
        raise RuntimeError(message["error"])
    return json.loads(stdout.decode())["output"]
```

**优点**：简单，无需额外软件。 **缺点**：无法限制文件系统访问，无法隔离网络。

### 2. 容器级隔离

**机制**：使用Docker/Podman将工具执行环境隔离在容器内。

```python
import docker
import json

class ContainerSandbox:
    """容器级隔离：每次工具调用创建新容器"""

    def __init__(self, image: str = "python:3.11-slim"):
        self.client = docker.from_env()
        self.image = image

    def run_tool(self,
                tool_code: str,
                timeout_sec: int = 30,
                memory_limit: str = "512m") -> str:
        """在容器内运行工具"""

        # 一次性容器(auto_remove=True)
        # 受限资源(mem_limit, memswap_limit)
        # 只读文件系统除了工作目录

        container = self.client.containers.run(
            self.image,
            command=["python", "-c", tool_code],

            # 资源限制
            mem_limit=memory_limit,
            memswap_limit=memory_limit,
            cpu_quota=100000,           # CPU份额
            cpu_period=100000,

            # 文件系统隔离：镜像文件系统只读，/tmp 是容器内临时 tmpfs
            read_only=True,
            tmpfs={"/tmp": "rw,noexec,nosuid,size=64m"},

            # 安全隔离
            cap_drop=["ALL"],           # 删除所有capability
            cap_add=["NET_BIND_SERVICE"],
            security_opt=["no-new-privileges"],
            pids_limit=10,              # 进程数限制

            # 网络隔离
            network_mode="none",        # 禁用网络

            # 超时
            timeout=timeout_sec,

            # 自动清理
            auto_remove=True,
            detach=False,
        )

        # 容器自动清理
        return container.decode() if isinstance(container, bytes) else str(container)

# 使用
sandbox = ContainerSandbox()
result = sandbox.run_tool(
    "print('Hello from sandbox')",
    timeout_sec=10
)
```

**优点**：强隔离，镜像文件系统只读，临时目录不把宿主路径读写挂入容器，网络隔离完全。 **缺点**：每次创建容器开销大(500ms-1s)，资源占用多。

### 3. 虚拟机级隔离

**机制**：使用轻量级VM（如Firecracker, gVisor）进行更强隔离。

```python
class VMSandbox:
    """VM级隔离：最强隔离,但开销最大"""

    def __init__(self, vm_image: str):
        self.vm_image = vm_image
        # 初始化Firecracker或其他轻量级VM管理器

    def run_tool(self, tool_code: str) -> str:
        """在隔离的VM内运行工具"""
        # 1. 启动VM (通常500ms)
        # 2. 注入代码
        # 3. 执行并收集结果
        # 4. 销毁VM
        pass
```

**适用场景**：极高安全要求（金融、医疗）。

### 沙箱选择矩阵

| 隔离级别 | 开销 | 文件隔离 | 网络隔离 | 进程隔离 | 适用场景  |
| ---- | -- | ---- | ---- | ---- | ----- |
| 进程   | 低  | 否    | 否    | 部分   | 开发调试  |
| 容器   | 中  | 是    | 是    | 是    | 生产环境  |
| VM   | 高  | 是    | 是    | 是    | 高安全要求 |

## 12.2.4 权限与沙箱的协同

最佳实践是 **权限决策 + 沙箱执行** 的两层防护：

```python
class SecureToolExecutor:
    def __init__(self,
                 permission_engine: PermissionDecisionEngine,
                 sandbox: ContainerSandbox):
        self.permission_engine = permission_engine
        self.sandbox = sandbox

    async def execute(self,
                     tool_call: "ToolCall",
                     user_id: str,
                     context: "ExecutionContext") -> "ToolResult":

        # 第一层：权限决策
        decision = await self.permission_engine.decide(
            tool_call, user_id, context
        )

        if decision == PermissionDecision.DENY:
            raise PermissionDenied(f"Tool {tool_call.tool_name} denied")

        if decision == PermissionDecision.ASK_USER:
            approval = await self._ask_user(tool_call, user_id)
            if not approval:
                raise PermissionDenied("User rejected")

        # 第二层：沙箱执行
        result = self.sandbox.run_tool(
            tool_code=tool_call.code,
            timeout_sec=tool_call.timeout or 30,
            memory_limit="512m"
        )

        return ToolResult(output=result, success=True)
```

***

**本节总结**：权限与沙箱是防护的两个维度。权限控制 **是否允许** 执行，沙箱限制 **执行的破坏范围**。两者结合形成纵深防护。
