Skip to content

Latest commit

 

History

15 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

reachability-toolkit

Find the vulnerabilities that actually matter, and prove the rest don't.

Your dependency scanner just flagged 200 vulnerabilities. Maybe five of them are reachable from your code. reachability-toolkit tells you which five, and gives you a written, re-checkable reason to deprioritize the other 195.

It runs inside Claude Code. Point it at a repo or an existing scan and it returns a verdict for every finding: reachable, not reachable, or uncertain, across Go, Node, Python, Java, and Ruby.

Why this exists

A scanner tells you a vulnerable version is installed. That is the cheap question. The expensive one is whether an attacker can reach that code in your application, and most teams answer it by hand, one CVE at a time, or never. So the backlog grows, everything looks urgent, and nothing is.

reachability-toolkit answers the expensive question for you, with three things a scanner does not have.

Call-site tracing. Is the vulnerable function actually invoked in production code? It follows framework indirection, not just literal calls: a Rails sanitize that delegates into loofah, a generic merge that reaches a prototype-pollution sink.

Taint analysis. Can attacker-controlled input reach that call, or does only trusted local data get there? A reachable function fed a hardcoded constant is a different risk than one fed a request body.

Adversarial verification. Every "not reachable" verdict has to survive a panel of skeptics actively trying to prove it wrong. If a verdict cannot be defended, it is downgraded to "uncertain," never silently dropped. A wrong "not reachable" ships an exploitable CVE, so that path is guarded hardest.

Every not-reachable finding comes with a CISA VEX justification, the exact commands run as evidence, residual-risk notes, and re-evaluation triggers. That is a suppression record an auditor can re-run, not an opinion you have to trust.

See a full sample of what it hands back: example output (scope report, rollup, per-finding verdicts, structured return).

Quickstart

Install it:

/plugin marketplace add cybrking/reachability-toolkit
/plugin install reachability-toolkit

Point it at any repo you care about. This works on any plan, needs no account, and never executes the repo's code:

/reachability-toolkit:sca-diagnostic <git-url or org/repo>

It clones the repo, scans its lockfiles, triages every finding, and hands you a report with the reachable CVEs on top and a VEX rationale for everything it set aside. That is your first audit-grade diagnostic, in one command.

Already ran a scan? Skip the clone and triage it against a local checkout:

/reachability-toolkit:sca-reachability <findings.json> <repo-path>

Commands

Three commands run the same reachability analysis. They differ only in how the findings and the code reach the engine, so pick by what you already have in hand.

Command You supply It fetches for you
sca-diagnostic <git-url> a git URL the scan and the code
sca-reachability <findings.json> <repo-path> a findings file and a local checkout nothing, you provide both
snyk-reachability <project|org|group> <id> a Snyk id and an Enterprise token the findings (Snyk API) and the code

sca-reachability is the engine. The other two are front doors that feed it: sca-diagnostic = clone + scan + sca-reachability + cleanup, and snyk-reachability = pull findings from the Snyk API, then sca-reachability per project. All three return the same verdicts: reachable, not reachable (with VEX rationale), uncertain.

Which one do I use?

If you have Use What it does
just a git URL, and want the fastest pass sca-diagnostic Clones, scans the lockfiles, triages, cleans up. Any plan (Free works), no account, never runs the target's code. Start here.
the repo already on disk, or you need control over the scan sca-reachability Triage only. You bring a findings.json (snyk test --json, or osv-scanner) and the local path. Use for a monorepo (--all-projects), a single subdirectory, a CI or teammate scan, or local uncommitted changes a fresh clone would miss.
a Snyk Enterprise org or group backlog to clear snyk-reachability Pulls findings from the Snyk API and re-triages only what Snyk left as No Path Found or Not Applicable. Enterprise-only (see Setup). Overkill for a single repo.

Not sure which Snyk plan you are on? Then snyk-reachability is not your command yet. Use sca-diagnostic, which needs no Snyk account at all.

A fourth command, sca-reachability-eval, scores the analysis against bundled fixtures. It is for testing the toolkit, not triaging your code.

Triage the backlog Snyk couldn't confirm

If you run Snyk, you already know it does reachability for some ecosystems. Where it says reachable, you patch. The problem is everything it left as No Path Found or Not Applicable. Those are not verdicts. No Path Found means Snyk's call graph came up empty, which the docs are explicit does not mean the code is unreachable. That unresolved pile is exactly where a security team is stuck.

snyk-reachability points at a Snyk project, org, or group, pulls the findings Snyk could not confirm, and runs the full source, taint, and adversarial analysis on just those. Snyk-confirmed reachable findings are skipped by default, because they are already answered. Ecosystems Snyk does not cover at all (Go, Ruby, PHP, Rust) get analyzed in full, since there this toolkit is the only reachability signal.

