05. 安全执行与外部集成:权限、审批、Sandbox 与 MCP
7 min read

05. 安全执行与外部集成:权限、审批、Sandbox 与 MCP

把文件访问、代码执行、MCP 和发布动作放进统一安全模型,梳理权限边界、审批、沙箱和外部集成。

05. 安全执行与外部集成:权限、审批、Sandbox 与 MCP#

Agent 的能力越强,越不能只依赖 prompt。文件访问、代码执行、MCP 工具和发布动作都可能产生真实副作用。本篇把这些入口放在同一个安全模型里讨论。

1. 权限和提示词不是一回事#

工具描述告诉模型“可以请求什么”;permissions、服务端校验和 sandbox 决定“系统是否真的允许”。模型可能误解用户,也可能被外部网页的 prompt injection 影响,因此最终授权必须在工具和运行时边界执行。

2. 文件权限:首个匹配规则#

typescript
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. 路径规范化#

typescript
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:

typescript
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

typescript
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. 审批生命周期#

text
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 配置:

typescript
import { createCodeInterpreterMiddleware } from "@langchain/quickjs";

const agent = createDeepAgent({
  model,
  middleware: [createCodeInterpreterMiddleware()],
  systemPrompt: "解释器只用于计算已提供的数据,不访问网络,不读取宿主机文件。",
});

需要 shell 时才使用隔离 Sandbox,并明确 CPU、内存、磁盘、时长、网络域名、凭据注入和销毁策略。不要用本机 shell 代替沙箱。

8. MCP 接入#

MCP adapter 负责发现并转换工具,之后它们和普通 LangChain tools 一样进入 Agent:

typescript
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 时使用领域前缀,避免多个 searchcreate 造成路由歧义。

9. 外部集成的信任模型#

把 MCP server 分为只读资料、内部业务读写、第三方副作用三类,分别配置凭据、网络、超时和审批。server 返回的文本仍可能包含 prompt injection,必须当作数据而不是系统指令。启动时记录 server 版本和工具清单,升级后重新跑只读、超时、拒绝和恢复测试。

本章练习与验收

  1. 只允许 /workspace/report/** 写入,测试 .env、其他目录和报告文件。
  2. 为发布工具实现 approve、edit、reject,并验证同一 thread 恢复。
  3. 对比 Interpreter、Sandbox 和 LocalShell 的权限边界。
  4. 接入一个只读 MCP server,再人为关闭它验证错误处理。

验收标准:没有审批不会产生高风险副作用;拒绝不会伪装成成功;路径越权被拒绝;解释器不被当作 shell;MCP 不会因为协议标准化而自动获得信任。

相关文章