Security

Dependency Risk: Auditing Your Third-Party Packages

Your app is mostly code you didn’t write

Run a quick count on any modern web project: lines of code you wrote versus lines sitting in node_modules or your virtualenv. The ratio is rarely better than 1:20 and often worse than 1:100. Every audit of “your” codebase is therefore, by volume, mostly an audit of your decisions about other people’s code: what you pulled in, whether it’s maintained, whether it’s carrying known holes, and whether anything in the install pipeline could be tampered with.

Teams tend to sit at one of two unhelpful extremes: ignoring npm audit output entirely (“it’s always red, it’s noise”), or panicking at every critical CVE regardless of relevance. Both are wrong, and the audit’s job is to replace them with something rational: triage by reachability.

CVEs: severity is not risk

Here’s the uncomfortable stat every auditor knows from experience: the overwhelming majority of “critical” findings from a dependency scanner don’t matter to your application; research on vulnerability reachability consistently puts exploitable-in-context findings at a small fraction of the raw total. A vulnerability is only your problem if the vulnerable code path is reachable with attacker-controlled input in your app.

Consider two hypothetical findings from the same scan:

  • Critical: prototype pollution in a deep-merge utility. But your app only ever calls it on two hardcoded config objects at boot. Real risk: near zero. Fix at leisure.
  • Moderate: header-parsing flaw in your HTTP framework’s multipart handler. Your app accepts file uploads from the public internet. Real risk: high. Fix this week.

A scanner ranks these backwards. A human ranks them correctly, and that ranking, applied across your whole manifest, is the difference between a dependency audit and a dependency printout. It’s the same scanner-vs-context gap that runs through everything a manual audit catches.

The audit workflow is: run the scanner (npm audit, pip-audit, cargo audit, or an SCA tool like osv-scanner), then for each critical/high finding ask three questions (Is the vulnerable function actually called? Can attacker input reach it? Is there a mitigating layer in front?) and re-rank accordingly.

The risks scanners don’t score at all

CVE scanning is the known-vulnerability layer. The audit spends at least as much time on the categories with no database entry.

Abandonment

An unmaintained package has no CVEs because nobody is looking, which is worse than having them. Signals the audit checks: last release date, open-issue trajectory, whether the maintainer has archived the repo or publicly walked away, and bus factor (how much of your stack depends on one person’s weekends; the left-pad and colors/faker incidents were both bus-factor stories, one an unpublish, one a maintainer protest-sabotaging his own packages).

Overexposure

Every dependency is attack surface and an upgrade obligation. The audit flags packages that buy you almost nothing: the one-liner utilities (is-odd and friends), the heavyweight library imported for a single function, the three date libraries doing one job. Fewer dependencies isn’t aesthetic minimalism; it’s a smaller supply chain to defend. This overlaps heavily with general codebase hygiene; see what “good code” looks like to an auditor.

Install-time execution

npm install runs arbitrary code via lifecycle scripts (postinstall), on every developer laptop and CI runner you have. Real-world supply-chain attacks (event-stream in 2018, ua-parser-js in 2021, the recurring waves of typosquats) overwhelmingly deliver their payloads this way. Mitigations the audit looks for: --ignore-scripts in CI where feasible, lockfiles committed and enforced (npm ci, not npm install, in pipelines), and dependency-review gates on PRs.

Typosquats and hallucinated packages

Two variants of the same trap. Classic typosquatting registers lodahs hoping for a fat-fingered install. The newer variant: LLM coding assistants sometimes confidently import packages that don’t exist, and attackers register those names (“slopsquatting”). If AI writes a meaningful share of your code, your dependency manifest deserves a suspicion pass; this is one of several failure modes covered in hidden debt in AI-generated code.

License risk

Not a security issue, but a due-diligence one that audits routinely surface: a copyleft (GPL/AGPL) dependency linked into a proprietary SaaS can become a genuine problem during acquisition or enterprise sales review. Cheaper to know now.

The dependency checklist

What we verify on the dependency pass of an audit:

  • Lockfile present, committed, and actually used in CI (npm ci / pip install -r with hashes / frozen lockfile)
  • Vulnerability scan run and criticals/highs triaged by reachability, not raw severity
  • No dependencies unmaintained > ~2 years on security-relevant paths (auth, crypto, parsing, serialization)
  • Direct dependency count sane for the app’s size; no trivial-utility imports
  • Pinning strategy is deliberate: ranges + lockfile + automated update PRs (Renovate/Dependabot), or strict pins with a scheduled review, not “whatever resolves today”
  • Update PRs actually get merged (a Dependabot queue with 200 open PRs is a decision to stay vulnerable, made passively)
  • CI doesn’t run lifecycle scripts it doesn’t need; tokens available to the install step are minimal
  • No internal package names resolvable from the public registry (dependency-confusion check)
  • Licenses inventoried; no copyleft surprises in the distribution path

The update-cadence question

The healthiest posture we see isn’t “always latest” (you become the ecosystem’s beta tester and the first victim of malicious releases; waiting a few days before adopting fresh releases is a legitimate defense) nor “frozen since 2024” (upgrades compound until they’re a rewrite). It’s boring cadence: automated update PRs, batched weekly, merged behind CI, with security patches fast-tracked. Dependency updates are the one chore where consistency beats heroics; six-monthly “upgrade sprints” are how teams end up three major versions behind on their framework with no incremental path back.

Get a free code audit

The dependency pass (scan, reachability triage, abandonment review, supply-chain hygiene) is part of every free audit we run. Genuinely free: a Webisoft engineer reviews your repository manually and sends a written report that tells you which of your scanner’s findings actually matter, which packages are quietly rotting, and what to fix first. If your npm audit output has been red for six months and nobody knows which lines to care about, get a free code audit. Turning that noise into a ranked list is exactly what it’s for.

FAQ

Frequently asked questions

My vulnerability scanner shows dozens of critical findings. Should I panic?

No. Severity scores describe the vulnerability in the abstract, not the risk to your application. A critical flaw in code your app never calls with attacker input can matter less than a moderate flaw in your public upload handler. Triage each finding by reachability: is the vulnerable function actually called, can attacker input reach it, and is there a mitigating layer in front of it.

Is an old package with no known CVEs safe to keep using?

Not necessarily. An unmaintained package often has no CVEs because nobody is looking, which is worse than a maintained package with a patch history. Check the last release date, the open-issue trajectory, and how much of your stack depends on one maintainer's spare time, especially on security-relevant paths like auth, crypto, and parsing.

How often should we update dependencies?

On a boring, regular cadence: automated update PRs batched weekly, merged behind CI, with security patches fast-tracked. Chasing every release the day it ships makes you the ecosystem's beta tester, and freezing for a year turns upgrades into a rewrite. Waiting a few days before adopting a fresh release is also a legitimate defense against malicious versions.

What is risky about the install step itself?

Package lifecycle scripts run arbitrary code on every developer laptop and CI runner at install time, and real supply-chain attacks overwhelmingly deliver their payloads that way. Commit and enforce lockfiles, use npm ci rather than npm install in pipelines, and skip lifecycle scripts in CI where feasible.

Why would a code audit care about software licenses?

Because a copyleft dependency linked into a proprietary product can become a genuine problem during an acquisition or an enterprise customer's review, and it is much cheaper to find out before someone else's lawyers do. The dependency pass of the free audit includes a license inventory alongside the reachability triage.

Free · no strings shown later

Get your free code audit

A repo URL and two minutes of your time. A Webisoft engineer sends back written findings (security, tech debt, performance) with file-and-line specifics.

Request my free audit