DEV Community

Cover image for Most Package Managers Won't Stop a Tampered Download by Default
Dhruv Agrawal
Dhruv Agrawal

Posted on

Most Package Managers Won't Stop a Tampered Download by Default

Keegan Ryan of the security firm Trail of Bits set up a fake compromised package server and pointed 25 popular package managers at it. Two of them, Go and vcpkg, verified every package they downloaded, even without a lockfile. Without one, the other 23 installed whatever they were given.

Lockfiles didn't rescue most of them either.

The test

Ryan routed each tool's traffic through a proxy that could rewrite downloads, then installed quickstart projects with default settings, with and without a lockfile. An attack counted as a success if the injected code ran. The threat model assumes any web-facing server, such as a registry or a mirror, can be compromised, but backend secrets like private signing keys can't.

The lockfile is the safety net, and it's mostly unplugged

A lockfile pins every dependency's exact version and, ideally, a hash: a fingerprint the download has to match. Fewer than half of the 25 tools checked those hashes by default.

Hand-drawn grid of 25 package managers colored by what they verify: Go and vcpkg always verify; nine verify when a lockfile is present; five need extra steps; nine don't verify or the check doesn't apply.

All 25 package managers, by default behavior. Source: Trail of Bits, October 14, 2026.

Nine verify when a lockfile is present, including npm, pnpm, Cargo, uv and Yarn. Five can verify, but only with extra steps. The last nine don't verify lockfile hashes at all, or the check doesn't apply to them.

Checks that don't stop anything

Tools with no check are the obvious failures. The sneakier ones run a check and install anyway:

  • Pub (Dart and Flutter) detects a hash mismatch, installs the package anyway and updates the lockfile to match, unless you pass --enforce-lockfile. Some Flutter commands run pub get too, and can overwrite the lockfile unless called with --no-pub.
  • pnpm overwrote the lockfile on a mismatch until version 11.4, in May 2026, so the tampered package simply became the new truth.
  • Composer (PHP) locks a Git commit, then downloads a ZIP archive it never checks against that commit.
  • CocoaPods hashes the package spec but fetches the source by Git tag, which a compromised host could serve with different contents.
  • cpanminus (Perl) still connects to its repository over plain HTTP by default.

Diagram of what happens when a download doesn't match the lockfile: stop the install, warn and install anyway, rewrite the lockfile to match, or install with nothing to check.

Four outcomes when the download and the lockfile disagree. Only the green one protects you.

Why the defaults are like this

None of this is hidden; it's documented behavior. Lockfiles were built for reproducible installs, and as Ryan notes, reading them through a supply-chain lens "is a recent interpretation that can be at odds with their original design." Strict pinning also has a cost: every update, security fixes included, needs a lockfile change.

Go shows the alternative. It checks every hash against a signed checksum database that works like a transparency log, which makes targeted tampering much harder, at the cost of concentrating trust in a single private key. It was the only tool that verified every download and checked signatures by default. As Ryan puts it, "Package signing is in a similar state now to where HTTPS was a decade ago."

What to do this week

  1. Commit your lockfile, at least for applications. Norms for libraries vary by ecosystem.
  2. Install in strict mode in CI: npm ci, pnpm install --frozen-lockfile, uv sync --locked, pip install --require-hashes -r requirements.txt (with hashes generated by pip-compile --generate-hashes) or dart pub get --enforce-lockfile.
  3. If your tool needs extra steps, take them: the conda-lock library for Conda, a verification-metadata.xml file for Gradle, the RestorePackagesWithLockFile property for NuGet.
  4. If your tool is in the last group, don't treat its lockfile as a security control. Maven can check signatures against trusted keys with the third-party pgpverify-maven-plugin. Beyond that, the fallback Conan's developers suggest applies widely: if integrity is critical, run your own repository server.

Then test it: change one hash in your lockfile on purpose and confirm CI fails.

Caveats

Each tool was tested with one package, one version and one platform, Ubuntu, and some setups, like PowerShell and CocoaPods on Ubuntu, don't reflect typical use. The study shows that reasonable defaults can leave you exposed, not that every setup is.

Verification comes before detection

Most supply chain tooling hunts for packages already known to be bad. This study is about the step before that: making sure the package you got is the package you asked for. For most of the 25 tools, that check is off by default or missing. Where it exists, turning it on is about the cheapest security win you'll find.

If you check what your package manager does on a hash mismatch, I'd like to hear what you find.

Sources

Top comments (1)

Collapse
 
gautamtalksdev profile image
Gautam K •

One hop up from the package manager there is a dependency with no lockfile at all: the workflow's own steps. uses: org/action@v4 is a tag, and GitHub's API shows where a tag points now, not where it pointed last week. In March 2026 an attacker force pushed 76 of 77 version tags of trivy-action to credential stealing code, per Aqua's advisory.

The fix is pinning each action to a full commit SHA, with a catch from that same advisory: a trivy-action pinned to a commit from before April 2025 could still pull a malicious setup-trivy by tag. Pinning the top does not pin what it calls. Your "change one hash" test has a twin: grep your workflows for third party uses lines that don't end in a 40 character SHA.