供应链保护

自主编码代理会不断安装软件包 —— 搭建一个项目会拉取数百个依赖项,而人类甚至从未读过它们的名字,更不用说变更日志了。这使得软件包注册表成为敌意代码进入沙箱的最宽通道:一个仿冒拼写的名字、一个刚被劫持的发布版本,或一个恶意的 postinstall 脚本,都会在 npm install 完成的那一刻以代理的完整权限运行。Bromure Agentic Coding 将每一次软件包获取都视为不受信任的输入,并在任何一个字节到达虚拟机之前,在宿主机上对其应用逐机器的策略。

本章说明拦截哪些内容、流水线中的每一项检查、检查触发时会发生什么,以及如何在安全时间线中解读结果。供应链策略按机器设置(在设置编辑器中即一个工作区)。该窗格逐字段的设置参考位于供应链设置。

为何软件包安装是攻击面

沙箱已经将代理包裹其中:它无法触及你的 Mac,其凭据也都是诱饵(参见 凭据)。沙箱本身无法做到的,是判断代理拉入代码的来源。供应链攻击恰恰利用了这一空隙:

  • 新发布版本劫持。 某位维护者的账户被攻破,随即发布了一个恶意版本。这类版本通常在数小时至数天内被检测并撤回 —— 这正是 时效门 拒绝比某一截止点更年轻版本的原因。
  • 已知易受攻击的版本。 代理将某个依赖解析到了一个已发布安全公告的版本。OSV 检查会捕获这些情况。
  • 恶意软件、仿冒拼写和恶意安装脚本。 从一开始就为作恶而构建的软件包。socket.dev 和 Depi 提供方会对其进行标记或过滤,而安装脚本剥离则直接移除了最常见的执行途径。

强制执行在设计上就位于宿主机侧。代理服务器在代理看到响应之前就对响应应用策略,因此虚拟机内运行的任何东西 —— 包括一个完全被攻陷的代理 —— 都无法放松规则。虚拟机内的 .npmrc 和 pip.conf 只能在代理服务器已经提供的基础上进一步收紧,绝不能放宽。信誉服务的 API 密钥(socket.dev、Depi)仅保存在宿主机上,从不导出到虚拟机中。

拦截如何工作

虚拟机发出的每一个网络请求都会经过宿主机侧的 MITM 代理服务器(参见 概念)。代理服务器识别对主要软件包注册表的请求,并对每一个请求进行分类:

  • 元数据 —— 某个软件包的版本清单(一个 npm packument、PyPI 的 JSON API 或 /simple/ 索引、一个 Cargo 稀疏索引条目,等等)。时效门在这里通过改写清单来运作。
  • 制品 —— 某一特定版本的可下载文件:一个 npm .tgz、一个 Python wheel 或 sdist、一个 .crate、.gem、.nupkg,或 Go 模块 .zip。OSV 查询、socket.dev 检查、脚本剥离和硬拦截都发生在制品获取时。
  • 透传 —— 这些宿主机上的其他一切(搜索、认证)。原样放行。

无论策略如何,每一次软件包获取都会被分类并记录在安全时间线中;检查本身只针对你启用的层运行。无需在虚拟机内安装或配置任何东西。

注意: 已存在于 npm 或 pip 本地缓存中的软件包从不触及网络,因此不会对它们做任何检查 —— 也不会记录。安装期间时间线一片安静,可能只是意味着一切都来自缓存。

裁决:允许、改写、拦截、暂挂

供应链层是一个个独立的开/关切换开关,每一个都带有固定的动作 —— 该窗格没有全局的拦截/警告/允许模式。(你可能从 提示注入 熟悉的记录/询问/拦截三选一单选,以及 护栏 中的强制执行模式,都是相互独立的系统。)每一次获取都以以下四种结果之一告终:

