Locking and updating dependencies
Two obligations pulling in opposite directions
| Requirement | What it demands | What it pushes you to do |
|---|---|---|
| Annex I, Part I — no known exploitable vulnerability at placing on the market | Components must be current | Update often |
| Annex I, Part II, point 2 — remediate without delay | The same, continuously | Update fast |
| Art. 13(8) — five-to-ten-year support period | Be able to rebuild identically a version shipped eight years ago | Freeze everything |
The Regulation therefore demands freshness and reproducibility at the same time. The answer is not a soft compromise: it is to freeze precisely, and update deliberately.
Freezing: the lock file
A manifest declares ranges (“version 5 or above”); a lock file records the exact resolution, transitive dependencies included, with hashes.
Without one, two builds of the same code on two dates produce two different artefacts. The consequences are directly regulatory:
- the SBOM no longer reliably describes what was shipped;
- rebuilding an old version becomes impossible, and so does the support period;
- a silent substitution of a dependency goes unseen.
The rule: the lock file is committed, it is part of the source, and a publishable build fails if resolution departs from it. That is what the strict install modes of the various ecosystems guarantee, by refusing to update the lock file on the fly.
Freeze what is not an application dependency too
This is the most common blind spot: libraries get locked, and everything else in the chain is left mutable.
| Element | Mutable reference — to avoid | Immutable reference |
|---|---|---|
| Reused CI action or step | Version tag | Full commit hash |
| Container base image | :latest, or a version tag |
Content digest |
| Remote script run by the chain | URL pointing at the main branch | Frozen URL + hash verification |
| Tool installed on the fly | “latest version” | Exact version, hash verified |
Why this is decisive. A tag can be repointed to another commit without any of your references changing. That is exactly the attack that exposed the secrets of thousands of chains in March 2025 — see Reference incidents. A hash cannot be repointed.
Updating: deliberately, not automatically
Freezing without updating produces the opposite of the intended result: a product that is reproducible and vulnerable.
Update bots — Dependabot, Renovate — are the standard tooling. They open pull requests when a version is published, with release notes and, depending on configuration, the vulnerability fixed.
But an update bot is itself a vector. An automatically merged version bump introduces third-party code straight into production. Three safeguards:
| Safeguard | Effect |
|---|---|
| Quarantine delay | No version published less than N days ago is proposed. Malicious packages are usually pulled within hours or days |
| Restricted auto-merge | Limited to security fixes on allow-listed components, and never for a major version change |
| Human review of sensitive dependencies | An explicit list of components whose every bump goes through a person |
Controlled ingestion: cache or decision point?
Routing dependencies through an internal mirror is widespread good practice, but two arrangements are often conflated:
| Cache or mirror | Decision point at ingestion | |
|---|---|---|
| Function | Serves faster, survives upstream outages | Allows, denies, quarantines |
| Timing | After download | Before entry into the organisation |
| Faced with a malicious package | It propagates it, and keeps it | It blocks it |
A cache accelerates; it does not govern. If the goal is to stop a typosquatted or freshly published package from entering, the decision must be applied before ingestion, with a policy that lives beside the code rather than in a console.
Often overlooked: configuration alone is not enough. As long as a workstation can reach the public registry directly, bypassing is trivial. Egress filtering is what makes the rule effective.
Reproducibility, a long-term requirement
On a product covered by a ten-year support period, you must be able to rebuild in eight years. That means retaining, alongside the code:
- the lock file of the shipped version;
- the hashes of base images and build tools;
- a copy of upstream artefacts — a public registry is not an archival service, and packages do disappear;
- the chain definition as it stood.
The reproducible builds effort takes the logic to its conclusion: two builds of the same code produce bit-for-bit identical artefacts. It is demanding, and it is the only proof a third party can genuinely verify.
The overlap is worth stating: what the Regulation requires you to retain for the support period, and what the GPL requires you to supply as “corresponding source”, are the same set of artefacts. See Desktop or command-line application.
Checklist
- Committed lock file for each ecosystem
- Publishable build in strict mode, failing if resolution departs from the lock file
- CI actions and steps pinned by commit hash
- Base images pinned by content digest
- Update bot active, with a quarantine delay
- Auto-merge limited to security fixes on allow-listed components
- List of dependencies requiring mandatory human review
- Dependencies served by an internal mirror, with egress filtering
- Upstream artefacts archived for the duration of the support period
- Annual test: rebuild a version shipped more than two years ago