CtrlK
BlogDocsLog inGet started
Tessl Logo

testland/reachability-analyzer

Runs dead-dependency analysis across JS, Python, and Rust projects using ecosystem-native static tools (`depcheck`/`knip` for JS, `vulture` for Python, `cargo-machete` for Rust), then cross-references the unused-dependency list against SCA findings to downrank vulns in code that is never loaded. Use when SCA output (from `osv-scanner`, `snyk-test`, or `npm-pip-maven-audit`) is too noisy to triage and the team needs to separate unreachable CVEs from exploitable ones before sprint planning; sibling cve-exploitability-triage ranks by EPSS/KEV exploitation signal, not code reachability.

79

Quality

99%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

Passed

No findings from the security scan

Overview
Quality
Evals
Security
Files
name:
reachability-analyzer
description:
Runs dead-dependency analysis across JS, Python, and Rust projects using ecosystem-native static tools (`depcheck`/`knip` for JS, `vulture` for Python, `cargo-machete` for Rust), then cross-references the unused-dependency list against SCA findings to downrank vulns in code that is never loaded. Use when SCA output (from `osv-scanner`, `snyk-test`, or `npm-pip-maven-audit`) is too noisy to triage and the team needs to separate unreachable CVEs from exploitable ones before sprint planning; sibling cve-exploitability-triage ranks by EPSS/KEV exploitation signal, not code reachability.
metadata:
{"keywords":"reachability, dead-dependencies, sca, depcheck, knip, vulture, cargo-machete, prioritization"}

reachability-analyzer

Overview

Static vulnerability scanners report a CVE for every declared dependency that matches a vulnerable version, regardless of whether the vulnerable code is ever executed. A critical-severity finding in a test-only helper that your production build tree never reaches is a different risk level than the same CVE in a hot-path dependency. This skill operationalizes a reachability heuristic: run an ecosystem-native dead-dependency tool, produce an unused-deps.txt artifact, and feed that list into your SCA prioritization step so it can assign reachable: false and route affected findings to Fix-Backlog instead of Fix-Now.

The approach is static-analysis heuristic only. A dependency flagged unused by these tools is a strong signal to deprioritize its CVEs, not a guarantee that the vulnerable code is unreachable at runtime. Treat it accordingly.

When to use

  • After any SCA scan produces more findings than the team can triage in one sprint and you need a principled filter.
  • Before feeding findings into SCA prioritization so the reachable field is populated.
  • During dependency cleanup: the same tooling surfaces dead weight (unused packages that add attack surface and install time with no benefit).
  • When a security reviewer asks "is this CVE in something we actually use?"

How to use

  1. Run SCA first and save the findings JSON - this skill annotates existing findings, it does not replace them. See osv-scanner (or snyk-test / npm-pip-maven-audit) for full setup:

    osv-scanner scan -r . --format json --output osv.json
  2. Run the ecosystem-native dead-dependency tool for your stack, then extract the unused names into unused-deps.txt. Full config, flags, exit codes, and suppression per tool: references/dead-dependency-tools.md:

    # JS/TS - knip (preferred; depcheck was archived June 2025)
    npx knip --reporter json | jq -r '.dependencies[].name' > unused-deps.txt
    # Python - vulture; --min-confidence 90 targets unused imports
    vulture src/ --min-confidence 90 > vulture-report.txt
    # Rust - cargo-machete; --with-metadata catches renamed/feature-gated crates
    cargo machete 2>&1 | grep "unused dependency" | awk '{print $NF}' > unused-deps.txt
  3. Keep production and dev unused-dep lists separate: dev deps with CVEs can still reach production in bundled builds.

  4. Cross-reference unused-deps.txt against the SCA JSON, setting reachable: false on every finding whose package is unused (script below).

  5. Verify: assert unused-deps.txt is non-empty and every finding in the annotated JSON carries a populated reachable field before handoff. If the file is empty or a finding lacks the field, re-run the dead-dependency tool (a silent tool failure or the wrong workspace root is the usual cause) and re-annotate before proceeding.

  6. Pass the annotated JSON to your SCA prioritization step; reachable: false routes to Fix-Backlog unless the CVE is in the CISA KEV catalog.

  7. Wire steps 2-4 into CI so the annotated artifact feeds the prioritization job on every run, and still schedule each unused package for cleanup.

