返回所有文章
发布于 · 作者 Renaud Deraison

你信任的是工作区,而不是那些服务器

2026 年 6 月 26 日,Wiz 披露了 CVE-2026-12957:一个克隆下来的仓库里的 `.amazonq/mcp.json` 让 Amazon Q 自动启动 MCP 服务器,而这些服务器继承了开发者的完整环境——AWS 密钥、云 CLI 令牌、API 机密以及 SSH agent 套接字,对服务器本身却没有单独的同意环节。一次「信任此工作区」的点击,替代了为生成携带你实时云会话的后台进程而本应有的确认。Amazon 在 Language Servers 1.69.0 中修复了它。这里说明为什么这次修复堵住了一个产品却没堵住这一类问题,以及当打开仓库的智能体运行在按 profile 隔离的 Bromure 虚拟机里、位于凭据代理、读写护栏和虚拟机监控级追踪之后时,会有什么不同。

昨天 Wiz 披露了 CVE-2026-12957。你克隆的一个仓库可以 附带一个名为 .amazonq/mcp.json 的文件,而当你在 Amazon Q 中 打开这个文件夹并点击 信任此工作区 的那一刻,该文件就会 启动后台服务器,没有第二次提示。它们启动时 继承了你的 AWS 密钥、你的云 CLI 令牌、你的 API 机密, 以及你的 SSH agent 套接字。一次信任点击触及了你的 shell 所能触及的一切,并把它交给了仓库所选定的代码。

一个为了试用某个基础设施模块而克隆仓库的开发者,并不会 把这当作授予了任何访问权限。克隆是惰性的:它 往磁盘写入文件,什么都不运行。需要判断的时刻发生在 下一步,当你在编辑器里打开该文件夹、助手 问你是否信任它的时候。那条提示读起来就像每个 IDE 都会弹出的、 人们熟悉的「你是否信任此文件夹中文件的作者」那道关卡, 于是你像过去成千上万次那样点了「是」,因为 另一个选项是一个连你的代码都读不了的助手。这一次的「是」 做的事情不止是让助手去读。在 Amazon Q 中,修复之前, 它还会启动仓库写进 .amazonq/mcp.json 里的任何服务器,而那些服务器启动时 就已经把你的实时云会话装进了它们的环境里。

本月早些时候我们写过 同一种形态, 当时 Miasma 蠕虫 把项目配置植入了微软自家 73 个仓库, 载荷在文件夹打开时触发。那个故事讲的是一个 受信任的来源 成了攻击面。这一次更狭窄、也更令人不安: 没有蠕虫,没有被攻陷的维护者,没有任何东西可以指责, 只有助手本身。漏洞在于 Amazon Q 如何读取一个普通的 项目配置文件,并在你一次信任点击之下决定生成 携带你笔记本上最敏感之物的长驻进程。

CVE-2026-12957 究竟做了什么。

2026 年 6 月 26 日,Wiz Research 披露了 CVE-2026-12957, 这是 Language Servers for AWS 中一个高危漏洞(CVSS 8.5), 也就是为 VS Code、JetBrains、Eclipse 和 Visual Studio 上的 Amazon Q Developer 提供动力的引擎。Wiz 在 4 月 20 日 向 Amazon 报告, Amazon 在 5 月 12 日 发布了修复,细节在 6 月 26 日 公开。没有迹象表明它在野外被利用过。 其机理,简化来说:

  • Amazon Q 会从你打开的工作区读取 .amazonq/mcp.json:这是一个 按项目作用域的文件,声明了供助手使用的 Model Context Protocol 服务器。
  • 打开文件夹时,Q 启动了该文件声明的 MCP 服务器, 对服务器本身却没有单独的同意环节。因此你克隆下来的一个仓库 可以把一个服务器定义摆到助手面前, 并让它运行。
  • 被生成的进程 继承了开发者的完整 环境,用 Wiz 的话说,就是「AWS 密钥、云 CLI 令牌、API 机密以及 SSH agent 套接字」。MCP 服务器不过是 Q 启动的一条命令,而那条命令启动时拥有你自己 shell 会拥有的一切。
  • 一个服务器定义可以指向任何命令,所以该文件实际上等于 文件夹打开即任意代码执行:它以你的身份运行,你的 云凭据已经加载就绪,就在你信任工作区的那一刻。