结果发生了什么可见标记
已允许响应原封不动地通过。安全时间线中绿色的已检查 / 已下载行。
已改写代理修改了响应:从元数据中移除过新的版本,或从 tarball 中剥离安装脚本。包管理器照常继续。X-Bromure-Rewritten: supply-chain 响应头;时间线中的蓝色行。
已拦截下载被以 HTTP 451("因法律原因不可用")响应拒绝。X-Bromure-Block: supply-chain 头;时间线中的红色行。
暂挂以待许可下载暂停,同时 Bromure 询问你 —— 针对锁文件固定的透传、没有任何已启用信誉源能够审查的软件包,以及被 OSV 或 socket.dev 标记的软件包(参见信任被拦截的软件包)。拒绝会将暂挂转为 451 拦截。一条系统许可警报;该决定会被记录。

451 拦截携带一段以 Bromure Supply-Chain Security blocked this request: 开头的纯文本正文,其后是确切原因,例如:

Bromure Supply-Chain Security blocked this request: npm package [email protected]
published 4 hours ago — policy requires 2 days minimum

npm、pip、cargo 及其他包管理器会在其错误输出中逐字打印该正文,因此代理和你都能精确看到安装失败的原因 —— 而代理往往能够自行绕开它(例如,固定到一个更旧的版本)。选择 451 状态是有意为之,这样供应链拦截就能一眼与 护栏 使用的 403 区分开来。

各生态系统的覆盖情况

代理服务器拦截八个软件包生态系统。并非每项检查都支持每一个生态系统:

生态系统被拦截的宿主机时效门OSVsocket.devDepi脚本剥离
npmregistry.npmjs.org、*.npmjs.org是是是是是
PyPIpypi.org、files.pythonhosted.org是是是——
Cargocrates.io、static.crates.io、index.crates.io是是否——
RubyGemsrubygems.org是是是——
Maven Centralrepo1.maven.org、repo.maven.apache.org、search.maven.org否是是——
NuGetapi.nuget.org、*.nuget.org否是是——
Go modulesproxy.golang.org否是是——
Packagistrepo.packagist.org、packagist.org是是是——

有两个值得了解的空缺:

  • 时效门 —— Maven、NuGet、Go。 这三个生态系统在其标准元数据响应中不携带逐版本的发布时间戳,因此它们的元数据不经过滤地透传,而制品后备也没有数据可供依据。时效门实际上今天不会拦截它们;npm、PyPI、Cargo、RubyGems 和 Packagist 则完全覆盖。
  • socket.dev —— Cargo。 socket.dev 不支持 Cargo。在启用 socket.dev 过滤的情况下,每一个 crates.io 制品都不会产生裁决,因此会触发未经验证软件包的许可提示(参见 离线与降级行为)。如果你的机器有大量 Rust 工作,要么回应这些提示,要么选择另一个提供方。

时效门

时效门拒绝比可配置天数更年轻的软件包版本,其原理是:一个刚发布的版本最有可能是刚被劫持的软件包 —— 恶意版本通常会被迅速报告并撤回,而对于常规开发来说,等过这段窗口几乎没有成本。它是唯一默认启用的层,默认最短 2 天。

机器 Web app 编辑器中的供应链窗格,显示时效门组已勾选拒绝比截止时间更新的软件包、最短年龄设为 2 天,下方的 OSV 漏洞检查组未勾选

它通过两个协同的机制运作:

  • 元数据改写。 代理服务器从注册表的版本清单中剥离过于新鲜的版本,并将 latest 和其他 dist-tag 重新指向最新的幸存版本。因此浮动引用 —— pkg@latest、semver 范围 —— 永远不会被硬拦截:包管理器会静默解析到足够老、能够通过的最新版本。从代理的视角看,比截止点更年轻的版本就仿佛尚不存在。
  • 制品获取后备。 代理服务器在元数据中看到的每一个逐版本发布时间都会被缓存在内存中(最多 50,000 条)。如果代理随后直接请求一个固定的、过于新鲜的版本,该获取会被以 451 拦截,其中说明该软件包的实际时效和所需的最短值。对于 pip —— 其默认的 PEP 503 HTML 索引不携带时间戳 —— Bromure 会按需查询 https://pypi.org/pypi/<pkg>/<version>/json 以获取发布时间。

