From 203662a570ee30e512f34f52d089bbf364a40bc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Thalheim?= Date: Sun, 15 Feb 2026 10:58:28 +0100 Subject: [PATCH] prefetch-npm-deps: strip volatile fields from dist objects in packuments The dist object in npm packuments contains fields that can change after a package is published, causing hash mismatches in fixed-output derivations: - signatures: changes when npm rotates registry signing keys (old key SHA256:jl3bwswu80Pjj... expired 2025-01-29) - npm-signature: legacy format being progressively removed - attestations: provenance metadata added post-publication Only keep the three fields npm actually needs during install: tarball, integrity, and shasum. Also strip the informational-only fileCount and unpackedSize fields. This is the same approach already used for top-level and version-level field whitelisting. Ref: https://github.com/numtide/llm-agents.nix/issues/2459 --- .../node/prefetch-npm-deps/src/main.rs | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/pkgs/build-support/node/prefetch-npm-deps/src/main.rs b/pkgs/build-support/node/prefetch-npm-deps/src/main.rs index 5ab0413cdcad..2882aee9ec20 100644 --- a/pkgs/build-support/node/prefetch-npm-deps/src/main.rs +++ b/pkgs/build-support/node/prefetch-npm-deps/src/main.rs @@ -67,6 +67,20 @@ const ALLOWED_VERSION_FIELDS: &[&str] = &[ "scripts", ]; +/// Allowed fields in `dist` sub-objects. +/// +/// Only fields that npm reads during `npm install` are included. +/// Excluded volatile fields: +/// - `signatures`: changes when npm rotates registry signing keys +/// - `npm-signature`: legacy signature format, also mutable +/// - `attestations`: provenance attestations, can be added/updated post-publish +/// - `fileCount`, `unpackedSize`: informational only, not used during install +const ALLOWED_DIST_FIELDS: &[&str] = &[ + "tarball", // URL to download the package tarball + "integrity", // SRI hash for verification (e.g., sha512-...) + "shasum", // SHA-1 fallback hash for older lockfiles +]; + fn normalize_packument( package_name: &str, data: &[u8], @@ -91,6 +105,12 @@ fn normalize_packument( for version_val in versions.values_mut() { if let Some(version_obj) = version_val.as_object_mut() { version_obj.retain(|key, _| ALLOWED_VERSION_FIELDS.contains(&key.as_str())); + + // Normalize the dist sub-object to strip volatile fields + // (signatures, npm-signature, attestations, fileCount, unpackedSize) + if let Some(Value::Object(dist_obj)) = version_obj.get_mut("dist") { + dist_obj.retain(|key, _| ALLOWED_DIST_FIELDS.contains(&key.as_str())); + } } } }