A platform team debates two dependency policies for 300 services. Policy A pins exact versions and hashes and updates quarterly. Policy B lets a bot merge minor and patch updates the day they appear. In November 2018 a maintainer of the popular npm package event-stream handed the project to a stranger, who added a dependency that carried a malicious payload aimed at one cryptocurrency wallet. What does each policy buy and cost, and which would you adopt?
Show the full answer Hide the answer
What each policy gains
Policy A (pin, hash, batch quarterly) means a malicious or broken release can only enter when a human chooses to bump it. In the event-stream case, the malicious version existed for roughly two months before discovery, and anything resolving a floating range during that window picked it up; a lockfile with hashes would have protected a project until its next deliberate update. The cost is exposure to known flaws: a critical CVE published the week after a quarterly bump stays in production for up to 12 weeks.
Policy B (auto-merge on release day) minimises patch latency, which dominates risk for well-known vulnerabilities. Log4Shell (December 2021) was exploited within hours of disclosure, and teams with fast, tested update paths fixed it in days. The cost is exactly the event-stream scenario in reverse: the first day of a release is the day with the least community scrutiny, and an auto-merging bot delivers a hijacked package to every service before anyone has looked at it.
What is paid beyond security
Policy A creates a quarterly update project with large diffs and a long tail of breakage; teams under deadline defer it, and the intervals stretch to a year. Policy B creates constant small merges and a dependence on test quality, since a green build is the only gate.
When the cost becomes visible
A: on the day of a critical advisory, when the team has no practiced path to ship a dependency change in hours. B: on the day a trusted package is hijacked, when every service has already merged it.
What I would adopt
A hybrid with explicit rules rather than a single policy:
- Pin with hashes everywhere, so builds are reproducible and substitution is detectable.
- Cooldown on auto-updates: the bot proposes updates only after a release is 3 to 7 days old, which gives the ecosystem time to detect hijacks, and merges automatically only if tests pass.
- Fast lane for security advisories: critical fixes bypass the cooldown, with an owner on call to review the diff.
- Reduce the tree: fewer transitive dependencies shrinks both risks. event-stream's payload arrived through a transitive dependency few knew they had.
How to keep the option to change
Keep the policy in configuration (bot rules, lockfile enforcement in CI), not in convention, so tightening or relaxing it is one change.
When the simple choice wins
A small service with ten dependencies and a team that reads changelogs does fine with Policy B and no ceremony. A regulated build with a vendored dependency mirror should use A.
Common weak answers
- "Always use the latest." It optimises one failure and invites the other.
- "Never update." Known vulnerabilities accumulate silently.