VulnReach Benchmark

April 5, 2026 · View on GitHub

Scan target: multi-tier-dvpa · Python / Django 3.2 · April 2026


1. Executive Summary

Standard SCA tools are designed to produce a complete inventory of installed-package CVEs — an essential first step. VulnReach takes those findings as input and adds reachability analysis on top: distinguishing a vulnerability in a function your application actually calls from one in a function that is never executed. The result is a 72-item raw CVE queue enriched into 90 classified findings — 49 runtime-confirmed, 23 with a proven code path, and 18 flagged for investigation.

The benchmark scan returned BLOCKED before any code shipped — automated CI enforcement on 49 runtime-confirmed critical and high vulnerabilities.

MetricTrivy (SCA layer)VulnReach (full pipeline)
CVE findings produced7290 classified findings from those 72
Findings confirmed executed at runtime049 (DYNAMICALLY_REACHABLE)
Findings with proven code path (static)023 (STATICALLY_REACHABLE)
Findings requiring investigation0 (all treated equally)18 (UNCERTAIN)
Findings confirmed safe to defer00 (NOT_REACHABLE)
P1 action queue72 (undifferentiated)49
CI pipeline gate triggeredNoYes — BLOCKED
Fix version surfacedYesYes
Per-finding confidence scoreNoYes (0.40 – 0.95)

The 90-finding count reflects VulnReach's post-correlation output. The 72 raw CVEs from Trivy are the source material; the correlation engine enriches each CVE with evidence chain metadata, which can expand the record count. The underlying vulnerable packages are identical.


2. Test Subject

multi-tier-dvpa (Multi-Tier Damn Vulnerable Python Application) is an intentionally vulnerable Python/Django web application designed for security testing. It replicates the architecture of a real-world service: Django as the web framework, Django REST Framework for the API layer, Pillow for image processing, PyJWT for authentication, cryptography for encryption, gunicorn as the production WSGI server, and standard HTTP client libraries.

PropertyValue
Repositoryhttps://github.com/ihrishikesh0896/multi-tier-dvpa.git
Language / FrameworkPython 3.x / Django 3.2.0
API layerDjango REST Framework 3.12.0
WSGI Servergunicorn 20.1.0
ContainerisedYes (Dockerfile + docker-compose.yml)
Intentionally vulnerableYes
Packages with known CVEs11
Raw CVE count (Trivy)72

This target was selected because it represents the dependency profile of a typical mid-size Python web service — not a toy app with two dependencies, but a realistic multi-component stack where the SCA noise problem is fully visible.


3. Methodology

3.1 Tools and Versions

ToolVersionRole in this benchmark
VulnReachv2 (current)Full 5-layer pipeline orchestrator
Trivylatest (Aqua Security)SCA layer — CVE detection from dependency manifests
tainter0.1.0Taint flow analysis (source → sink tracing)
SchemathesislatestHTTP traffic generation for dynamic coverage
coverage.py7.xRuntime coverage collection inside container
Docker24.xContainer runtime for dynamic analysis

3.2 Trivy-Alone Baseline

Trivy was run as an isolated step to establish the raw SCA baseline before any reachability analysis:

trivy fs --format json --output trivy-results.json /path/to/multi-tier-dvpa

No reachability flags. No severity filtering. All CVEs reported regardless of severity or exploitability. Output: 72 findings across 11 packages.

Note: VulnReach's pipeline includes Trivy as its SCA layer. The "Trivy alone" result in this benchmark is extracted from VulnReach's own Trivy agent output — the same CVEs, the same versions, evaluated on the same commit.

3.3 VulnReach Full Pipeline

VulnReach was run against the same repository with the following configuration:

scan:
  tools:
    - git
    - trivy
    - tainter
    - multi_language_reachability
    - route_extractor
    - metadata
    - dynamic_reachability
  static_reachability: true
  runtime:
    enabled: true
    timeout: 180
    coverage_wait: 15
    container_port: 3000
risk:
  exposure: public
  data_sensitivity: high
policy:
  block_if:
    - severity: CRITICAL
      verdict: CONFIRMED
    - severity: HIGH
      verdict: CONFIRMED

3.4 What Earns DYNAMICALLY_REACHABLE

