大厂 MCP 面试实录:基于 TypeScript SDK 与 stdio 传输防御提示注入攻击

本文采用模拟面试形式,复盘 MCP 安全方向的高频面试场景,围绕「防止提示注入诱导 MCP Tool 执行高风险操作」的核心命题展开,面向具备基础 TypeScript 开发经验的 MCP 开发者。

面试场景:某客户端团队基于 TypeScript MCP SDK v2 开发本机工具服务,通过 stdio 传输与 AI Host 通信,提供文件操作、系统信息查询等能力。近期出现安全事件:攻击者通过提示注入诱导模型调用文件删除工具,删除了用户的重要数据。面试官围绕该场景考察候选人的 MCP 安全落地能力。


面试官:今天我们考察 MCP 安全落地能力,先看这个场景:你所在的团队用 TypeScript MCP SDK 开发了一套本机工具服务,通过 stdio 传输和 AI Host 通信,最近出现了提示注入攻击,诱导工具执行了删除本地文件的危险操作。请你先说说你会从哪些层面做防御?

候选人:我会做分层防御,核心结论是把模型传入的所有内容都视为不可信输入,结合 stdio 传输的本机隔离特性,从传输、协议、工具、执行、审计五个层面切断提示注入的利用路径。具体来说: 1. 传输层利用 stdio 的特性做通道隔离,把协议消息和日志、调试信息拆到不同的流,避免日志污染通信; 2. 协议层严格校验 JSON-RPC 消息的合法性,拒绝畸形消息; 3. 工具层做权限分级和参数语义校验,高风险工具强制要求用户确认; 4. 执行层遵循最小权限原则,限制工具的访问范围; 5. 审计层记录全链路调用信息,方便事后溯源。 这个思路的核心是兼顾本机场景的灵活性和安全性,不会过度限制合法调用。

面试官:你提到传输层要利用 stdio 的特性做隔离,具体怎么实现?如果 Host 是 Electron 应用,会不会有跨进程通信的风险?

候选人:首先,stdio 传输的核心特性是 Host 启动 MCP Server 作为子进程,Server 的标准输出(stdout)专门用于传输 MCP 协议消息,调试日志必须写到标准错误(stderr),否则日志内容会混入协议消息导致通信中断,这是资料里明确提到的传输规则 [资料1]。 在 TypeScript MCP SDK 中,我们可以通过传输层初始化参数显式绑定流,示例伪代码如下:

// 伪代码:基于 TypeScript MCP SDK 的 stdio 传输初始化
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";

const transport = new StdioServerTransport({
  stdin: process.stdin,
  stdout: process.stdout,
  // 显式绑定 stderr 用于日志,避免污染协议通道
  stderr: process.stderr,
});

对于 Electron 场景的风险,主要来自两个点:一是如果 Electron 启用了渲染进程的 Node 集成,XSS 攻击可能拿到子进程的流句柄,直接注入恶意协议消息;二是子进程默认继承父进程的全部权限,可能被利用提升权限。我们的处理方式是:1. 仅允许主进程启动 MCP 子进程,禁止渲染进程直接操作流;2. 启动子进程时通过系统 API 降权,限制其只能访问用户临时目录等低敏感区域;3. 显式关闭子进程的 shell 继承,避免命令注入。

面试官:好,现在到了工具层的防御。你提到要对工具做参数校验和权限分级,但提示注入可能会绕过 schema 校验,比如攻击者在参数里注入 "; rm -rf / # 这种内容,schema 是字符串类型根本拦不住,你怎么处理?

