Safeguard
AppSec

What a Dependency Scanner Does and Which One to Pick

A dependency scanner reads your lockfiles, resolves the full dependency tree, and matches every package against known vulnerability data. Here is how that works and what separates a good one from a noisy one.

Marcus Chen
DevSecOps Engineer
7 min read

A dependency scanner reads the packages your project pulls in — direct and transitive — resolves them to exact versions, and cross-references each one against vulnerability databases to tell you which ones ship a known flaw. That is the whole job in one sentence, but the difference between a scanner you trust and one you mute after a week lives entirely in the details of how it does each step.

Most teams reach for a dependency scanner after an incident. A CVE lands in a package three levels deep in the tree, nobody knew it was there, and the postmortem action item reads "add scanning to CI." This post walks through what happens under the hood, where scanners disagree, and how to evaluate one without getting buried in false positives.

How a dependency scanner actually resolves your tree

The first job is not matching CVEs — it is figuring out what you actually depend on. Your package.json or pom.xml lists direct dependencies with version ranges. What ships to production is the resolved graph, and that graph lives in the lockfile: package-lock.json, yarn.lock, poetry.lock, go.sum, Gemfile.lock.

A scanner that reads only the manifest and not the lockfile is guessing. If your package.json says "lodash": "^4.17.0" and your lockfile pins 4.17.21, only the lockfile tells the truth about what is installed. Good scanners parse the lockfile as the source of truth and fall back to manifest resolution only when no lock exists.

The tree matters because most real vulnerabilities are transitive. You install a web framework; it pulls in a parser; the parser pulls in a compression library; the compression library has the CVE. You never typed that library's name. An open source dependency scanner worth using surfaces the full path — "your-app depends on framework@2.1 which depends on parser@1.4 which depends on zlib-wrapper@0.9 (vulnerable)" — so you know whether you can bump the top-level package or need a resolution override.

# npm shows you the path to a vulnerable transitive dep
npm ls zlib-wrapper

# and can attempt a fix within your declared ranges
npm audit fix

Matching against vulnerability data

Once the tree is resolved, the scanner maps each (package, version) pair to advisories. The data comes from a few overlapping sources: the National Vulnerability Database (NVD), the GitHub Advisory Database, ecosystem-specific feeds like the PyPA advisory database or RustSec, and increasingly the OSV (Open Source Vulnerabilities) schema that normalizes all of these.

The important thing to understand is that matching is version-range logic, not string equality. An advisory says affected: >=2.0.0, <2.4.1, fixed in 2.4.1. The scanner has to correctly interpret that range against your pinned version, including pre-release and epoch quirks that differ across ecosystems. A scanner that gets range math wrong either misses real hits or flags versions that were already patched.

This is also where quality diverges. NVD entries are often coarse — they may list a whole product as affected without pinning which package versions carry the fix. Scanners that lean only on NVD tend to over-report. Ones that enrich with GitHub advisories and OSV get tighter, more accurate ranges. If you are comparing tools, this is worth testing directly; our writeup on choosing an SCA tool goes deeper on data-source quality.

Reachability: the feature that kills false positives

Here is the uncomfortable truth about dependency scanning: a package can contain a vulnerable function that your code never calls. The classic example is a serialization flaw in a library you only use for its logging helpers. The CVE is real, your exposure is zero, but a naive scanner flags it as critical.

Reachability analysis tries to answer "does a call path exist from my code to the vulnerable function?" It is the single biggest lever on signal-to-noise. Not every scanner does it, and the ones that do vary in language coverage — it is far more mature for Java and JavaScript than for, say, Ruby. When you evaluate tools, ask specifically whether reachability is available for your primary language, because a scanner without it will hand you a backlog you cannot triage.

Where dependency scanners fall short

No dependency scanner catches everything, and pretending otherwise leads to false confidence:

  • Vendored or copy-pasted code does not appear in any lockfile. If someone pasted a vulnerable function directly into your repo, the scanner has nothing to match.
  • Zero-days are invisible until an advisory is published. Scanning tells you about known vulnerabilities.
  • Malicious packages (typosquats, dependency confusion) are a different problem than CVEs and need behavioral or reputation signals, not just version matching.
  • License risk is orthogonal to security but often bundled into the same tool; do not conflate a GPL warning with a security finding.

Fitting it into CI without wrecking the build

The failure mode teams hit is turning on a scanner, seeing 400 findings, and setting it to fail the build. Developers route around it within days. A saner rollout:

  1. Run the scanner in report-only mode first and triage the existing backlog.
  2. Fail the build only on new criticals introduced by a PR — the diff, not the world.
  3. Set a severity and reachability threshold, not "any finding."
  4. Give developers a suppression path with a required justification and an expiry date, so mutes do not become permanent.
# Example CI gate: fail only on new, high-or-critical, reachable findings
scan:
  fail_on:
    severity: high
    new_only: true
    reachable: true

This turns the scanner from a source of noise into a gate people actually respect.

Choosing one

The practical checklist: Does it read your lockfiles natively for every language you ship? Does it show transitive paths? Is its advisory data enriched beyond raw NVD? Does it offer reachability for your main languages? Can it run offline or air-gapped if you need that? And does its output plug into your existing workflow — PR comments, ticket creation, pricing that scales with repos rather than punishing you per developer.

Open source options like OWASP Dependency-Check, Trivy, and Grype are excellent starting points and cost nothing. Commercial tools add reachability, better data enrichment, and remediation automation. An SCA platform such as Safeguard can also track findings across projects over time so a CVE fixed in one repo does not quietly reappear in another. Start with the open source scanner, learn what noise you can tolerate, then decide whether the paid features earn their keep.

FAQ

What is the difference between a dependency scanner and SCA?

They are effectively the same thing. Software Composition Analysis (SCA) is the umbrella term for analyzing open source components in your software; a dependency scanner is the tool that does the scanning. SCA sometimes also includes license analysis and SBOM generation.

Can a dependency scanner fix vulnerabilities automatically?

Some can attempt it. npm audit fix, Dependabot, and commercial tools can open PRs that bump versions within your constraints. But automatic fixes can break builds when a patched version has breaking changes, so treat auto-fix PRs as suggestions that still need review and tests.

Do I need a dependency scanner if I use GitHub?

GitHub's Dependabot alerts are a solid free baseline and cover many ecosystems. Dedicated scanners add reachability analysis, tighter advisory data, air-gapped scanning, and cross-repo tracking. If Dependabot's noise is manageable for you, it may be enough; larger orgs usually outgrow it.

How often should scans run?

On every pull request (to catch new introductions) and on a schedule against your main branch (because new advisories land against code you already shipped). A dependency that was clean yesterday can be flagged today when a CVE is published — the code did not change, the data did.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.