返回知识库
0

第十章:安全沙箱与隔离

本章目标:帮助你理解 dsh 的沙箱隔离机制——Landlock/Seatbelt/bwrap 三种后端、进程树隔离、权限提升(Escalation)流程、以及与 Guard 机制的配合。阅读本章后,你应该能回答"Agent 执行的命令是如何被隔离的"以及"如何配置沙箱策略"。


10.1 沙箱架构概览

10.1.1 为什么需要沙箱

Agent 可能执行任意命令(Bash、文件写入、网络请求等)。沙箱确保:

  1. 最小权限:Agent 只能访问授权的资源

  2. 故障隔离:一个 Agent 的失败不影响其他 Agent

  3. 审计追踪:所有操作可被记录和审查

10.1.2 架构

预览
源码

依赖

平台后端

sandbox/ 能力

sandbox/ (Service Definition)

sandbox-local/ (Provider)

消费者: shell, subprocess, code-runtime

Landlock (Linux)

Seatbelt (macOS)

bwrap (Bubblewrap)

subprocess/

graph TB
    subgraph "sandbox/ 能力"
        A["sandbox/ (Service Definition)"]
        B["sandbox-local/ (Provider)"]
        C["消费者: shell, subprocess, code-runtime"]
    end

    subgraph "平台后端"
        D["Landlock (Linux)"]
        E["Seatbelt (macOS)"]
        F["bwrap (Bubblewrap)"]
    end

    subgraph "依赖"
        G["subprocess/"]
    end

    A --> B
    B --> D
    B --> E
    B --> F
    B --> G
    C --> A

10.2 沙箱服务定义

10.2.1 SandboxService 接口

// 来自 packages/sandbox/sandbox/src/index.ts(概念性)
interface SandboxService {
  /** 在沙箱中执行命令 */
  confine(command: string, options: SandboxOptions): Promise<SandboxResult>
  
  /** 检查沙箱是否可用 */
  isAvailable(): boolean
  
  /** 获取当前沙箱类型 */
  getType(): SandboxType
}

type SandboxType = 'landlock' | 'seatbelt' | 'bwrap' | 'none'

interface SandboxOptions {
  /** 工作目录 */
  cwd?: string
  /** 环境变量 */
  env?: Record<string, string>
  /** 超时(毫秒)*/
  timeout?: number
  /** 限制 */
  restrictions: SandboxRestrictions
}

interface SandboxRestrictions {
  /** 允许的路径 */
  allowedPaths?: string[]
  /** 禁止的路径 */
  deniedPaths?: string[]
  /** 允许的网络访问 */
  allowedNetwork?: boolean
  /** 允许的进程创建 */
  allowedProcessSpawn?: boolean
  /** 最大内存(字节)*/
  maxMemory?: number
  /** 最大 CPU 时间(毫秒)*/
  maxCpuTime?: number
}

10.2.2 SandboxResult

interface SandboxResult {
  /** 退出码 */
  exitCode: number
  /** 标准输出 */
  stdout: string
  /** 标准错误 */
  stderr: string
  /** 是否被沙箱拦截 */
  confined: boolean
  /** 被拦截的原因(如果有)*/
  confinementReason?: string
}

10.3 三种平台后端

10.3.1 Landlock(Linux)

Landlock 是 Linux 内核提供的沙箱机制,无需 root 权限。

// 来自 packages/sandbox/sandbox-local/src/landlock.ts(概念性)
class LandlockSandbox implements SandboxBackend {
  /** 创建 Landlock 限制 */
  async createRules(restrictions: SandboxRestrictions): Promise<LandlockRuleset> {
    const ruleset = new LandlockRuleset({
      handledAccess: [
        'file_read',
        'file_write',
        'file_execute',
        'directory_read'
      ]
    })
    
    // 添加允许的路径
    for (const path of restrictions.allowedPaths ?? []) {
      ruleset.addRule({
        type: 'path_beneath',
        path,
        access: ['file_read', 'file_write']
      })
    }
    
    return ruleset
  }
  
  /** 执行受限命令 */
  async exec(command: string, ruleset: LandlockRuleset): Promise<ProcessResult> {
    // 应用 Landlock 规则
    await ruleset.apply()
    
    // 执行命令
    return exec(command)
  }
}

优势

  • 无需 root 权限

  • 内核级别隔离

  • 细粒度文件系统控制

10.3.2 Seatbelt(macOS)

Seatbelt 是 macOS 的沙箱机制(sandbox-exec)。

// 来自 packages/sandbox/sandbox-local/src/seatbelt.ts(概念性)
class SeatbeltSandbox implements SandboxBackend {
  /** 生成 Seatbelt 配置 */
  generateProfile(restrictions: SandboxRestrictions): string {
    return `
      (version 1)
      (allow default)
      ${restrictions.deniedPaths?.map(p => `(deny file-read-data (regex "${p}"))`).join('\n')}
      ${restrictions.deniedPaths?.map(p => `(deny file-write-data (regex "${p}"))`).join('\n')}
      ${!restrictions.allowedNetwork ? '(deny network*)' : ''}
    `
  }
  