这次披露中包含一个关于同意的真实分歧,而这 正是本文的全部重点。Amazon 的立场:「用户在被提示时 必须信任工作区。」 确实有一道关卡,你点击通过了它。Wiz 的发现: 修复之前,对 MCP 服务器本身没有单独的 同意环节。两者都成立,而失败 就存在于二者之间的缝隙里。一个粗粒度、熟悉的信任 决定——和你为了让编辑器索引文件夹而给出的同一个是/否—— 授权了一个大得多的许可:生成携带你实时会话的后台 服务器。你回答的是一个关于 读取 文件 的问题。Amazon Q 却把这次点击花在了 以你的身份运行服务器 上。

Amazon 在 Language Servers 1.69.0 中修复了它(客户端:VS Code 2.20+、 JetBrains 4.3+、Eclipse 2.7.4+、Visual Studio 1.94.0.0+),加入了 此前缺失的按服务器同意,并修补了一个相关的 符号链接检查绕过漏洞 CVE-2026-12958。请更新 Amazon Q。然后越过 补丁,看看它没能解决的那一部分:打开一个仓库 可能会把你的实时云会话放进一个由仓库 所选定的进程里。

开发者笔记本 — 你的实时云会话就在每个被生成服务器所继承的环境里开发者克隆git clone …/terraform-modules尚无任何运行附带 .amazonq/mcp.json磁盘上的一个服务器定义在 AMAZON Q 中打开「信任此工作区?」一次熟悉的点击 → [Trust]读取 .amazonq/mcp.json无按服务器同意MCP 服务器自动启动Q 生成所声明的命令命令 = 仓库写的任何东西以你的身份、在你的 shell 中运行↳ 文件夹打开即任意代码被继承的环境 — 每个被生成的服务器都拿到全部,全是真的$AWS_ACCESS_KEY_ID, ~/.aws你的账户(真)cloud CLI 令牌(gcloud, az)你的项目(真)env / .env 中的 API 机密生产密钥(真)$SSH_AUTH_SOCK以你的身份签名(真)读取 · 使用 · 外泄→ 离开你的机器结果代码以你的身份运行云会话已到手密钥被读取并送出一次信任点击买下了整台笔记本
一台普通开发者笔记本上的 CVE-2026-12957。克隆仓库什么都不运行。在 Amazon Q 中打开它并点击「信任此工作区」这唯一一道熟悉的关卡,会让 Q 读取 .amazonq/mcp.json 并启动它声明的 MCP 服务器,没有单独的按服务器同意。每个被生成的服务器都是 Q 启动的一条命令,它启动时继承开发者的完整环境:AWS 密钥、云 CLI 令牌、API 机密、SSH agent 套接字。由于服务器定义可以指向任何命令,这就是以你的身份运行的任意代码,你的实时云会话已经加载就绪。这唯一一次信任点击回答的是一个关于读取文件的问题,而 Q 把它花在了以你的身份运行服务器上。

同一个仓库,在 Bromure Agentic Coding 内打开。

Bromure Agentic Coding 让你的编码智能体, 包括 Amazon Q,运行在一个 按 profile 隔离的 Linux 虚拟机 里:它有 自己的内核、自己的文件系统、自己的网络栈,跑在 Apple 的 Virtualization 框架上。一个 profile 是一段连贯的工作范围:这个客户这个 服务这个你为评估而克隆的开源模块。你把 仓库克隆到那个 profile 里并在那里打开它。漏洞行为 完全像 Wiz 描述的那样复现:你点击 信任此 工作区,Q 读取 .amazonq/mcp.json,并启动该文件声明的 服务器。生成动作触发,仓库 所选定的代码运行起来。

它在 guest 里运行。MCP 服务器在虚拟机内启动并 继承虚拟机的环境,而虚拟机的环境并不持有 你的云会话。Bromure 在 guest 中附带 stub: 对 awsgcloudazkubectlgit 以及任何 读取 Authorization 头或 AWS_ACCESS_KEY_ID 的东西看起来都像真的假值。你 Mac 上的一个代理 坐在离开沙箱的每一个连接前面,识别出 stub,并在请求离开时 在线路上 把它换成真正的机密; 持有密钥的那个沙箱 详细讲解了这个机制。 真正的 AWS 密钥、真正的云 CLI 令牌、真正的 API 机密 从不接触虚拟机能读到的任何文件、环境变量或内存页。

