05. 安全执行与外部集成:权限、审批、Sandbox 与 MCP#
Agent 的能力越强,越不能只依赖 prompt。文件访问、代码执行、MCP 工具和发布动作都可能产生真实副作用。本篇把这些入口放在同一个安全模型里讨论。
1. 权限和提示词不是一回事#
工具描述告诉模型“可以请求什么”;permissions、服务端校验和 sandbox 决定“系统是否真的允许”。模型可能误解用户,也可能被外部网页的 prompt injection 影响,因此最终授权必须在工具和运行时边界执行。
2. 文件权限:首个匹配规则#
const agent = createDeepAgent({
model,
permissions: [
{ operations: ["read", "write"], paths: ["/.env", "/**/.env"], mode: "deny" },
{ operations: ["write"], paths: ["/workspace/report/**"], mode: "allow" },
{ operations: ["read"], paths: ["/workspace/**"], mode: "allow" },
{ operations: ["write"], paths: ["/**"], mode: "deny" },
],
});规则按声明顺序匹配,首个匹配决定结果;没有匹配的行为要结合当前版本语义显式处理。生产配置先保护 .env、凭据和用户数据,再开放业务目录,最后写 catch-all 策略。权限只作用于 Deep Agent 内置文件工具,不等价于 shell 沙箱。
3. 路径规范化#
function normalizeAgentPath(input: string) {
if (!input.startsWith("/")) throw new Error("path must be absolute");
if (input.split("/").includes("..")) throw new Error("parent traversal forbidden");
return input.replace(/\\+/g, "/");
}应用层、backend、rootDir 和 permissions 都应检查边界。不要把用户路径直接拼接到宿主机目录,也不要只用字符串前缀判断路径归属。
4. Human-in-the-loop#
删除、发信、发布和部署等高风险工具需要 interrupt:
import { MemorySaver } from "@langchain/langgraph";
const agent = createDeepAgent({
model,
tools: [removeFile, publishReport],
interruptOn: {
remove_file: { allowedDecisions: ["approve", "reject"] },
publish_report: { allowedDecisions: ["approve", "edit", "reject"] },
},
checkpointer: new MemorySaver(),
});中断结果包含 action requests。恢复时必须使用相同 thread_id:
import { Command } from "@langchain/langgraph";
const config = { configurable: { thread_id: "thread-123" } };
let result = await agent.invoke({
messages: [{ role: "user", content: "发布报告" }],
}, config);
if (result.__interrupt__) {
result = await agent.invoke(new Command({
resume: {
decisions: [{ type: "reject", message: "本次只保存草稿,不发布。" }],
},
}), config);
}approve 使用原参数;edit 修改后执行;reject 跳过调用并把拒绝原因反馈给模型。多个请求时 decisions 要按 action request 顺序提供。MemorySaver 只适合本地演示,生产需要持久化 checkpointer。
5. 审批生命周期#
tool_requested
→ interrupt_persisted
→ waiting_for_human
→ approved | edited | rejected | expired
→ executed | skipped | failed审批 UI 应显示工具名称、规范化参数、用户、thread、预计影响、允许决策和过期时间。等待期间再次校验权限和参数;编辑值也必须经过 schema 和业务校验。审批记录保存操作者、参数 hash、时间和最终执行结果,同时脱敏。
6. 副作用工具的两阶段设计#
高风险动作可以拆为 preview 和 commit:先生成待发布内容与影响范围,人工审批 preview;批准后由确定性服务用幂等键 commit。这样即使 Agent 恢复或重试,也不会重复发布。
7. Interpreter、Sandbox 和 LocalShell#
| 能力 | 适合 | 不应假设 |
|---|---|---|
| QuickJS Interpreter | 循环、聚合、确定性转换 | 没有宿主机文件、网络和 shell |
| Sandbox backend | 测试、CLI、依赖和项目文件 | 默认资源和网络安全 |
LocalShellBackend | 受控本地开发 | 适合多租户不可信输入 |
QuickJS 配置:
import { createCodeInterpreterMiddleware } from "@langchain/quickjs";
const agent = createDeepAgent({
model,
middleware: [createCodeInterpreterMiddleware()],
systemPrompt: "解释器只用于计算已提供的数据,不访问网络,不读取宿主机文件。",
});需要 shell 时才使用隔离 Sandbox,并明确 CPU、内存、磁盘、时长、网络域名、凭据注入和销毁策略。不要用本机 shell 代替沙箱。
8. MCP 接入#
MCP adapter 负责发现并转换工具,之后它们和普通 LangChain tools 一样进入 Agent:
import { MultiServerMCPClient } from "@langchain/mcp-adapters";
const client = new MultiServerMCPClient({
docs: {
transport: "stdio",
command: "node",
args: ["./mcp/docs-server.js"],
},
});
const mcpTools = await client.getTools();
const agent = createDeepAgent({
model,
tools: mcpTools,
systemPrompt: "使用 docs 工具查资料,避免写入外部系统。",
});不同 adapter 版本的 transport 配置可能变化,以当前 LangChain MCP 文档为准。连接多个 server 时使用领域前缀,避免多个 search 或 create 造成路由歧义。
9. 外部集成的信任模型#
把 MCP server 分为只读资料、内部业务读写、第三方副作用三类,分别配置凭据、网络、超时和审批。server 返回的文本仍可能包含 prompt injection,必须当作数据而不是系统指令。启动时记录 server 版本和工具清单,升级后重新跑只读、超时、拒绝和恢复测试。
本章练习与验收
- 只允许
/workspace/report/**写入,测试.env、其他目录和报告文件。 - 为发布工具实现 approve、edit、reject,并验证同一 thread 恢复。
- 对比 Interpreter、Sandbox 和 LocalShell 的权限边界。
- 接入一个只读 MCP server,再人为关闭它验证错误处理。
验收标准:没有审批不会产生高风险副作用;拒绝不会伪装成成功;路径越权被拒绝;解释器不被当作 shell;MCP 不会因为协议标准化而自动获得信任。