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

标签页里的 IDE,一键交出了一个 GitHub 令牌

github.dev 里的一个零日漏洞,让一个恶意预览窗格得以逃出它的沙箱、悄悄安装一个扩展,并读到一个能访问受害者所能触及的每一个私有仓库的 GitHub OAuth 令牌。官方修复对自身的局限很坦诚——而更高明的一招,是一开始就别把你的令牌带进一个陌生人的仓库。

在 github.dev 里打开一个仓库,点一下某个链接,于是一个跑在浏览器 标签页里的编辑器,悄无声息地安装了一个扩展、读到了你的 GitHub OAuth 令牌,并开始列举你的私有仓库。没有下载。没有「允许」按钮。 整个 IDE 都活在一个网页里——这恰恰是为什么爆炸半径是一个关于浏览器 的问题,而不是一个关于 IDE 的问题。

2026 年 6 月 2 日,安全研究员 Ammar Askar 发布了 一个针对 github.dev 零日漏洞的概念验证——github.dev 就是你在任意 GitHub 仓库上按 . 进入的那个浏览器内版本的 Visual Studio Code。 所谓零日,是指一个厂商还没来得及修复的漏洞,因为它公开的那一刻 正是它为人所知的那一刻。在这个案例里,照 Askar 的说法,公开细节 是在通知微软之后大约一小时放出去的。他说自己之所以转向对 VS Code 发现的漏洞做完全公开披露,是出于对此前向微软安全响应中心提交的 报告被如何处理的沮丧

这套机制值得放慢来看,因为它是某一类攻击的干净范例——不是一个 一次性的 bug,而是一种基于浏览器的开发工具会不断制造出来的攻击 形态。

预览窗格不该成为一个发射台。

代码编辑器要渲染大量不可信内容。你打开的一个 Markdown 文件会得到 一份渲染后的预览。一个 Jupyter notebook 会显示它的输出。这些预览 跑在一个 webview 里——一个嵌在编辑器里的沙箱化迷你浏览器,被 刻意地与编辑器自身的权限隔开,好让一个恶意的 README 没法直接接管 你的机器。

那道墙就是全部意义所在。Askar 的漏洞利用翻了过去。

webview 通过一条消息传递通道与主编辑器对话——这是两边用来协调 的一条狭窄管道。通过滥用那条通道,跑在不可信 webview 里的恶意 JavaScript 可以在主编辑器窗口里 模拟按键 ——合成的 keydown 事件。它选择的按键是 Ctrl+Shift+P,也就是 打开命令面板的快捷键,而命令面板这个文本框,能让 VS Code 运行 它所知的几乎任何命令。

从那里开始,攻击把一个正当功能反过来用在了它自己身上。VS Code 支持本地工作区扩展:放在某个项目的 .vscode/extensions 文件夹 里的扩展,本意是让一个仓库可以自带它的工具链。因为一个恶意仓库 控制着它自己的文件,它就能自带它自己的扩展——而那个本该问「你真的 想运行这个发布者的代码吗?」的信任提示,被通过在扩展的 package.json 里精心构造自定义键位绑定给绕了过去。发布者信任对话框,这条链路里 唯一一个由人把关的检查点,从头到尾就没出现过。

1 · WEBVIEW恶意预览窗格不可信 JS 运行2 · 按键伪造Ctrl+Shift+P经由消息管道3 · 面板命令面板打开运行任意命令4 · 扩展本地工作区安装信任提示被跳过5 · 令牌OAuth 令牌被读取 + 外泄列举私有仓库一点击就打开了仓库。在那之后的一切都是自动的。
github.dev 的一键链条。一个预览窗格(一个沙箱化的 webview)驱动它所栖身的那个编辑器:它伪造一个 Ctrl+Shift+P 按键、打开命令面板,并触发一个本地工作区扩展,而这个扩展自己的键位绑定跳过了发布者信任提示。随后这个扩展读到了那个页面持有的 GitHub OAuth 令牌。

战利品不是你打开的那个仓库。

这条链条的载荷是一个令牌。github.dev 用 GitHub 帮你登录,并持有 一个 OAuth 令牌——这是浏览器向 GitHub 的服务器出示、用来证明 它在代表你行事的一份凭据。OAuth 的意义在于这个令牌可以被限定 scope:一个应用应当只拿到它所需要的那一小块访问权。