要配置它,请打开机器的供应链窗格并使用时效门组:

  1. 切换 拒绝比截止点更年轻的软件包。
  2. 用步进器设置 最短时效:(0–90 天)。
  3. 如果某个特定软件包必须能够立即安装 —— 例如,它由你自己的团队发布 —— 用 添加条目 按钮将其添加到 豁免软件包 下。条目使用 允许列表格式:npm:axios 将豁免限定到一个生态系统,而裸写的 axios 则在每个生态系统中匹配该软件包名。匹配不区分大小写。

提示: npm 元数据请求会被静默升级为完整的 packument,以便时效门能看到发布时间 —— 对于 npm 的缩略元数据格式,你无需做任何事情。

OSV 漏洞检查

OSV 检查会在 api.osv.dev 上查询每个下载制品的生态系统、软件包和版本 —— 这是免费的开源漏洞数据库,聚合了 GitHub Advisory Database、PyPI 公告、Go 的漏洞数据库、RubySec 等。如果该确切版本的任何公告达到或高于你所选的严重性,下载就会被以 451 拦截。它覆盖全部八个生态系统,且不需要 API 密钥。

严重性在公告带有 GHSA 标签时取自该标签;否则 Bromure 从公告的向量字符串计算 CVSS v3 基准分数。

在 OSV 漏洞检查 组中:

  1. 切换 在 api.osv.dev 上查询软件包(免费,无需密钥)。
  2. 选择 在此严重性拦截: —— 低及以上、中及以上、高及以上,或 仅严重。

该检查默认关闭,严重性阈值默认为 高及以上 —— 正如该窗格自身所指出的,传递性子包中的一个低严重性 CVE 不应打断工作流。查询仅在制品下载时运行(而非元数据获取),结果会在应用运行期间缓存于内存,最多 16 个查询并行运行,瞬时网络错误会以退避方式重试最多 5 次。如果完全无法访问 OSV,软件包会被暂挂以待你许可,而非静默允许 —— 参见 离线与降级行为。

软件包过滤:socket.dev 和 Depi

软件包过滤 组通过一个互斥的单选选择信誉提供方:无、socket.dev 或 Depi。这两个提供方的工作方式根本不同 —— socket.dev 是代理服务器在放行获取前所查询的预检查询;而 Depi 则完全替换 npm 注册表。在设计上一次只能有一个处于激活状态(同时运行两者会对每一次安装双重过滤),选择 无 会禁用两者,同时保留任何已存储的密钥。无论你选择哪个提供方,本章中的其他层 —— 时效门、OSV、脚本剥离 —— 仍会在其之上应用。

默认为 无。在该单选存在之前创建的、已经拥有 socket.dev 密钥的工作区,会被自动推断为 socket.dev。

socket.dev

socket.dev 是一项商业软件包信誉服务。在选择 socket.dev 并输入 API 密钥的情况下,代理服务器会在提供每个制品下载之前,对照 socket.dev 的问题 API 进行检查。它支持 npm、PyPI、Go、Maven、RubyGems、NuGet 和 Packagist —— 但不支持 Cargo(参见 各生态系统的覆盖情况)。

你需自备密钥:点击 API key: 字段旁的 获取 API 密钥(它会打开 socket.dev/dashboard/settings/api-tokens),创建一个令牌,并将其粘贴到安全字段中。两个拦截切换开关在输入密钥之前保持禁用,而空密钥无论切换开关如何都会完全禁用 socket.dev。密钥存储在宿主机侧的工作区 profile.json 中,从不进入虚拟机;所有查询都源自你的 Mac。