Cross-reference unused deps with SCA findings

Once you have an unused-deps.txt for your ecosystem, annotate the SCA JSON output before passing it to your SCA prioritization step:

import json

with open("osv.json") as f:
    findings = json.load(f)

with open("unused-deps.txt") as f:
    unused = {line.strip() for line in f}

for finding in findings.get("results", []):
    pkg = finding.get("package", {}).get("name", "")
    finding["reachable"] = pkg not in unused

with open("osv-annotated.json", "w") as f:
    json.dump(findings, f, indent=2)

Pass osv-annotated.json to your SCA prioritization step. Per the priority logic, findings with reachable: false route to Fix-Backlog regardless of severity, unless they are in the CISA KEV catalog.

CI integration

jobs:
  reachability:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5
      - name: JS unused deps (knip)
        run: npx knip --reporter json | jq -r '.dependencies[].name' > unused-deps.txt
      - name: Annotate OSV output
        run: python3 ci/annotate-reachability.py osv.json unused-deps.txt
      - uses: actions/upload-artifact@v4
        with:
          name: osv-annotated
          path: osv-annotated.json

The annotated artifact is then consumed by the prioritization job.

Worked example

A team's OSV scan of a Next.js service returns 60 CVEs - too many to patch in one sprint. They run the JS dead-dependency tool (step 2), which flags 12 of the 80 declared dependencies as unused, including moment (CVE-2022-31129, CVSS 7.5). The cross-reference script sets reachable: false on the moment finding because moment appears in unused-deps.txt. Prioritization routes that CVE to Fix-Backlog instead of Fix-This-Sprint, and the team removes moment in the next dependency-cleanup PR, eliminating both the CVE and the unused weight in one change.

Anti-patterns

Anti-patternWhy it failsFix
Skip reachability; sort SCA output by CVSS onlyTeams spend sprints patching vulns in unused code while exploitable issues sit in Fix-BacklogRun the cross-reference step before triage
Treat reachable: false as safeStatic heuristic; dynamic imports and plugin systems can load code at runtimeUse as deprioritization signal only; still schedule Fix-Backlog cleanup
Run vulture at default confidence (60%)Reports unused functions/variables alongside imports; noisy for CVE mappingUse --min-confidence 90 to target imports specifically
Use depcheck on new JS projectsArchived June 2025; per github.com/depcheck/depcheck, maintainers recommend knipSwitch to knip for new setups
Skip --with-metadata in Rust monoreposRenamed crates not detected; false "used" verdictsAlways pass --with-metadata in CI
Ignore devDependencies entirelyDev deps with CVEs can reach production in bundled buildsSeparate prod/dev lists; confirm --omit=dev in audit scope

Limitations

  • All three tools use static analysis only. Dynamic imports (require(variable) in JS, importlib.import_module in Python) and compile-time proc macros in Rust can defeat detection, producing false "unused" verdicts.
  • Import-to-package mapping for Python requires manual maintenance when import names differ from PyPI package names.
  • Monorepo setups may require per-workspace runs; a dependency unused in one package may be used in another.
  • cargo-machete per github.com/bnjbvr/cargo-machete is described as "fast yet imprecise" - the trade-off is speed over recall.
  • These tools detect unused declarations; they do not trace call graphs to the vulnerable function within a used package. For function-level reachability, runtime instrumentation (e.g. Snyk's reachability analysis, CodeTF-based call-graph tools) is required.

References

Workspace
testland
Visibility
Public
Created
Last updated
Publish Source
GitHub
Badge
testland/reachability-analyzer badge