依赖与供应链:四十一个直接依赖、一百八十六条告警

四十一个直接依赖背后是约三百八十个间接依赖,一年一百八十六条告警只修二十三条。本文按高危、中、低三档排处理顺序,复算 10.3 小时修复加 4 小时审计,如何压住一次按 40 小时计的供应链事件,并给出每周二十分钟的固定动作。

你引用的每一个包,都在你的构建里拥有与你相同的权限。

一、一条被标成低危的告警

三月的第一个周一,扫描器照例生成周报:186 条告警,11 条高危、47 条中、128 条低。司南花四十分钟把高危看完,升级了三个包,剩下的按级别归档。其中一条低危来自一个间接依赖,包名和他常用的那个只差一个连字符。

两周后他做 typosquatting 演练,把几个形近包名放进一个空项目安装。其中一个在安装脚本里发起了一次外部请求,虽然只发到一个他自己控制的地址,但足以说明问题:那条被他归档的低危,指向的是同一种包。

以下情境为教学假设,人物、引语和数字不代表经核实的真实案例。

供应链的风险不在你选的包,在你没选的包。 41 个直接依赖是他逐个看过 README 才加的;约 380 个间接依赖,他一个都没主动选过,却全部进了构建、全部能执行安装脚本。

告警的级别由漏洞评分决定,不由你的用法决定。 一个在测试工具里被标低危的反序列化问题,如果那个工具在 CI 里跑,风险和标高危的没有区别。

安装脚本是供应链最容易被忽略的执行点。 包被安装时会跑代码,权限与构建进程相同;只读源码不跑安装,等于只看了一半。

演练比告警更能让人记住。 一条低危躺两周没人看,一次自己动手的安装演练,十分钟就让人明白低危也可能是入口。

二、41 与 380:间接依赖才是大头

直接依赖是他写进清单的,间接依赖是清单自己长出来的。两者的可见性差一个数量级。

层级数量谁选的可见性处理方式
直接依赖41他逐个评估过锁版本,升级需看变更
间接依赖约 380上游决定只在锁文件里锁文件入库,定期扫描
传递更深层计入 380上游的上游几乎不可见只依赖扫描与最小化

41 个直接依赖已经算克制。 每加一个包,等于把它的全部依赖树一起引进;评估一个包时,看的是它的依赖数量与最近一次发布时间,不只看 star 数。

锁文件必须入库,且必须能复现。 同一份清单在两次安装里装出不同版本,扫描结果就没有意义;npm ci 这类严格按锁文件安装的命令要成为唯一入口。

间接依赖的升级由上游驱动,不由他决定。 他能做的是:锁定顶层版本、定期扫描、在出现高危时用覆盖机制临时顶住,再等上游跟进。

依赖越少,攻击面越小,但不是越少越好。 自己实现加密、解析、时区,出错概率远高于用一个成熟包;判断标准是「这个包是否只做一件事,且维护活跃」。

每个季度删一轮。 列出 30 天没有被 import 的包,确认后移除;未使用的依赖仍然是依赖,仍然会被安装、仍然会跑脚本。

许可证也要顺手看。 扫描器只管漏洞,不管许可;一个间接依赖换成 AGPL,分发时就多一层义务,具体以专业意见为准。

安装来源要固定。 只从官方 registry 装,配置镜像时确认镜像的同步来源;一个被替换的镜像,能把整棵树换成别的。

三、186 条告警,只修 23 条

全修既不现实也没必要,关键是每一条都有明确去向:修、记录、或登记忽略。

级别数量修复数单条耗时处理依据
高危111125 分钟有修复版本就升级,无则临时覆盖
中471215 分钟只修可达路径上的
低12801 分钟记录记录影响面,不主动修
忽略登记——共 35 分钟写清原因与复查日期

高危 11 条全修,没有例外。 25 分钟是平均时间,包含升级、跑测试、看变更日志;有两条需要连带升级三个包,实际花了 50 分钟,也从别处省回来。

中危只修可达的 12 条。 47 条里大部分位于未使用的功能或仅开发环境;判断「可达」靠一条 grep:这个包的入口函数有没有被调用过。

低危只记录,不修。 128 条平均 1 分钟,写清包名、影响面、为什么现在不修;记录的价值在于下次同类告警出现时不用重新判断。

忽略项要登记,不能静默。 35 分钟登记了 9 条永久忽略项,每条写明「为什么可以忽略」与「什么时候复查」;没有登记的忽略,半年后会被当成新问题重查一遍。

