Safeguard
Developer Tools•

Should a library pin its dependencies or use version ranges?

Libraries should declare ranges with a tested lower bound, and apps should pin. Here is the reasoning, using Python packaging guidance as the source.

Safeguard Security Team
3 min read

A library should declare ranges with a sensible lower and upper bound, and an application should pin exact versions in a lockfile or requirements file. Pinning inside a library blocks your users from receiving dependency upgrades, including security fixes, until you cut a new release.

The rule in plain terms

Python's packaging guide separates two things. install_requires lists the minimum a project needs to run, and its advice is to add known lower and upper bounds such as A>=1,<2. It calls exact pins and sub-dependency lists "overly-restrictive" because they block users from getting upgrades.

A requirements file, by contrast, is a list of pip arguments that often pins exact versions to give repeatable installs of a whole environment. pip does not read it automatically when installing a project. That makes it the right home for an application's pins.

Other ecosystems differ in syntax, but the split holds. npm separates dependencies ranges from the lockfile, and Cargo separates Cargo.toml from Cargo.lock.

Why pinning inside a library hurts security

Suppose your library pins parser==2.3.0. An advisory lands for 2.3.0 with a fix in 2.3.1. Every project that uses your library is now stuck with the vulnerable parser until you release a version that pins 2.3.1, and until they upgrade to that release. If they use another library that needs 2.3.1, their resolver reports a conflict.

With a range such as parser>=2.3.1,<3, the user's installer can pick the fix as soon as it exists.

A lower bound is a security statement

The lower bound should be the oldest version you actually test. When an advisory affects a dependency and your code can reach it, raise the floor to the patched version. That is the one place where changing the range is a security action.

The upper bound is a stability statement. Many maintainers use the next major version, because breaking changes are expected there. Keep it, and check on new majors in CI before users hit them.

Steps for a library maintainer

  1. Write ranges, not exact pins, for runtime dependencies.
  2. Set the lower bound to the oldest version in your test matrix.
  3. Add a CI job that installs the lowest allowed versions and one that installs the latest, so both ends of each range are exercised.
  4. Keep an application-style lockfile or requirements file for your own CI and docs builds.
  5. On an affected advisory, raise the floor and release a patch.

Where pins are right

Pin build tools, test tools and GitHub Actions in your own repository. Those only affect you. Pinning your workflow actions to a commit SHA is a documented hardening step in GitHub's security guide.

Quick check

Is it ever fine for a library to pin exactly?

Rarely. A known incompatibility with one release may justify excluding that release, but excluding a version is different from pinning everything.

Where should an application pin?

In a lockfile or a requirements file with exact versions, so deployments are repeatable.

Does a lower bound make my library vulnerable?

No. It stops resolvers from choosing a version below your floor. Raise it when an advisory affects code you call.

Safeguard's dependency scanning shows which direct and transitive versions a project resolves, and its reachability analysis helps you decide whether a floor needs to move. The free scan is a quick way to look.

Sources

Last reviewed: 19 October 2026.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.