Dev ToolingDev Tooling
Getting Started With Dependency Management Tools
Dev Tooling

Getting Started With Dependency Management Tools

Lewis SmithArticle by Lewis Smith

Dependency management tooling appeared the first time two machines built the same commit and produced different software, and no person could name the version of the library that made the difference. The mechanism behind that failure is one loop. A manifest declares a version range. A resolver picks the newest release that satisfies it. A maintainer publishes something new in the gap. Every package manager, lock file, update bot, artefact repository and vulnerability scanner in this category closes one part of that loop or reports on it. Read them in that order and the field no longer looks like a list of brand names.

A manifest requests a version range and the resolver selects which version is delivered.

A dependency is external code your application needs in order to run. Import a library and you take a direct dependency; that library's own dependencies arrive with it whether you asked for them or not. Endor Labs reports that a project directly using about 20 packages inherits roughly 200 transitive ones, and that large applications run into the thousands. The resolver handles that tracking to determine what actually goes out.

Semantic versioning is the contract that makes resolution possible at all. A version string such as 2.5.1 splits into MAJOR.MINOR.PATCH, and each field conveys a promise about what changed: breaking change, additive change, fix. The manifest then specifies how much of that promise you accept, and the caret or tilde in front of a version is where most incidents begin.

Version conflict is the second problem the resolver solves. When two packages require incompatible versions of one shared library, no single answer satisfies both, and something has to give. The strategies are familiar: upgrade the direct dependencies until their transitive requirements converge, or force one version and accept the runtime bugs that forcing can introduce. Endor Labs calls the blocked state dependency hell, and it can hold a feature back for days.

A lock file freezes the answer the resolver gave

A manifest specifies a range, and a lock file saves the exact version the resolver selected for every package in the tree, direct and transitive. That distinction is the whole point. Two developers running the same install command against the same manifest on different days can receive different transitive versions unless a lock file pins them. Endor Labs notes that npm creates package-lock.json and Yarn creates yarn.lock, and that the install then reproduces a snapshot.

The lock entry is not just a version number. npm includes a resolution URL and an integrity hash:

"node_modules/lodash": {
  "version": "4.17.21",
  "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
  "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
}

The integrity field is what lets the client refuse a tarball whose bytes do not match the one the registry published. Drop the lock file and you drop that check along with reproducibility.

The install command matters as much as the file. npm ci installs strictly from the lock and fails when the manifest and the lock disagree; npm install may rewrite the lock. A continuous integration job that runs the second and commits the result has quietly moved resolution into the pipeline, on every build, with no review.

Update automation closes the version gap on a schedule you control

Pinning every version solves reproducibility and creates a new problem: No part of the project progresses until a human progresses it. The GDS Way is explicit that dependencies should be updated frequently because small changes can be tested automatically. It names Dependabot as the tool most teams at GDS use, allows Snyk as an alternative, and requires a cooldown period so that a freshly published version is not adopted before the community has had time to flag it.

The GOV.UK Developer Documentation shows what that configuration looks like in practice. Its recommended .github/dependabot.yml runs a monthly schedule, sets a three-day cooldown on application ecosystems and seven days on Terraform and Docker, groups related packages into one pull request, and raises open-pull-requests-limit to at least 10. The default is 5, and the page notes that a low cap has blocked security updates in a past incident. Security updates ignore the schedule and are raised immediately.

The configuration is short enough to read in one pass:

version: 2
updates:
  - package-ecosystem: npm
    directory: /
    schedule:
      interval: monthly
    cooldown:
      default-days: 3
    open-pull-requests-limit: 10
    allow:
      - dependency-type: direct
    groups:
      lint:
        patterns:
          - "eslint"
          - "eslint-*"
          - "prettier"

Every key there changes behaviour. dependency-type: direct limits the bot to top-level packages and leaves transitive versions to the resolver, which is a deliberate trade: fewer pull requests, less coverage. groups collapses a dozen lint bumps into one review. cooldown is the mitigation against a compromised release that reaches the registry before anyone notices.

HMRC's engineering standards treat the schedule as a requirement. The HMRC dependency management standard requires that teams version all software dependencies, keep them up to date, be able to produce an inventory of all of them, and fetch artefacts from a source that stores each version immutably. The reason given for versioning is reproducibility: the build has to identify exactly which code produced a given release.

Scanning turns the dependency tree into a list of decisions

