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. A resolved call-graph closure is on the roadmap.