AI 是确定性房子中的非确定性访客:停止构建聊天机器人,开始构建沙盒
Source: Dev.to

信号:具有法律约束力的幻觉
最近,一家大型航空公司的客户支持聊天机器人“幻觉”出了一条关于丧葬票价政策的错误信息。客户要求退款,航空公司拒绝,仲裁庭裁定支持客户。该聊天机器人被认定为公司的法律代理人。
问题不在于大型语言模型产生了幻觉,而在于它被允许 直接 与客户以及数据库对话,且没有任何监管。当你让一个非确定性的访客在没有约束的情况下进入你的确定性系统时,你将对由此引发的灾难承担法律和财务责任。
我们必须停止把 AI 当作开放式的“聊天”界面来使用,而应把它视为 不可信、极易波动的代码执行。
第 1 阶段:架构赌注
-
供应商陷阱 – Chat Completion API。
它鼓励你构建开放的文本框,让用户可以询问任何内容,AI 也会返回任何内容。它依赖 system prompts 来强制行为——就像让小偷在离开时请把门锁上。 -
所有权路径 – Isolate Sandbox。
我们不需要对话者;我们需要一个函数,接受输入,在加密和内存硬化的环境中运行,并输出严格类型的负载,我们会在它触及主线程之前进行验证。
第2阶段:安全审计(为何您当前的沙箱是风险)
上周我提议使用原生 Node.js vm 模块 来对代理输出进行沙箱隔离。我们的首席 QA 与安全测试员把该 Pull Request 撕得粉碎。以下是迫使我们进行架构重写的审计报告:
高级测试员审计报告
| 严重性 | 问题 | 详情 |
|---|---|---|
| 严重 | 沙箱逃逸 | 原生 Node.js vm 模块 不是 安全边界。官方文档明确指出:“不要用它来运行不可信的代码。”LLM 可以轻易产生原型污染攻击,遍历原型链,并在宿主机器上执行远程代码执行(RCE)。 |
| 严重 | 事件循环拒绝服务 | vm.runInContext 在主线程上运行。如果 LLM 生成一个简单的 while(true){} 循环,它将完全阻塞 Node.js 事件循环。您的服务器会立即丢失所有活跃的用户连接。 |
| 高 | 状态损坏 | 将活对象(例如数据库连接)传入 VM 上下文会允许代理全局修改它们。 |
结论: 我们不能使用原生 Node.js 工具。必须降级到 C++ V8 引擎 级别。
第三阶段:生产实现(V8 隔离)
为了构建真正的 “Boss Battle” 竞技场,我们使用 isolated‑vm。它会创建一个完全独立的 V8 JavaScript 引擎实例,拥有自己的内存堆。如果 AI 触发无限循环或尝试逃逸,我们可以直接终止该 isolate 线程,而不会影响主 Node.js 服务器。
const ivm = require('isolated-vm');
const { trace } = require('@opentelemetry/api');
const tracer = trace.getTracer('ai.hardened_sandbox');
class FortressSandbox {
constructor(memoryLimitMB = 64, timeoutMs = 1500) {
this.memoryLimitMB = memoryLimitMB;
this.timeoutMs = timeoutMs;
}
async executeUntrustedAgent(aiGeneratedLogic, safeInputPayload) {
return tracer.startActiveSpan('v8_isolate_execution', async (span) => {
// 1. Hard Boundary – create a separate V8 heap
const isolate = new ivm.Isolate({ memoryLimit: this.memoryLimitMB });
const context = isolate.createContextSync();
const jail = context.global;
try {
// 2. State Management – pass data as deeply cloned strings, NEVER by reference
jail.setSync('global', jail.derefInto());
jail.setSync('_inputData', JSON.stringify(safeInputPayload));
// 3. Compile the Agent's logic
const script = isolate.compileScriptSync(`
// Agent must parse input, do its logic, and return a stringified result
const input = JSON.parse(_inputData);
let output = {};
${aiGeneratedLogic}
JSON.stringify(output);
`);
// 4. Dead‑Man’s Switch – run with strict timeout
// If it loops infinitely, the isolate is terminated. Main thread survives.
const resultStr = script.runSync(context, { timeout: this.timeoutMs });
span.setAttribute('sandbox.status', 'success');
return JSON.parse(resultStr);
} catch (error) {
span.recordException(error);
span.setAttribute('sandbox.status', 'terminated');
// The guest tried to burn the house down. The house won.
return {
error: `GUARD INTERVENTION: Agent execution terminated. Reason: ${error.message}`
};
} finally {
// 5. Memory Cleanup – destroy the arena
isolate.dispose();
span.end();
}
});
}
}
// Example Usage:
// const fortress = new FortressSandbox();
// const output = await fortress.executeUntrustedAgent(
// "output.action = 'refund'; output.amount = input.amount;",
// { amount: 500 }
// );
已清理的 Markdown 结束。
第四阶段:检查清单(接下来要构建的内容)
-
实现 Zod 出口过滤
FortressSandbox的输出在代码执行层面是安全的,但数据仍然不可信。将输出直接传入 Zod 模式验证器。如果验证失败,丢弃请求。 -
基于尾部的 OTel 采样
沙箱会经常失败(设计如此)。配置你的 OpenTelemetry 收集器,仅对sandbox.status === 'terminated'保存完整的追踪跨度,以降低 Datadog/Honeycomb 成本。 -
多代理防火墙
如果 Agent A 将数据传递给 Agent B,必须在中间进行模式检查。绝不让两个代理共享同一个 V8 隔离内存空间。
结论:
将 LLM 输出视为 1999 年公共互联网的用户输入。对其进行清理、隔离,并默认假设它们是恶意的。构建防护。限制来客。
