系列前两篇解决了"server 从哪来"(供应链)和"工具描述能不能信"(投毒防御),这篇聊最后一块拼图:权限模型。前两篇的所有防御都有一个隐含前提——你已经决定了哪些工具可以被调用、哪些参数可以被接受。这个"决定"本身就是一套需要设计的系统。
一个真实场景:你给 Agent 接了 GitHub server,工具列表里有 list_issues 也有 delete_file。用户随口一句"帮我清理一下仓库里没用的东西",模型完全可能推导出"清理 = 删文件"。权限模型的作用,就是让这类推导在系统层面走不通,而不依赖模型每次都"懂事"。
不要在 server 级别做开关(要么全开要么全关),要在工具级别分层:
{
"mcpServers": {
"github": {
"toolPolicy": {
"default": "deny",
"allow": [
{ "tool": "list_*", "level": "read" },
{ "tool": "get_*", "level": "read" },
{ "tool": "create_issue", "level": "write" },
{ "tool": "delete_*", "level": "dangerous", "requireConfirmation": true }
]
}
}
}
}三条设计原则:
弹确认框不等于有人看。信息设计决定确认的有效性:
反例是那种"确定要继续吗?"的无信息确认框,用户会形成"无脑点确定"的肌肉记忆——确认疲劳比没有确认更危险,因为它污染了所有确认的可信度。
当客户端同时挂了多个 server,新的风险出现了——权限混淆(confused deputy):
场景:文件系统 server 有读 ~/.ssh 的权限,网络 server 有出网权限。单独看都合理,但模型可以把从前者读到的内容喂给后者发出去。两个"低危" server 组合出了一个"高危"能力。
防御要点:
1. token 不共享:每个 server 用独立凭证,scope 最小化
2. 数据流标注:客户端对 server 产出内容打标,跨 server 传递敏感标记内容时强制确认
3. 会话隔离:一次任务会话内只激活相关 server,不搞"全家桶常驻"
4. 出网白名单按 server 划分:文件 server 没有任何出网理由其中数据流标注成本最高,但它是唯一能拦住"组合攻击"的机制——单 server 视角的权限检查在多 server 场景下天然有盲区。
不需要重型框架,客户端启动时加载策略文件,每次工具调用前过一遍:
import fnmatch, json
class Policy:
def __init__(self, path):
self.rules = json.load(open(path))["toolPolicy"]
def check(self, server: str, tool: str, args: dict):
rule = self.rules.get(server, {})
for item in rule.get("allow", []):
if fnmatch.fnmatch(tool, item["tool"]):
level = item["level"]
if level == "read":
return True, None
confirm = {"confirmed": item.get("requireConfirmation", False),
"args": args, "level": level}
return level != "dangerous", confirm
return False, None # default deny三十行代码,换来的是所有工具调用都有明确的策略裁决点——审计和回放也顺手解决了。
三篇连起来,MCP 安全的完整链路:server 从哪来(供应链)→ 描述能不能信(投毒防御)→ 调用被允许做什么(本篇)→ 出事怎么查(审计)。安全从来不是某个单点配置,而是这条链上每一环都不省事。
后面如果时间允许,会再写一篇实战向的:把三篇的防御整合成一个开箱即用的 MCP 客户端配置模板。有问题欢迎评论区交流。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。