处理率 23/186 约 12.4%,这是设计而非偷懒。 把 186 条全修,时间会从 10.3 小时涨到几十小时,结果是下个季度没人再看报告。

告警要闭环到人。 每周固定 20 分钟过一遍新增,标记去向;超过两周没有去向的告警,升级为待办。

同一个包可能被算成两条。 它在直接与间接两处出现时,报告会各算一次;按包名去重后再算处理率,数字才可比。

四、账:618 分钟与一次 40 小时的事件

修复成本是可以算的,事件成本只能估;两个数放在一起,才知道该花多少时间在预防上。

复算:
告警 186 = 高危 11 + 中 47 + 低 128
高危 11 × 25 分钟 = 275 分钟
中危修 12 × 15 分钟 = 180 分钟
低危 128 × 1 分钟 = 128 分钟
忽略项登记 = 35 分钟
合计 275 + 180 + 128 + 35 = 618 分钟 = 10.3 h
加每月审计 20 分钟 × 12 = 240 分钟 = 4 h
全年 = 10.3 + 4 = 14.3 h
处理率 = 23 ÷ 186 ≈ 12.4%
一次供应链事件按 40 h 计 → 14.3 h 的预防只要挡下 0.36 次就回本

40 小时是一次教学假设。 它包含定位被污染的版本、回滚构建、轮换构建密钥、通知受影响客户;真实事件可能更长,也可能只是一次虚惊。

14.3 小时占 1,920 小时的 0.74%。 这个比例低于攻击面盘点加密钥轮换,但它防的是唯一能一次影响所有客户的入口。

回本点很低,所以不值得讨价还价。 一年挡下不到半次事件就够本;真正的约束不是时间,是每周那 20 分钟能不能稳定空出来。

可复现构建是这条账的地基。 锁文件、固定工具链版本、记录构建产物的摘要;没有这些,出了问题连「装的是哪个版本」都答不上来。

演练要包含安装阶段。 把形近包名放进空项目,观察安装脚本是否发起外部请求;一次十分钟的演练,比读十篇供应链文章有效。

每次部署记下产物摘要。 产物 digest 与锁文件哈希一起留存;出事时能确认线上跑的到底是哪一版,回滚也有依据。

五、行动清单与复算验收

先把锁文件与扫描固定下来,再谈处理率。

  • 锁文件入库,安装只走严格按锁文件的命令。
  • 每周固定 20 分钟过新增告警,每条标记去修、记录或忽略。
  • 高危 11 条全部升级,无修复版本的写临时覆盖与复查日期。
  • 中危只修可达路径,用一次调用检索确认可达性。
  • 低危逐条记录影响面,不主动升级。
  • 建一张忽略登记表,写原因与复查日期。
  • 每季度列出未使用依赖并移除。
  • 做一次 typosquatting 安装演练,记录安装脚本行为。

验收标准:每条告警都有去向,忽略项都有复查日期;抽查三条,能说清为什么修或不修。

使用边界与实操验收

依赖管理处理的是「别人写的代码进了你的构建」,不覆盖自己代码里的漏洞。

扫描器只报已知漏洞。 没有 CVE 的恶意包、刚投毒还没被收录的版本,扫描器都不会报;安装脚本演练与依赖最小化补的是这一块。

自动升级不是默认安全。 自动合并小版本能减少告警,也会把未经审查的代码直接送进构建;高危可以自动提 PR,合并仍然要跑测试并看一眼变更。

可复现构建有成本。 固定工具链、记录摘要、维护制品库,都要时间;对一个人来说,做到「同一把锁文件装出同一棵树」已经足够,不必追求逐字节可复现。

供应链事件不总是技术问题。 包被收购、维护者失联、许可证变更,都会影响可用性;这些不在漏洞库的覆盖范围内,只能靠季度复核发现。

本文不构成合规结论。 依赖清单、漏洞处置时限与软件物料清单的要求因行业与司法辖区而异,以专业意见为准。

关键参考与核验范围

链接只用于核对一般做法,具体配置以各自工具链为准。

以上链接于 2026-10-26 实测可公开访问;未核验版本、功能范围与政策更新。核验日期:2026-10-26。

范围上只讨论直接与间接依赖的盘点、告警分级与修复成本,不讨论制品签名、私有 registry 搭建与供应商审计;那些属于更重的供应链题目。

系列导航

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「Indie」更多文章

  1. 开源项目验收:十项能力、四维评分与三种不该开源的项目
  2. 治理、fork 与退出:17 位贡献者、82% 的提交与一次改协议的代价
  3. 双许可与企业版:九家询价、两家成交与 4,800 美元