An update bot tells you a newer version exists. It does not tell you whether the version you run is exploitable. That is the scanner's job, and the two are frequently confused. HMRC's standard is direct about the limit: scanning tools do not consider the context in which you use a dependency. A vulnerability in a code path you never call may wait for the next regular update; a vulnerability in a path you exercise needs action now.

The Home Office engineering guidance SEGAS-00007 sets out where scanning has to sit. Source code scanning runs on the repository, so problems are caught during development. Pipeline scanning runs as the artefact is built, and can fail the build on set criteria. Continuous scanning of built artefacts catches vulnerabilities disclosed after a successful release, which the source tree no longer reflects. The same page points at the OSSF Scorecard for judging a project before you adopt it, and at Software Bills of Materials generated during the build, because a manifest does not always state a precise version or enumerate nested dependencies.

The GOV.UK Service Manual adds the container layer. Docker base images are dependencies, they are frequently updated, and the guidance is to rebuild images regularly, prefer official base images, and minimise what you have to monitor. Managed orchestration such as AWS Fargate removes the operating system and control plane from your vulnerability list entirely.

Which way to take on external code?

The GOV.UK Service Manual names three ways a team can take on external code, and they differ on who performs the maintenance. The table below sets them against the attributes that decide the choice.

How the code arrives Who tracks upstream changes What fixes a version Licence constraint
A dependency management tool the tool, at the cadence you configure the lock file, for every direct and transitive package each dependency keeps its own licence
Vendoring the code into your project your team, by hand your copy, frozen at the moment you took it some licences forbid it, others force your project under the same terms
A fork in a repository you control your team, pulling from the original your fork's commit the upstream licence still applies

Source: GOV.UK Service Manual, Managing software dependencies.

The manual recommends the first and treats vendoring as a last resort, because it breaks the link with upstream and turns every security fix into manual work. That is the same trade written the other way round: control against maintenance.

What each layer of tooling is for

Once the loop is clear, the categories stop overlapping. The second table maps each layer to the question it answers and to the tools the sources above name.

Layer The question it answers Tools named in the sources
Package manager what versions exist and how do I fetch one Maven, Bundler, Pip, sbt, Leiningen, Composer, npm (GOV.UK Service Manual)
Lock file which exact versions did this build use package-lock.json, yarn.lock (Endor Labs)
Update automation a newer version exists, should we move Dependabot, Snyk (GDS Way)
Vulnerability scanner is what we run exploitable, and where Dependabot, Trivy (Home Office); npm audit, Sonatype, Snyk (GOV.UK Service Manual)
Artefact repository where do the bytes come from, immutably Nexus Repository (Sonatype); the trustworthy source requirement (HMRC)

No single tool covers a row and a column. A team that installs a scanner and no lock file can still ship two different builds from one commit. A team with a lock file and no scanner can ship a build it has never checked.

How does an AI assistant affect the dependency loop?

An assistant that creates your manifest still creates a version range, and the resolver still has the final say. What changes is the rate at which ranges enter the tree, and the human attention available per range. Sonatype reports that when an AI assistant is confident about a component choice it is accurate 98 per cent of the time, but that it is confident only 4 per cent of the time when selecting a safe, high-quality component. The remainder arrives with no assurance of security, licence compliance or maintenance.

Sonatype's answer is to put component intelligence in front of the agent, so that a generated manifest only names packages that pass policy, and to automate the upgrade pull request once a safe version exists. The Home Office guidance requires that external components, including generated dependencies, be assessed before the commit lands.

How to secure the supply chain

Pin every version you distribute and commit the lock file next to the manifest, because any subsequent process is meaningless without it. Turn on an update bot with a cooldown, a pull request limit above the default and grouping so the review load stays readable. Run a scanner in the repository and again in the pipeline, and configure the pipeline to fail on known exploitable findings. Generate a bill of materials as part of the build and hand it to whoever operates the service. Review the dependency tree before adding a package, and prefer the smallest base image and the managed service over the one you have to patch yourself.

Four of those steps are configuration and take an afternoon. The fifth, review before adding, is the one that keeps the other four from becoming a queue you never clear.

Sources

The figures behind this

Transitive packages (20 direct)
200 (Endor Labs)
Docker cooldown
7 days
App ecosystem cooldown
3 days

Related guides

Build Automation Tools, Compared
Build Automation Tools, Compared
Performance Profiling Tools, Explained
Performance Profiling Tools, Explained
Static Code Analysis Tools: Where to Start
Static Code Analysis Tools: Where to Start

← All Guides