It is scope-first. Phase one enumerates the eligible projects and counts the unconfirmed findings with a rough cost estimate, cheaply, with no analysis. It runs the expensive per-project analysis only after you confirm, so an org-wide or group-wide sweep never surprises you with a bill. The run ends with a rollup that puts the newly reachable findings first: the ones Snyk marked unconfirmed that turned out to have a real path. Design notes live in docs/snyk-reachability-design.md.

Setup for snyk-reachability

Two things new users trip on. The other commands need neither, so you can ignore this section until you want the Snyk-backlog flow.

  1. Plan. Programmatic access to Snyk projects and issues is a Snyk Enterprise feature. On Free and Team plans the API authenticates you but returns 403 on project and issue reads.
  2. Token scope. The token has to be able to read projects and issues. An identity-scoped Personal Access Token authenticates but 403s on resources. Use a PAT created with project-read and issue-read scopes, or the classic API Key (Account settings, General, Auth Token, which inherits your full permissions), or an Org service account.

Set the token where the workflow can read it. snyk config set api= is the reliable channel, because it persists on disk. An export SNYK_TOKEN=... in an interactive shell often does not reach the workflow's agents.

snyk config set api=<your-snyk-api-token>

Verify it can actually read. A 200 means you are ready; a 403 means the plan or scope is wrong. Swap in one of your org IDs from GET /rest/orgs:

curl -s -o /dev/null -w '%{http_code}\n' \
  -H "Authorization: token $(snyk config get api)" \
  "https://api.snyk.io/rest/orgs/<ORG_ID>/projects?version=2024-10-15&limit=1"

Regional tenants (EU, AU) pass snyk_api_base (for example https://api.eu.snyk.io) in the command args.

Or skip the manual steps. With the plugin installed, paste this into Claude Code and it will check your access and tell you exactly what to fix:

Verify my Snyk API access for the reachability-toolkit. Check whether a Snyk
token is configured (SNYK_TOKEN, else `snyk config get api`), then confirm it
can read my orgs and my projects and issues via the Snyk REST API. If a project
or issue read returns 403, tell me whether it is a token-scope problem (recreate
the PAT with project and issue read, or use the classic API Key) or the
Enterprise-plan API gate. Give me the exact token to create and the
`snyk config set api=` command to fix it. Never print the token value.

How it works

findings (Snyk or osv-scanner)  +  your codebase
   |
   |  ingest + recon        normalize findings, map entry points, prod vs test dirs
   |  OSV enrichment        vulnerable symbols, attack preconditions, version-in-range
   |  trace (per package)   imports to call sites (follows framework delegation)
   |                        then attacker-control taint analysis
   |  adversarial verify    reachable gets one skeptic, not-reachable gets prosecutors
   |  report                a verdict plus VEX-mapped rationale per finding
   v
reachable  /  not reachable (with rationale)  /  uncertain

Per-language specialists. Go, Node, Python, and Java each get a profile encoding that language's dispatch traps. Java forbids ruling a jackson gadget chain "symbol not invoked." Node knows prototype pollution triggers through generic merge calls. Ruby delegates to a dedicated sub-workflow using per-CVE Semgrep rules, a Brakeman cross-reference, and a wider skeptic panel.

Snyk reachability, used as a prior. When a findings file already carries Snyk's own call-graph reachability, the toolkit reads it and cross-references its own analysis rather than duplicating it. Snyk says reachable but the taint analysis shows trusted input, and the two reconcile.

Cost

The reasoning, adversarial-panel, and report agents default to Claude Sonnet, validated on a real Rails codebase (OWASP RailsGoat) to match or beat Opus on 11 of 12 findings at meaningfully lower cost. Extraction agents run on Haiku. Pass reasoning_model: "opus" for a high-stakes engagement. If Sonnet's safety classifier refuses an adversarial-panel prompt, that agent automatically retries on Opus so the panel keeps its quorum.

Prerequisites

  • sca-diagnostic: git, and either an authenticated snyk CLI or osv-scanner on PATH.
  • snyk-reachability: a Snyk Enterprise org and an API token that can read projects and issues (see Setup), plus git.
  • Ruby analysis: semgrep, bundler-audit, brakeman on PATH. Degrades gracefully if missing.
  • Eval and tests: Node.js, for the sandboxed test harnesses.

Tests

fixtures/ holds deliberately vulnerable Node, Python, and Java apps, each with a hand-authored findings file and a ground-truth.json answer key. See fixtures/README.md.

tests/run-all.sh runs the real workflow scripts in a node:vm sandbox with canned agent responses. Zero tokens, about two seconds. It is a deterministic regression guard over the orchestration logic (grading, panel demotion, Opus fallback, Snyk reconciliation, the scope and rollup math). Run it after editing any workflow.

Pure triage

These workflows produce verdicts and rationale. They do not open PRs or modify code. Feed the output to a remediation step. The diagnostic path is deliberately build-free and lockfile-only, so it is safe to run against a repo you do not trust.

License

MIT

About

Exploitability-aware SCA reachability triage for Claude Code reachable/not-reachable verdicts with VEX rationale across Go/Node/Python/Java/Ruby

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages