Dependency Management
How to select, version, audit, and maintain third-party dependencies without accumulating supply-chain risk.
Overview
Third-party dependencies extend a team's capability at a cost that is often underestimated: every dependency brings its own dependencies, its own security vulnerability surface, its own upgrade burden, and its own supply-chain risk. A project with 50 direct dependencies may have 500 transitive ones, each of which represents a trust decision the team made implicitly.
Dependency management is the practice of making those trust decisions explicitly, keeping the dependency set current, and maintaining visibility into the risk it represents. The goal is not zero dependencies — it is a dependency set that is intentional, current, and audited.
For automated vulnerability scanning as part of the CI gate, see Static Analysis. For secrets that may be embedded in dependency configuration, see Secrets Management. For security design considerations including supply-chain trust, see Security Architecture.
Why It Matters
Outdated dependencies are the most common source of known vulnerabilities. The most important word there is "known" — the CVE database contains the vulnerabilities discovered yesterday in libraries that were downloaded three years ago. A project that has not updated its dependencies in 12 months is running code with published vulnerabilities it has chosen not to fix.
Dependency drift compounds. A single missed minor version is easy to catch up on. A dependency that has fallen 18 months behind has accumulated breaking changes, deprecated APIs, and migration work that grows as the gap widens. Dependencies that are not updated regularly become dependencies that cannot be updated without major project work.
Supply-chain attacks are real and growing. Malicious packages that impersonate legitimate ones, compromised maintainer accounts, and typosquatting attacks have all resulted in malicious code executing in production applications. Trust decisions about which packages to install are security decisions.
Dependency bloat slows builds, increases attack surface, and obscures the dependency graph. Every unused dependency is build toolchain weight that does not pay off. It is also a package that could be compromised and executed in the application without anyone noticing its absence.
Standards & Best Practices
Lock files are not optional
A lock file (package-lock.json, yarn.lock, poetry.lock, go.sum, Gemfile.lock) pins the exact version — including transitive dependencies — that will be installed. Without a lock file:
- Builds are not reproducible (different developers, different CI runs, may install different versions)
- A compromised transitive dependency can appear silently in the next build
- Production and development environments may diverge
Lock files must be committed to the repository. They are not generated artifacts — they are authoritative records of what the dependency tree is. Changes to the lock file are reviewed as part of the PR, not ignored.
Pin versions with discipline
Direct dependencies should specify a version range that expresses the intended compatibility:
# Wrong — any version, uncontrolled updates
requests
# Risky — accepts breaking changes from major version bumps
requests>=2.0.0
# Better — compatible releases (minor updates, no breaking changes)
requests~=2.31.0
# Best for production services — exact version, updated deliberately
requests==2.31.0The appropriate strategy depends on the type of project:
- Production services — exact versions for direct dependencies; rely on the lock file for transitive ones
- Libraries — looser ranges are necessary to avoid version conflicts with consumers
- Development and CI tools — exact versions; tools that drift produce inconsistent results
In all cases, the lock file is the source of truth for what is actually installed.
Automate dependency updates
Manual dependency updates accumulate as a chore and then stop happening. Automated update tooling (Dependabot, Renovate, or equivalent) keeps the dependency set current without depending on engineers to remember:
- Raise a PR for each update (not a mega-PR for all updates) so each can be reviewed and tested independently
- Auto-merge patch updates after CI passes (patch versions are bug fixes and security patches)
- Require human review for minor and major version bumps
- Configure grouping for closely related packages (React ecosystem, testing tools) to reduce noise
The goal is a flow of small, low-risk dependency updates rather than infrequent large batches.
Audit dependency security on a schedule
Automated tooling (npm audit, pip-audit, Snyk, Dependabot alerts, GitHub Advanced Security) identifies known vulnerabilities in the current dependency tree. The security audit must be:
- Run in CI — failing the build on critical and high CVEs
- Checked regularly — new CVEs are published continuously; a dependency tree that was clean last week may not be clean today
- Acted on with SLAs — define how quickly known vulnerabilities must be remediated:
| Severity | Remediation SLA |
|---|---|
| Critical | Within 24 hours |
| High | Within 7 days |
| Medium | Within 30 days |
| Low | Next scheduled update cycle |
If a vulnerability cannot be remediated within the SLA (e.g. no fix is available), document the decision to accept the risk with a named owner and a revisit date.
Evaluate new dependencies before adopting them
Adding a dependency is a trust decision. Before adding a new direct dependency, evaluate:
- Is it necessary? — Can the functionality be implemented in a reasonable amount of code, avoiding the dependency entirely?
- Is it maintained? — When was the last release? Are issues being addressed?
- What is its transitive footprint? — How many transitive dependencies does it add?
- What is its license? — Is the license compatible with the project's distribution model? (GPL in a commercial product is typically not acceptable without legal review)
- What is its security track record? — Has it had CVEs? Were they addressed promptly?
- Who maintains it? — Is it a single individual, a company, or a foundation? What is the risk of abandonment?
The evaluation does not need to be a formal process for every dependency — a quick check before npm install <package> is significantly better than none.
Prune unused dependencies
Unused dependencies are pure cost: build time, attack surface, and lock file complexity. Standards:
- Remove a dependency when the code that uses it is removed
- Audit the full dependency tree periodically for packages that are no longer used
- Use tools (
depcheck,deptry,pip-autoremove) to identify candidates for removal
Periodic pruning is easier than infrequent large-scale cleanups.
Software Bill of Materials (SBOM) for regulated contexts
An SBOM is a formal inventory of a project's components: every package, version, and origin. SBOM requirements are appearing in regulatory frameworks (US EO 14028, EU Cyber Resilience Act) and in enterprise procurement requirements.
For projects where supply-chain attestation is required:
- Generate an SBOM as part of the build pipeline (using
cyclonedx,syft, or equivalent) - Store the SBOM alongside the release artifact
- Verify incoming artifacts from suppliers carry SBOMs or equivalent attestation
For most internal projects, SBOM generation is currently optional but is becoming a default expectation. Starting the practice before it is required is less disruptive than retrofitting it under a deadline.
How to Implement
Dependency review checklist for a PR that adds a new package
- Is this dependency necessary, or can the functionality be implemented without it?
- Is the package actively maintained?
- Is the license compatible with the project's distribution requirements?
- Has the package or its maintainers had known security incidents?
- Is the transitive dependency footprint acceptable?
- Is the version pinned appropriately?
- Is the lock file committed and updated?
CI gate for dependency security
The CI pipeline must:
- Run a security audit (
npm audit --audit-level=high,pip-audit, Snyk scan, etc.) - Fail the build on critical and high severity vulnerabilities
- Produce a dependency audit report that is stored as a build artifact
Common Pitfalls
Not committing the lock file. A repository without a committed lock file cannot produce reproducible builds. Every developer and CI run may install slightly different dependency versions. The lock file is not optional.
Never reviewing lock file changes. A PR that updates 30 packages in the lock file without any corresponding change to the direct dependency list is worth examining. Transitive dependency updates can introduce vulnerabilities or breaking changes.
Installing packages from unknown publishers. A package published three weeks ago by an account with no history, offering to replace a well-known package, is a supply-chain attack candidate. Favour packages with long track records, known maintainers, and substantial adoption.
Ignoring security audit output. A CI pipeline that runs npm audit but does not fail on high-severity results treats the audit as reporting, not enforcement. The audit gate must block the build for vulnerabilities above the threshold.
Adding a library to avoid writing 10 lines of code. A dependency that introduces 200 transitive packages to avoid writing a trivial function is not a good trade. The cost of the dependency accumulates forever; the 10 lines of code are paid once.
Letting the dependency tree grow unchecked. Dependencies that are added and never removed are technical debt that compounds. A codebase with dependencies that have not been used for years is carrying operational risk it may not even be aware of.