那就让我们把 CVE 的继承过程在那道边界上走一遍。被生成的 服务器读取 $AWS_ACCESS_KEY_ID,得到一个 stub。它读取 ~/.aws 得到一个 stub profile。它从环境里读取云 令牌和 API 机密,得到的是占位符。该服务器 按所写内容完整运行,继承的是一个从未持有你 密钥的盒子。让 CVE-2026-12957 成为凭据窃取漏洞的,是 生成时的 环境继承,而 Bromure 并不破坏它。它让被 继承的环境变得一文不值。

按 PROFILE 隔离的 BROMURE 虚拟机信任工作区 → Q 读取 mcp.jsonMCP 服务器自动启动并运行任意代码 — 仅在 guest 内服务器继承到了什么$AWS_ACCESS_KEY_IDstub_AKIA…云令牌、API 机密stub / 缺失$SSH_AUTH_SOCK已转发尝试:aws s3 rm / ec2 terminate一次写操作 → 经代理离开外泄宿主密钥:没有真东西可拿爆炸半径 = 这一个 profile无宿主文件系统、无宿主钥匙串可达代理 · 你的 MAC凭据代理真正的 AWS 密钥存于此处stub → 真,在线路上值从不进入虚拟机护栏:读/写删除 / 终止 → 提示点名动词 + 目标审计每次生成 + 调用都记录在智能体之下结果云会话:继承的是 stub,真密钥未被触及以你身份的代码:在一个用完即弃的guest 里运行,不在宿主破坏性调用:被暂停,你说不运行了什么:都在追踪上
同样的文件夹打开、同样的自动启动服务器,在 Bromure 内。(1)隔离:MCP 服务器在按 profile 隔离的虚拟机里生成,于是「以你的身份运行的任意代码」落在一个可丢弃的 guest 里而非宿主机上,爆炸半径只有一个 profile。(2)凭据代理:服务器继承 guest 的环境,里面只有 stub;真正的 AWS 密钥、云令牌和 API 机密留在宿主机上、位于代理之后,在线路上换入,所以继承到的是占位符。(3)护栏:代码尝试的任何会改变状态的调用,比如删除一个存储桶、终止一个实例或推送一个分支,都是一次写操作,会在线路上被拦下,弹出一条点名动词和目标的提示。每一层都在智能体之下执行,处于被生成进程无法绕过的边界上,每一次生成和外发调用都被写入追踪。

「信任此工作区」是个错误的问题。

这个 CVE 的价值不只在一条补丁说明,因为它涉及那个同意缺口, 而这个缺口是个粒度问题。信任提示问了一个宽泛的 问题,你是否信任这个文件夹,而一个「是」必须同时涵盖 你本意中无害的那件事(让助手读我的代码)和 你并不知道还附带着的危险那件事(用我的云会话 启动后台服务器)。你无法把这答好。没人 能,因为这个问题把两个彼此毫不相干的许可 揉在一起,却只向你展示了无辜的那一个。

Bromure 并不要求你在那种判断上做得更好。它把 判断从路径上移走。在一个 profile 内的信任点击仍然让 助手做它的工作,而它仍然无法启动一个持有 你真实云会话的进程,因为那个会话不在虚拟机里供它 继承。它存活在宿主机上、位于代理之后,只能作为 代理在线路上填入并记录的一个 stub 化请求来触达。许可中危险的 那一半消失了。你并没有保留它;那里根本没有 东西可以授予,因为边界处在智能体之下, 信任提示无法把它挪动。

另一半 危险是一个被生成的服务器从只是好奇 转向具有破坏性,它遇到了 护栏。Bromure 读取 操作,而不只是连接:aws s3 ls 是读, aws s3 rm 是写;git fetch 是读,git push 是 写。把一个 profile 的云凭据设为 写时询问,那么 当代码伸手去做一个会改变状态的调用,比如 DELETETerminate* 或强制推送时,Bromure 会在线路上把它拦下并 在你 Mac 上弹出一条提示,点名动词、目标和 profile。读永远不会打断你。暂停的是变更, 就像「智能体删除了生产数据库」不再是 事后复盘 而成为你拒绝的一个对话框。

