Npm Slop & Wonky Software Supply Chains

The problem with the two most widely used open-source package ecosystems, npmjs.com and pypi.org , is that they are not actually source-based. Instead, they are based on unreproducible developer-uploaded bundles and binaries. In general, published packages can't be reproduced from their original source and for packages without provenance attestation, you can't even know if the package was built from the linked source repo in the first place. This has led to supply chain incidents on both registries (e.g. 1, 2).1

Npm offers no reliable path to install from source. Even packages with no dependencies often require patches or other tricks to be usable without going through npmjs.com .2 This isn't easy to fix either. Many popular npm packages pull dozens or even hundreds of transitive dependencies, making it impractical to switch to better dependency handling or vendoring. I think of these as npm slop.

Example: OpenClaw Express Vite OpenClaw is what happens when you make no effort at all to rein in dependencies. The famous AI agent pulls 385 npm packages (324 without attestation). For the morbidly curious, you can look at the dependency graph in the dependency explorer. Express is the canonical Node.js web server. It pulls 65 packages, all unattested. Luckily, there are no-dependency alternatives such as Hono which can be used from source as described in my post Versatile Npm-Free Web Stack. Vite is a popular build tool in the JS ecosystem. Among the three, it's the only one with a dependency graph small enough to be shown here in full. Still, installing it pulls 15 packages, mostly bundled JS , not the original source, plus two pre-compiled Rust binaries. E.g. lightningcss is a binary uploaded by the maintainer with nothing tying it back to any specific source. npm install vite dependency graph

Dependency Explorer. Check out our interactive dependency explorer. It shows dependency graphs and information about npm, pip, and nix packages. Besides highlighting unattested packages, it makes it easy to explore transitive dependencies and might help you decide which packages or frameworks to rely on.

Pip, unlike npm, does offer an option to build the entire dependency graph from source, but it requires all used dependencies to publish their source to pypi.org . Many packages containing native binaries don't do that (e.g. PyTorch or JAX )3.

Both npmjs.com and pypi.org have adopted attestation (see SLSA and PEP 740 ). Attestation certifies that a package was built by some trusted provider from a specific source commit. Attestation is a real improvement, but it doesn't make it easier to rebuild from source. Also, the attestation protocol itself has gaps: the source commit and the workflow file (e.g. publish.yml ) are pinned, but the runner image and anything the workflow downloads at build time are not.

Example: Rolldown. Rolldown's publish workflow runs on a GitHub-hosted runner VM whose image is not identified anywhere in the attestation. On top of that unpinned base, the workflow uses third-party actions to install Rust, Zig, and other build tools. Each of those actions is pinned by commit but is itself an unattested binary, and downloads further unattested binaries at run time. The attestation at the top pins nothing underneath it.

Any supply chain where the source of its dependencies isn't pinned via hash is wonky. While npm and pip do pin dependencies via lock files, the hashes in those lock files cover the built artefacts and not the sources.

There are many ways to do better here, for example by using git submodules for dependencies, or using custom workflows like the one in Versatile Npm-Free Web Stack. Unlike npm, with pip just specifying git repos with commit hashes as dependencies works well for pure Python packages. For the really ambitious, today, systems like Nix and Guix allow you to go even beyond that and source pin entire build environments and native runtime dependencies.