这个令牌并不窄。正如 Askar 所说, 这个令牌"并未限定到你所交互的那个特定仓库,这意味着它对你有权访问 的每一个其他仓库都拥有完全访问权"——读和写,私有仓库也包括在内。 所以攻击者拿到的不是你点进去的那个公开项目。他们拿到的是一把能触 及你账号所能触及一切的钥匙:你的私有仓库、如果你有权限的话还有你 雇主的私有仓库、躺在那些仓库里的部署密钥和 secrets、以及那些你从 没给任何人看过的源码。概念验证用它列举了受害者的私有仓库——而这份 列表,对大多数在职开发者来说,本身就是敏感的。

这就是可以推而广之的那部分。基于浏览器的 IDE 和云端开发环境之所以 便利,恰恰是因为它们替你携带着你的凭据。github.dev 持有一个 GitHub 令牌。一个云端 IDE 持有一个云令牌。一个跑在浏览器标签页里的代理式 编码助手,持有它推送代码和调用 API 所需的任何东西。它们每一个,都 是一份高价值的 secret,离一个你没写过的网页只隔着一次沙箱逃逸。

坦诚的那部分:它修好了吗?

两份报道,两幅不同的图景,而这道差距很要紧。

BleepingComputer 在撰文之时,把这个问题描述为没有官方补丁、也 没有分配 CVE,并给出了一个手动的临时办法:清掉 github.dev 的 cookie 和本地站点数据,好让最初的登录警告重新出现。

The Hacker News 则带来了微软工程师的说法。合伙人软件工程经理 Alexandru Dima 说 "这个问题不影响 VS Code Desktop" ——这个 bug 专属于浏览器托管的 github.dev,而非你安装在自己机器上 的那个应用。微软随后表示这个漏洞"已针对我们的服务被缓解,无需客户 采取任何行动",这指向的是一项服务端改动,而不是一个你下载的客户端 补丁。

我们没有立场去独立验证这项缓解,所以我们不会告诉你威胁已经过去。 我们能谈的,是当一条这样的链条得手时,爆炸落在哪里——因为那是一个 架构问题,而架构正是那个不取决于你是否相信一份新闻稿的部分。

IDE 实际上跑在哪里。

关于 github.dev,有一点让它对我们而言很有意思:整个编辑器就是一个 网页。webview、命令面板、扩展宿主、OAuth 令牌——所有这些都活在一个 浏览器标签页里。这意味着「这能造成多大破坏」这个问题,破天荒地,跟 Bromure 当初被造出来要回答的、关于任意标签页的那个问题,是同一个 问题。

在 Bromure 里,每个标签页都跑在它自己的一次性虚拟机里——一个跑在 Mac 上的可丢弃 Linux 访客,没有通往你的文件、你的钥匙串或你其他 标签页的路径。在一个 Bromure 配置文件里跑 github.dev,于是失窃的 OAuth 令牌、那个恶意 webview,以及那个被悄悄安装的本地工作区扩展, 全都被限定在那一个 VM 里。在一个非持久会话里关掉窗口,它们栖身的 那个世界就被抹掉了:令牌缓存、cookie、扩展、整个访客,没了。

常规浏览器一个共享的配置文件github.dev 标签页打开了恶意仓库OAuth 令牌失窃触及的是同一个罐子里的:工作 GitHub私有仓库其他登录关掉标签页,而令牌、cookie 和扩展依然活着。持久化是默认值。Bromure一个一次性 VM密封的配置文件github.dev 标签页打开了恶意仓库OAuth 令牌失窃仅被收纳在这个 VM 里工作 GitHub另一个 VM银行另一个 VM个人另一个 VM关掉窗口:令牌、扩展和 VM 都被抹掉。
同一个 github.dev 漏洞利用,两种浏览器。左边是一个常规浏览器:这个标签页与你所有其他 GitHub 会话共享一个 cookie 罐和一个配置文件,而失窃的令牌是长寿命的。右边是 Bromure:这个标签页是一个一次性 VM。令牌、webview 和被植入的扩展都被限定在一个你关掉它时就丢弃掉的配置文件里。而最锋利的那个版本没有画出来——在一个从未登录过的配置文件里去打开陌生人的仓库,于是根本就没有令牌可偷。

这套几何关系改变了两件事,还有一件它没改变。

