Dependency Pinning

Dependency Pinning

What Is Dependency Pinning?

Dependency pinning is the practice of fixing the exact version of every library a build resolves, so the same source produces the same dependency tree every time. It is expressed through exact version constraints and lockfiles rather than ranges. Pinning governs package versions; it does not govern container images, which are pinned separately by digest.

Most package managers default to ranges. A manifest that asks for ^2.4.0 is asking for whatever 2.x release the registry offers at install time, which means the build is resolved fresh on every run.

That flexibility is what pinning removes. The resolution decision moves from install time, where nobody is watching, to the moment a human commits a changed lockfile.

How Dependency Pinning Works

Pinning is not one control but four, and a build is only reproducible when all of them hold:

  • Exact constraints — 2.4.1 in the manifest rather than ^2.4.0, so the declared intent names a single release.
  • Lockfiles — a generated record of the resolved version of every package, direct and transitive, committed to the repository.
  • Install commands that honour the lock — npm ci installs exactly what the lockfile says and fails when the manifest and lock disagree; a plain install may rewrite the lock instead.
  • Integrity hashes — a version number names a release, while the hash in the lockfile verifies the bytes that arrived.

Ranges are the attacker's lever. Unit 42's analysis of npm supply chain attacks describes version inflation, where a malicious release is published with a version number above the latest legitimate one so that range resolvers select it. The same research records how fast this moves: 84 malicious artifacts across 42 @tanstack/* packages in six minutes on 11 May 2026, and 639 malicious versions across 323 @antv packages in about an hour on 19 May.

Because most malicious packages are removed within 24 to 72 hours of publication, Unit 42 recommends blocking versions published inside that window. Lockfiles installed with npm ci appear on the same list of controls.

What Pinning Does Not Fix

A pinned tree is a frozen tree, and freezing has a cost that compounds quietly.

  • Staleness — pinned dependencies stop receiving fixes until something moves the pin, so security debt accrues by default.
  • Transitive ranges — pinning direct dependencies alone leaves ranges in the sub-tree; only a lockfile reaches a transitive dependency.
  • Image contents — pinned packages say nothing about base image layers, which need digest pinning to be fixed.
  • Already-compromised versions — if the pinned release is the malicious one, pinning reproduces it faithfully on every build.

Pinning also does nothing about the moment the pin moves. That is where a package cooldown belongs: a waiting period before a newly published version becomes eligible, so the pin never advances onto a release that the ecosystem has not yet had time to examine.

How to Pin Dependencies in Production Environments

The aim is reproducibility without stagnation, which takes a deliberate update path.

  • Commit the lockfile and treat changes to it as reviewable, because that diff is where new code enters.
  • Install in CI with the command that fails on a lockfile mismatch rather than the one that rewrites it.
  • Pin the base image by digest as well, so the layers under the application are as fixed as the packages above it.
  • Disable install-time scripts, which run before anything has inspected the package.
  • Automate update proposals so staleness surfaces as pull requests, and apply a cooldown before accepting a brand-new release.

How Hardened Container Images Keep Pinned Trees Current

Pinning buys reproducibility and creates maintenance debt. Someone still has to move the pin when a fix lands, and in a large estate that work dominates.

Echo rebuilds and updates images continuously, and workloads can stay on the versions that suit them without forced migrations (echo.ai/product). Critical and high CVEs are triaged within 24 hours and fixed in up to seven days.

That does not move your pin for you. It makes sure there is a patched version to move to, so the decision is scheduling rather than remediation.

FAQs

Is pinning the same as using a lockfile?

Closely related, but not identical. A lockfile records the versions a resolver chose, including transitive ones, and is the practical mechanism most ecosystems use. Exact constraints in the manifest express the same intent at the declaration level. Pinning holds properly only when both are present and the install command respects them.

Does pinning make a build reproducible?

It gets most of the way. Fixed versions and integrity hashes remove the largest source of variation between builds, which is dependency resolution. Full reproducibility also depends on the base image, the toolchain and the build environment, so pinning packages alone leaves several other inputs free to change underneath you.

Should transitive dependencies be pinned too?

Yes, and a lockfile is how. Direct dependencies are a small fraction of a real tree, and the versions beneath them are where most unreviewed code arrives. Pinning only what the manifest names leaves ranges resolving freshly in the sub-tree, which is exactly the surface that published attacks aim at.

How do I avoid pinned dependencies going stale?

Pair pinning with an automated update mechanism. Bots that open pull requests for new versions make staleness visible instead of silent, and the review step keeps a human in the path. Adding a cooldown before a new release becomes eligible gives the wider ecosystem time to withdraw anything malicious first.

Is pinning by version as strong as pinning by digest?

No. A version number is a label the registry resolves, and a label can in principle be moved or republished. A digest is derived from the content itself, so it names exactly one immutable artifact. Integrity hashes in a lockfile narrow that gap for packages, while images rely on digests directly.

Ready to eliminate CVEs at the source?