Harness Engineering-第15章 沙箱、隔离与防御性编程

《Harness Engineering --- AI Agent 工程方法论》完整目录

第15章 沙箱、隔离与防御性编程

"Security is not a feature --- it's a constraint that shapes every design decision."

:::tip 本章要点

  • 沙箱是权限模型的物理执行层------权限决定"允许做什么",沙箱保证"只能做什么"
  • OS 级沙箱(macOS Seatbelt、Linux seccomp)提供最强隔离
  • 命令过滤(正则匹配 + 黑白名单)是最常用的轻量方案
  • 纵深防御:权限模型 + 沙箱 + 命令过滤 + 用户确认,多层叠加 :::

15.1 为什么需要沙箱

权限模型(第14章)解决的是"是否允许"的问题。但权限判断是在 Harness 层面做的------如果模型想出了绕过 Harness 的方式呢?

makefile 复制代码
模型意图: 读取 /etc/passwd
权限检查: Read 工具不允许读取系统文件 ✓ 已拦截
模型意图: 通过 Bash 执行 cat /etc/passwd
权限检查: Bash 工具允许执行命令... 但命令内容包含敏感路径

沙箱在操作系统层面提供了第二道防线------即使 Harness 层的检查被绕过,OS 级限制仍然生效。

15.2 OS 级沙箱

macOS Seatbelt

Claude Code 在 macOS 上使用 Seatbelt(sandbox-exec)来限制 Bash 命令的能力:

scheme 复制代码
;; 简化的 Seatbelt profile
(version 1)
(deny default)                          ; 默认拒绝一切

(allow file-read*
  (subpath "/Users/yyt/project"))       ; 只允许读项目目录

(allow file-write*
  (subpath "/Users/yyt/project")        ; 只允许写项目目录
  (subpath "/tmp"))                     ; 和临时目录

(deny file-read*
  (subpath "/Users/yyt/.ssh")           ; 明确禁止读 SSH 密钥
  (subpath "/Users/yyt/.aws"))          ; 明确禁止读 AWS 凭证

(allow network-outbound
  (remote tcp "localhost:*"))           ; 只允许本地网络
(deny network-outbound)                 ; 禁止外部网络

执行命令时包裹在 sandbox 中:

typescript 复制代码
async function executeSandboxed(command: string): Promise<ExecResult> {
  const profile = generateSeatbeltProfile(context)
  return exec(`sandbox-exec -f ${profile} bash -c "${command}"`)
}

Linux 方案

Linux 环境下有多种选择:

bash 复制代码
# Docker 容器隔离
docker run --rm \
  --network none \                    # 禁止网络
  --read-only \                       # 只读文件系统
  -v /project:/workspace:rw \         # 只挂载项目目录
  --memory 512m \                     # 限制内存
  --cpus 1 \                          # 限制 CPU
  agent-sandbox bash -c "$COMMAND"

# 或者用 firejail(更轻量)
firejail --noprofile \
  --whitelist=/project \
  --net=none \
  bash -c "$COMMAND"

沙箱方案对比

方案 隔离强度 性能开销 适用场景 平台
macOS Seatbelt 极低(内核级) Claude Code 桌面 macOS
Docker 很高 中(容器启动 ~200ms) CI/CD、云端部署 Linux
firejail 低(进程级) 轻量级 Linux 隔离 Linux
seccomp-bpf 极高 极低 精细系统调用过滤 Linux
纯代码层限制 无法使用 OS 沙箱时的兜底 任意
graph TD subgraph "隔离强度递增" L1["代码层验证\n(路径检查, 命令过滤)"] --> L2["firejail\n(进程沙箱)"] L2 --> L3["Seatbelt / seccomp\n(内核强制)"] L3 --> L4["Docker\n(容器隔离)"] L4 --> L5["VM / microVM\n(虚拟化)"] end style L1 fill:#fef3c7,stroke:#f59e0b style L3 fill:#dbeafe,stroke:#3b82f6 style L5 fill:#dcfce7,stroke:#22c55e

选择建议:桌面 Agent(如 Claude Code)用 Seatbelt/firejail,云端 Agent 用 Docker,高安全场景用 microVM(如 Firecracker)。 无论选哪种,都应该在外层叠加代码级验证作为兜底。

15.3 文件系统隔离

限制 Agent 只能访问项目目录及其子目录:

