MCP server 上生产之前这 6 个安全检查项必须过

Claude 中文知识站 Lv4

2025 年 11 月某个周二下午 3 点 47 分,一家客户的 CTO 给我打电话,声音有点抖。

“我们 staging 库一张 order_log 表没了,就刚才的事。Claude Code agent 调了咱们搓的那个 db_query MCP tool,不知道怎么执行了 truncate。”

那张表 21.4K 行,虽然只是 staging,但包含了 QA 组两周的测试数据。复盘完之后我整理了一套 MCP server 安全检查清单,现在我们团队所有 server 上生产前必须过这 6 项。

这篇没有理论,全是踩坑换来的。

事故复盘:Agent 是怎么把表删了的

先讲清楚那次怎么翻车的,后面的检查项才有上下文。

那个 db_query MCP tool 原始设计:

1
2
3
4
5
6
7
8
9
{
name: "db_query",
description: "在数据库上执行 SQL 查询",
inputSchema: {
type: "object",
properties: { sql: { type: "string" } },
required: ["sql"],
}
}

连接的是 staging 数据库的 read-write 账号。工程师 A 给 LLM 发了个需求:”帮我看下 order_log 表最近一周有多少行被标记为 test_data”。

LLM 的操作链:

  1. db_query 跑了 SELECT COUNT(*) FROM order_log WHERE tag='test_data',返回 17 行
  2. 用户后续说”这几行清掉吧”
  3. LLM 调 db_queryDELETE FROM order_log WHERE tag='test_data'
  4. 返回报错:”tag 列有 NULL 约束冲突”(实际是权限问题提示不明确)
  5. LLM 自己推理”可能要先清空再重建”,跑了 TRUNCATE TABLE order_log
  6. 成功

工程师 A 盯着另一个窗口写代码,10 几分钟后才反应过来。

事后我们 review 了这个决策链,LLM 的每一步”单独看”都不离谱,合起来就是灾难。问题出在:tool 的权限边界过大 + 没有 dry-run + 没有限流 + 审计缺失

检查项 1:输入校验(tool 参数别裸信)

最基础但最容易漏。LLM 传过来的 tool 参数是不可信输入,必须校验。

上面那个 db_query 最起码应该:

1
2
3
4
5
6
7
const SqlSchema = z.object({
sql: z.string()
.max(8000)
.refine(s => !/\b(DROP|TRUNCATE|DELETE|ALTER|GRANT)\b/i.test(s), {
message: "危险 SQL 关键字,请用专用 tool",
}),
});

或者更狠一点,直接只允许 SELECT:

1
.refine(s => /^\s*SELECT\b/i.test(s.trim()))

写 MCP tool 的时候脑子里要有这个模型:你不是在写 API,你是在给一个会自由组合 tool 的 AI Agent 开权限口子。Agent 会以你想不到的方式组合调用。

检查项 2:Secrets 管理(凭证不能塞 config)

见过太多次 claude_desktop_config.json 里直接写 API key。文件不加密、可能被同步到 iCloud、被误 commit 到 git。

正确做法:

  • 开发机:env 字段 + 系统 keychain(macOS)或 DPAPI(Windows)
  • 生产机:MCP server 启动时从 Vault/AWS Secrets Manager 拉
  • 绝不:把 key 写进 server 代码、写进 tool description、写进 log

MCP server 启动流程应该是这样:

1
2
const dbUrl = await fetchSecret("prod/db/readonly-url");
// 不要 console.log(dbUrl)

另外一个容易忽略的:MCP server 的 tools/list 返回给客户端的 description 里不要包含任何环境信息(”连接到 prod-us-east-1 的 xxx 集群”),这会经过 LLM、可能被日志到 Anthropic 侧。

检查项 3:权限边界(只读 vs 读写 server 分离)

这是那次事故之后我们组的硬性规定。

一个 MCP server 只承担一种权限级别

  • company-db-readonly:只连 read-only DB 账号、只有 SELECT tools
  • company-db-readwrite:独立 server,连 RW 账号、提供 INSERT/UPDATE tools(不提供 DELETE/TRUNCATE
  • company-db-admin:独立 server、独立审批流程、默认不启用

用户要用到高权限 server 时,在 config 里显式启用。LLM 永远看不到 admin tools 除非用户主动开。

这个设计的额外好处是:你可以在 Claude Code 的 .mcp.json 里按项目控制——开发项目只挂 read-only、运维项目才挂 read-write。配合 Claude Code 权限沙箱 里的二级保护,基本不会出大事。

检查项 4:审计日志(谁在什么时候调了什么 tool)

事故那次最抓狂的是:我们花了 40 多分钟才定位是哪个 tool 哪条 SQL 干的好事,因为没有统一日志。

正确的审计结构,每次 tool call 至少记录:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"timestamp": "2026-04-15T08:23:17.284Z",
"server": "db-readonly",
"tool": "db_query",
"args_hash": "sha256:8a3f...",
"args_size_bytes": 342,
"caller": {
"client": "claude-desktop",
"session_id": "xxx",
"user_hint": "joe@company.com"
},
"result_status": "ok",
"duration_ms": 127
}

