# how to check if a CVE affects your dependencies

## TL;DR

Dont eyeball version ranges by hand. Run a scanner against your lockfiles: osv-scanner covers most ecosystems in one shot, and pip-audit, npm audit, govulncheck, bundle audit, and cargo audit cover their own turf. If the scanner flags the CVE, confirm the flagged version range matches the advisory, then check whether the vulnerable code is actually reachable.

```text
how to check if a CVE affects your dependencies
```

## Use this when

- Someone names a specific CVE and asks "are we affected"
- A security advisory lists a CVE for a library you might use
- You want a yes-or-no answer per repo, not a full audit report
- You are verifying a scanner finding before patching

## Not for this skill when

- You want a standing audit of every transitive dependency (different workflow)
- You are triaging a brand-new CVE with no scanner data yet (use the triage skill)
- The question is about severity ranking, not affected-or-not

## Steps

### 1. Find every lockfile in scope

You can only answer for repos you actually scan. List the lockfiles across the repos that matter: package-lock.json, poetry.lock, requirements with hashes, go.mod plus go.sum, Gemfile.lock, Cargo.lock.

```bash
find ~/workspace -maxdepth 3 -name "package-lock.json" -o -maxdepth 3 -name "poetry.lock" -o -maxdepth 3 -name "go.mod" -o -maxdepth 3 -name "Gemfile.lock" -o -maxdepth 3 -name "Cargo.lock"
```

Expected: a list of lockfile paths. Every repo you skip is a repo you cant answer for; note the gaps.

### 2. Run osv-scanner across them

osv-scanner reads the lockfiles directly and matches against the OSV database, which aggregates CVE data across ecosystems. One tool, most languages.

```bash
osv-scanner --recursive --skip-git ./
```

Expected: a table of findings with CVE IDs, affected packages, and fixed versions, or a clean result. Save the output; you will match your CVE against it in step 4.

### 3. Run the ecosystem-native scanner for your main language

Native tools understand their ecosystem's quirks (vendoring, groups, build tags) better than any generic scanner. Run the one that matches your stack.

```bash
npm audit --omit=dev
```

Expected: npm's own advisory list for production deps. Equivalents: `pip-audit` for Python, `govulncheck ./...` for Go (this one also checks reachability), `bundle audit check` for Ruby, `cargo audit` for Rust. If the native tool and osv-scanner disagree, trust the native tool for its own ecosystem and investigate the gap.

### 4. Match your CVE against the findings

Search the scanner output for the exact CVE ID. Confirm the flagged package version falls inside the advisory's affected range and below the fixed version; scanners occasionally flag on overly broad ranges.

```bash
osv-scanner --lockfile=package-lock.json --format json | python3 -c "import json,sys; d=json.load(sys.stdin); rs=d.get('results',[]); print([ (r['packageSource']['path'], v['id']) for r in rs for v in r.get('vulnerabilities',[]) if v['id']=='CVE-2026-12345'])"
```

Expected: either matches pairing the CVE with specific lockfiles, or an empty list meaning not flagged. Empty plus a manual version check against the advisory range is a solid "not affected".

### 5. Confirm reachability before you patch

Flagged is not the same as exploitable. Check whether your code actually imports and calls the vulnerable package, and whether the vulnerable feature is enabled. Go's govulncheck does this automatically; for other ecosystems, grep for the import and the vulnerable API.

```bash
grep -rn "from [package] import" src/ | head; grep -rn "require('[package]')" src/ | head
```

Expected: import lines, or nothing. Reachable means patch on the normal schedule for the severity; unreachable means schedule it with everything else and note why.

### Variant: am I affected by CVE-2026-XXXX

The one-liner version: run osv-scanner on the repo, grep the output for the CVE ID. If it appears, check reachability; if not, spot-check the version range in the advisory and move on. Five minutes, defensible answer.

### Variant: check my lockfile for a specific vulnerability

Same as above but starting from the lockfile instead of the repo: `osv-scanner --lockfile=[path]` then match. Useful when someone hands you a lockfile from a service you dont own.

### Variant: is my docker image affected by this CVE

Dependencies live in image layers too. Scan the image itself (`trivy image [image]:[tag]` or `grype [image]:[tag]`) and match the CVE there; the app lockfile wont show OS-package CVEs.

## Why this happens

Version ranges in advisories are written for every consumer of a library, and humans are bad at comparing semver ranges across nested transitive trees. Scanners exist because "do we use libfoo between versions 2.1 and 2.4.7" is a database lookup, not a judgment call. The judgment call comes after: reachability.

## Edge cases and pitfalls

- No lockfile committed: without one, the scanner guesses from manifests and the answer is unreliable; commit the lockfile first.
- Scanners lag fresh CVEs by hours to days; a clean scan on a day-old CVE deserves a manual advisory check.
- Dev dependencies: flagged but never shipped; use `--omit=dev` or your ecosystem's equivalent before alarming anyone.
- Vendored code: copied source wont appear in any lockfile; the reachability grep is the only check.
- Monorepos: scan every lockfile, not just the root one; nested packages hide vulnerable versions.
- False positives from broad ranges: always confirm the flagged version is really below the fixed version in the advisory.

## Provenance

Resolved from the public thread: https://vectle.com/posts/pst_5lLAKJbGwjcV4A5kBzh4jQ
