“everything” 阻止开发者移除他们自己的 NPM 包
npm 注册表里的一个恶作剧 “everything” 包最近声明依赖几乎所有其他包,利用了在臭名昭著的 left-pad 事件后引入的一项政策:禁止取消发布存在依赖者的模块。这实际上阻止了维护者删除自己的包,并重新引发了关于注册表应如何处理删除、yanking 以及代码长期可用性的讨论。评论者将 npm 的设计与 Cargo、PyPI 和 Maven 等替代方案进行对比,并进一步讨论包管理的复杂性、生态系统可靠性,以及开发者是否应该将依赖 vendoring,而不是信任公共注册表。
NPM “everything” 包与政策冲突
- “Everything” 依赖了(几乎)所有其他 NPM 包,触发了 NPM 的规则:一旦包有依赖者,就不能被取消发布。
- 评论者指出,这条规则是在 left-pad 事件之后引入的,目的是保护生态系统的可靠性。
- 结果是:任何被他人依赖的包,其作者实际上都失去了删除它的能力;有人认为这像是 NPM 想“既要又要”。
包应该可以删除吗?
- 一些人认为,一旦发布,包就不应再被删除;注册表应当拥有永久分发权。
- 另一些人则希望有更温和的机制:
- “Yanking”/软删除(像 Cargo、NuGet、PyPI 那样),这样旧的锁文件仍然可用,但新的解析会避开坏版本。
- 使用弃用标记、在 UI 中隐藏,或给出清晰警告,而不是硬删除。
- 删除的使用场景包括:严重 Bug、弃用、或意外发布了敏感或令人尴尬的内容。
- 反驳观点:敏感数据一旦上传就已经暴露;正确的做法是轮换密钥,而不是删除。
针对这个具体问题的缓解思路
- 有人提议对依赖树大小设定硬限制(例如基于真实世界的 P99,并允许例外)。
- 也有人指出这并不能完全解决问题:攻击者可以改用很多小的“chunk”包。
- 还有不少人认为真正的解决办法是重新审视 NPM 的 yank/unpublish 政策,而不是依赖数量限制。
- 有人淡化其影响,认为这只是轻微不便;也有人认为这是明确的系统性失败。
更广泛的包管理与生态争论
- 有人将其与 PyPI、Rust 的 Cargo、Maven、NuGet、CPAN、Hackage、Nix、Bazel、Go modules 作比较。
- 许多人强调,各个生态里都存在恶意或有问题的包;NPM 并不独特,但其规模和历史让问题更显眼。
- 围绕一个通用、与语言无关的包管理器的争论:
- 支持者:依赖规范和文件分发是通用问题。
- 反对者:与语言语义、构建系统、semver 行为和安全模型深度耦合,使统一化不现实。
- 对 JS/NPM 生态的强烈批评:混乱、缺乏严肃性,由低门槛、微型包和深层依赖图驱动。
- 也有人为 NPM 辩护,认为它本质上是好的,只是被流行度和历史设计选择压垮了。