它改变了令牌能触及什么。在一个常规浏览器里,你所有的 GitHub 会话共享一个 cookie 罐和一个配置文件。一个从你的 github.dev 标签页 里偷走的令牌,就紧挨着那个浏览器持有的所有其他东西。在 Bromure 里, 你可以在它自己的配置文件里跑 github.dev——它自己的 VM、它自己的 cookie、它自己的存储——与你的银行、你的邮箱和你其他的 GitHub 身份 隔开。那个一次性配置文件被攻陷,并不会把其他文件里的 cookie 交给 攻击者。它们根本不在同一台机器里。

它改变了这个立足点能维持多久。这条链条里最有价值的一招是那个 被悄悄安装的扩展——一个每次你重开编辑器都还在的立足点。面对一个 一次性会话,没什么可回来的。那个扩展被安装进了一个不复存在的 VM。

它解决不了什么。

它没法把失窃的令牌变回没被偷。当那个恶意会话还活着的时候,那个 OAuth 令牌是真的,攻击者可以用它实时地、从他们自己的基础设施上 列举并读取你的私有仓库。隔离收纳的是 secret 栖身在哪里;它没法 跨越互联网伸到 GitHub 的服务器去吊销一份攻击者已经握在手里的凭据。 那里能用的缓解措施是寻常的那些:短寿命且严格限定 scope 的令牌、在 任何可疑情况之后吊销会话,以及一个厂商——这里是微软——关上那条逃逸 通道,好让那个令牌一开始就根本够不到。

有一处对比值得说精确,因为很容易被含糊带过。Bromure 压根不允许 安装 Chrome 扩展——不沙箱化、不经审核、不来自任何商店,统统不行。 这是一个刻意的产品立场:浏览器扩展是现代 web 上被滥用得最多的立足 点之一,所以 Bromure 干脆把这一整类都移除掉。但这个故事里的扩展 是一个 VS Code 扩展,由 github.dev 这个 web 应用安装进它自己的 编辑器里——这跟一个 Chrome 扩展是两码事,它活在页面里面而不是页面 周围。Bromure 的「无扩展」规则伸不进 github.dev 里去拦它。Bromure 所做的,是把整个编辑器、连同令牌、扩展以及一切,都收纳进一个你能 丢掉的 VM 里。

除非根本没有令牌可偷。

有一种更锋利的方式来运用这同一套架构,而且它分文不费:把你是谁你访问什么分开。

整条链条存在的目的就是去够到一个 secret——那个因为你登录过而被 github.dev 持有的 OAuth 令牌。那个令牌只需要存在于你处理自己代码 的地方。所以就给它恰好那么大的地盘:一个专用的 Bromure 配置文件, 登录到 GitHub,你在里面打开你的仓库和你早已信任的那些仓库。令牌 住在那里,而陌生人的代码不住在那里。

其他一切——从链接来的那个有意思的仓库、某人贴出的概念验证、你正在 评估的那个依赖——都在一个你谁都不是的地方打开:一个从未登录过 GitHub 的配置文件。以匿名访客的身份在 github.com 上读那些代码;如果你想用 编辑器,没登录的 github.dev 没有任何值得交出去的东西。一个未认证的 会话泄不出一个它根本不持有的令牌。那条漏洞利用链条,就算跑得起来, 也是跑在一个口袋空空的一次性 VM 里。

这改变了防御的形态。收纳是在第五步、在令牌被取走之后限制损害。 分隔则在第一步就斩断这条链条:恶意仓库根本碰不到那份凭据。这条 纪律一句话就能装下——在你工作的地方登录,在你游荡的地方谁都不是 ——而 Bromure 配置文件把它变成一个双图标的习惯,而不是一条安全策略。

这个普遍的教训会比这个具体的 bug 活得更久。随着越来越多的开发挪进 浏览器——云端 IDE、github.dev、跑在标签页里并携带推送权限的代理式 编码代理——这些工具所持有的凭据就成了一个常立的靶子,离一个你没写 过的页面只隔着一次 webview 逃逸。站得住脚的姿态不是「信任 IDE 里面 的那个沙箱」。而是假定那个沙箱终将泄漏,并把事情安排成:它泄进去的 那个页面,既是一次性的,又是身无分文的。

把那个有风险的标签页跑在它自己的世界里——一个你谁都不是的世界。当 有东西钻进来时,没什么可拿的,而你反正都会把这个世界关掉。

那就是 Bromure 的用途所在。装上它,给你自己的 GitHub 一个它自己的 配置文件,以陌生人的身份去访问别人的代码,并把最坏的情况变成一个 你能关掉的窗口。