Vulnerable Dependencies: How to Find and Fix Them
Practice4 minInnovialabs editorial team
In short
Scan dependencies regularly (npm audit, pip-audit), automate updates with Dependabot or Renovate, commit your lockfiles, and fix first what is actually reachable and exploitable in your application.
The short answer
Most of the code in a typical project is third-party libraries, and vulnerabilities in them are found all the time. A working setup:
- A scanner finds known vulnerabilities:
npm audit,pip-auditand similar tools. - An update bot (Dependabot or Renovate) opens pull requests with new versions for you.
- A lockfile pins exact versions so production is built from exactly what you tested.
- Risk assessment lets you fix what is truly dangerous first, rather than everything at once.
Scanning: npm audit and pip-audit
npm audit checks your dependency tree against a database of known vulnerabilities:
npm audit # all dependencies
npm audit --omit=dev # only what ships to production
npm audit fix # update within allowed ranges
Be careful with npm audit fix --force: it may install a new major version with breaking changes. Use it deliberately and run your tests afterwards.
If a transitive dependency is vulnerable and the direct one has not been updated yet, you can force a version through overrides in package.json:
{
"overrides": {
"vulnerable-lib": "^2.3.1"
}
}
For Python there is pip-audit:
pip install pip-audit
pip-audit -r requirements.txt
Add scanning to CI so new vulnerabilities show up in a report on their own, not whenever someone remembers.
Automated updates: Dependabot and Renovate
Dependabot is built into GitHub. A minimal setup:
# .github/dependabot.yml
version: 2
updates:
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "weekly"
groups:
minor-and-patch:
update-types: ["minor", "patch"]
Grouping bundles small updates into one pull request so the bot does not flood the team.
Renovate works with GitHub, GitLab and other platforms and is more configurable: schedules, rule-based automerge, grouping by any criteria.
{
"extends": ["config:recommended"],
"packageRules": [
{
"matchUpdateTypes": ["patch"],
"automerge": true
}
]
}
Automerge only makes sense with good test coverage and required CI checks.
Judging real exploitability
A scanner shows a severity level (often a CVSS score), but it does not know how you use the library. Ask:
- Does the package ship to production? A flaw in a build or test tool is usually less urgent.
- Is the vulnerable function actually called? If the bug is in a YAML parser you never use, the risk is lower.
- Does an attacker control the input? A flaw in handling user uploads is more dangerous than one in parsing your own config.
- Is it exploited in the wild? Check whether the vulnerability is in the CISA KEV catalog and what its EPSS score — the estimated probability of exploitation — is.
If you decide not to fix something now, record the decision and the reason instead of silently ignoring the warning.
Lockfiles
- Always commit
package-lock.json,pnpm-lock.yaml,poetry.lockor the equivalent. - In CI, install with
npm ci: it installs strictly from the lockfile and fails on mismatches. - In Python, pin versions with pip-tools, Poetry or uv; for extra protection, use package hashes.
Without a lockfile, the same commit builds with different versions on different days, and a vulnerability can arrive without a single code change.
A sane update routine
- Security patches for production dependencies — right away, once tests pass.
- Minor and patch versions — in a batch every week or two.
- Major versions — as a planned task: read the changelog, update the code, run the tests, deploy to staging.
- Before release, staging and smoke tests; after release, error monitoring.
- Remove unused dependencies: no package, no vulnerability.
Common mistakes
- Ignoring the report because "there is always something red".
- Running
npm audit fix --forceright before a release. - Not committing the lockfile.
- Letting updates pile up for years, then jumping several major versions at once.