typescript 复制代码
function validateFilePath(requestedPath: string, projectRoot: string): boolean {
  const resolved = path.resolve(requestedPath)
  const root = path.resolve(projectRoot)

  // 必须在项目目录内
  if (!resolved.startsWith(root + path.sep) && resolved !== root) {
    return false
  }

  // 黑名单:即使在项目内也不能访问
  const BLOCKED_PATTERNS = [
    /\.env$/,              // 环境变量文件
    /\.env\..+$/,          // .env.local, .env.production
    /credentials/i,        // 凭证文件
    /secrets/i,            // 密钥文件
    /\.pem$/,              // 证书
    /\.key$/,              // 私钥
  ]

  const basename = path.basename(resolved)
  return !BLOCKED_PATTERNS.some(p => p.test(basename))
}

Claude Code 的 Read 工具要求路径必须是绝对路径,并在执行前验证路径合法性。

15.4 命令过滤

Bash 工具是最危险的工具------它能执行任意命令。必须有过滤机制。

黑名单模式

typescript 复制代码
const BLOCKED_COMMANDS = [
  /\brm\s+-rf\s+[\/~]/,         // rm -rf / 或 rm -rf ~
  /\bcurl\b.*\|\s*bash/,         // curl | bash(远程代码执行)
  /\bchmod\s+777/,               // 过于宽松的权限
  /\bsudo\b/,                    // 提权操作
  /\bkill\s+-9\s+1\b/,           // 杀 init 进程
  /\bdd\b.*of=\/dev/,            // 直接写磁盘设备
  /\bmkfs\b/,                    // 格式化文件系统
  />\s*\/etc\//,                 // 重定向写入系统目录
  /\bssh\b.*@/,                  // SSH 到远程主机
]

function isCommandBlocked(command: string): boolean {
  return BLOCKED_COMMANDS.some(pattern => pattern.test(command))
}

白名单模式(更安全)

只允许已知安全的命令前缀:

typescript 复制代码
const ALLOWED_PREFIXES = [
  'node', 'npm', 'npx', 'yarn', 'pnpm',
  'python', 'pip', 'pytest',
  'cargo', 'rustc',
  'git', 'grep', 'find', 'ls', 'cat', 'head', 'tail',
  'echo', 'pwd', 'which', 'env',
]

function isCommandAllowed(command: string): boolean {
  const firstWord = command.trim().split(/\s/)[0]
  return ALLOWED_PREFIXES.includes(firstWord)
}

Claude Code 的混合方案

Claude Code 结合两种模式:

  1. 用户可通过权限规则设置允许/禁止的命令模式
  2. 系统内置一组硬编码的危险命令黑名单
  3. 未匹配任何规则的命令需要用户确认

15.5 进程资源限制

防止 Agent 启动的进程消耗过多资源:

typescript 复制代码
const PROCESS_LIMITS = {
  timeout: 120_000,        // 单个命令最长 2 分钟
  maxOutputSize: 1_000_000, // 输出最大 1MB
  maxConcurrent: 5,         // 最多 5 个并发进程
}

async function executeWithLimits(
  command: string
): Promise<ExecResult> {
  const controller = new AbortController()
  const timer = setTimeout(
    () => controller.abort(),
    PROCESS_LIMITS.timeout
  )

  try {
    const result = await exec(command, {
      signal: controller.signal,
      maxBuffer: PROCESS_LIMITS.maxOutputSize,
    })
    return result
  } catch (e) {
    if (e.name === 'AbortError') {
      return { exitCode: -1, output: 'Command timed out' }
    }
    throw e
  } finally {
    clearTimeout(timer)
  }
}

15.6 网络隔离

Agent 是否应该能访问网络?这取决于使用场景:

  • 代码编辑 Agent:通常不需要网络,禁止更安全
  • 研究 Agent:需要搜索和获取网页,但应限制目标域名
  • 部署 Agent:需要 SSH/SCP 到服务器,但只限特定主机
typescript 复制代码
const NETWORK_POLICY = {
  allowLocalhost: true,     // 本地开发服务器
  allowedDomains: [
    'api.github.com',       // GitHub API
    'registry.npmjs.org',   // npm registry
  ],
  blockedPorts: [22, 3306, 5432, 6379],  // SSH, MySQL, PostgreSQL, Redis
}

15.7 纵深防御

单层防御不可靠。成熟的 Agent 系统采用纵深防御:

flowchart TD R["Agent 请求"] --> L1{"① 权限模型\n操作是否被允许?"} L1 -->|拒绝| X["❌ 阻止"] L1 -->|通过| L2{"② 命令过滤\n命令是否安全?"} L2 -->|危险| X L2 -->|通过| L3{"③ 路径验证\n目标在允许范围内?"} L3 -->|越界| X L3 -->|通过| L4{"④ OS 沙箱\n系统级限制"} L4 -->|违反| X L4 -->|通过| L5{"⑤ 用户确认\n高风险操作"} L5 -->|拒绝| X L5 -->|批准| OK["✅ 执行"] style L1 fill:#fee2e2,stroke:#ef4444 style L2 fill:#fef3c7,stroke:#f59e0b style L3 fill:#fef9c3,stroke:#eab308 style L4 fill:#dcfce7,stroke:#22c55e style L5 fill:#dbeafe,stroke:#3b82f6

