Nix 的二进制缓存(substituters)为构建速度和开发体验带来了显著提升,但在便利背后隐藏着被广泛忽视的安全风险。很多开源项目和团队鼓励用户将它们的缓存和签名密钥加入本地配置,以节省本地编译时间。然而,任何对缓存具有上传权限的人都能将恶意构件写入缓存,从而为远程代码执行和提权打开方便之门。理解这些攻击路径与现实威胁是减少潜在损害的第一步。 Nix 缓存被污染的核心在于信任边界过大。常见做法是将 CI(如 GitHub Actions、GitLab CI)生成的构建产物上传到共享缓存,随后开发者和 CI 自身通过相同缓存获取二进制产物以避免重复构建。
为了实现自动上传,构建流水线往往需要保存上传密钥或凭证到 CI 的机密存储中。对于拥有代码写权限的贡献者,许多 CI 平台会允许他们触发运行,这意味着这些人间接获取了对缓存的写入能力。一旦某个密钥被滥用或某个贡献者被攻破,攻击者就能把恶意程序替换入缓存,进而影响每一台查询并接受该缓存签名的机器。 更糟糕的是,缓存污染不必针对当前存在于官方包仓库(nixpkgs)中的软件。攻击者可以上传看起来像是未来或热门软件的新版本,或构造与常见包名相似的候选名,诱导用户安装。即便某些系统更倾向于官方 NixOS 缓存,攻击者也可能借助 import-from-derivation 和 fetchClosure 等特性,从非官方包一路"转向"最终影响到被信任包的使用路径,形成复杂的攻击链条。
提权场景真实且危险。许多常用工具可能以 root 权限运行或与系统级守护进程交互。比如通过 nix-daemon 或执行 nixos-rebuild,攻击者控制的二进制替代物可能在 sudo 环境下被执行,从而获得系统权限。除此之外,攻击不仅限于显式执行的程序:恶意构件可以包含后门、盗取凭证、替换库或注入持久化代码,感染后来所有依赖该缓存的构建过程与执行环境。 在评估风险时要注意 CI 与缓存管理之间的信任扩散。GitHub Actions 等平台的默认设计使得"写权限等于能读取/使用机密"的情形并不罕见。
很多开源项目为了便捷,会将共享上传密钥放进仓库对应的 CI 配置,从而让任何有合并或写权限的人都能触发带凭证的构建并将产物上传到缓存。哪怕攻击操作只在若干个月后才显露,CI 日志保留周期可能早已过期,追溯和归因变得更加困难。 那么应该如何在现实中降低风险?首先,尽量减少你信任的缓存数量。默认不要把第三方或开源项目的缓存添加到系统级配置中。检查并清理 /etc/nix/nix.conf、~/.config/nix/nix.conf、你的 NixOS configuration.nix 中的 substituters、extra-substituters、trusted-substituters 与 trusted-public-keys。还要注意用户级的持久信任记录文件 ~/.local/share/nix/trusted-settings.json,那里保存着你对某些 flake 缓存与公钥的永久接受记录。
定期审计这些配置能有效降低意外信任扩散的概率。 区分"按项目授权的缓存"和"全局信任的缓存"。将缓存仅在特定 flake 中配置,能限制它只能为该项目的构建提供替代品,而不会对你机器上所有构建造成影响。尽管如此,项目级缓存仍然意味着该项目的任何维护者在某种程度上能影响你安装的产物,因此即便是项目缓存也应谨慎接受,尤其当项目维护者数量众多或 CI 管理松散时。 从架构上更安全的做法是将"签名"责任交由构建服务本身,而不是交由众多维护者。像 garnix 的做法就是只有少数受控实体签名产物 - - 构建者与签名者职责分离,签名密钥只掌握在可信的构建服务方。
Hydra 的权限模型也接近这一方向:构建者可以生成产物,但只有受控的上传路径和受限权限才能把产物推到公共缓存,从而降低单一贡献者污染缓存的风险。 对于依赖公共 CI 的项目,近期出现的工件可证明(artifact attestation)与供应链可追溯性(provenance)技术为溯源与归责提供了帮助。GitHub 的工件 attestation 功能能证明某个构建产物确实来自特定的 CI 运行,并包含构建元数据用于归因。配合自动化的后置钩子与验证流程,可以在发现可疑产物时追踪到可能的来源并迅速响应。但这些机制并不自动消除攻击面,它们更多是为检测与归责提供支持。 多签名、多构建者共识模型(例如 Trustix 等尝试)引入了"多方同意"方能信任某件工件的概念,通过要求多个独立流水线和缓存对同一产物达成一致来提升安全性。
该方向值得关注,但成本与复杂度也明显增加:额外的缓存查询会显著影响缓存命中率与构建延迟,并且在许多现有 CI 审批模型中,多个流水线的访问控制仍可能被攻破以绕过多签名防线。 在日常运维与个人工作站层面,可以采取一系列可立即落实的防护措施。删除未知或不再需要的替代器配置;只为可信的、权限受限的缓存保持信任;为关键生产环境保留离线或受限的构建路径;把上传密钥放在硬件安全模块(HSM)或受限的密钥管理系统中,避免在普通 CI 机密存储中长期暴露高权限密钥;将上传凭证设置为短寿命或使用一次性令牌,降低长期滥用风险。 对 CI 的权限做细粒度限制同样重要。为构建机器和上传任务创建最小权限账户,区分"构建者"与"上传者"角色,只有通过安全审核和签名证书的构建产物才能由上传者推向缓存。这种职责分离能在出现恶意提交时形成额外阻隔。
若可能,把关键签名动作放到受控的、独立于普通 CI 的签名工作流里,该工作流仅接受经过验证的构建证明再执行签名操作。 监控与审计是检测缓存污染和追溯攻击源的第二道防线。对缓存上传与签名行为保持日志记录与告警,确保日志保留期足够长以支持事后分析。对上传者清单、使用的密钥和每次上传的元数据建立透明列表,便于在发现异常时快速隔离可疑密钥和受影响的构件。 在技术上,也应尽量把可信度移向更小且可审计的实体。例如将签名密钥集中到少数受信任的构建池,并定期由独立团队进行审计。
硬件根信任(如基于 TPM 的远端证明或硬件计量)能提高对构建主机和签名设备完整性的信任度。长期而言,推动构建平台与 CI 平台提供针对 Nix 的原生可证明产物支持,可以实质性减少对分散缓存的信任需求。 开发者与项目维护者也需要改变习惯。不要默认接受来自他人 flake 的缓存,不要在 README 或快速开始指南中把第三方缓存的添加作为常规推荐选项。项目可以提供清晰的说明,指出为何不建议普通用户全局添加项目缓存,或者在提供缓存的同时明确列出谁拥有上传权限并提供可核查的构建证明。 实际迁移策略应结合成本与风险权衡。
对于有严格安全需求的场景,最佳策略是完全移除未经验证的外部缓存,采用内部受控缓存或强签名的第三方缓存;对于开发环境,可以在对受益与风险评估后有选择性地接受某些缓存,但应始终限制在项目级别、并监控其变化。对企业级环境,推荐建立内部的构建与缓存设施,或采用第三方服务但强制要求签名与溯源证明、定期审计以及最小上传权限模型。 最后,教育和沟通非常关键。许多开发者并非出于恶意而上传产物,但对安全后果缺乏足够认识。让维护者、贡献者与 DevOps 团队理解"写入缓存即具有影响所有消费者的能力"这一事实,能够显著改善配置实践与权限管理。同时,社区层面的改进,例如在 Nix 生态中推广可证明构建路径和更严格的签名/证明标准,会从根本上降低缓存污染引发的供应链风险。
拥抱二进制缓存带来的效率红利的同时,必须严肃对待其安全代价。审视并缩小你信任的替代器清单,分离构建与签名职责,强化 CI 的最小权限设计,采用可证明的构建与工件 attestation,以及建立可追溯的监控与审计流程,都是降低被缓存污染所带来危险的有效手段。通过这些实践,可以在保持开发效率的同时,大幅减少因缓存信任扩散而导致的系统性风险。 。