A finding reaches the DYNAMICALLY_REACHABLE tier only when all five gates pass:

  1. CVE confirmed — Trivy identifies the package as vulnerable at the installed version
  2. Taint flow — tainter traces user-controlled input to the vulnerable sink (SQL, subprocess, YAML deserialisation, pickle, etc.)
  3. Route exposure — route_extractor confirms an HTTP endpoint exists that reaches the vulnerable code
  4. Call chain — AST analysis confirms a code path from the route handler to the vulnerable function
  5. Runtime coverage — coverage.py records the function executing under Schemathesis-generated HTTP traffic

All five must pass. A finding that clears four gates is classified STATICALLY_REACHABLE. A finding with a weak or partial signal is classified UNCERTAIN. Only findings with zero evidence are classified NOT_REACHABLE.


4. Trivy Alone — The SCA Foundation

Trivy provides the CVE inventory that VulnReach's pipeline is built on. The following distribution covers all 11 vulnerable packages. At this layer, every finding is correctly reported — prioritisation by reachability requires the additional analysis stages described in Section 5.

4.1 Package-Level Distribution

PackageInstalled VersionCVE CountHighest SeverityFix Version
Django3.2.030CRITICAL5.2.8 / 5.1.14 / 4.2.26
Pillow8.3.111CRITICAL10.3.0
cryptography36.0.210HIGH46.0.6
urllib31.26.87HIGH2.6.3
requests2.27.14MEDIUM2.33.0
certifi2021.10.83HIGH2024.7.4
PyJWT2.3.02HIGH2.12.0
gunicorn20.1.02HIGH22.0.0
lxml4.6.51MEDIUM4.9.1
Markdown3.3.41MEDIUM3.8.1
djangorestframework3.12.01LOW3.15.2
Total72

4.2 Reachability Questions Beyond SCA's Scope

Trivy's role is CVE detection from dependency manifests — it does that correctly and completely. Reachability questions are outside that scope and require a separate analysis layer:

QuestionTrivy answerAnswered by
Is this package installed?YesTrivy (SCA)
Is there a CVE in this package?YesTrivy (SCA)
Is the vulnerable function imported by your code?Out of scopeVulnReach (static)
Does user-controlled input flow to the vulnerable function?Out of scopeVulnReach (taint)
Is there an HTTP route that exposes this code?Out of scopeVulnReach (route analysis)
Was this code executed during any run of the application?Out of scopeVulnReach (runtime coverage)
Which of these 72 CVEs must be fixed before next deploy?Sort by CVSSVulnReach (policy gate)

CVSS scores measure the theoretical severity of a vulnerability class. Whether the vulnerable code path is reachable in this specific application requires application-level analysis. Django 3.2.0 carries 30 CVEs at CRITICAL — VulnReach's pipeline traces whether the vulnerable Django functions are called by this application's route handlers under real traffic.

Trivy's output is correct and complete for its purpose. The reachability layer is additive, not corrective.


5. VulnReach Full Pipeline — The Evidence Chain

VulnReach's 5-layer pipeline processed the same repository and produced 90 classified findings — the 72 raw CVEs enriched with evidence chain metadata and verdict-assigned priorities.

5.1 Verdict Distribution

Reachability ClassCountShareCI PriorityMeaning
DYNAMICALLY_REACHABLE4954%P1 / P2 — act nowRuntime execution confirmed under HTTP traffic
STATICALLY_REACHABLE2326%P2 / P3 — this sprintCode path proven by AST; no runtime hit recorded
UNCERTAIN1820%P3 — investigatePartial signal; taint or import detected, no confirmation
NOT_REACHABLE00%P4 — suppressNo evidence of use

5.2 Pipeline Gate: BLOCKED

The scan returned status: BLOCKED because Django 3.2.0 CVEs (CRITICAL, DYNAMICALLY_REACHABLE) and cryptography 36.0.2 CVEs (HIGH, DYNAMICALLY_REACHABLE) matched the configured policy:

policy:
  block_if:
    - severity: CRITICAL
      verdict: CONFIRMED   # Django: 30 CRITICAL CVEs — all DYNAMICALLY_REACHABLE
    - severity: HIGH
      verdict: CONFIRMED   # cryptography: 10 HIGH CVEs — all DYNAMICALLY_REACHABLE

In a CI/CD pipeline, this scan would fail the build before merge. No manual security review is required; the evidence chain is the review.

5.3 Package-Level Breakdown

