Skip to main content

Reachability annotation

Vulkro annotates each dependency-CVE finding with whether your code looks like it calls into the vulnerable package, using a textual call-shape heuristic (not a symbol-resolved call graph). The goal is noise reduction: demote the long tail of CVEs in packages you pull in but never exercise.

  • [reachable] - a call shape for the package (e.g. _.merge(, yaml.load() appears somewhere in your source.
  • [unreachable - vulnerable symbol not called from your code] - the package is in your dependency tree, but no matching call shape was found in your source.

This cuts CVE noise on dependency-heavy projects. It is a heuristic, not a proof: see How it works and Limitations.

Scope

Vulkro produces CVE findings for five ecosystems: npm (package.json), PyPI (requirements*.txt, Pipfile, pyproject.toml), Go (go.mod), Cargo (Cargo.toml + Cargo.lock), and Maven (pom.xml). The reachability annotation below applies to npm and PyPI only: the call-shape set has no Go, Cargo, or Maven fragments, so those findings are emitted without a [reachable] / [unreachable] tag (the CVE is still reported). Gradle, RubyGems, and NuGet are not yet scanned for CVEs. See Supported languages.

Curated call-shape list

Reachability matches a pinned set of hand-written call-shape fragments for high-traffic packages (the regex is (?:fragment)\s*( run over your file text). The current fragments cover:

  • npm: lodash (_.merge(, _.set(), axios, ws, node-fetch, express, marked, handlebars, minimist, moment.
  • PyPI: requests (requests.get(), urllib3, pyyaml (yaml.load(), jinja2, django, flask.

When a CVE record ships its own vulnerable_symbols, those names are also matched as call shapes in your source. Outside this set, CVE findings are emitted without [reachable] / [unreachable] tags - the same conservative behaviour as Snyk / Dependabot.

How it works

  1. Parse the npm / PyPI manifest and match (package, version, CVE) tuples against the offline CVE bundle.
  2. For each package, pick its call-shape fragments: the curated list above, plus any vulnerable_symbols the CVE record carries.
  3. Build a regex from those fragments and run it over each source file's text. If any file matches, the finding is [reachable]; otherwise [unreachable].
  4. By default, [unreachable] findings are downgraded one severity tier and annotated (not dropped). vulkro scan --reachable-only drops them entirely; --no-reachability-filter turns the whole pass off.

This is a textual heuristic, deliberately biased toward [reachable] (false-positive-safe): a call to the package that the regex does not recognise still shows up, just without the demotion.

Where this matters

A typical vulnerability heavy-tail looks like:

Without reachability:
CVE-2021-23337 (lodash) your project | 1 finding
CVE-2020-14343 (pyyaml) your project | 1 finding
CVE-2023-32681 (requests) your project | 1 finding
CVE-2024-... x 47 more

With reachability:
CVE-2021-23337 (lodash) [reachable] <- a `_.template(` call was found
CVE-2020-14343 (pyyaml) [unreachable] <- no `yaml.load(` in source
CVE-2023-32681 (requests) [reachable] <- a `requests.get(` call was found
CVE-2024-... x 47 more mostly [unreachable]

Net effect on a typical Node monorepo: a large share of CVE findings move to [unreachable] and out of the High-confidence default view.

Limitations

The heuristic is intentionally shallow. Be aware that:

  • It is a substring match over your source text, not a resolved call graph. A literal _.merge( in a comment or string marks lodash reachable; it does not confirm the call binds to the vulnerable package.
  • It does not resolve the vulnerable symbol inside the dependency, and it does not require the file to import the package.
  • A call reached through an alias (const m = _.merge; m(x)) or a re-export is marked [unreachable].
  • The reachability annotation covers npm and PyPI only; Go, Cargo, and Maven CVE findings (and any future ecosystem) are emitted without a [reachable] tag until call-shape fragments exist for them.

Treat [reachable] as "worth looking at first," not as proof of exploitability.

Reachability as a finding verdict

The call-shape annotation above is the dependency-CVE (SCA) form of reachability. Vulkro also carries reachability as a first-class verdict on findings more broadly, and grades findings for exploitability to order your triage queue. When the SCA reachability gate is on (VULKRO_SCA_REACHABLE=1), each dependency finding is stamped with a reachability_verdict and its supporting entry-point call-chain evidence, and unreachable CVEs are downgraded. For how the verdict and the exploitability grade are produced, where they appear in output, and how they change triage order, see Exploitability and reachability.