A neutral, non-blocking risk-triage layer that sits on top of Fortify (SAST) and Black Duck (SCA) and tells developers, in their pull request, which findings actually matter.
Enterprise AppSec teams running Fortify and Black Duck are drowning: 40-80% of SAST findings are non-exploitable, engineers burn up to 80% of their week on triage, and backlogs of 15,000-30,000 findings make everything look equally (un)urgent. Every commercial ASPM that promises to fix this eventually pressures you to rip out Fortify and Black Duck and adopt its own scanners.
Reachlayer is the missing open-source alternative: it ingests unmodified Fortify and Black Duck output, adds static reachability analysis, EPSS/KEV exploitability signals, and plain-language blast-radius context, then posts one prioritized, developer-readable comment on the pull request — as advisory context, never as a blocking gate. It answers the only question a developer actually asks:
Of these 847 SAST + 312 SCA findings, which handful can actually hurt us, and how do I fix them?
- Never fail or block the build. Output is advisory only (PR comment, SARIF, or a Markdown file). Merge gating is explicitly out of scope.
- Layer, never replace. Consumes native Fortify FPR/FVDL XML and Black Duck BDIO/JSON exports as-is. Fortify and Black Duck stay exactly as they are.
- Never suppress a finding. Static reachability is unsound (false negatives are possible).
Reachability (
reachable/unreachable/unknown) is additive — used for ranking and grouping, never for hiding a finding. - Every finding gets three things: a blended, explainable risk score; a plain-language blast-radius explanation; and a concrete fix suggestion (LLM-generated when a provider is configured, templated otherwise).
- Open source & pluggable. Apache-2.0.
ScannerConnector,LlmProvider,OutputRendererandSignatureSourceare SPIs so the community can add scanners, providers, and outputs without touching the core.
See PLAN.md for the full architecture and roadmap this repository implements.
If you just want to see it run, with no prior knowledge of this project required:
# 1. You need a JDK 21 installed. Check with:
java -version # must print 21.x — if not, install Temurin 21 from https://adoptium.net
# 2. Clone this repo and build it (this compiles everything and runs the test suite):
git clone https://github.com/reachlayer/reachlayer.git
cd reachlayer
./gradlew build
# 3. Run it against the sample scanner exports already checked into this repo:
java -jar cmd/build/libs/cmd-all.jar \
--fortify fixtures/sample-fpr/audit.fvdl \
--blackduck fixtures/sample-bdio/scan.json \
--repo . \
--out report.md
# 4. Read the result:
cat report.mdThat's it — no scanner license, no GitHub token, no API keys needed for this. Step 3 is the CLI running fully standalone. If any output is unclear, jump to the CLI flag reference below, which lists every flag in plain language.
Prefer Docker? No JDK install needed at all:
docker build -t reachlayer .
docker run --rm -v "$PWD:/workspace" -w /workspace reachlayer \
--fortify fixtures/sample-fpr/audit.fvdl \
--blackduck fixtures/sample-bdio/scan.json \
--repo /workspace \
--out /workspace/report.md
cat report.mdThis is the exact same image action.yml runs as a GitHub Action — see
Running as a GitHub Action below for the CI-native version of this.
Fortify FPR/FVDL + Black Duck BDIO/JSON
│ connectors/{fortify,blackduck} → canonical Finding[]
▼
reachability/ (SootUp CHA call graph from Spring/servlet entry points
∩ vulnerable-method/component signatures)
▼
enrich/ (EPSS + CISA KEV disk-cached clients, blast-radius heuristics)
▼
scoring/ (deterministic, explainable riskScore + riskExplanation, reachlayer.yml-tunable)
▼
advisor/ (LlmProvider SPI: templated "noop" fallback by default, Anthropic provider optional)
▼
core.baseline (optional) (tag new-vs-pre-existing findings against a prior baseline run)
▼
output/ (Markdown → single upserted PR comment; SARIF 2.1.0 for GitHub code scanning; metrics JSON)
Everything is wired together by core's Orchestrator, which is driven by the cmd picocli CLI
— runnable standalone, via Docker, or from the Docker-based GitHub Action (action.yml).
| Module | Purpose |
|---|---|
core |
Canonical Finding/Location/BlastRadius/FixSuggestion/Cvss model, the 4 SPIs, Orchestrator, reachlayer.yml config loading, baseline/diff mode (core.baseline), pipeline observability (core.metrics). |
connectors |
ScannerConnector SPI plus fortify (FVDL/FPR) and blackduck (BDIO/JSON) parsers. |
reachability |
SootUp-based CHA call-graph construction, Spring/servlet entry-point discovery, ReachabilityTagger. |
enrich |
EPSS + CISA KEV disk-cached clients, blast-radius heuristics. |
scoring |
Deterministic blended risk score + per-factor explanation. |
advisor |
LlmProvider SPI: noop (templated) default, anthropic optional provider. |
output |
OutputRenderer SPI; api (Markdown formatting), github-pr (single-comment upsert), sarif (SARIF 2.1.0 for GitHub code scanning). |
cmd |
picocli Main CLI entry point, produces the fat JAR used by the Docker action. |
fixtures |
Synthetic (never real vendor) sample Fortify/Black Duck exports, a tiny vulnerable Spring Boot app used by reachability tests, and a larger synthetic-corpus generator for the evaluation pipeline. |
Requires JDK 21.
./gradlew build # compiles all modules and runs the full test suite
./gradlew :cmd:shadowJar # builds the standalone fat JAR at cmd/build/libs/cmd-all.jarRun the CLI directly against local scanner exports:
java -jar cmd/build/libs/cmd-all.jar \
--fortify fixtures/sample-fpr/audit.fvdl \
--blackduck fixtures/sample-bdio/scan.json \
--repo . \
--config reachlayer.yml \
--out report.mdWith GITHUB_TOKEN, GITHUB_REPOSITORY and GITHUB_PR_NUMBER set, the CLI will also upsert the
rendered report as a single PR comment via output/github-pr.
Every flag the CLI accepts, in plain language. All flags are optional except where noted; a blank or omitted value means "skip this feature" — nothing here is required to get a basic run working.
| Flag | Default | What it does |
|---|---|---|
--fortify <path> |
(none) | Path to a Fortify audit.fvdl or .fpr export to ingest. Omit if you have no Fortify data. |
--blackduck <path> |
(none) | Path to a Black Duck JSON/BDIO export to ingest. Omit if you have no Black Duck data. |
--repo <path> |
. |
Path to the checked-out repository — used to pull code snippets for fix suggestions. |
--classes <path> |
(none) | Path to your app's compiled .class files. Without this, every finding's reachability is tagged unknown (safe, honest default) instead of reachable/unreachable. |
--config <path> |
(none) | Path to a reachlayer.yml file to override default scoring weights, advisor settings, etc. See reachlayer.yml for an example. |
--out <path> |
(none) | Also write the rendered Markdown report to this file (in addition to printing it to the console). |
--sarif-out <path> |
(none) | Also write a SARIF 2.1.0 report to this file, for upload to GitHub code scanning via a separate github/codeql-action/upload-sarif step. |
--baseline-in <path> |
(none) | Path to a baseline JSON file (finding IDs from a prior run, e.g. a scheduled scan of your base branch). When set, the report highlights only new findings. See docs/baseline-mode.md. |
--baseline-out <path> |
(none) | Write this run's finding IDs to this path, to be used as a future run's --baseline-in. |
--metrics-out <path> |
(none) | Write pipeline observability data (per-stage timing, finding counts, renderer outcomes) as JSON to this path. See docs/observability.md. |
--cache-dir <path> |
.reachlayer-cache |
Where EPSS/KEV data is cached on disk (refreshed daily). |
--post-pr-comment <true|false> |
true |
Whether to upsert a PR comment via the GitHub REST API. Requires GITHUB_TOKEN/GITHUB_REPOSITORY/GITHUB_PR_NUMBER — if any are missing, this is silently skipped (logged, not an error). |
Run java -jar cmd/build/libs/cmd-all.jar --help at any time to see this list from the CLI itself.
- uses: reachlayer/reachlayer@v0
with:
fortify-fvdl: audit.fvdl # optional — omit if you have no Fortify data
blackduck-bdio: scan.json # optional — omit if you have no Black Duck data
classes-dir: target/classes # optional — enables real reachable/unreachable tagging
sarif-out: reachlayer.sarif # optional — see docs/sarif-output.md
baseline-in: reachlayer-baseline.json # optional — see docs/baseline-mode.md
baseline-out: reachlayer-baseline.json # optional
metrics-out: reachlayer-metrics.json # optional — see docs/observability.md
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}The action never fails the build — it only annotates the PR. Every input mirrors a CLI flag above
one-for-one (e.g. fortify-fvdl → --fortify); see action.yml for the complete
list of inputs/outputs.
| Doc | What it covers |
|---|---|
PLAN.md |
Full vision, design principles, phased roadmap, tech stack, scoring formula. |
docs/architecture.md |
Module/package table, data-flow diagram, testing strategy. |
docs/scoring.md |
The exact risk-scoring formula and tunable weights. |
docs/configuration.md |
Full reachlayer.yml reference: scoring weights, advisor/output settings, entry-point overrides. |
docs/connectors.md |
How to write a new ScannerConnector for another scanner. |
docs/reachability-caveats.md |
Honest soundness/false-negative disclosure for the reachability engine. |
docs/sarif-output.md |
The SARIF renderer, its GitHub code-scanning mapping, and the upload-sarif workflow step. |
docs/baseline-mode.md |
Baseline/diff mode: surfacing only new findings vs. a prior run. |
docs/observability.md |
Pipeline metrics: per-stage timing, finding counts, renderer outcomes. |
docs/evaluation-pipeline.md |
The larger synthetic-corpus evaluation pipeline and its CI job. |
docs/success-criteria.md |
Concrete, falsifiable success/failure criteria for the repo and its tests, including honest gaps. |
docs/architecture-optimization.md |
Network-design and optimization review. |
docs/competitive-landscape-commercial-technical.md |
Blunt research pass on commercial ASPM competitors and JVM call-graph technical state of the art. |
docs/competitive-landscape-oss.md |
Blunt research pass on OSS (and vendor-native) reachability/triage tools — what's genuinely uncontested vs. not. |
docs/lessons-learned.md |
Running log of concrete findings from building this project. |
The full pipeline — connectors → reachability → enrich → scoring → advisor → baseline → output —
is wired end-to-end via cmd's Main CLI and runnable today (see Getting Started above).
PLAN.md Phase 0 (MVP) and the first two Phase 1 items (SARIF output, baseline/diff mode) are
implemented; pipeline observability metrics and a dedicated evaluation pipeline (see
docs/evaluation-pipeline.md) have also landed. Reachability uses
CHA/RTA-style call-graph approximation and component-level signatures; blast-radius and
entry-point discovery are intentionally simple, conservative heuristics that lean toward
unknown rather than claiming certainty. See docs/reachability-caveats.md.
See CONTRIBUTING.md for setup and PR expectations, CODE_OF_CONDUCT.md
for community standards, and SECURITY.md for how to report a vulnerability.
Apache-2.0, see LICENSE.