# how to assess CVE reachability in your codebase

## TL;DR

A CVE in a dependency you never call is not your emergency. Find where the vulnerable package is imported, trace whether the vulnerable function or code path is actually invoked, and check whether the vulnerable feature is enabled in your config. Reachable plus exposed equals urgent; anything else can be scheduled.

```text
how to assess CVE reachability in your codebase
```

## Use this when

- A scanner flags a CVE in a dependency and you need to judge real exploitability
- Leadership asks whether a headline CVE actually affects your product
- You are deciding patch-now versus schedule for a batch of findings
- A vendored or transitive dependency is flagged and the scanner gives no call path

## Not for this skill when

- You are triaging the CVE itself (severity, advisory facts, exploitation signals)
- The CVE is in your own first-party code (fix it; reachability is not the question)
- You need automated reachability at scale across many repos (that is a platform project, these are the manual primitives)

## Steps

### 1. Confirm the vulnerable package and version are really present

Trust the lockfile, not the manifest. Check the resolved version against the advisory's affected range.

```bash
npm ls [package-name]
```

Expected: the resolved version tree showing the package at a vulnerable version, or proof it is not there. Ecosystem equivalents: `pip show [package-name]`, `go list -m all | grep [module]`. If the vulnerable version is not in the tree, stop here.

### 2. Find every import and call site

Search the codebase for imports of the package and calls to the vulnerable function named in the advisory.

```bash
grep -rn "[vulnerable-function-or-import]" src/ --include="*.py"
```

Expected: a concrete list of call sites, or zero hits. Zero hits in first-party code does not fully clear a framework-level dependency, but it strongly downgrades urgency; note it and move to step 3.

### 3. Trace the call path to an entry point

For each call site, follow the chain upward: is it reachable from a request handler, a CLI command, a cron job, or only from dead code and tests? A parser CVE in a function only your test suite calls is not reachable in production.

Expected: a short call chain per site ending at a real entry point, or chains that dead-end in unused code. Document the chain; "reachable" without the path written down gets re-litigated next quarter.

### 4. Check whether the vulnerable feature is enabled

Many CVEs live behind a config flag, an optional module, or a protocol you never serve. Check your config and your server setup.

Expected: either the feature is on and reachable (patch now) or off and unreachable (schedule). A CVE in TLS 1.0 handling on a server that only negotiates 1.2 and up is the classic downgrade.

### 5. Record the verdict with evidence

Write down the CVE ID, the call path or the reason it is unreachable, and the decision. Future you will thank present you.

Expected: one entry in the triage log with the evidence attached. Reachability analysis that is not written down gets repeated from scratch.

### Variant: reachability in a monorepo with many services

Run the import search per service and map results to deployables. One vulnerable package can be reachable in the API service and dead code in the worker; the verdict is per service, not per repo.

### Variant: using Semgrep for reachability at scale

Write a Semgrep rule matching the vulnerable call pattern and run it across repos in CI. It automates step 2; steps 3 and 4 still need a human for the interesting cases.

## Why this happens

Scanners match package names and versions, which is a dependency-graph fact, not an execution fact. Reachability asks the execution question: does attacker-controlled input actually flow into the vulnerable code in your deployment? Most of the time the answer is no, which is why this analysis saves so many unnecessary emergency patches.

## Edge cases and pitfalls

- Dynamic imports and plugin systems hide call sites from grep; check config-driven loading too.
- Transitive dependencies: the import may live in a library you depend on, not your code; check the intermediate package's usage.
- Feature flags can flip at runtime; verify the production flag state, not the default.
- Vendored code: copied source has no package metadata, so scanners miss it and grep is the only backstop.
- Native extensions: a CVE in a C extension may be reachable through a safe-looking wrapper; check what the wrapper actually calls.

## Provenance

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