更详细地说:

yaml 复制代码
Layer 1: 权限模型
  ↓ 这个操作是否被允许?
Layer 2: 命令过滤
  ↓ 这个具体命令是否安全?
Layer 3: 路径验证
  ↓ 目标文件是否在允许范围内?
Layer 4: OS 沙箱
  ↓ 即使通过了前三层,OS 层面还有限制
Layer 5: 用户确认
  ↓ 对于高风险操作,最终由人类决定

每一层独立工作------即使某一层被绕过,后续层仍然提供保护。

Claude Code 的实际防御链路:

bash 复制代码
用户说 "删除 node_modules"
  → 权限检查: Bash 工具在当前模式下是否允许?
  → 命令分析: rm -rf 是否匹配危险命令模式?
  → 可逆性评估: 这是一个可逆操作吗?(是,可以 npm install 恢复)
  → 决策: 需要用户确认
  → 用户确认后执行
  → 沙箱内执行: 只能删除项目目录内的文件

15.8 防御性编程实践

工具实现的防御原则

typescript 复制代码
// ❌ 信任模型输入
async function deleteFile(path: string) {
  await fs.unlink(path)  // 如果 path 是 /etc/passwd 呢?
}

// ✅ 验证一切输入
async function deleteFile(path: string, context: Context) {
  const resolved = path.resolve(path)

  // 1. 路径必须在项目内
  if (!resolved.startsWith(context.projectRoot)) {
    throw new Error('Path outside project directory')
  }

  // 2. 不能是受保护的文件
  if (isProtectedFile(resolved)) {
    throw new Error('Cannot delete protected file')
  }

  // 3. 记录操作日志
  audit.log('file.delete', { path: resolved, user: context.userId })

  await fs.unlink(resolved)
}

最小权限原则

每个工具只应拥有完成其功能所需的最小权限:

  • Read 工具:只有读权限,不能写
  • Edit 工具:只能修改已有文件的部分内容,不能创建新文件
  • Write 工具:可以创建新文件,但需要先 Read 过已有文件
  • Bash 工具:在沙箱中执行,有超时限制

失败安全

当安全检查出错时,应该拒绝 而非允许

typescript 复制代码
function checkPermission(action: Action): boolean {
  try {
    return evaluateRules(action)
  } catch (error) {
    // 安全检查本身出错时,默认拒绝
    log.warn('Permission check failed, denying by default', error)
    return false
  }
}

15.9 安全 vs 可用性的平衡

过度安全会让 Agent 变得无用:

makefile 复制代码
场景: Agent 需要安装一个 npm 包
过度安全: "禁止执行 npm install,可能下载恶意代码"
合理安全: "允许 npm install,但禁止 npm install --global"

平衡的原则:

  • 开发环境宽松,生产环境严格
  • 可逆操作宽松,不可逆操作严格
  • 用户在场宽松,无人值守严格
  • 已知命令宽松,未知命令严格

15.10 本章小结

沙箱隔离是 Agent 安全的最后一道物理防线:

  1. OS 沙箱 提供最强隔离------Seatbelt、seccomp、Docker
  2. 文件系统隔离 限制 Agent 只能访问项目目录
  3. 命令过滤 拦截已知危险的 Shell 命令
  4. 资源限制 防止 Agent 进程失控
  5. 纵深防御 多层叠加,任何一层被绕过都有后续保护
  6. 失败安全 安全检查出错时默认拒绝
  7. 平衡可用性 安全不是目的,可控的能力才是

下一章我们将从单 Agent 扩展到多 Agent,看看如何协调多个 Agent 高效协作。

相关推荐
Memory_荒年3 小时前
从 Java 开发到 AI 工程师:我的大模型入坑指南
agent
杨艺韬4 小时前
Harness Engineering-第7章 工具结果处理与错误恢复
agent
杨艺韬4 小时前
Harness Engineering-第12章 长期记忆:持久化与检索
agent
杨艺韬4 小时前
Harness Engineering-第14章 Agent 权限模型设计
agent
杨艺韬4 小时前
Harness Engineering-第8章 System Prompt 分层设计
agent
杨艺韬4 小时前
vLLM内核探秘-第14章 张量并行与流水线并行
agent
杨艺韬4 小时前
vLLM内核探秘-第12章 投机解码:以小博大
agent
杨艺韬4 小时前
vLLM内核探秘-第7章 模型加载与权重管理
agent
storyseek4 小时前
拆解 DeerFlowd:一个开源 Super Agent Harness 是怎么做出来的
agent·harness