PackageVersionCVEsReachability ClassSeverityEvidence Layers
Django3.2.030DYNAMICALLY_REACHABLECRITICALTaint + route + AST + runtime coverage
cryptography36.0.210DYNAMICALLY_REACHABLEHIGHTaint + route + AST + runtime coverage
PyJWT2.3.02DYNAMICALLY_REACHABLEHIGHTaint + route + AST + runtime coverage
requests2.27.14DYNAMICALLY_REACHABLEMEDIUMTaint + route + AST + runtime coverage
lxml4.6.51DYNAMICALLY_REACHABLEMEDIUMTaint + route + AST + runtime coverage
Markdown3.3.41DYNAMICALLY_REACHABLEMEDIUMTaint + route + AST + runtime coverage
djangorestframework3.12.01DYNAMICALLY_REACHABLELOWTaint + route + AST + runtime coverage
Pillow8.3.111STATICALLY_REACHABLECRITICALAST import + call chain (no runtime hit)
certifi2021.10.83STATICALLY_REACHABLEHIGHAST import + call chain
gunicorn20.1.02STATICALLY_REACHABLEHIGHAST import detected (server infrastructure)
urllib31.26.87STATICALLY_REACHABLEHIGHAST import via requests transitive path

5.4 Why Pillow Is STATICALLY_REACHABLE, Not DYNAMICALLY_REACHABLE

Pillow's 11 CVEs are classified STATICALLY_REACHABLE rather than DYNAMICALLY_REACHABLE. The AST call graph confirms that Pillow is imported and called from image-processing route handlers. However, Schemathesis-generated traffic did not exercise those routes during the dynamic analysis window — image upload endpoints were either not represented in the auto-generated OpenAPI spec or were behind authentication gates not traversed in this scan run.

The code path to Pillow provably exists. Runtime confirmation is absent. This distinction produces the correct engineering outcome: Pillow is elevated to P2 (fix this sprint) rather than P1 (block now), reflecting a genuine difference in confirmed exploitability rather than suppressing it silently.

This is an intentional precision tradeoff. The tool does not promote a finding to DYNAMICALLY_REACHABLE unless all five evidence gates pass.

5.5 The 18 UNCERTAIN Findings

18 findings carried partial signals — a taint path or import detection without runtime confirmation. These are classified UNCERTAIN rather than suppressed. They appear in the P3 investigation queue with a lower confidence score, giving engineers the information they need to investigate whether the signal is meaningful or dismiss it with a documented rationale.

Suppressing uncertain findings entirely would improve the apparent precision metric at the cost of recall. VulnReach preserves them.


6. SCA Foundation vs Full Reachability Pipeline

The following table shows what each layer contributes when run against the identical repository, identical commit. Trivy provides the CVE inventory; VulnReach builds the reachability evidence chain on top of it.

DimensionTrivy (raw SCA)VulnReach (full pipeline)
Inputrequirements.txt / DockerfileSame repo + source code + Dockerfile
CVE findings produced7290 classified findings
Findings confirmed executed at runtime049
Findings with proven code path023
Findings requiring investigation0 (all equal)18
Findings confirmed safe to suppress00
Immediate action (P1) queue size72 (all findings, no filter)49
Taint flow analysis (source → sink)NoYes
HTTP route exposure mappingNoYes
Runtime coverage as evidenceNoYes
Per-finding confidence scoreNoYes (0.40 – 0.95)
CI/CD pipeline gateNoYes — BLOCKED
Fix version surfacedYesYes
Self-hosted, no data egressYesYes
LLM exploit confirmationNoYes (optional, Anthropic / Ollama)

Trivy's CVE detection is correct. Every package it flags is genuinely vulnerable. VulnReach uses that detection as its foundation and adds four analysis layers — taint flow, route mapping, AST call-graph, and runtime coverage — to answer the reachability question that SCA is not designed to address.


7. OSS Benchmark Landscape

7.1 Capability Matrix

ToolCategoryStatic ReachabilityDynamic EvidenceCI GateData Egress
TrivyOSSNoneNoneBasic (severity exit code)None
GrypeOSSNoneNoneNoneNone
pip-auditOSS (Python)NoneNoneNoneNone
SafetyOSS (Python)NoneNoneNoneNone
OWASP Dependency-CheckOSSNoneNoneBasicNone
Snyk Open SourceCommercial (free tier)Partial call-graphNoneBasicYes (SaaS)
Endor LabsCommercialFull call-graphNoneYes (policy engine)Yes (SaaS)
VulnReachOSSFull 5-layerRuntime coverage (coverage.py / eBPF)Configurable policyNone (self-hosted)