候选人:首先明确一个核心原则:Tool 的参数 schema 只是结构约束,不能代替服务端校验和授权 [资料1]。针对这种绕过 schema 的注入,我们会做三层校验: 1. 结构校验:用 SDK 自带的 schema 校验工具,确保参数类型、必填项符合要求,过滤掉畸形结构; 2. 语义校验:针对高风险工具的参数做业务层校验,比如文件路径工具会先用 path.resolve 解析真实路径,再做路径遍历校验,确保路径在允许的白名单目录内,拒绝包含 ../ 等遍历字符的路径; 3. 权限控制:高风险工具(比如文件删除、命令执行)默认只允许操作预定义的白名单范围,比如删除工具只能操作 ~/temp 目录,命令执行工具只能执行 lscat 等预定义的低风险命令,禁止执行任意 shell 命令。 同时,我们会给高风险工具的 description 加上明确的副作用提示,比如“⚠️ 高风险操作:将删除指定路径下的文件,操作不可逆,执行前需用户明确确认”,让模型在调用时主动提醒用户,降低被诱导的概率。比如删除工具的校验逻辑伪代码如下:

// 伪代码:高风险工具的语义校验示例
server.tool("delete_file", { /* schema 定义 */ }, async (params) => {
  const realPath = path.resolve(params.filePath);
  // 路径白名单校验
  if (!realPath.startsWith(ALLOWED_DIR)) {
    throw new Error(`拒绝操作:路径 ${realPath} 不在允许操作的范围内`);
  }
  // 校验文件类型,仅允许删除普通文件
  const stat = await fs.promises.stat(realPath);
  if (!stat.isFile()) throw new Error("仅支持删除普通文件");
  // 返回需要确认的信息,由 Host 层弹出确认框
  return { content: [{ type: "text", text: `即将删除 ${realPath},请确认` }], requiresConfirmation: true };
});

这里的关键取舍是:严格的白名单会限制工具的灵活性,但对于高风险操作,安全优先级高于灵活性。

面试官:你刚才提到需要 Host 层做用户确认,但如果 Host 是自动化流程,没有人工干预的场景,怎么处理?还有,如果攻击者通过提示注入让模型连续调用多个低风险工具,组合起来完成高风险操作,比如先调用“获取系统信息”工具拿到当前用户,再调用“写文件”工具写入恶意脚本,最后调用“执行命令”工具运行,这种链式攻击怎么防御?

候选人:针对这两个问题,我们的处理思路是调用链上下文校验 + 最小权限固化: 1. 对于无人干预的自动化场景,我们会提前配置允许执行的操作白名单,只有白名单内的工具调用才会被执行,参数也必须严格匹配预定义规则,完全禁止白名单外的操作,从规则上切断注入的利用路径; 2. 针对链式攻击,我们会在 Host 层维护当前会话的调用上下文,给每次工具调用生成唯一的会话 ID,记录调用的工具、参数、结果。如果检测到多个低风险工具的组合匹配预定义的高风险 pattern(比如“获取敏感信息 + 写文件 + 执行命令”),直接拦截会话并告警; 3. 同时遵循最小权限原则,把大权限的工具拆成多个小权限的工具,比如把“执行任意命令”工具拆成“执行白名单命令”工具,仅允许 lsgrep 等只读命令,没有执行权限的工具就算被串联起来也无法完成高风险操作。 另外,我们还会配置审计日志,记录每次工具调用的调用方、时间、参数、结果,对敏感字段(比如路径中的用户名、命令中的密码)做脱敏处理,方便事后溯源 [资料1]。

面试官:现在到了可观测性和异常处理的问题。如果攻击者诱导工具执行了部分操作,比如写入了恶意文件但还没执行,你怎么快速发现?还有,stdio 传输如果被注入恶意消息,会不会导致服务崩溃?怎么处理?