  /** 执行受限命令 */
  async exec(command: string, profile: string): Promise<ProcessResult> {
    // 使用 sandbox-exec 执行
    return exec(`sandbox-exec -f ${profile} ${command}`)
  }
}

优势

  • macOS 原生支持

  • 基于 Scheme 的策略语言

  • 细粒度权限控制

10.3.3 bwrap(Bubblewrap)

Bubblewrap 是一个用户空间沙箱工具。

// 来自 packages/sandbox/sandbox-local/src/bwrap.ts(概念性)
class BwrapSandbox implements SandboxBackend {
  /** 构建 bwrap 命令 */
  buildCommand(restrictions: SandboxRestrictions): string[] {
    const args = ['bwrap']
    
    // 只读绑定
    for (const path of restrictions.allowedPaths ?? []) {
      args.push('--ro-bind', path, path)
    }
    
    // 禁止网络
    if (!restrictions.allowedNetwork) {
      args.push('--unshare-net')
    }
    
    // 临时文件系统
    args.push('--tmpfs', '/tmp')
    
    // 要执行的命令
    args.push('--', 'sh', '-c', command)
    
    return args
  }
}

优势

  • 用户空间实现

  • 不需要特殊内核支持

  • 灵活的绑定选项


10.4 LocalSandboxProvider

10.4.1 实现

// 来自 packages/sandbox/sandbox-local/src/index.ts
class LocalSandboxProvider implements SandboxProvider {
  private backend: SandboxBackend
  
  constructor(private ctx: Context) {
    // 自动检测可用的后端
    this.backend = this.detectBackend()
  }
  
  /** 检测平台沙箱 */
  private detectBackend(): SandboxBackend {
    if (process.platform === 'linux') {
      if (this.isLandlockAvailable()) {
        return new LandlockSandbox()
      }
      return new BwrapSandbox()
    }
    
    if (process.platform === 'darwin') {
      return new SeatbeltSandbox()
    }
    
    return new NoopSandbox()
  }
  
  /** 在沙箱中执行 */
  async confine(command: string, options: SandboxOptions): Promise<SandboxResult> {
    // 1. 检查权限
    const escalation = await this.checkEscalation(options.restrictions)
    if (!escalation.approved) {
      return { exitCode: 1, stdout: '', stderr: 'Permission denied', confined: true }
    }
    
    // 2. 应用沙箱限制
    const result = await this.backend.exec(command, options)
    
    // 3. 记录审计日志
    await this.auditLog(command, options, result)
    
    return result
  }
  
  /** 检查权限提升 */
  private async checkEscalation(restrictions: SandboxRestrictions): Promise<EscalationResult> {
    // 检查是否需要权限提升
    if (this.needsEscalation(restrictions)) {
      return this.ctx.escalation.request({
        permissions: this.getRequiredPermissions(restrictions),
        reason: 'Command execution requires elevated permissions',
        risk: 'medium'
      })
    }
    
    return { approved: true, granted: [] }
  }
}

10.5 Escalation(权限提升)

10.5.1 流程

预览
源码
UserEscalationApproverSandboxProviderAgentUserEscalationApproverSandboxProviderAgentconfine(command, options)检查是否需要权限提升request(permissions)显示权限请求批准/拒绝result应用沙箱限制执行结果
sequenceDiagram
    participant Agent
    participant Sandbox as SandboxProvider
    participant Escalation as EscalationApprover
    participant User

    Agent->>Sandbox: confine(command, options)
    Sandbox->>Sandbox: 检查是否需要权限提升
    Sandbox->>Escalation: request(permissions)
    Escalation->>User: 显示权限请求
    User->>Escalation: 批准/拒绝
    Escalation-->>Sandbox: result
    Sandbox->>Sandbox: 应用沙箱限制
    Sandbox->>Agent: 执行结果

10.5.2 EscalationApprover

// 来自 packages/sandbox/sandbox/src/escalation.ts
interface EscalationApprover {
  /** 请求权限提升 */
  request(options: EscalationRequest): Promise<EscalationResult>
}

interface EscalationRequest {
  /** 请求的权限 */
  permissions: string[]
  /** 原因 */
  reason: string
  /** 风险等级 */
  risk: 'low' | 'medium' | 'high'
}

interface EscalationResult {
  /** 是否批准 */
  approved: boolean
  /** 授予的权限 */
  granted: string[]
}

10.5.3 权限类型

type Permission =
  | 'file:read'           // 读取文件
  | 'file:write'          // 写入文件
  | 'file:execute'        // 执行文件
  | 'network:outbound'    // 出站网络
  | 'network:inbound'     // 入站网络
  | 'process:spawn'       // 创建进程
  | 'memory:unlimited'    // 无限内存

10.6 进程树隔离

10.6.1 概念