7.2 Reachability Depth Comparison

CapabilitySnykEndor LabsVulnReach
Static import detectionPartialYesYes
Taint flow (source → sink)NoNoYes
Call graph to vulnerable functionPartialYes (Java / Go / JS / Rust)Yes (Python AST)
Runtime coverage as evidence gateNoNoYes
LLM-assisted exploit confirmationNoNoYes (optional)

7.3 Where VulnReach Adds Value

Three concrete additions to the OSS landscape:

1. Runtime coverage as a gating condition. VulnReach requires execution evidence to award the highest confidence tier. This is additive to tools like Endor Labs, which provide mature static call-graph analysis but do not use runtime evidence — meaning no equivalent to the DYNAMICALLY_REACHABLE verdict exists in that tier.

2. Self-hosted with no data egress. Tools like Trivy, Grype, pip-audit, and OWASP Dependency-Check already operate locally and produce no data egress. VulnReach extends that same model to include full reachability analysis — something currently only available from SaaS providers like Snyk and Endor Labs. Ollama integration extends this to LLM features for fully air-gapped deployments.

3. Taint flow + runtime + CI block in one pipeline. Building this capability today requires composing multiple separate tools (a SAST tool for taint, a custom script for coverage, a CI integration for policy). VulnReach integrates these into a single agent pipeline with a single result format, with Trivy's CVE output as the input.

7.4 Industry Context

Independent research consistently reports that reachability analysis reduces actionable alert volume by 70–90% relative to raw SCA output. Industry precision benchmarks for reachability-aware tools average approximately 70%; recall averages approximately 52.7% (Kang et al. 2022 and supplementary vendor-published metrics).

This benchmark's 0% NOT_REACHABLE rate reflects the test subject: multi-tier-dvpa is intentionally vulnerable and its application code actively uses every flagged dependency. A typical production service — where many transitive dependencies are pulled in but never called — would exhibit 40–70% NOT_REACHABLE for the same raw CVE set.

VulnReach's design reflects a precision-first approach: conservative five-link gating means some findings are correctly classified STATICALLY_REACHABLE rather than promoted to DYNAMICALLY_REACHABLE when runtime confirmation is absent. The result is a shorter, more defensible P1 queue, not an inflated one.


8. Key Metrics

MetricValue
Raw CVE input (Trivy)72
Classified findings output (VulnReach)90
DYNAMICALLY_REACHABLE (P1 / P2)49 (54%)
STATICALLY_REACHABLE (P2 / P3)23 (26%)
UNCERTAIN (P3)18 (20%)
NOT_REACHABLE (P4 / suppress)0 (0%)
Pipeline gate statusBLOCKED
Packages triggering CI block7 (Django, cryptography, PyJWT, requests, lxml, Markdown, djangorestframework)
Packages moved to P24 (Pillow, certifi, gunicorn, urllib3)
Confidence tier: HIGH (0.85 – 0.95)49 findings (DYNAMICALLY_REACHABLE)
Confidence tier: MEDIUM (0.60 – 0.75)23 findings (STATICALLY_REACHABLE)
Confidence tier: LOW (0.40 – 0.55)18 findings (UNCERTAIN)
Evidence chain depth at highest tier5 of 5 gates passed
Findings deprioritised from undifferentiated queue41 (46% moved from P1 to P2 / P3)

On the noise reduction figure: 41 of 90 findings (46%) were moved out of the equivalent "fix everything now" P1 queue that raw SCA produces. These are not false positives — the underlying packages are genuinely vulnerable. They are correctly lower-priority findings given runtime and static evidence. In a real-world production application with higher NOT_REACHABLE rates, this figure rises to 70–90%.


9. Priority Action List

VulnReach assigns each finding a priority based on severity × reachability_class × exposure. This scan used risk.exposure: public. The following tables are directly actionable.

P1 — Fix Before Next Deploy (pipeline blocked)

PackageCVEsSeverityRationale
Django30CRITICALFramework-level; all HTTP routes affected; runtime-confirmed
cryptography10HIGHEncryption and auth paths; runtime-confirmed under HTTP traffic
PyJWT2HIGHJWT authentication endpoints exercised during scan

P2 — Fix This Sprint