有两个独立的拦截可用:

  • 拦截被攻陷的软件包(恶意安装脚本、被标记恶意软件、仿冒拼写、可疑遥测) —— 针对 socket.dev 攻击特征的供应链风险问题触发。确定性的恶意软件信号(恶意软件、已知恶意软件、GPT 检测到的恶意软件、恶搞软件包、被攻陷的 SSH 密钥)在任何严重性下都拦截。噪声更大的攻击特征信号 —— 混淆代码、可疑字符串、安装脚本、仿冒拼写、shell 访问、异常 HTTPS 使用 —— 仅在 socket.dev 将其评为高或严重时才拦截,因此一个带有良性 postinstall 的软件包不会被捕获。纯质量和属性信号(新作者、环境变量读取、网络访问、遥测)则有意永不拦截。
  • 拦截带有已知 CVE 的软件包 —— 针对 socket.dev 的漏洞类别,在达到或高于 CVE 拦截阈值: 选择器(与 OSV 相同的四个级别;默认 高及以上)时触发。

两种拦截默认均为关闭。结果在应用运行期间缓存,最多并行执行 16 个调用,瞬时失败会以退避方式重试 5 次。认证错误会连同 HTTP 状态码和响应正文预览一起写入应用日志。

注意: 如果你同时启用 OSV 和 socket.dev 的 CVE 拦截,两者都会检查每个制品 —— 这是允许的(它们是不同的层),只是在 CVE 覆盖上冗余。许多用户改为将 socket.dev 的被攻陷软件包拦截与免费的 OSV 检查搭配使用。

Depi

Depi 是 Lupin & Holmes(landh.tech)推出的即插即用安全 npm 注册表,它提供预先审查过的软件包。Bromure 不是查询软件包,而是将每一个 npm 注册表请求 —— 元数据、压缩包、审计,任何寻址到 registry.npmjs.org 或 *.npmjs.org 的请求 —— 重新路由到 Depi 位于 depi-npm-proxy.landh.tech:443 的兼容 npm 的过滤注册表,附上你的密钥作为 Authorization: Bearer 头,并剥离客户机发送的任何 Authorization 头。Depi 会改写其 packument 中的压缩包 URL 使其指向自身,因此这些后续获取也会在宿主机侧被注入密钥。

要启用它,请在单选按钮中选择 Depi,并将你的密钥粘贴到 API 密钥: 安全字段中(获取 API 密钥会打开 landh.tech)。字段为空时,会显示一条橙色警告:请输入 API 密钥 —— 没有密钥时 Depi 将保持关闭。 每个被重新路由的请求都会写入应用日志:

[depi] GET registry.npmjs.org/… → https://depi-npm-proxy.landh.tech

错误处理是明确的而非静默的:

  • 401(密钥被拒绝)。 Bromure 会替换为一段 npm 会打印的清晰纯文本错误("Bromure: the Depi registry rejected the configured API key…",带有头 X-Bromure-Block: depi-auth),记录它,并针对每个工作区与密钥的组合弹出一次性 GUI 警报:"Depi rejected your API key"。无界面的 SSH/TUI 会话不会收到 GUI 警报;它们依赖日志行和改写后的 npm 错误。
  • 403(软件包被 Depi 拦截,或密钥未获授权访问它)。 记录并原样透传,因此 npm 会报告 Depi 自己的拒绝文本。

Depi 仅影响 npm —— 其他七个生态系统不受其影响。Depi 在单选中替换的是 socket.dev,而非你的本地策略:时效门、OSV 检查和脚本剥离仍会对 Depi 提供的内容运行。

安装脚本剥离

npm 的 preinstall、install、postinstall 和 prepare 钩子会在安装时运行任意代码,是现实世界 npm 恶意软件的主力。启用 动态从 npm 压缩包中剥离 preinstall / install / postinstall / prepare(在 安装脚本 组中)后,代理服务器会实时改写每个 npm 压缩包:它对 .tgz 解压缩,定位顶层的 package/package.json,移除那四个脚本键,重新计算 tar 头校验和,并重新压缩。

由于压缩包字节发生了改变,代理服务器也会从软件包的注册表元数据中清除 dist.integrity 和 dist.shasum,这样 npm 会自行从剥离后的压缩包计算哈希,其验证对于未固定的安装仍能通过。每次剥离都会被记录("stripped install scripts from"该软件包及版本,以橙色显示);经检查确认干净的压缩包仅被打上 X-Bromure-Rewritten: supply-chain 头。一旦发生任何解析失败,原始压缩包会原样透传 —— 该层为失败开放(fail open),因为它绝不能弄坏一个格式良好的安装。

有些软件包确实需要安装脚本 —— 诸如 better-sqlite3 或 node-canvas 这样的原生绑定编译器。将它们添加到 允许以下软件包运行安装脚本(格式 npm:better-sqlite3),它们就会保留其钩子。

注意: 这一代理层独立于虚拟机内 npm 自身的安装脚本策略。较新的 npm 版本只为 allow-scripts 列表上的软件包运行安装脚本,因此 Bromure 在每台机器中托管的 ~/.npmrc 恰好只为它随附的代理 CLI 放行 —— @anthropic-ai/claude-code、@openai/codex、@moonshot-ai/kimi-code 和 @oh-my-pi/pi-coding-agent —— 使通过 npm install -g 自我更新的代理能获得其原生二进制文件。其他所有软件包遵循 npm 的默认行为。如果你在这里开启脚本剥离,也请把这些软件包加入允许运行安装脚本的软件包,否则代理的自我更新会丢失安装步骤。

该切换开关默认关闭,且有两个限制:

  • 仅限 npm。 PyPI sdist 不会被改写 —— setup.py 就是任意代码,因此在那里剥离不可行。
  • 仅限未固定的安装。 一个在 package-lock.json 中固定了完整性哈希的压缩包,无法在不导致验证失败的情况下被改写。那种情况由下一层管理。

锁文件固定的安装

一个 锁文件固定的安装 —— npm ci —— 会在锁文件中固定每个压缩包的完整性哈希。Bromure 无法在不破坏哈希验证的情况下改写那些压缩包,因此它唯一的选择是原样透传它们,或者拦截它们。默认情况下它们会静默透传。

如果你想有发言权,在 锁文件固定的安装 组中启用 在原样透传锁文件固定的压缩包(npm ci、pip --require-hashes)之前提示。一批中第一个锁文件固定的获取(对于 npm,通过 npm-command: ci 请求头检测到)随即会弹出一个宿主机许可对话框,标题为 透传来自工作区"…"的 npm ci(锁文件固定的安装)?,带有按钮 允许 15 分钟、允许一次、在本会话剩余时间内允许 和 不允许。整批并发的获取会汇聚到那一个提示上并遵循你的决定;拒绝会以 451 拦截该获取,且该拒绝会被记住 60 秒,以便重试不会再次提示。

注意: 尽管 UI 标签提及了 pip --require-hashes,目前检测仅对 npm 的 npm-command: ci 头实现 —— pip 哈希固定的安装不会触发该提示。(它们也从不会被改写,因为 PyPI 制品永不被修改。)

信任被拦截的软件包

信誉裁决不一定适合你:socket.dev 因 workerd 的安装脚本而标记它,但它是 Cloudflare 的运行时,你的项目可能需要它。OSV 或 socket.dev 拦截某个软件包下载时,不会默默返回 451,而是暂挂该下载并询问:尽管有供应链警告,仍要从工作区“…”安装 … 吗?,并引述原因。选项如下:

选项效果
在本次会话中允许 …安装它,以及该软件包的任何其他版本,直到应用退出。
在此工作区中始终允许 …安装它,并将其加入工作区的受信任的软件包列表,之后的下载便会跳过对它的 OSV 和 socket.dev 检查 —— 只要它仍在列表上。
不允许 …,并且本次会话中不再询问保持 451;在应用退出之前,同一安装会持续被拦截,不再询问。
暂不暂时保持 451;一分钟后会再次询问。不作回应即等同于此选项。

socket.dev 的恶意软件裁决(恶意软件、已知恶意软件、GPT 检测到的恶意软件、恶作剧软件包、被攻陷的 SSH 密钥)永远不提供覆盖选项,也永远不受列表覆盖:这些下载一律被拦截。时效门保留自己的豁免软件包列表,安装脚本剥离也有自己的允许运行安装脚本的软件包列表 —— 在这里信任某个软件包,并不会使它免于这两者中的任何一项。

你也可以事后添加软件包:安全时间线中被拦截的行提供 允许… → 在此工作区中始终允许 …,列表本身位于供应链窗格的受信任的软件包下(格式 npm:workerd)。更改会立即应用到正在运行的机器。

许可提示与授权

所有供应链询问用户的路径 —— 锁文件透传以及下文描述的未经验证软件包暂挂 —— 都会经过一个带有突发汇聚的共享许可代理:针对相同范围的并发请求会等待在单个对话框上,而非堆叠警报。锁文件透传提供以下四个决定(未经验证软件包暂挂有自己的选项,逐个软件包——请参阅离线与降级行为):

决定效果
不允许以 451 拦截;记住 60 秒,自动拒绝即时重试。
允许一次放行这一个请求(或汇聚的突发)。
允许 15 分钟授予该范围 15 分钟。
在本会话剩余时间内允许授予该范围直至应用退出。

活动的授权与拒绝在审批 UI(窗口 → 凭据审批… 区域)中列为 供应链决定 —— 与护栏决定相分离 —— 你可以在那里提前撤销某个授权。授权仅存于内存,不会在应用重启后保留。

在远程 SSH/CLI 会话中没有 GUI 对话框:同样的问题会以选择器的形式呈现在机器的 tmux 中。不作回应即表示拒绝。参见远程访问。

配置策略

供应链策略按机器配置,位于机器设置中安全下的供应链窗格。完整的逐字段参考见供应链设置;默认值一览:

设置默认值
拒绝比截止点更年轻的软件包(时效门)开启,最短时效: 2 天,无豁免
在 api.osv.dev 上查询软件包(免费,无需密钥)关闭;在此严重性拦截: 高及以上
软件包过滤无(无 socket.dev 或 Depi 密钥)
拦截被攻陷的软件包 / 拦截带有已知 CVE 的软件包(socket.dev)关闭;CVE 拦截阈值: 高及以上
动态从 npm 压缩包中剥离 preinstall / install / postinstall / prepare关闭,允许列表为空
在原样透传锁文件固定的压缩包之前提示关闭(静默透传)

三个操作细节:

  • 编辑即时生效。 保存设置会立即将新策略推送到正在运行的会话 —— 代理按请求读取策略,因此永远不需要重启虚拟机。每一项新的或更改的策略都会在应用日志中以一行摘要确认,例如:

    [supply-chain] policy engaged for 1a2b3c4d: age-gate=2d osv=high socket.dev=compromised+cve=high strip-scripts
    

    配置错误会直接在该摘要中标出:socket.dev=key-set-but-no-toggle 表示已输入密钥但两个拦截均未启用,而 depi=selected-but-no-key 表示已选择 Depi 但因缺少密钥而关闭。

  • 存储位置。 策略存放于工作区位于 ~/Library/Application Support/BromureAC/profiles/<id>/ 下的 profile.json 中(仅写入非默认字段)。socket.dev 和 Depi 的 API 密钥也存储在那里 —— 仅在宿主机侧,在 UI 中显示为安全字段,永不复制到虚拟机中。没有供应链专属的环境变量或启动参数。

  • 远程配置。 SSH/TUI 远程菜单以相同字段暴露相同的 供应链 窗格,因此无界面实例无需 GUI 即可配置。参见 远程访问。

在安全时间线中观察安装

供应链流水线所做的每一项判定都可以在安全时间线中看到 —— 打开窗口 → 安全…,或用 ⌘K 搜索它。其概览标签页上的已检查的软件包磁贴统计过去 24 小时的软件包获取次数;点击它,或在时间线标签页的引擎筛选中选择供应链,即可逐行查看。

