<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Ekene Ejike</title>
    <description>The latest articles on DEV Community by Ekene Ejike (@kenzman).</description>
    <link>https://dev.to/kenzman</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3810695%2Fac9b960d-e745-42f4-9514-a125c2ee3981.jpg</url>
      <title>DEV Community: Ekene Ejike</title>
      <link>https://dev.to/kenzman</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kenzman"/>
    <language>en</language>
    <item>
      <title>From Base Images to Runtime Factories: Rebuilding Containers based on Risk</title>
      <dc:creator>Ekene Ejike</dc:creator>
      <pubDate>Sat, 21 Mar 2026 22:57:31 +0000</pubDate>
      <link>https://dev.to/kenzman/from-base-images-to-runtime-factories-eliminating-sca-noise-with-event-driven-rebuilds-3egm</link>
      <guid>https://dev.to/kenzman/from-base-images-to-runtime-factories-eliminating-sca-noise-with-event-driven-rebuilds-3egm</guid>
      <description>&lt;p&gt;Most “secure” container pipelines are doing unnecessary work.&lt;/p&gt;

&lt;p&gt;They rebuild images every night.&lt;br&gt;&lt;br&gt;
They rescan the same vulnerabilities.&lt;br&gt;&lt;br&gt;
They ignore half the findings.&lt;/p&gt;

&lt;p&gt;And none of it reduces real risk.&lt;/p&gt;

&lt;p&gt;Worse, it creates the wrong incentives.&lt;/p&gt;

&lt;p&gt;Teams spend time silencing scanners instead of reducing attack surface.&lt;br&gt;&lt;br&gt;
Developers learn to ignore security signals entirely.&lt;/p&gt;

&lt;p&gt;The real problem isn’t finding vulnerabilities.&lt;/p&gt;

&lt;p&gt;It’s knowing which ones actually matter.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;This is the same problem we see in SAST, detecting vulnerable code is easy; proving it’s reachable is the hard part.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When scanners flag CVEs in code paths your application never executes, the signal breaks.&lt;/p&gt;

&lt;p&gt;So we changed the model entirely.&lt;/p&gt;

&lt;p&gt;We stopped rebuilding containers on a schedule.&lt;/p&gt;

&lt;p&gt;Instead, we replaced base images with a &lt;strong&gt;Runtime Factory&lt;/strong&gt; built on three constraints:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Minimal OS surface&lt;/strong&gt; (Wolfi)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Declarative compilation&lt;/strong&gt; (apko)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Event-driven rebuilds&lt;/strong&gt; (Rebuild &lt;em&gt;only&lt;/em&gt; when risk changes)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here’s how the model works.&lt;/p&gt;


&lt;h2&gt;
  
  
  1. Event-Driven Self-Healing (The Core Insight)
&lt;/h2&gt;

&lt;p&gt;We removed scheduled rebuilds entirely.&lt;/p&gt;

&lt;p&gt;Rebuilds now happen based on risk, not time.&lt;/p&gt;

&lt;p&gt;A lightweight monitor checks the deployed images daily:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No changes → do nothing&lt;/li&gt;
&lt;li&gt;If a new CVE appears → trigger a rebuild immediately&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This flips the model:&lt;/p&gt;

&lt;p&gt;Traditional pipelines: &lt;code&gt;rebuild → scan → ignore&lt;/code&gt;&lt;br&gt;&lt;br&gt;
Runtime factory: &lt;code&gt;detect → rebuild → deploy&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The order matters.&lt;/p&gt;

&lt;p&gt;Here is the exact trigger:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Trigger Rebuild if Vulnerable&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;COUNT=$(jq '[.Results[].Vulnerabilities[]? | select(.Severity=="CRITICAL" or .Severity=="HIGH" or .Severity=="MEDIUM")] | length' results.json)&lt;/span&gt;
          &lt;span class="s"&gt;if [ "$COUNT" -gt 0 ]; then&lt;/span&gt;
            &lt;span class="s"&gt;curl -f -X POST \&lt;/span&gt;
              &lt;span class="s"&gt;-H "Authorization: Bearer ${{ secrets.PAT_TOKEN }}" \&lt;/span&gt;
              &lt;span class="s"&gt;-H "Accept: application/vnd.github+json" \&lt;/span&gt;
              &lt;span class="s"&gt;https://api.github.com/repos/${{ github.repository }}/dispatches \&lt;/span&gt;
              &lt;span class="s"&gt;-d '{"event_type": "cve-rebuild-trigger"}'&lt;/span&gt;
          &lt;span class="s"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Note: &lt;code&gt;GITHUB_TOKEN&lt;/code&gt; cannot trigger &lt;code&gt;repository_dispatch&lt;/code&gt; events. GitHub intentionally blocks this to prevent infinite workflow loops. You'll need a Personal Access Token with repo scope stored as &lt;code&gt;PAT_TOKEN&lt;/code&gt; in your repository secrets.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;All of our downstream caller workflows (&lt;code&gt;secure-java&lt;/code&gt;, &lt;code&gt;secure-node&lt;/code&gt;, etc.) listen silently for this exact signal. The moment the signal fires, all runtime workflows rebuild and publish patched images, ready for downstream deployment.&lt;/p&gt;

&lt;p&gt;Rebuilds now happen only when something meaningful changes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;new vulnerabilities in included packages&lt;/li&gt;
&lt;li&gt;upstream package updates&lt;/li&gt;
&lt;li&gt;runtime dependency changes&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  2. Declarative Builds (Compiling the OS)
&lt;/h2&gt;

&lt;p&gt;To eliminate SCA noise, you must eliminate the components generating the noise. Instead of writing standard &lt;code&gt;Dockerfiles&lt;/code&gt; iterating on &lt;code&gt;FROM alpine&lt;/code&gt; or &lt;code&gt;debian-slim&lt;/code&gt;, we use &lt;code&gt;apko&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;We use &lt;code&gt;apko&lt;/code&gt; to define container images declaratively—no Dockerfiles, no incremental layers. We pair this with &lt;strong&gt;Wolfi&lt;/strong&gt;, which provides a minimal, frequently patched package ecosystem designed for containers.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Figuej9nuas6at0gvtuxc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Figuej9nuas6at0gvtuxc.png" alt=" " width="800" height="1173"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here is a declarative Runtime configuration (&lt;code&gt;wolfi-node.yaml&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;repositories&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;https://packages.wolfi.dev/os&lt;/span&gt;
  &lt;span class="na"&gt;packages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;wolfi-baselayout&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ca-certificates-bundle&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;nodejs-24&lt;/span&gt;

&lt;span class="na"&gt;accounts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;users&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;appuser&lt;/span&gt;
      &lt;span class="na"&gt;uid&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1000&lt;/span&gt;
  &lt;span class="na"&gt;run-as&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;appuser&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When compiled, the resulting container contains Node.js 24 and the exact minimal glibc dependencies it needs to run. No unnecessary utilities (no shell, no package manager, no debugging tools unless explicitly included).&lt;/p&gt;

&lt;p&gt;This comes with tradeoffs.&lt;/p&gt;

&lt;p&gt;You lose the convenience of standard base images and take on responsibility for defining and maintaining your runtime.&lt;/p&gt;

&lt;p&gt;For many teams, that’s not worth it.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Strict SLSA Provenance
&lt;/h2&gt;

&lt;p&gt;Building a minimal image is only half the battle. Supply chain integrity requires cryptographic proof of &lt;em&gt;how&lt;/em&gt; the runtime was built.&lt;/p&gt;

&lt;p&gt;When our factory builds a runtime, it strictly pins generation tools by their SHA256 hashes. It then utilizes &lt;strong&gt;Sigstore&lt;/strong&gt; and GitHub's OIDC identity to keylessly sign the image and generate an &lt;strong&gt;SLSA Level 3 Provenance Attestation&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Any developer can now verify both the integrity and origin of the image locally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;➜ gh attestation verify oci://index.docker.io/kenzman/ns-wolfi-go@sha256:61ea...

✓ Verification succeeded!
- Build repo: Ekene95/secops-base-images
- Workflow: secure-go.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This proves exactly which pipeline built the image and what inputs were used.&lt;/p&gt;

&lt;p&gt;If anything changes, verification fails.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. The Benchmarks
&lt;/h2&gt;

&lt;p&gt;Transitioning from standard base images to an event-driven, compiled runtime factory yielded significant improvements across the board.&lt;/p&gt;

&lt;p&gt;These are not theoretical gains. We measured them on identical workloads.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Legacy (&lt;code&gt;node:20-alpine&lt;/code&gt;)&lt;/th&gt;
&lt;th&gt;Runtime Factory&lt;/th&gt;
&lt;th&gt;Improvement&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Attack Surface&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;12 - 40+ known CVEs&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Significantly reduced&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Minimal package inclusion&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Image Size&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;135 MB&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~45 MB&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;66% Smaller&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Idle Memory Overhead&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~4-6 MB&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&amp;lt; 1 MB&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Lower runtime overhead due to reduced background processes and dependencies&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Execution Context&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;root&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;uid 1000&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Secure by default&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;In our tests, workloads sensitive to libc differences (e.g. Python numerical operations, JVM warm-up) showed measurable improvements compared to Alpine-based images.&lt;/p&gt;




&lt;h2&gt;
  
  
  Stop Solving the Wrong Problem
&lt;/h2&gt;

&lt;p&gt;If your pipeline is constantly fighting CVEs in packages your application never touches, you don’t have a vulnerability problem.&lt;/p&gt;

&lt;p&gt;You have a signal problem.&lt;/p&gt;

&lt;p&gt;Security tools are good at finding issues.&lt;br&gt;
They are not good at telling you which ones matter.&lt;/p&gt;

&lt;p&gt;Until that changes, reducing noise is just as important as fixing vulnerabilities.&lt;/p&gt;

&lt;p&gt;Base images optimize for convenience.&lt;br&gt;&lt;br&gt;
Runtime factories optimize for control.&lt;/p&gt;

&lt;p&gt;And if you don’t control what goes into your runtime, your scanner will keep shouting and your team will keep ignoring it.&lt;/p&gt;




&lt;p&gt;If you’re dealing with SCA noise today, this pattern is worth experimenting with.&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://github.com/Ekene95/secops-base-images" rel="noopener noreferrer"&gt;Ekene95/secops-base-images on GitHub&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Steal the pattern. Improve it. Break it.&lt;/p&gt;

&lt;p&gt;If you want to experiment without building the full pipeline, I’ve published prebuilt images here:&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://hub.docker.com/u/kenzman" rel="noopener noreferrer"&gt;/kenzman on Docker hub&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;These are generated from the same runtime factory, with minimal packages and signed provenance.&lt;/p&gt;

&lt;p&gt;Use them as a reference or as a starting point before building your own.&lt;/p&gt;

&lt;p&gt;Stop rebuilding containers blindly.&lt;br&gt;&lt;br&gt;
Start rebuilding based on risk.&lt;/p&gt;

</description>
      <category>devsecops</category>
      <category>architecture</category>
      <category>githubactions</category>
      <category>security</category>
    </item>
    <item>
      <title>39 CVEs in WebGoat. Only 36 Were Reachable.</title>
      <dc:creator>Ekene Ejike</dc:creator>
      <pubDate>Mon, 09 Mar 2026 20:13:38 +0000</pubDate>
      <link>https://dev.to/kenzman/39-cves-in-webgoat-only-36-were-reachable-3i6m</link>
      <guid>https://dev.to/kenzman/39-cves-in-webgoat-only-36-were-reachable-3i6m</guid>
      <description>&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;CI pipeline flagged &lt;strong&gt;39 CVEs&lt;/strong&gt; in a Spring Boot application.&lt;/p&gt;

&lt;p&gt;Security policy is always simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Block the release.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But Engineering reality is different:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"We can't triage &lt;strong&gt;39 vulnerabilities&lt;/strong&gt; today."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So I tried a different approach.&lt;/p&gt;

&lt;p&gt;Instead of asking &lt;em&gt;"How many vulnerabilities exist?"&lt;/em&gt;,&lt;br&gt;&lt;br&gt;
So I wrote a reachability engine to answer a different question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;How many of those vulnerabilities can the application actually execute?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To test this, I ran the engine against &lt;strong&gt;OWASP &lt;a href="https://owasp.org/www-project-webgoat/" rel="noopener noreferrer"&gt;WebGoat&lt;/a&gt;&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;203 Maven dependencies
&lt;/li&gt;
&lt;li&gt;158,000 methods in the call graph&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The first scan took &lt;strong&gt;33 minutes&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;After fixing a hidden &lt;strong&gt;graph traversal bug&lt;/strong&gt;, the analysis finished in &lt;strong&gt;29 seconds&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;All code and experiments in this article are reproducible. &lt;a href="https://github.com/Netshield-Enterprise/netshield-analyzer" rel="noopener noreferrer"&gt;NetShield Analyzer on GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here is what the engine found:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;th&gt;Count&lt;/th&gt;
&lt;th&gt;Detail&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Reachable&lt;/td&gt;
&lt;td&gt;36&lt;/td&gt;
&lt;td&gt;xstream deserialization entry point called&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unknown&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;commons-lang3 package used&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unreachable&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;jose4j never invoked&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Thirty-six of 39 CVEs had a static execution path from application code to the vulnerable method. Two were definitively unreachable. One required manual review.&lt;/p&gt;


&lt;h3&gt;
  
  
  The Question SCA Tools Don't Answer
&lt;/h3&gt;

&lt;p&gt;Every AppSec team runs Software Composition Analysis. Snyk, Dependabot, OWASP Dependency-Check, they all answer the same question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Does a vulnerable version exist in your dependency tree?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is a valid question. But it is not the question that determines risk:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Can my application actually trigger the vulnerable code?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;WebGoat imports xstream 1.4.5, which has 36 known CVEs. But the reason those CVEs are reachable is because WebGoat's lesson code directly calls the affected deserialization entry point:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// From WebGoat's VulnerableComponentsLesson&lt;/span&gt;
&lt;span class="nc"&gt;XStream&lt;/span&gt; &lt;span class="n"&gt;xstream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;XStream&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="nc"&gt;Object&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;xstream&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fromXML&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If that call didn't exist, none of those 36 CVEs would have a static execution path from the application.&lt;br&gt;
Meanwhile, WebGoat also has jose4j on the classpath. jose4j has 2 CVEs. But WebGoat never calls any jose4j methods. Those CVEs are &lt;strong&gt;unreachable&lt;/strong&gt;, present in the dependency tree, but impossible to trigger.&lt;/p&gt;

&lt;p&gt;A traditional SCA tool treats both equally: 39 CVEs, all blocking.&lt;/p&gt;
&lt;h4&gt;
  
  
  Defining Reachability
&lt;/h4&gt;

&lt;p&gt;Before going further, a precise definition:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A vulnerability is marked REACHABLE if a static execution path exists from an application entry point to a method known to be affected by the CVE.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This does not guarantee exploitability. A reachable CVE means the vulnerable code can execute under some input conditions. Whether a full exploit chain exists depends on runtime factors, gadget chains, input validation, security managers that static analysis cannot fully resolve.&lt;br&gt;
What reachability does guarantee: if no static path exists, the vulnerable code cannot execute, regardless of input.&lt;/p&gt;
&lt;h4&gt;
  
  
  The Reachability Problem
&lt;/h4&gt;

&lt;p&gt;Your application calls methods. Those methods call other methods. This creates a call graph, a directed graph of execution paths from your entry points through your code and into your dependencies.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WebGoatApplication.main()
      ↓
Spring Dispatcher
      ↓
VulnerableComponentsLesson.execute()
      ↓
XStream.fromXML(payload)          ← 36 CVEs reachable through this path

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If no path exists from your application entry points to the vulnerable method, the vulnerability is &lt;strong&gt;unreachable&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WebGoatApplication.main()
      ↓
... (no path to jose4j) ...

JsonWebEncryption.decrypt()       ← jose4j CVE — never called

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is static call graph analysis. It is well studied in research, beginning to appear in commercial tools, and exactly what I implemented in NetShield Analyzer.&lt;/p&gt;




&lt;h3&gt;
  
  
  Architecture
&lt;/h3&gt;

&lt;p&gt;The analysis pipeline has four stages: dependency resolution → bytecode parsing → call graph construction → CVE reachability analysis.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────┐    ┌──────────────────┐    ┌───────────────────────┐
│  Maven Project  │───▶│  Dependency Tree │───▶│  Parallel JAR        │   │
│    pom.xml      │    │  (203 deps)      │     │  bytecode parsing     │
└─────────────────┘    └──────────────────┘     │  (10 workers)         │
                                                └───────────┬───────────┘
                                                            │
┌─────────────────┐    ┌──────────────────┐    ┌────────────▼──────────┐
│  Risk Triage    │◀───│  OSV CVE Lookup  │◀───│  Call Graph          │
│  + Trust Score  │    │  (10 concurrent) │    │  158K nodes           │
│                 │    │                  │    │  3M edges             │
│  REACHABLE      │    └──────────────────┘    └───────────────────────┘
│  UNREACHABLE    │
│  UNKNOWN        │
└─────────────────┘

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For WebGoat (203 dependencies), the engine builds a call graph with &lt;strong&gt;158,410 nodes&lt;/strong&gt; and &lt;strong&gt;3,059,079 edges&lt;/strong&gt;, then identifies &lt;strong&gt;76,710 reachable methods&lt;/strong&gt; from application entry points.&lt;/p&gt;




&lt;h3&gt;
  
  
  Engineering Challenges
&lt;/h3&gt;

&lt;p&gt;Building a naive call graph is easy. Building one that works on real applications like WebGoat, with 203 dependencies, Spring Boot, Thymeleaf, Hibernate, and OAuth2 is hard.&lt;/p&gt;

&lt;h4&gt;
  
  
  Entry Point Discovery
&lt;/h4&gt;

&lt;p&gt;Frameworks hide execution paths. A Spring Boot application doesn't have a simple &lt;code&gt;main → doWork&lt;/code&gt; flow.&lt;br&gt;
The engine automatically detects:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;main()                              ← Standard Java
Spring @Controller methods          ← Web MVC
Servlet doGet() / doPost()          ← Jakarta Servlet
JAX-RS @Path Resource methods       ← REST APIs
Kafka onMessage() / consume()       ← Event-driven
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;WebGoat has dozens of &lt;code&gt;@Controller&lt;/code&gt; classes. Missing any of them would mean missing execution paths to vulnerable code.&lt;/p&gt;

&lt;h4&gt;
  
  
  Virtual Dispatch Resolution
&lt;/h4&gt;

&lt;p&gt;Java is polymorphic. When code calls a method through an interface, the actual implementation depends on the runtime type. A naive call graph sees only the interface method, a dead end.&lt;br&gt;
The engine performs class hierarchy traversal. For every &lt;code&gt;invokevirtual&lt;/code&gt; or &lt;code&gt;invokeinterface&lt;/code&gt; instruction, it walks the known subclass tree and adds edges to all concrete implementations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// BFS through class hierarchy to find all concrete implementations&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Builder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;addVirtualEdges&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cg&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CallGraph&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;callerID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;targetClass&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;methodName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;descriptor&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="n"&gt;visited&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;queue&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;targetClass&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;currentClass&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;queue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;visited&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;currentClass&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;continue&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;visited&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;currentClass&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;classData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;classRegistry&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;currentClass&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;classData&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Methods&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;methodName&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Descriptor&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;descriptor&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;currentClass&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;targetClass&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="n"&gt;virtualID&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetMethodID&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                            &lt;span class="n"&gt;currentClass&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;methodName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;descriptor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                        &lt;span class="n"&gt;cg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddEdge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;callerID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;virtualID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                            &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CallTypeVirtual&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="p"&gt;}&lt;/span&gt;
                    &lt;span class="k"&gt;break&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;subclasses&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exists&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;classHierarchy&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;currentClass&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt; &lt;span class="n"&gt;exists&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;queue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;subclasses&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without this, any application using interfaces (which is every real Java application) would have incomplete call graphs.&lt;/p&gt;

&lt;h4&gt;
  
  
  Reflection and Lambdas
&lt;/h4&gt;

&lt;p&gt;Reflection (&lt;code&gt;Class.forName()&lt;/code&gt;, &lt;code&gt;Method.invoke()&lt;/code&gt;) and &lt;code&gt;invokedynamic&lt;/code&gt; (lambdas, method references) are patterns that defeat naive static analysis. The engine tags these cases with &lt;code&gt;IsReflective&lt;/code&gt;, &lt;code&gt;IsLambda&lt;/code&gt;, and &lt;code&gt;IsDynamic&lt;/code&gt; flags. When reachability depends on a reflective path, the result is &lt;code&gt;UNKNOWN&lt;/code&gt; rather than a false negative.&lt;br&gt;
A tool that never says "I don't know" is a tool you cannot trust.&lt;/p&gt;


&lt;h3&gt;
  
  
  The Accuracy Problem (and How I fixed it)
&lt;/h3&gt;

&lt;p&gt;The first version of the engine had a critical blind spot. When I ran it against WebGoat, all 36 xstream CVEs came back as &lt;code&gt;UNREACHABLE&lt;/code&gt;. The verdict was "SAFE TO SHIP", dangerously wrong for an application that directly calls &lt;code&gt;xstream.fromXML()&lt;/code&gt;.&lt;/p&gt;
&lt;h4&gt;
  
  
  Two bugs:
&lt;/h4&gt;
&lt;h5&gt;
  
  
  Bug 1: Package Name Matching
&lt;/h5&gt;

&lt;p&gt;Maven coordinates don't always map cleanly to Java package paths. The engine was constructing package names like &lt;code&gt;com/thoughtworks/xstream/xstream&lt;/code&gt; but the actual classes live in &lt;code&gt;com/thoughtworks/xstream/XStream.class&lt;/code&gt;.&lt;br&gt;
The fix: generate multiple candidate package prefixes from Maven coordinates:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;buildPackageCandidates&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;groupID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;artifactID&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// Pattern 1: groupId/artifactId&lt;/span&gt;
    &lt;span class="c"&gt;//   org/yaml/snakeyaml&lt;/span&gt;
    &lt;span class="c"&gt;// Pattern 2: groupId only (when artifactId duplicates last segment)&lt;/span&gt;
    &lt;span class="c"&gt;//   com/thoughtworks/xstream&lt;/span&gt;
    &lt;span class="c"&gt;// Pattern 3: Strip common prefixes (jackson-, commons-, spring-)&lt;/span&gt;
    &lt;span class="c"&gt;//   com/fasterxml/jackson/databind&lt;/span&gt;
    &lt;span class="c"&gt;// Pattern 4: Hyphen-split last segment&lt;/span&gt;
    &lt;span class="c"&gt;//   org/apache/commons/lang3&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h5&gt;
  
  
  Bug 2: CVE Method Extraction
&lt;/h5&gt;

&lt;p&gt;The OSV vulnerability database doesn't provide structured method-level data. The engine needs to infer which methods are vulnerable from CVE descriptions. The original heuristic only checked for "jndi" and "deserialize", far too narrow.&lt;br&gt;
The fix: a multi-layered heuristic that extracts affected methods from CVE descriptions and known vulnerability patterns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Keyword-based: CVE mentions "arbitrary code execution" → map to fromXML, readObject
// Package-based: Package is "xstream" → map to fromXML, unmarshal
// DatabaseSpecific: OSV provides affected_classes → use directly
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After both fixes, the same WebGoat scan correctly identified all 36 xstream CVEs as REACHABLE. The verdict flipped from "SAFE TO SHIP" to "DO NOT SHIP", the correct answer for an application calling &lt;code&gt;xstream.fromXML()&lt;/code&gt; on an ancient version with 36 known deserialization vulnerabilities.&lt;/p&gt;




&lt;h3&gt;
  
  
  The Performance Problem (33 Minutes → 29 Seconds)
&lt;/h3&gt;

&lt;p&gt;The first working scan of WebGoat took 33 minutes. For a CI/CD security gate, that's unacceptable.&lt;/p&gt;

&lt;h4&gt;
  
  
  Bottleneck 1: Sequential JAR Parsing
&lt;/h4&gt;

