过去一周,三份简报里反复出现同一个信号:MCP 的安全债务已经从「没鉴权」升级到「架构级供应链风险」。OX Security 把 STDIO 传输的命令注入称为 the mother of all AI supply chains;Codersera 的 2026 审计显示 43% 的 MCP Server 存在命令注入、四成不要求认证;Windsurf 的 CVE-2026-30615 已经实锤。与此同时,豆包和千问关停智能体入口、阿里禁用 Claude Code、Meta 承认 Agent 进展不及预期——大厂集体踩刹车,背后有一条共同逻辑:Agent 工具链的安全边界还没跟上功能扩张的速度。
如果你在用 Cursor、Claude Code 或 Windsurf 跑本地 MCP Server,这篇文章讲的是最容易被忽视、但杀伤力最大的一类漏洞。
攻击面:为什么 STDIO 比想象中危险
很多人把 MCP 的 stdio 模式当成「天然安全」:Server 跑在本机子进程里,不暴露端口,似乎比 HTTP 远程部署省心。但 stdio 只解决了网络暴露问题,没解决进程执行问题。
典型本地 MCP 启动方式长这样:
{
"mcpServers": {
"my-tool": {
"command": "npx",
"args": ["-y", "some-mcp-package@latest"]
}
}
}
Client(IDE / Agent)会 fork 子进程,把 command + args 拼起来执行。问题出在两个环节:
- 安装阶段:
npx -y会从 npm 拉最新包,README 里的 postinstall 脚本、package.json 的 bin 字段都可以执行任意命令 - 运行阶段:部分 Client 会把工具描述(tool schema / description)拼进 shell 命令或环境变量,攻击者可通过投毒的工具元数据注入
OX Security 披露的 Windsurf 案例(CVE-2026-30615)攻击链可以概括为:
恶意 MCP 包 README
→ 诱导用户 npx 安装
→ postinstall 写 ~/.config 启动项
→ 下次 IDE 启动 MCP 时零点击 RCE
更隐蔽的路径是工具描述投毒:Agent 在选工具时会把 description 字段喂给模型,如果 Server 返回的 description 里夹了 shell 元字符,而 Client 用不安全的方式拼接命令,就能在「用户完全没点运行」的情况下触发。
与远程 MCP 的安全对比
| 维度 | STDIO(本地) | Streamable HTTP / SSE(远程) |
|---|---|---|
| 网络暴露 | 无端口监听 | 需 TLS + 防火墙 |
| 鉴权 | 常被忽略(「本地=可信」) | OAuth 2.1 已成规范要求 |
| 命令执行 | Client 直接 fork shell | Server 侧隔离,Client 只发 JSON-RPC |
| 供应链 | npm/pip 包投毒风险极高 | 容器镜像 + 签名验证 |
| 审计 | 几乎无日志 | 可接入 API Gateway 日志 |
远程 MCP 要过鉴权、限流、传输加密四道关(参见 MCP Server 生产实战),但本地 stdio 往往四道关全跳过。这就是审计里「四成 Server 不要求认证」的数字来源——不是远程部署忘了鉴权,而是大量 Server 压根跑在 stdio 模式下,压根没设计鉴权层。
Anthropic 对 OX Security 披露的回应是更新 SECURITY.md,把消毒责任甩给下游 Client 开发者。翻译成人话:协议层不会帮你挡命令注入,你得自己在 Client 侧做白名单。
加固方案:Client 侧 command 白名单
不管你用 Cursor 还是自研 Agent,只要会 fork MCP Server 子进程,第一道防线是禁止任意 command 字符串。
Python 示例:白名单 + 参数校验
import shlex
import subprocess
from dataclasses import dataclass
ALLOWED_COMMANDS: dict[str, list[str]] = {
"npx": ["-y", "@modelcontextprotocol/server-filesystem"],
"uvx": ["mcp-server-git"],
"node": ["/opt/mcp/verified-server/index.js"],
}
@dataclass
class McpServerConfig:
command: str
args: list[str]
def validate_mcp_config(cfg: McpServerConfig) -> None:
if cfg.command not in ALLOWED_COMMANDS:
raise PermissionError(f"command not in allowlist: {cfg.command}")
allowed_args = ALLOWED_COMMANDS[cfg.command]
# 严格模式:args 必须完全匹配预注册配置
if cfg.args != allowed_args:
raise PermissionError(
f"args mismatch for {cfg.command}: "
f"got {cfg.args}, expected {allowed_args}"
)
def spawn_mcp_server(cfg: McpServerConfig) -> subprocess.Popen:
validate_mcp_config(cfg)
# 不用 shell=True,避免元字符注入
return subprocess.Popen(
[cfg.command, *cfg.args],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
shell=False,
env={"PATH": "/usr/local/bin:/usr/bin"}, # 最小化 PATH
)
关键原则:
shell=False:永远不要把 command 字符串交给/bin/sh -c- 固定 args:不允许用户/模型动态拼接 args,
npx -y后面的包名必须预注册 - 最小 PATH:防止 PATH 劫持替换
npx/node - 版本锁定:用
npx @scope/pkg@1.2.3而非@latest
TypeScript 示例:Cursor 配置审计脚本
团队可以 CI 里跑一个配置审计,拒绝 @latest 和未知包名:
import { readFileSync } from "fs";
interface McpEntry {
command: string;
args?: string[];
}
const ALLOWED_PACKAGES = new Set([
"@modelcontextprotocol/server-filesystem@1.0.0",
"@modelcontextprotocol/server-github@0.6.0",
]);
function auditMcpConfig(configPath: string): string[] {
const raw = JSON.parse(readFileSync(configPath, "utf-8"));
const errors: string[] = [];
for (const [name, entry] of Object.entries<McpEntry>(
raw.mcpServers ?? {}
)) {
if (entry.command === "npx" && entry.args?.[0] === "-y") {
const pkg = entry.args[1] ?? "";
if (pkg.includes("@latest") || !pkg.includes("@")) {
errors.push(`${name}: must pin exact version, got ${pkg}`);
}
if (!ALLOWED_PACKAGES.has(pkg)) {
errors.push(`${name}: package not in allowlist: ${pkg}`);
}
}
}
return errors;
}
const errs = auditMcpConfig(".cursor/mcp.json");
if (errs.length) {
console.error("MCP config audit failed:\n" + errs.join("\n"));
process.exit(1);
}
工具描述投毒:第二道防线
即使 command 白名单做好了,Server 返回的 tool schema 仍可能投毒。防御策略:
- Schema 消毒:对
description字段做长度限制 + 字符黑名单(`、$、;、|、$() - 模型侧隔离:不要把原始 tool description 直接拼进 shell;tool 调用走结构化 JSON-RPC,不走字符串模板
- Mcpsnoop 抓包:Jul 4 简报提到的 Mcpsnoop 可以做 MCP 版 Wireshark,上线新 Server 前先抓一轮流量确认没有异常字段
arXiv 2601.17549 的实测数据也值得记住:MCP 的架构选择能把攻击成功率放大 23–41%,链式攻击对 Function Calling 和 MCP 都能打到 91–96%。单点加固不够,要假设工具链会被串联利用。
和大厂「Agent 收缩」的关联
本周国内科技简报里,豆包和千问关停智能体入口、美团限制豆包、阿里禁用 Claude Code——表面是合规和产品策略,底层是同一类风险:Agent 能调外部工具后,攻击面从 prompt 扩展到整个本机执行环境。
MateClaw 1.7.0 的更新方向很有代表性:审批不能挂死、长任务不能黑盒、Token 花销不能看不见。这三条恰好对应 Agent 生产化的「三道关」:
| 关卡 | 对应风险 | MateClaw 1.7.0 做法 |
|---|---|---|
| 审批 | 工具调用无人工确认 | 审批流程防挂死 |
| 可观测 | 黑盒执行无法审计 | 长任务状态可视化 |
| 成本/安全 | Token 与权限失控 | Token 花销面板 |
MCP 供应链安全是第四道关:在工具接入层就做白名单,而不是等 RCE 了再补审批。
行动建议
今天就做(15 分钟)
- 打开
.cursor/mcp.json(或 Claude Code / Windsurf 等价配置),列出所有command+args - 把
npx -y xxx@latest全部改成固定版本号 - 删除不再使用的 Server 条目——每多一个 Server 就多一个供应链入口
本周完成(团队级)
- 把上面的 TypeScript 审计脚本接进 CI,PR 改 MCP 配置必须过审
- 新接入 Server 前用 Mcpsnoop 抓包 + 在隔离 VM 里首次运行
- 制定团队 MCP 允许列表(package 名 + 版本 + 负责人)
延伸阅读
- OX Security: MCP STDIO 供应链分析
- MCP Server 生产实战:鉴权与远程部署
- Agent 安全:Prompt 注入防御
- WebKit: Safari MCP Server 官方实现 — 厂商原生 MCP 的安全边界参考
MCP 正在从「本地玩具」变成 Agent 工具链的主动脉。STDIO 模式的便利不该以零点击 RCE 为代价——白名单不是过度工程,是 2026 年跑 Agent IDE 的最低门槛。