候选人:针对可观测性和异常处理,我们做了四层防护: 1. 全链路日志:所有工具调用的参数、执行结果、错误信息都会记录到审计日志,敏感字段脱敏,同时日志全部写到 stderr,避免污染 stdio 的协议通道; 2. 消息合法性校验:Host 层在转发 MCP 消息前,会校验 JSON-RPC 消息的结构,拒绝超长、畸形、包含非法字符的消息,避免 Server 解析崩溃;同时设置单条消息的大小限制,防止内存溢出攻击; 3. 异常熔断:如果同一个会话短时间内调用超过阈值个数的工具,或者调用的参数包含敏感路径(比如 /etc/root),立即熔断会话,触发安全告警; 4. Web 场景的 CSP 防护:如果 Host 是 Web 应用,会按照安全最佳实践配置 Content Security Policy,设置 script-src 'self' 禁止执行内联脚本,防止 XSS 攻击注入恶意代码操作 MCP 通道 [资料2]。 对于 stdio 传输的异常,比如 Host 进程异常退出,Server 会监听 stdin 的 EOF 事件,优雅关闭子进程,避免留下僵尸进程。

面试官:最后,请你把这些方案整合成一个可落地的架构,说明适用边界和容易踩坑的细节。

候选人:整体架构分为三层: - Host 层:负责用户交互、调用链校验、用户确认、CSP 防护、消息合法性校验; - 传输层:负责 stdio 通道隔离、消息大小限制、异常关闭; - Server 层:负责工具权限分级、参数语义校验、审计日志、最小权限执行。

适用边界

这个方案主要适用于本机部署、有 Host 交互的 MCP 场景,如果是远程部署的 MCP Server,还需要额外增加认证授权、限流、会话管理、超时控制等能力 [资料1];如果是完全无人干预的自动化场景,需要牺牲部分灵活性,采用更严格的白名单规则。

关键取舍

  1. 安全与灵活性的平衡:高风险工具的白名单规则会限制使用场景,需要根据业务风险等级调整白名单的范围;
  2. 交互成本与安全的平衡:用户确认环节会增加操作步骤,低风险工具可以跳过确认,高风险工具必须确认,需要根据工具的风险等级做分级。

容易踩坑的细节

  1. 日志污染协议通道:很多开发者会把调试日志写到 stdout,导致协议消息被破坏,通信中断,必须严格把日志写到 stderr;
  2. 路径校验不严谨:直接用用户传入的路径做拼接,没有用 path.resolve 解析真实路径,会被路径遍历攻击绕过;
  3. 过度依赖 schema 校验:认为 schema 能拦住所有注入,实际上 schema 只做结构校验,业务层的语义校验必不可少。

面试官点评

考察点
  1. 对 MCP 客户端/服务端架构、传输特性的理解;
  2. 对提示注入攻击的防御思路,以及 MCP 安全边界(不可信输入、最小权限、显式确认)的掌握;
  3. 结合 TypeScript SDK 和 stdio 传输特性做方案设计的能力,以及对可观测性、异常处理、取舍的思考。
合格回答

能说出分层防御的核心思路,明确 schema 校验的局限性,提到日志写到 stderr、路径白名单等基础防御点,对 stdio 传输的特性有基本了解。

加分项

能结合链式攻击、自动化场景、Electron 特殊场景做针对性防御,提到审计日志脱敏、CSP 防护、调用链上下文校验等进阶方案,能给出可落地的伪代码示例,明确方案的适用边界和取舍。

总结

MCP 场景下的提示注入防御,核心是遵循“默认不可信”的安全原则,不能依赖单一层面的校验。对于 stdio 传输的本机场景,要充分利用其进程隔离的特性做通道隔离;对于工具层,要严格遵循最小权限原则,把高风险操作的控制权交还给用户。远程场景下还需要额外考虑认证授权、会话管理等能力,才能构建完整的 MCP 安全体系。


参考资料

  1. MCP 基础知识
  2. Security Best Practices | https://modelcontextprotocol.io/docs/tutorials/security/security_best_practices
  3. MCP Python SDK | https://github.com/modelcontextprotocol/python-sdk
  4. MCP TypeScript SDK | https://github.com/modelcontextprotocol/typescript-sdk
Logo

欢迎加入 MCP 技术社区!与志同道合者携手前行,一同解锁 MCP 技术的无限可能!

更多推荐