&lt;p&gt;203 dependency JARs were parsed one at a time. Each JAR is independent and there's no reason they can't be parsed in parallel.&lt;br&gt;
Fix: 10-worker goroutine pool with a channel for results:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;maxWorkers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
&lt;span class="n"&gt;sem&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;chan&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="n"&gt;maxWorkers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;resultsCh&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;chan&lt;/span&gt; &lt;span class="n"&gt;jarResult&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dependencies&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dep&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;dependencies&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;wg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;jarPath&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;wg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Done&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;sem&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{}{}&lt;/span&gt;
        &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;sem&lt;/span&gt; &lt;span class="p"&gt;}()&lt;/span&gt;

        &lt;span class="n"&gt;analyzer&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;bytecode&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewJARAnalyzer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;jarPath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;classes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;analyzer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AnalyzeJAR&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;resultsCh&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;jarResult&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;classes&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;classes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;jarPath&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;jarPath&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}(&lt;/span&gt;&lt;span class="n"&gt;dep&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;JARPath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Bottleneck 2: O(V×E) DFS
&lt;/h4&gt;

&lt;p&gt;This was the biggest hidden bottleneck. The DFS reachability traversal did this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// BEFORE: O(V×E). Scans ALL edges for every visited node&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;edge&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;cg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Edges&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;edge&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;From&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;methodID&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;dfsReachability&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;edge&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;To&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reachable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;visited&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With 76K visited nodes and 3M edges, that's approximately 228 billion iterations. The DFS scanned the entire edge list for every visited node.&lt;br&gt;
Fix: add an &lt;strong&gt;adjacency list&lt;/strong&gt; to the call graph and use iterative stack-based DFS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// AFTER: O(V+E). Follow only outgoing edges via adjacency list&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cg&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;CallGraph&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;AddEdge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;callType&lt;/span&gt; &lt;span class="n"&gt;CallType&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;cg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Edges&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Edges&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;CallEdge&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;From&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;To&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;callType&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="n"&gt;cg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AdjList&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AdjList&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c"&gt;// ← one extra line&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// DFS now uses the adjacency list&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;neighbor&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;cg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AdjList&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;visited&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;neighbor&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;stack&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;neighbor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From 228 billion iterations to 3 million. Same results. Same accuracy. Orders of magnitude fewer operations.&lt;/p&gt;

&lt;h4&gt;
  
  
  Bottleneck 3: Linear Triage Lookups
&lt;/h4&gt;

&lt;p&gt;The triage step iterated all 76K reachable methods for every vulnerability check.&lt;br&gt;
Fix: pre-index reachable methods by class name at construction time. Lookups drop from O(76K) to O(unique classes per query).&lt;/p&gt;
&lt;h4&gt;
  
  
  Benchmark
&lt;/h4&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Phase&lt;/th&gt;
&lt;th&gt;Before&lt;/th&gt;
&lt;th&gt;After&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Total runtime&lt;/td&gt;
&lt;td&gt;33 min&lt;/td&gt;
&lt;td&gt;29 sec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Call graph traversal&lt;/td&gt;
&lt;td&gt;~15 min&lt;/td&gt;
&lt;td&gt;&amp;lt; 1 sec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JAR parsing&lt;/td&gt;
&lt;td&gt;~12 min&lt;/td&gt;
&lt;td&gt;~2 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reachability triage&lt;/td&gt;
&lt;td&gt;~6 min&lt;/td&gt;
&lt;td&gt;~27 sec&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;None of these optimizations trade accuracy for speed. The adjacency list produces identical results to scanning all edges. Parallel parsing produces identical results to sequential parsing. The pre-index is the same data in a faster structure.&lt;/p&gt;


&lt;h3&gt;
  
  
  WebGoat: Full Results
&lt;/h3&gt;

&lt;p&gt;Here is the scan output:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmq9addso3npi99hwfuud.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmq9addso3npi99hwfuud.png" alt="WebGoat scan output" width="800" height="679"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  Cross-Referencing with Snyk
&lt;/h4&gt;

&lt;p&gt;To validate coverage, I ran the same project through Snyk, one of the most widely used SCA platforms:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;snyk &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;--file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;pom.xml
Tested 163 dependencies &lt;span class="k"&gt;for &lt;/span&gt;known issues, found 48 issues, 48 vulnerable paths.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Snyk found &lt;strong&gt;48 CVEs&lt;/strong&gt; and &lt;strong&gt;48 vulnerable paths&lt;/strong&gt;. NetShield Analyzer found &lt;strong&gt;39&lt;/strong&gt;. The 9 additional CVEs came from packages that the OSV database does not yet cover:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Package&lt;/th&gt;
&lt;th&gt;Snyk&lt;/th&gt;
&lt;th&gt;NetShield (OSV)&lt;/th&gt;
&lt;th&gt;Gap&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="mailto:xstream@1.4.5"&gt;xstream@1.4.5&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;36&lt;/td&gt;
&lt;td&gt;36&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="mailto:commons-lang3@3.14.0"&gt;commons-lang3@3.14.0&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="mailto:jose4j@0.9.3"&gt;jose4j@0.9.3&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="mailto:logback-core@1.5.18"&gt;logback-core@1.5.18&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;+2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="mailto:tomcat-embed-core@10.1.46"&gt;tomcat-embed-core@10.1.46&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;+4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="mailto:jackson-core@2.19.2"&gt;jackson-core@2.19.2&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;+1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="mailto:jruby-stdlib@10.0.0.1"&gt;jruby-stdlib@10.0.0.1&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;+2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;48&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;39&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;9&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This is not a reachability gap. It is a database coverage gap. NetShield Analyzer queries &lt;a href="https://osv.dev/" rel="noopener noreferrer"&gt;osv.dev&lt;/a&gt;, an open vulnerability database. Snyk uses a proprietary database with a dedicated research team that catalogs vulnerabilities faster, especially newly published ones (several of the missing CVEs were marked "new" in Snyk's output).&lt;br&gt;
NetShield Analyzer correctly classified every CVE it received from OSV. It simply never received the 9 additional CVEs because OSV doesn't have them yet. A future version could integrate additional sources to close this gap.&lt;/p&gt;
&lt;h4&gt;
  
  
  What This Comparison Shows
&lt;/h4&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Snyk (SCA only)&lt;/th&gt;
&lt;th&gt;NetShield (SCA + Reachability)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;CVE database&lt;/td&gt;
&lt;td&gt;Proprietary (broader)&lt;/td&gt;
&lt;td&gt;OSV.dev (open, narrower)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CVEs reported&lt;/td&gt;
&lt;td&gt;48&lt;/td&gt;
&lt;td&gt;39&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reachable identified&lt;/td&gt;
&lt;td&gt;Not assessed&lt;/td&gt;
&lt;td&gt;36&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unreachable identified&lt;/td&gt;
&lt;td&gt;Not assessed&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unknown&lt;/td&gt;
&lt;td&gt;Not assessed&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Manual triage required&lt;/td&gt;
&lt;td&gt;48&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The tools answer different questions. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Snyk answers: what vulnerabilities exist?&lt;br&gt;
NetShield Analyzer answers: which ones can your code trigger? &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;They are complementary, not competitive.&lt;/p&gt;


&lt;h3&gt;
  
  
  Accuracy Considerations
&lt;/h3&gt;

&lt;p&gt;Every static analysis tool must address false positives and false negatives.&lt;br&gt;
&lt;strong&gt;False positives&lt;/strong&gt; (marked reachable but not exploitable) may occur when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reflection resolves differently at runtime than predicted statically&lt;/li&gt;
&lt;li&gt;Dependency shading changes package names post-build&lt;/li&gt;
&lt;li&gt;A method is statically reachable but guarded by runtime checks (e.g., input validation, security managers)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;False negatives&lt;/strong&gt; (marked unreachable but actually exploitable) may occur when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Dynamic class loading introduces new call paths at runtime&lt;/li&gt;
&lt;li&gt;Runtime proxies (CGLIB, Byte Buddy) generate methods not visible in bytecode&lt;/li&gt;
&lt;li&gt;Native JNI code creates paths invisible to JVM bytecode analysis&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The engine mitigates false negatives by reporting &lt;code&gt;UNKNOWN&lt;/code&gt; when analysis is incomplete, rather than assuming safety. The commons-lang3 finding in WebGoat is an example: the package is referenced, but the engine could not confirm or deny the specific vulnerable method, so it flagged it for human review.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CVE database coverage&lt;/strong&gt; is another source of gaps. As the Snyk cross-reference showed, the OSV database does not contain every known advisory. NetShield Analyzer can only assess reachability for CVEs it knows about. Integrating additional vulnerability sources would reduce this blind spot.&lt;/p&gt;


&lt;h3&gt;
  
  
  Methodology
&lt;/h3&gt;

&lt;p&gt;For reproducibility, here is how the experiment was conducted:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Project analyzed:    OWASP WebGoat (v2023.8)
Language:            Java 17
Build system:        Maven
Dependencies:        203 (transitive)

Analysis environment:
  CPU:               12-core
  RAM:               16 GB
  OS:                Linux

NetShield Analyzer configuration:
  Entry points:      main + Spring MVC controllers (auto-detected)
  Call graph:        Static bytecode analysis
  Reflection:        Tagged as UNKNOWN
  CVE source:        OSV.dev database
  Analysis date:     March 2026
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  CI/CD Integration
&lt;/h3&gt;

&lt;p&gt;The engine is designed for CI/CD pipelines. Exit codes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0 = No reachable vulnerabilities   → Pass the build
1 = Reachable vulnerability found  → Fail the build
2 = Analysis failure               → Fail the build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output control flags for clean CI logs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Minimal CI output: no progress, no CVE details&lt;/span&gt;
netshield-analyzer &lt;span class="nt"&gt;--packages&lt;/span&gt; com.yourcompany &lt;span class="nt"&gt;--quiet&lt;/span&gt; &lt;span class="nt"&gt;--summary-only&lt;/span&gt;

&lt;span class="c"&gt;# JSON for programmatic consumption&lt;/span&gt;
netshield-analyzer &lt;span class="nt"&gt;--packages&lt;/span&gt; com.yourcompany &lt;span class="nt"&gt;--format&lt;/span&gt; json &lt;span class="nt"&gt;--quiet&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  What the Engine Cannot Do
&lt;/h3&gt;

&lt;p&gt;No static analysis tool is perfect. The engine reports &lt;code&gt;UNKNOWN&lt;/code&gt; when it encounters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Dynamic class loading via external configuration&lt;/li&gt;
&lt;li&gt;Heavy reflection where targets are runtime-computed strings&lt;/li&gt;
&lt;li&gt;Native JNI code&lt;/li&gt;
&lt;li&gt;Runtime code generation (CGLIB proxies)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This happened with the commons-lang3 finding in WebGoat. The package is used, but the engine couldn't confirm whether the specific vulnerable method is called. That gets flagged for human review, not silently ignored or not falsely marked safe.&lt;br&gt;
Security engineers respect tools that admit limits.&lt;/p&gt;




&lt;h3&gt;
  
  
  Related Work
&lt;/h3&gt;

&lt;p&gt;Several commercial and open-source tools are beginning to incorporate reachability analysis:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Snyk Reachability&lt;/strong&gt; — proprietary call graph analysis integrated into the Snyk platform&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Endor Labs&lt;/strong&gt; — function-level reachability with support for multiple languages&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Chainguard&lt;/strong&gt; — supply chain security with reachability-aware scanning&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;NetShield analyzer focuses on transparent call graph analysis with CI-native output, open-source implementation, and explicit methodology. The engine's approach is intentionally auditable. Every edge in the call graph can be inspected, and the DFS traversal is deterministic.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Traditional SCA answers a &lt;strong&gt;dependency question&lt;/strong&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Does a vulnerable version exist?&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Reachability analysis answers the &lt;strong&gt;execution question that actually determines risk&lt;/strong&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Can the application run the vulnerable code?&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In the &lt;strong&gt;WebGoat experiment&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;39 CVEs&lt;/strong&gt; existed in the dependency tree&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;36 CVEs&lt;/strong&gt; were reachable from application entry points&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;2 CVEs&lt;/strong&gt; were definitively unreachable&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;1 CVE&lt;/strong&gt; required manual review&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The difference between those numbers is the difference between &lt;strong&gt;alert fatigue&lt;/strong&gt; and &lt;strong&gt;actionable security&lt;/strong&gt;.&lt;/p&gt;




&lt;p&gt;&lt;a href="https://github.com/Netshield-Enterprise/netshield-analyzer" rel="noopener noreferrer"&gt;NetShield Analyzer&lt;/a&gt; source code is available on GitHub.&lt;/p&gt;

</description>
      <category>security</category>
      <category>appsec</category>
      <category>java</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