每个 Agent 的执行都在独立的进程树中,确保:

  1. 信号隔离:一个 Agent 的 SIGTERM 不影响其他 Agent

  2. 资源隔离:每个进程树有独立的资源限制

  3. 清理隔离:进程树结束时,所有子进程被清理

预览
源码

进程树 2 (Agent B)

进程树 1 (Agent A)

信号隔离

Agent 主进程

子进程 1

子进程 2

Agent 主进程

子进程 1

graph TB
    subgraph "进程树 1 (Agent A)"
        A1[Agent 主进程]
        A2[子进程 1]
        A3[子进程 2]
        A1 --> A2
        A1 --> A3
    end

    subgraph "进程树 2 (Agent B)"
        B1[Agent 主进程]
        B2[子进程 1]
        B1 --> B2
    end

    A1 -.->|"信号隔离"| B1

10.6.2 进程组管理

// 来自 packages/subprocess/subprocess-local/src/index.ts(概念性)
class LocalSubprocessProvider {
  /** 启动进程组 */
  async spawnGroup(command: string, options: SpawnOptions): Promise<RunningProcessGroup> {
    // 创建新的进程组
    const group = new ProcessGroup()
    
    // 启动主进程
    const mainProcess = spawn(command, {
      ...options,
      detached: true,  // 创建新进程组
      stdio: ['pipe', 'pipe', 'pipe']
    })
    
    group.add(mainProcess)
    
    // 监听子进程
    mainProcess.on('spawn', (child) => {
      group.add(child)
    })
    
    return {
      main: mainProcess,
      group,
      async kill(signal = 'SIGTERM') {
        // 杀死整个进程树
        group.killAll(signal)
      }
    }
  }
}

10.7 与 Guard 机制的配合

10.7.1 Guard 检查点

预览
源码

重复调用

超时

通过

需要权限

不需要

批准

拒绝

Agent 执行

Guard 检查

拒绝执行

强制终止

沙箱检查

请求 Escalation

执行命令

拒绝执行

记录审计日志

graph TB
    A[Agent 执行] --> B{Guard 检查}
    B -->|"重复调用"| C[拒绝执行]
    B -->|"超时"| D[强制终止]
    B -->|"通过"| E{沙箱检查}
    E -->|"需要权限"| F[请求 Escalation]
    E -->|"不需要"| G[执行命令]
    F -->|"批准"| G
    F -->|"拒绝"| H[拒绝执行]
    G --> I[记录审计日志]

10.7.2 集成示例

// 概念性描述:Guard 和 Sandbox 的集成
async function executeWithGuardAndSandbox(
  ctx: Context,
  command: string,
  options: ExecutionOptions
): Promise<ExecutionResult> {
  // 1. Guard 检查
  const guardResult = await ctx.guard.check(command, options)
  if (!guardResult.allowed) {
    return { error: guardResult.reason }
  }
  
  // 2. 沙箱执行
  const sandboxResult = await ctx.sandbox.confine(command, {
    ...options,
    restrictions: options.restrictions
  })
  
  // 3. 审计日志
  await ctx.audit.log({
    command,
    options,
    result: sandboxResult,
    timestamp: new Date()
  })
  
  return sandboxResult
}

10.8 配置

10.8.1 沙箱配置

# cordis.patch.yml
- id: sandbox
  config:
    # 沙箱类型
    type: 'auto'  # 'auto' | 'landlock' | 'seatbelt' | 'bwrap' | 'none'
    
    # 默认限制
    defaults:
      allowedPaths:
        - '/tmp'
        - '/workspace'
      deniedPaths:
        - '/etc/shadow'
        - '/root'
      allowedNetwork: false
      allowedProcessSpawn: true
      maxMemory: 536870912  # 512MB
      maxCpuTime: 30000     # 30s
    
    # 特定命令的覆盖
    overrides:
      'bash':
        allowedPaths:
          - '/tmp'
          - '/workspace'
          - '/usr'
        allowedNetwork: true

10.8.2 Escalation 配置

# cordis.patch.yml
- id: escalation
  config:
    # 审批模式
    mode: 'interactive'  # 'interactive' | 'auto-approve' | 'auto-deny'
    
    # 自动批准的权限
    autoApprove:
      - 'file:read'
    
    # 自动拒绝的权限
    autoDeny:
      - 'network:inbound'
      - 'memory:unlimited'

10.9 小结

概念一句话解释
SandboxService沙箱服务接口
LandlockLinux 内核级沙箱
SeatbeltmacOS 沙箱机制
bwrap用户空间沙箱工具
Escalation权限提升审批机制
进程树隔离每个 Agent 独立的进程树
Guard循环卫生检查

下一步第十一章:Session 日志深度解析——深入理解 JSONL 格式、事件序列、投影算法和日志重放。

DeepSeek-Harness / 10-安全沙箱与隔离 0 0 iliuqi
2026-09-04T07:48:53.324986980Z 2026-09-04T07:57:49.823629447Z