每一行在其条件列中列出软件包(npm · [email protected]),并在其判定列中用颜色标示发生了什么:

判定颜色含义
已检查绿色已获取软件包的版本列表,且通过检查。
已下载绿色已获取软件包制品,且通过检查。
已检查——比冷却期更新的版本已隐藏蓝色时效门改写了版本列表。这是时效门在做它的日常工作,并非拦截。
已剥除安装脚本蓝色已从 npm tarball 中移除安装钩子。
已拦截——…红色获取以 451 被拒绝;原因跟在破折号之后(例如已拦截——已知恶意软件)。你可以覆盖的拦截会在该行末尾有一个**允许…**按钮。
已下载 — 尽管有以下警告,你仍允许了:…蓝色被 OSV 或 socket.dev 标记、但因你允许而安装的软件包(通过提示,或受信任的软件包)。

无论是否开启任何供应链层,软件包获取都始终会被记录 —— 强制执行遵循策略,记录则不受策略影响。apt 安装也会被记录:每个从 Ubuntu 或 Debian 归档 pool 获取的 .deb 都会以 apt 行出现。它们只被观察;本章的任何检查都不适用于它们。

想实际看看:打开时间线,在会话中运行任意安装(npm install、pip install、cargo add、gem install、apt install 等),然后看着各行陆续出现。事件在磁盘上保留 90 天,应用重启后依然存在;清空只会把当前的行从窗口中隐藏。**导出…**会把标签页所显示的内容保存为 CSV 文件。

注意: 时间线记录的是判定,而不是每一个步骤。详细的流水线日志行 —— 每一次出站查询、"policy engaged" 摘要、Depi 重新路由、API 错误 —— 会写入位于 ~/Library/Logs/BromureAC/bromure-ac.log 的应用日志(从终端启动时也会写入应用的 stderr)。

离线与降级行为

流水线的信誉检查依赖于从你的 Mac 发出的出站调用(绝不从虚拟机):OSV 用 api.osv.dev、socket.dev 用 api.socket.dev、PyPI 发布时间后备用 pypi.org、Depi 用 depi-npm-proxy.landh.tech。当一个已启用的源无法产生裁决时 —— 重试后网络仍中断、HTTP 错误、限速、认证失败或不受支持的生态系统 —— Bromure 会 失败关闭(fail closed),而非静默允许。

下载暂停,并有一个 未经验证软件包暂挂 以标题为 要在不检查信誉的情况下从工作区“…”安装 软件包 版本 吗? 的面板询问你。面板会说明为何没有判定——socket.dev 没有响应、拒绝了 API 密钥(请在工作区的 供应链 设置中检查)、正在对该密钥限速、尚未完成该版本的分析,或不认识该版本(它可能刚发布)——并说明安装它意味着接受一个未对照你所配置的信誉源检查过的软件包。选项如下:

  • 允许 软件包 版本 ——安装此版本,本次会话余下时间不会再询问。
  • 15 分钟内允许所有未核验的包 ——此软件包,以及工作区在接下来 15 分钟内所有得不到判定的其他软件包。
  • 本次会话余下时间不允许未核验的包 ——此软件包及之后的每一个都会被拒绝,不再询问。
  • 不允许 软件包 版本 ——拒绝(以 451)。倒计时结束前未作答、按 Esc 或关闭面板,效果相同。

每个工作区同一时间只显示一个问题:当 uv sync 或 npm install 遇到多个未经验证的软件包时,会逐个询问(面板会显示还有几个在等待),而针对全部的回答会一次性解决其余软件包。

这是有意的保护:没有它,一个能对信誉服务诱发限速的攻击者就可能悄悄溜过未经检查的软件包。实际后果如下:

  • 完全离线工作 且启用了 OSV 或 socket.dev,意味着每个未缓存的软件包都会有一个提示。在离线期间禁用这些层 —— 时效门无需连接即可从已缓存的元数据继续工作。
  • socket.dev 加 Cargo 意味着每个 crate 都有一个提示,因为 socket.dev 根本无法审查 Cargo(授权按 package@version 键控,因此一次会话授权只覆盖那一个 crate 版本)。
  • PyPI 发布时间后备是唯一的例外: 在纯网络错误时它会为那一次获取失败开放(该失败不缓存,因此下次会重试该查询)。时效门的元数据改写不受影响。