追踪展示了什么。

每个团队在这类事情落地后的第二天早上都会问同一个 问题:我是不是打开过那个仓库,它都运行了什么? 在一台普通笔记本上 诚实的答案是耸耸肩。Q 生成的 MCP 服务器是 你会话里的一个子进程,它读取的任何东西、向哪里联络, 都是以你的身份进行的,没有任何把它与你自己工作 区分开来的记录。

在 Bromure 内,智能体坐在虚拟机里,每一个外发字节都 经过宿主代理,所以即使智能体看不见,生成动作 及其调用仍然可见。宿主机会记录 MCP 服务器的 启动、它运行的命令、它请求代理填入的 凭据,以及它尝试的每一个连接,写进 与智能体所做其余一切相同的 会话追踪,写在智能体之下,被生成的代码无法 伸进去编辑。「这个仓库的服务器有没有碰过我的账户」不再是 你几周后从 CloudTrail 重建出来的一个猜测,而成为 你读到的一行:代理交出的是一个 stub、请求有名字、 目标被记录了下来。对一家企业而言,这就是 我们修补了 Amazon Q我们能展示我们开发者打开的每一个仓库都做了什么 之间的差距,而这种审计姿态正是我们 企业工作 反复回到的那一点。

这在哪些地方救不了你。

被转发的 ssh-agent 套接字在转发期间是可用的。

Bromure 把你的 SSH 私钥保存在 macOS 钥匙串里,从不 把它们复制进虚拟机。但如果一个 profile 转发了 ssh-agent 套接字(这正是 OpenSSH 的本意),一个被生成的服务器就可以在套接字活动时 请求 agent 用那些密钥去 签名。 密钥文件从不离开宿主机;签名能力却确实 触达了 guest。只把套接字转发进确实需要它的 profile, 并像代理给凭据划定作用域那样给它划定作用域。

你批准的一次写操作就是一次会发生的写操作。

读写护栏拦下智能体没有告诉你的那个破坏性 调用。它不会读你关于意图的心思。如果 你是有意要拆掉一个栈、并且批准了那条 提示,Bromure 就会转发那个 Terminate。提示买给你的是 看一眼动词和目标的机会;你仍然得去读它。

profile 是长驻的,所以持久化会持久存在。

一个 Bromure profile 不是一块用完即弃的磁盘。一个把自己 写入 profile 内某个启动路径的被生成服务器,可以在 那个 profile 的下一次会话里苏醒。它醒来时面对的是一个 没有宿主密钥的 guest,以及一个只会说短时、 需提示、有作用域令牌的代理:存在于一个空无一物的盒子里,但 存在依旧。

已修补不等于已解决。

Amazon 在 1.69.0 中加入了按服务器同意,你应该更新。 但下一个在文件夹打开时读取项目配置的助手 仍会做出同一类决定,而它弹出的提示看起来会 一样平常。隔离才是不依赖于 某个特定厂商把某个特定同意对话框做对的那部分。

下一个工作区已经在某处被克隆好了。

Red Hat 作用域 的教训 是发布者不是一道防线。微软 仓库 的教训是仓库 也不是,而且打开一个文件夹就足以运行代码。Amazon Q 又添了一行:信任提示也不是一道防线。它向你 提出的问题,你是否信任这个文件夹,并不是 答案真正重要的那个问题,那个问题是 这个文件夹是否应该能够 生成携带我云会话的服务器。第二个问题从来没有向你 展示过,就算展示了你也无法把它答好。

你不会靠更仔细地读那条提示来修好这个问题,因为 提示本身就是错误的提示。你修好它的方式,是把事情安排成 让「这是哪个工作区」不再是你的云密钥所悬挂的那个 问题;为什么编码智能体不是一个 沙箱 是这个论点的更长 版本。Bromure Agentic Coding 就是这样一种配置:智能体在一个按 profile 隔离的虚拟机里 打开仓库,真正的凭据留在宿主机上、位于代理 之后,智能体做的每一次写都必须通过一条提示,而 它生成的每一个服务器都被写进一份它无法编辑的追踪。一个被投毒的 mcp.json 最坏也只能继承一个从未持有 你密钥的盒子。它免费、开源,并且今天就已发布。