
最近折腾 AWS API MCP Server(应用程序接口模型上下文协议服务器)的安全策略,踩了几个很典型的坑。这篇把两次策略层修复拆开聊清楚,也谈谈服务器内部防护应该放在什么位置。
AWS API MCP Server 可以让 AI(人工智能)代理通过一个工具执行 AWS CLI(命令行界面)命令。它自带策略文件,可以拦截或放行特定命令。今年的更新日志里,已经两次记录了策略层缺陷。
在 2026 年 7 月发布的 1.3.47 版本中,更新日志写道:如果启动时只读操作索引加载失败,“服务器会继续运行,并在整个进程生命周期内静默跳过已配置的安全策略(denylist / elicitList),导致被拒绝的操作未经检查便能执行。”到了 8 月发布的 1.5.1 版本,条目更短,只有一句:“安全策略中的操作名称匹配(#4432)。”
这是一个基于公开项目历史的案例分析。我们查看了更新日志、README、项目主分支上的策略源码,并按照 2026 年 11 月 11 日这个时间点核对了 PyPI 上的发布日期。我们没有实际运行服务器,当前环境也无法访问 NVD(国家漏洞数据库)或 AWS 安全公告,因此这里不引用任何 CVE(常见漏洞与披露)编号。聚合网站列出了与 1.5.1 修复相关的一个编号,但在 AWS、GitHub 或 NVD 正式发布公告之前,应当把它视为尚未确认的信息。
之所以选择这个服务器,是因为作者把分层设计写得比较清楚,正好可以用来讨论每个采用 MCP(模型上下文协议)服务器的团队都必须回答的问题:当服务器自带的护栏失效时,会发生什么?
README 描述了位于 ~/.aws/aws-api-mcp/mcp-security-policy.json 的配置文件,其中包含两个列表。denyList 中的命令会被阻止,elicitList 中的命令则要求用户通过 MCP elicitation(模型上下文协议征询机制)确认,前提是客户端支持该功能。
配置项采用 aws <service> <operation> 格式,全部使用 kebab case(短横线分隔格式),例如 aws iam delete-user。README 明确说明,匹配只使用精确字符串匹配,不支持通配符。
同一份 README 也讲清了它在整个安全体系中的位置:IAM(身份与访问管理)权限“仍然是主要的安全控制机制”,策略文件“无法覆盖 IAM 限制”。免责声明也要求用户必须使用 IAM 管理 AWS 资源访问权限。
作者的意思很明确:这是一个附加在 IAM 之外的第二层防护。更新日志里的两个缺陷,则展示了第二层防护一旦被误当成第一层,会发生什么。
1.3.47 的更新日志描述了一次典型的 fail-open(失败开放)。加载启动数据时遇到一次临时网络错误,服务器便继续运行,整个进程生命周期内都没有执行任何策略。拒绝列表悄无声息地变成空列表,不会有任何告警。
在某些部署中,代理一次启动后可以连续运行数天。一次失败启动带来的策略失效,可能比很多正常会话活得更久。
根据更新日志,修复方式是:数据无法加载时拒绝启动;如果执行阶段拿不到索引,则拒绝请求。这才是护栏应有的实现方式。
但部署侧仍然要回答一个问题:你的服务器在策略输入损坏时,具体会做什么?
我们查看了当前的 policy.py,确认它如何处理策略文件。如果文件不存在,代码会记录一条警告,表示没有应用额外安全策略,然后继续运行;如果文件存在但解析失败,代码会记录错误,然后以空列表继续运行。
在开发者电脑上,这两种默认行为都合情合理。但放在共享主机或生产环境中,一个 JSON(JavaScript 对象表示法)拼写错误,就可能悄悄得到一个没有拒绝列表的服务器。以上结论来自源码阅读,没有实际运行验证,部署时仍应针对具体版本进行确认。
同一个文件里还有一种行为值得注意。当配置项位于 elicitList,但客户端不支持 elicitation 时,代码会把它按拒绝处理。这是正确的失败关闭行为,但你的代理会收到原本希望通过人工确认后放行的命令被拒绝。务必使用实际客户端进行测试。
1.5.1 的更新日志除了标题和拉取请求编号,没有提供更多细节,因此不猜测具体机制。但从代码可以看出,这类问题为什么会出现。
查询之前,策略层会使用 botocore 的 xform_name 转换操作名称,把下划线替换成短横线,再拼接为 aws <service> <operation>,最后检查目标字符串是否存在于配置集合中。整套逻辑都依赖两个字符串在一套你没有编写、也无法直接看到的标准化规则之后完全一致。
高层命令还会增加一条查询路径。以 aws s3 cp 为例,代码先从一份静态映射表中查找对应的定制项,该表列出了底层 API 调用,然后逐个检查这些调用是否命中拒绝列表和征询列表。
更新日志的 Unreleased 部分列出了“静态映射表中缺少 S3 定制项对应的 API 调用(#4548)”。这是我们结合代码和更新日志作出的解读,AWS 本身并未如此表述。我们将它理解为:没有出现在映射表中的命令,拒绝列表无法按名称识别它。
精确匹配策略的能力上限,取决于它所匹配的命令词表是否完整。
这不是在批评 AWS 团队。把 CLI 语法映射到数百个服务的 API 操作,本身就不是一件容易的事。更新日志也说明他们正在公开发现并修复这些缺口。对采用者而言,问题更实际:服务器内部的精确匹配列表一定会随着时间出现遗漏,你需要提前决定由哪一层来兜底。
README 已经给出了答案:IAM。对于一个能够调用 AWS 的代理,我们会搭建三层防护,并把服务器自带的策略文件放在第三层。
第一层是服务器运行所使用的角色。这个角色只能执行你愿意看到代理在“状态最差的一天”执行的操作。README 建议检查类任务使用带有 ReadOnlyAccess 的角色。如果代理确实需要修改资源,就创建一个限定到具体服务和资源的独立角色,并且不要把它放进默认配置。
第二层是在身份策略、权限边界,或者你使用 AWS Organizations 时的服务控制策略中,对关键破坏性操作添加显式拒绝。IAM 中的显式拒绝优先于任何允许规则,也不依赖命令字符串如何标准化。下面是一个起始示例,并非来自项目源码:
{ "Version": "2012-10-17", "Statement": [ { "Sid": "AgentNeverDeletesIdentityOrState", "Effect": "Deny", "Action": [ "iam:DeleteUser", "iam:DeleteRole", "iam:DeletePolicy", "iam:DeleteAccessKey", "cloudformation:DeleteStack", "rds:DeleteDBInstance", "s3:DeleteBucket" ], "Resource": "*" } ]
}
第三层才是服务器自带的策略文件,用它处理 IAM 无法表达的需求,例如要求人工确认某条具体命令。但必须把它视为纵深防御,同时确保它不会在无人察觉的情况下失效。
对抗失败开放最省事的办法,是写一个脚本,在服务器启动前运行。脚本可以放进同一个服务单元或容器入口点,一旦策略文件缺失、无法解析、内容为空,或者包含格式错误的配置项,就拒绝继续启动。下面这个脚本是我们自己写的:
#!/usr/bin/env python3
import json, re, sys
from pathlib import Path path = Path.home() / ".aws" / "aws-api-mcp" / "mcp-security-policy.json"
entry = re.compile(r"^aws [a-z0-9-]+ [a-z0-9-]+$") try: policy = json.loads(path.read_text())["policy"]
except Exception as e: sys.exit(f"policy file unusable: {e}") deny, elicit = policy.get("denyList", []), policy.get("elicitList", [])
if not deny: sys.exit(f"denyList is empty") bad = [c for c in deny + elicit if not entry.match(c)]
if bad: sys.exit(f"entries not in 'aws <service> <operation>' form: {bad}") print(f"ok: {len(deny)} denied, {len(elicit)} need consent")
运行方式是 python3 preflight.py && exec your-mcp-launch-command。正则表达式与 README 中描述的格式保持一致,因此像 iam:delete-* 或 aws iam DeleteUser 这样的配置项会明确报错,而不是变成一条永远匹配不到任何内容的死规则。
第二项检查应该放在预发布账号中。写一个简短测试,通过服务器发送每一条拒绝列表中的命令,并使用原本有权执行这些操作的角色,然后断言调用会被拒绝。每次升级服务器都要运行这套测试,因为今年至少有两个版本修改过策略匹配代码。1.5.0 上测试通过,不代表当前 PyPI 上的 1.6.0 也能通过。
IAM 显式拒绝比较粗暴。它会作用于所有携带该策略的主体。如果工程师确实需要在沙箱账号中执行 rds:DeleteDBInstance,就必须配置范围受限的例外,通常通过账号或标签条件实现。这种摩擦确实存在,但它正是不依赖字符串匹配的防护机制应该付出的代价。
固定服务器版本可以帮助控制行为变更的时间,但同时也意味着你必须停留在一个已知存在缺口的版本上,直到主动升级。1.5.1 的修复已经列在可固定的版本中,因此实际规则应该是:每次升级前都阅读更新日志中的 Security 和 Fixed 部分,然后在预发布环境中重新执行测试。
对于无人值守的代理,elicitation 是三层防护中最弱的一层。确认提示需要支持该机制的客户端,也需要一个一直盯着的人。源码在客户端无法发起征询时,会把 elicit 配置项按拒绝处理。如果任务完全不需要人工参与,就使用拒绝列表和 IAM。
找出所有代理能够访问、并且自带允许或拒绝配置的 MCP 服务器,逐个记录配置缺失或格式损坏时的行为。对于任何持有云端凭据的服务器,把最不希望在事故后解释的三四个操作放进 IAM 显式拒绝规则,同时在启动命令前加一道预检。
如果正在使用 AWS API MCP Server,先对照更新日志检查已安装版本,并确认已经升级到 1.5.1 或更高版本。