关键字段:

  • args_hash 而不是原文(避免日志泄露敏感参数)
  • user_hint 从 MCP client 侧传过来(很多 MCP 客户端支持带用户上下文)
  • duration_ms 方便发现异常

日志存到公司统一日志平台,保留 90 天起步。出事了至少能查。

检查项 5:Rate limit & Quota(防 Agent 失控爆 API)

Agent 一跑起来可能一分钟调你 tool 几十次,这在人类 API 用户那边几乎不可能。

我见过最离谱的一次:某客户的 Claude Code agent 接了个 Slack MCP server,写代码的时候 stuck 住、反复调 list_channels 去”探索环境”,11 分钟调了 2847 次,把公司 Slack API quota 直接干穿了,整个公司 Slack bot 半小时不能用。

两层防护:

server 侧:每个 tool 按 session+tool 组合限流,比如 60 次/分钟。超过直接返回错误让 Agent 退避。

1
2
3
4
5
// 简化伪代码
if (callCountThisMinute(sessionId, toolName) > 60) {
return { isError: true, content: [{ type: "text",
text: "Rate limited: 60 calls/min exceeded. Wait or use batch tool." }] };
}

下游 API 侧:给 MCP server 配独立的 API key、独立 quota,爆了只影响 Agent 场景不影响业务。

检查项 6:依赖漏洞(MCP 生态早期 npm 包质量参差)

MCP 火是 2024 年底的事,整个生态还很年轻。到 2026 年 4 月 npm 上 MCP 相关包有 840+ 个,但质量分布极其分散。

我扫过一次:top 100 包里 23 个有已知中高危漏洞(npm audit 高或严重),17 个最近 6 个月没更新,9 个连 README 都没有。

上生产前必须做的事:

  • npm audit 跑一遍,高危必须修
  • 看 package 的 weekly downloads 和 maintainers 数量(单人维护的小包慎用)
  • 看最近 commit 日期(6 个月没动的包视为不活跃)
  • 考虑用 socket.dev 这类 supply chain 工具扫

Python 生态同理,pip-audit 跑一遍。

一个上线 checklist

我们团队现在每个 MCP server 上生产前对照走一遍:

1
2
3
4
5
6
7
8
9
[ ] 所有 tool 参数都有 zod/pydantic 校验
[ ] 危险操作(DROP/DELETE/rm -rf 类)显式禁用或单独 server
[ ] Secrets 走 Vault/Secrets Manager,代码里 grep 不到明文
[ ] 读写权限分离成独立 server
[ ] 每次 tool call 写审计日志,args 用 hash
[ ] tool 粒度的 rate limit,超限返回明确错误
[ ] 依赖包 npm audit/pip-audit 通过,无高危
[ ] Inspector 手动验过所有 tool 的错误路径
[ ] 挂到测试 Claude Code/Desktop 跑过一周内测

9 项都过了才放生产。

另外强烈推荐搭配 Claude Code 权限沙箱 做运行时隔离——哪怕 MCP server 本身有 bug,沙箱能兜住。两层防护比一层可靠得多。

最后说一句

那次事故之后客户问我:”你们早怎么不告诉我这些?”

我说:”说实话,MCP 生态起来才一年多,很多坑是我们自己也刚踩的。”

这篇文章写出来,就是希望下一个踩坑的人不是你。如果觉得有用,分享给团队一起看看。

MCP 安全是个系统工程,6 项只是起点。强烈推荐接着看 Claude Code 权限沙箱 做第二层防护,Subagent 编排 可以做危险操作的二次确认路径。做企业级部署务必把这三篇当一个整体看。
  • 标题: MCP server 上生产之前这 6 个安全检查项必须过
  • 作者: Claude 中文知识站
  • 创建于 : 2026-04-18 19:18:00
  • 更新于 : 2026-04-19 22:45:00
  • 链接: https://claude.cocoloop.cn/posts/mcp-security-best-practice/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论