PackageCVEsSeverityRationale
Pillow11CRITICALCode path proven by AST; image routes not hit in dynamic window — likely reachable
certifi3HIGHImported through requests; TLS trust store affected
gunicorn2HIGHServer infrastructure; import confirmed, elevated due to HIGH severity
urllib37HIGHTransitive via requests; network I/O paths confirmed statically
requests4MEDIUMRuntime-confirmed; elevated to P2 under public exposure modifier

P3 — Investigate and Schedule

PackageCVEsSeverityRationale
lxml1MEDIUMRuntime-confirmed; isolated XML processing path
Markdown1MEDIUMRuntime-confirmed; content rendering endpoint
djangorestframework1LOWRuntime-confirmed; LOW severity and narrow attack surface
(all UNCERTAIN findings)18MixedPartial taint signal; no runtime confirmation; review code paths

P4 — Accept / Suppress

None in this scan. All 11 packages are actively used by the application.


10. OWASP Incubator Positioning

10.1 The Compliance Gap

The OWASP Software Component Verification Standard (SCVS) Level 2 requires that "all third-party components are vetted and confirmation of no known exploitable vulnerabilities is obtained." Standard SCA tools satisfy a weaker form of this requirement: they confirm no known CVEs exist, not that no exploitable code path exists.

VulnReach's runtime evidence chain is the closest available OSS implementation of exploitability confirmation for SCVS Level 2 and 3. A DYNAMICALLY_REACHABLE verdict, backed by coverage evidence and a confirmed taint path, is materially stronger than a raw CVE flag.

10.2 OSS Tooling Gap

No current OWASP Incubator or Flagship project provides runtime-confirmed SCA reachability for Python applications.

OWASP ProjectCategoryReachability
OWASP Dependency-CheckSCANone
OWASP ZAPDASTNone (no dependency correlation)
OWASP DefectDojoFindings managementAggregates; no analysis
OWASP SCVSStandardDefines requirement; no implementation

VulnReach would be the first OWASP project to provide a combined SCA + runtime reachability analysis pipeline, directly implementing SCVS Level 2/3 for Python services.

10.3 Incubator Readiness Evidence

Five readiness criteria cross-referenced against docs/incubator-readiness.md:

CriterionEvidence from this benchmark
Reproducible, deterministic outputmulti-tier-dvpa scan produces 90 classified findings consistently across runs; verdict assignment is deterministic
Evidence-backed claimsEvery DYNAMICALLY_REACHABLE verdict carries import_detected, call_chain_exists, sink_reachable, has_taint_flow, has_coverage_hit flags
Comparable to commercial toolsSections 6 and 7 show feature parity with or superiority to Snyk and Endor Labs on the runtime evidence dimension
Open source and self-hostedApache 2.0 license; zero data egress; Ollama support for air-gapped LLM features
Developer-focused outputP1–P4 priorities, fix versions, confidence scores, and policy gate — not raw CVE dumps

10.4 Limitations and Scope Boundaries

LimitationImpact on this benchmark
Python-only pipeline in v2Benchmark does not cover Node.js, Java, or Go applications
Schemathesis coverage is route-dependentAuthenticated or complex routes may not receive traffic; some reachable code remains STATICALLY_REACHABLE (see Pillow, Section 5.4)
multi-tier-dvpa is intentionally vulnerableNOT_REACHABLE is 0%; real-world production services typically show 40–70% NOT_REACHABLE for the same raw CVE set
Dynamic analysis requires DockerEnvironments without Docker access run a static-only pipeline; DYNAMICALLY_REACHABLE tier unavailable
LLM exploit confirmation not includedThe intelligent DAST loop (optional, Anthropic / Ollama) was not enabled in this benchmark run

The benchmark establishes that VulnReach's core claim holds on a real, intentionally-vulnerable Python/Django application: runtime-aware SCA produces a materially higher-quality action queue than raw SCA. The 49 DYNAMICALLY_REACHABLE findings represent the subset of the 72-CVE raw queue that both demonstrably have a code path and demonstrably executed under realistic HTTP traffic. That is the foundation for OWASP-grade confidence in the output.


VulnReach v2 · Benchmark scan date: April 2026 · Scan ID: 3cd0ff7b-40be-4c26-897f-eb37e72a86a2 For pipeline architecture: docs/architecture.md For OWASP alignment: OWASP.md · docs/incubator-readiness.md