Agent 沙箱隔离技术:Docker、gVisor 与 WASM 方案对比
深入对比 AI Agent 沙箱隔离的三种主流方案,分析安全性、性能和易用性的权衡,帮助选择最适合的隔离技术。
当 AI Agent 获得执行代码、操作文件系统、调用外部 API 的能力时,一个关键问题随之而来:如何防止 Agent 的操作越界?沙箱(Sandbox)隔离技术为 Agent 提供了一个受控的执行环境,确保即使 Agent 做出错误决策,也不会损害宿主系统。
为什么需要沙箱
┌──────────────────────────────────────────┐
│ Agent 操作风险 │
│ │
│ 代码执行 → 可能访问文件系统 │
│ 工具调用 → 可能执行危险命令 │
│ API 调用 → 可能泄露敏感数据 │
│ 文件操作 → 可能覆盖重要文件 │
│ │
│ ┌──────────────────────────────────┐ │
│ │ 沙箱隔离层 │ │
│ │ 限制文件访问 | 网络隔离 │ │
│ │ 资源限制 | 权限控制 │ │
│ └──────────────────────────────────┘ │
└──────────────────────────────────────────┘
方案对比
| 特性 | Docker | gVisor | WASM |
|---|---|---|---|
| 隔离级别 | 进程级 | 内核级 | 字节码级 |
| 启动速度 | 秒级 | 秒级 | 毫秒级 |
| 内存开销 | 50-200MB | 30-100MB | 1-10MB |
| 系统调用 | 完整 | 受限 | 受限 |
| 安全性 | 中 | 高 | 高 |
| 生态系统 | 完善 | 成长中 | 早期 |
| 适合场景 | 通用 | 不可信代码 | 轻量级函数 |
Docker 沙箱
基础配置
class DockerSandbox {
private docker: Docker;
async create(config: SandboxConfig): Promise<Sandbox> {
const container = await this.docker.createContainer({
Image: config.image || 'node:20-slim',
Cmd: config.command || ['/bin/bash'],
HostConfig: {
// 资源限制
Memory: config.memoryLimit || 256 * 1024 * 1024, // 256MB
CpuQuota: config.cpuQuota || 50000, // 50% CPU
PidsLimit: config.pidLimit || 100,
// 网络隔离
NetworkMode: config.networkEnabled ? 'bridge' : 'none',
// 只读根文件系统
ReadonlyRootfs: !config.writableRootfs,
// 安全选项
SecurityOpt: ['no-new-privileges'],
CapDrop: ['ALL'],
CapAdd: config.capabilities || [],
// 挂载卷
Binds: config.volumes || [],
},
});
await container.start();
return new Sandbox(container);
}
async execute(sandbox: Sandbox, command: string): Promise<ExecResult> {
const exec = await sandbox.container.exec({
Cmd: ['sh', '-c', command],
AttachStdout: true,
AttachStderr: true,
});
const stream = await exec.start({ Detach: false });
return this.readStream(stream);
}
}
网络策略
class NetworkPolicy {
private allowedDomains: Set<string> = new Set();
private blockedPorts: Set<number> = new Set([22, 3389, 5900]);
createIptablesRules(): string[] {
const rules: string[] = [];
// 默认拒绝所有出站
rules.push('iptables -A OUTPUT -j DROP');
// 允许 DNS
rules.push('iptables -A OUTPUT -p udp --dport 53 -j ACCEPT');
// 允许特定域名(通过 IP)
for (const domain of this.allowedDomains) {
const ips = this.resolveDomain(domain);
for (const ip of ips) {
rules.push(`iptables -A OUTPUT -d ${ip} -p tcp --dport 443 -j ACCEPT`);
}
}
// 阻止危险端口
for (const port of this.blockedPorts) {
rules.push(`iptables -A OUTPUT -p tcp --dport ${port} -j DROP`);
}
return rules;
}
}
gVisor 沙箱
gVisor 在用户空间实现了一个操作系统内核,提供了比 Docker 更强的隔离:
class GVisorSandbox {
async create(config: SandboxConfig): Promise<Sandbox> {
// gVisor 通过 runsc 运行时集成 Docker
const container = await this.docker.createContainer({
Image: config.image,
HostConfig: {
Runtime: 'runsc', // 使用 gVisor 运行时
// gVisor 特有的安全配置
SecurityOpt: [
'no-new-privileges',
`seccomp=${this.createSeccompProfile()}`,
],
// 限制系统调用
ReadonlyRootfs: true,
NetworkMode: 'none',
},
});
await container.start();
return new Sandbox(container);
}
private createSeccompProfile(): string {
// 创建严格的 seccomp 配置文件
return JSON.stringify({
defaultAction: 'SCMP_ACT_ERRNO',
architectures: ['SCMP_ARCH_X86_64'],
syscalls: [
{
names: [
'read', 'write', 'open', 'close',
'stat', 'fstat', 'mmap', 'mprotect',
'brk', 'exit_group',
],
action: 'SCMP_ACT_ALLOW',
},
],
});
}
}
WASM 沙箱
WebAssembly 提供了最轻量级的隔离方案:
class WasmSandbox {
private runtime: WasmRuntime;
async create(config: SandboxConfig): Promise<WasmSandboxInstance> {
const module = await this.runtime.compile(config.wasmBinary);
const instance = await this.runtime.instantiate(module, {
// 导入的宿主函数
env: {
log: (msg: string) => console.log(msg),
fetch: config.networkEnabled
? (url: string) => this.restrictedFetch(url, config.allowedDomains)
: undefined,
},
// 内存限制
memory: new WebAssembly.Memory({
initial: config.memoryPages || 256, // 16MB
maximum: config.maxMemoryPages || 1024, // 64MB
}),
});
return new WasmSandboxInstance(instance);
}
async execute(instance: WasmSandboxInstance, funcName: string, args: any[]): Promise<any> {
const func = instance.exports[funcName];
if (!func) throw new Error(`Function ${funcName} not found`);
// WASM 执行天然受限:无法访问文件系统、网络等
return func(...args);
}
}
class WasmCodeExecutor {
async executeUserCode(code: string, input: any): Promise<any> {
// 将用户代码编译为 WASM
const wasmBinary = await this.compileToWasm(code);
const sandbox = new WasmSandbox();
const instance = await sandbox.create({
wasmBinary,
memoryPages: 128, // 8MB
networkEnabled: false,
});
return await instance.execute('main', [JSON.stringify(input)]);
}
private async compileToWasm(code: string): Promise<Buffer> {
// 使用 AssemblyScript 或 Rust 编译为 WASM
// 这里简化处理
return await this.compiler.compile(code, {
target: 'wasm32',
optimization: 'size',
});
}
}
资源限制
class ResourceLimiter {
private limits: Map<string, ResourceLimit> = new Map();
setLimit(sandboxId: string, limit: ResourceLimit): void {
this.limits.set(sandboxId, limit);
}
async enforce(sandboxId: string): Promise<void> {
const limit = this.limits.get(sandboxId);
if (!limit) return;
const usage = await this.getUsage(sandboxId);
if (usage.memory > limit.memory) {
throw new ResourceExceededError('memory', usage.memory, limit.memory);
}
if (usage.cpuTime > limit.cpuTime) {
throw new ResourceExceededError('cpu', usage.cpuTime, limit.cpuTime);
}
if (usage.executionTime > limit.executionTime) {
throw new ResourceExceededError('time', usage.executionTime, limit.executionTime);
}
}
private async getUsage(sandboxId: string): Promise<ResourceUsage> {
// 从 cgroup 或容器统计中获取资源使用情况
return {
memory: await this.getMemoryUsage(sandboxId),
cpuTime: await this.getCpuTime(sandboxId),
executionTime: await this.getExecutionTime(sandboxId),
};
}
}
interface ResourceLimit {
memory: number; // bytes
cpuTime: number; // milliseconds
executionTime: number; // milliseconds
diskSpace: number; // bytes
networkBandwidth: number; // bytes/sec
}
沙箱管理器
class SandboxManager {
private sandboxes: Map<string, SandboxInstance> = new Map();
private docker: DockerSandbox;
private gvisor: GVisorSandbox;
private wasm: WasmSandbox;
async createSandbox(config: SandboxConfig): Promise<string> {
const id = generateId();
let sandbox: Sandbox;
switch (config.type) {
case 'docker':
sandbox = await this.docker.create(config);
break;
case 'gvisor':
sandbox = await this.gvisor.create(config);
break;
case 'wasm':
sandbox = await this.wasm.create(config);
break;
default:
throw new Error(`Unknown sandbox type: ${config.type}`);
}
this.sandboxes.set(id, {
id,
sandbox,
config,
createdAt: Date.now(),
status: 'running',
});
return id;
}
async execute(sandboxId: string, command: string): Promise<ExecResult> {
const instance = this.sandboxes.get(sandboxId);
if (!instance) throw new Error(`Sandbox ${sandboxId} not found`);
// 检查超时
if (Date.now() - instance.createdAt > instance.config.timeout) {
await this.destroy(sandboxId);
throw new Error('Sandbox execution timeout');
}
return await instance.sandbox.execute(command);
}
async destroy(sandboxId: string): Promise<void> {
const instance = this.sandboxes.get(sandboxId);
if (instance) {
await instance.sandbox.destroy();
this.sandboxes.delete(sandboxId);
}
}
async destroyAll(): Promise<void> {
for (const [id] of this.sandboxes) {
await this.destroy(id);
}
}
}
常见问题(FAQ)
Agent 真的需要沙箱吗?
如果 Agent 要执行代码、写文件或访问网络,沙箱是必须的。即使 Agent 只读取数据,沙箱也能防止意外的系统调用。
沙箱会影响性能吗?
Docker 约增加 5-10% 开销,gVisor 约 10-20%,WASM 几乎无开销。选择取决于安全需求和性能要求。
如何选择沙箱方案?
不可信代码用 gVisor,轻量级函数用 WASM,通用场景用 Docker。生产环境通常组合使用。
总结
沙箱隔离是 Agent 安全的基石。Docker 提供了成熟的通用方案,gVisor 提供了更强的内核级隔离,WASM 提供了最轻量级的字节码隔离。根据安全需求和性能要求选择合适的方案,或组合使用以获得最佳平衡。