攻陷检测

供应链检查降低了敌意代码进入沙箱的几率;攻陷检测则捕获确实进来的敌意代码试图行动的那一刻。它是凭据系统而非软件包流水线的一部分 —— 诱饵凭据和令牌交换的完整图景在 凭据 中 —— 但它的警报在此记录,因为一个被投毒的软件包是它最可能的触发因素。

虚拟机发出的每一个出站请求都会在单次遍历中被扫描,查找凭据交换层铸造的诱饵("伪造")令牌。一个限定于某一宿主机(比如 github.com)的伪造令牌,出现在发往任何其他宿主机的请求中,就是凭据外泄的特征。当这种情况发生时:

  1. 代理服务器以 451 拦截该请求 —— 目的地永远收不到一个字节。
  2. 虚拟机会立即暂停。如果机器原本在后台运行,会先被带到屏幕上。冻结的画面会染成红色。
  3. 会出现一条标题为此环境可能已被攻陷的严重警报,说明 Bromure 检测到有人试图从所指明的机器,将会话凭据泄露到非其签发对象的主机,且虚拟机已被暂停 —— 并为每一次泄露列出一行详细信息:令牌预览(例如 sk-a…f9q3)、凭据名称、其签发对象主机,以及观察到它正被发往的主机。

该警报提供四种响应:

  • 关机(默认)—— 停止虚拟机并将机器标记为已被攻陷。
  • 阻止 —— 只拦下这一个请求并恢复虚拟机;如果代理再次尝试,检测器会再次触发。
  • 允许 —— 在本次会话的剩余时间内信任所观察到的主机:凭据会被替换并发往该处,对它的请求也不再被标记。只对你认得的目的地使用。
  • 保存以供调查 —— 你选择一个文件夹;Bromure 会导出 disk.img(虚拟机系统磁盘的副本)、home.tar.gz(机器的主目录),以及每个共享文件夹的 shares/<name>.tar.gz,然后关机并将机器标记为已被攻陷。RAM 状态会被丢弃。

只有关机响应回车键;其他三项都需要明确点击。镜像该机器的远程客户端会收到关机、阻止和允许。完整说明,包括允许背后的按会话允许列表,参见凭据。

被标记为已被攻陷的机器在你明确擦除它之前会拒绝再次启动,而擦除流程会警告共享文件夹不会被擦除 —— 它们可能仍包含受污染的文件,因此请手动检查。

对于声明了主机作用域的凭据,检测始终开启。没有固定主机的手动凭据("任意主机")永远无法触发它,而 Claude/Codex 令牌使用放宽的同一注册域匹配(为 api.anthropic.com 签发的令牌出现在另一个 anthropic.com 主机上时不会报警)。一次只显示一条警报;警报打开期间的重复事件会被丢弃。机器可以在其护栏窗格中用凭据外泄时不弹出警报抑制警报和暂停;泄露仍会被拦截,并仍以红色的凭据中转行出现在安全时间线中(参见应急手段)。

企业级可见性

对于位于已在 bromure.io 注册的 Mac 上的机器,每一次元数据和制品获取还会向企业事件流发出一个 supply_chain.fetch 事件,携带生态系统、软件包、版本、请求类型、结果(allowed、rewritten、blocked 或 stripped),以及原因类型(age_gate、osv、socket_compromised、socket_cve、verify_unavailable、lockfile_denied、scripts_stripped)。仅注册本身就赋予管理员组织范围的软件包下载可见性 —— 一份每个代理所安装一切的清单 —— 即便在关闭了所有强制执行层的机器上也是如此。

注册如何工作,以及管理员在另一端看到什么,都在 企业 中介绍。