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.
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
- Parse the npm / PyPI manifest and match (package, version, CVE) tuples against the offline CVE bundle.
- For each package, pick its call-shape fragments: the curated list above,
plus any
vulnerable_symbolsthe CVE record carries. - 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]. - By default,
[unreachable]findings are downgraded one severity tier and annotated (not dropped).vulkro scan --reachable-onlydrops them entirely;--no-reachability-filterturns 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.