<?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: Ahmed Yasser</title>
    <description>The latest articles on DEV Community by Ahmed Yasser (@ahmed_yasser_mmo).</description>
    <link>https://dev.to/ahmed_yasser_mmo</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%2F3859570%2Fd0311a97-7f90-4993-992a-77d1de674268.jpeg</url>
      <title>DEV Community: Ahmed Yasser</title>
      <link>https://dev.to/ahmed_yasser_mmo</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ahmed_yasser_mmo"/>
    <language>en</language>
    <item>
      <title>How I Got Enterprise-Grade DAST for Free in GitHub Actions (And Why Vanilla ZAP Finds 0 API Endpoints)</title>
      <dc:creator>Ahmed Yasser</dc:creator>
      <pubDate>Mon, 13 Apr 2026 14:48:33 +0000</pubDate>
      <link>https://dev.to/ahmed_yasser_mmo/how-i-got-enterprise-grade-dast-for-free-in-github-actions-and-why-vanilla-zap-finds-0-api-jgk</link>
      <guid>https://dev.to/ahmed_yasser_mmo/how-i-got-enterprise-grade-dast-for-free-in-github-actions-and-why-vanilla-zap-finds-0-api-jgk</guid>
      <description>&lt;h1&gt;
  
  
  How I Got Enterprise-Grade DAST for Free in GitHub Actions
&lt;/h1&gt;

&lt;p&gt;Most developers don't run Dynamic Application Security Testing (DAST) in their CI pipelines. Not because they don't want to — but because every viable option seems to cost $180k/year, take 60 minutes per scan, or require a PhD in ZAP configuration.&lt;/p&gt;

&lt;p&gt;I spent months building &lt;strong&gt;&lt;a href="https://github.com/AlphaSudo/zerodast" rel="noopener noreferrer"&gt;ZeroDAST&lt;/a&gt;&lt;/strong&gt; to prove that's a false choice.&lt;/p&gt;

&lt;p&gt;This is the story of what I found, the real data behind it, a hands-on tutorial, and an honest comparison with enterprise DAST.&lt;/p&gt;




&lt;h2&gt;
  
  
  Part 1: Why Vanilla ZAP Discovers 0 API Endpoints
&lt;/h2&gt;

&lt;p&gt;I started where most teams start: running OWASP ZAP against my API.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-t&lt;/span&gt; zaproxy/zap-stable zap-api-scan.py &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-t&lt;/span&gt; http://my-api:3000/api-docs &lt;span class="nt"&gt;-f&lt;/span&gt; openapi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The scanner ran. It produced a report. And the result was &lt;strong&gt;essentially useless for API security.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I tested vanilla ZAP against &lt;strong&gt;four high-profile open-source targets&lt;/strong&gt; — NocoDB (48k ⭐), Strapi (67k ⭐), Directus (29k ⭐), and Medusa (27k ⭐). Same GitHub-hosted runners, same ZAP version (&lt;code&gt;2.17.0&lt;/code&gt;), same Docker Compose targets. The vanilla baseline scripts are &lt;a href="https://github.com/AlphaSudo/zerodast/tree/main/benchmarks/vanilla-baseline" rel="noopener noreferrer"&gt;public and executable&lt;/a&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Target&lt;/th&gt;
&lt;th&gt;⭐ Stars&lt;/th&gt;
&lt;th&gt;Vanilla ZAP API URIs&lt;/th&gt;
&lt;th&gt;ZeroDAST API URIs&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;NocoDB&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;48k+&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;7&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Strapi&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;67k+&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;8&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Directus&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;29k+&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;30&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Medusa&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;27k+&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;3&lt;/strong&gt;&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;171k+&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;48&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That's not a typo. Vanilla ZAP discovered &lt;strong&gt;zero API endpoints&lt;/strong&gt; across all four targets. It found some frontend/static findings — CSP headers, cookie flags — but nothing behind authentication. The entire API surface was invisible.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Three Auth Walls
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Wall 1: Non-standard auth headers.&lt;/strong&gt; NocoDB uses a custom header called &lt;code&gt;xc-auth&lt;/code&gt; — not &lt;code&gt;Authorization&lt;/code&gt;, not &lt;code&gt;X-Token&lt;/code&gt;. If your scanner sends &lt;code&gt;Authorization: Bearer &amp;lt;token&amp;gt;&lt;/code&gt;, NocoDB ignores it. Every request returns &lt;code&gt;401&lt;/code&gt;. The spider crawls the Nuxt.js SPA shell and finds frontend issues, but the API is locked.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Wall 2: Nested token responses.&lt;/strong&gt; Even if you know the right header, you need to extract the token from the login response. Strapi returns &lt;code&gt;{ "data": { "token": "..." } }&lt;/code&gt;. Directus goes deeper: &lt;code&gt;{ "data": { "access_token": "..." } }&lt;/code&gt;. Vanilla ZAP's auth mechanisms expect the token at the root level. When it's nested under &lt;code&gt;data&lt;/code&gt;, ZAP extracts nothing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Wall 3: Admin vs user API separation.&lt;/strong&gt; Strapi has two distinct auth systems — &lt;code&gt;/api/auth/local&lt;/code&gt; for frontend users and &lt;code&gt;/admin/login&lt;/code&gt; for admins. If you authenticate as a frontend user, the entire admin API surface is invisible. And the admin surface is usually &lt;strong&gt;where the interesting vulnerabilities live&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Full Signal Impact
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Target&lt;/th&gt;
&lt;th&gt;Vanilla ZAP&lt;/th&gt;
&lt;th&gt;ZeroDAST&lt;/th&gt;
&lt;th&gt;Signal Lift&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;NocoDB&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;8M/15L/7I — &lt;strong&gt;0 API&lt;/strong&gt; URIs&lt;/td&gt;
&lt;td&gt;11M/15L/8I — &lt;strong&gt;7 API&lt;/strong&gt; URIs&lt;/td&gt;
&lt;td&gt;+13% findings, superset&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Strapi&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;3M/7L/4I — &lt;strong&gt;0 API&lt;/strong&gt; URIs&lt;/td&gt;
&lt;td&gt;8M/10L/8I — &lt;strong&gt;8 API&lt;/strong&gt; URIs&lt;/td&gt;
&lt;td&gt;+86% findings, superset&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Directus&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;10M/10L/8I — &lt;strong&gt;0 API&lt;/strong&gt; URIs&lt;/td&gt;
&lt;td&gt;13M/12L/26I — &lt;strong&gt;30 API&lt;/strong&gt; URIs&lt;/td&gt;
&lt;td&gt;+82% findings, superset&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Medusa&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;2M/3L/0I — &lt;strong&gt;0 API&lt;/strong&gt; URIs&lt;/td&gt;
&lt;td&gt;4M/2L/0I — &lt;strong&gt;3 API&lt;/strong&gt; URIs&lt;/td&gt;
&lt;td&gt;Superset&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;FastAPI&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;0&lt;/strong&gt; — scan failed entirely&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;14 API&lt;/strong&gt; alert URIs&lt;/td&gt;
&lt;td&gt;∞ (vanilla can't scan it)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Fleet Total&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;77 findings, 0 API&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;117 findings, 48 API&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;+52% more findings&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;ZeroDAST is a &lt;strong&gt;strict superset&lt;/strong&gt; of vanilla ZAP — it finds everything vanilla finds (frontend/static) &lt;strong&gt;plus&lt;/strong&gt; all authenticated API findings. Switching has zero signal regression.&lt;/p&gt;

&lt;p&gt;Auth isn't an incremental improvement. It's the &lt;strong&gt;entire difference&lt;/strong&gt; between a DAST scan that finds real API vulnerabilities and one that wastes your CI minutes on frontend noise.&lt;/p&gt;




&lt;h2&gt;
  
  
  Part 2: What ZeroDAST Actually Is
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;ZeroDAST&lt;/strong&gt; is an open-source CI DAST orchestration framework — not a scanner itself. It wraps OWASP ZAP inside a security-hardened pipeline with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Adapter-based authentication&lt;/strong&gt; — 4 proven auth styles, zero custom scripting&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Two-lane privilege isolation&lt;/strong&gt; — untrusted PR code never touches the DAST runner&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Container hardening&lt;/strong&gt; — &lt;code&gt;cap-drop ALL&lt;/code&gt;, &lt;code&gt;no-new-privileges&lt;/code&gt;, read-only root, memory limits&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Delta-scoped PR scanning&lt;/strong&gt; — only scan changed routes, not the whole API&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Intelligent reporting&lt;/strong&gt; — diff-aware baselines, remediation guides, API inventory&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The auth problem is solved through a &lt;strong&gt;declarative adapter model&lt;/strong&gt; where auth configuration is config — not code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"auth"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"adapter"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"json-token-login"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"loginUrl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/api/auth/login"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"responseTokenField"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"data.token"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"headerName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Authorization"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"headerValuePrefix"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Bearer "&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Non-standard headers? Set &lt;code&gt;"headerName": "xc-auth"&lt;/code&gt;. Nested token? Use dot-path notation: &lt;code&gt;"responseTokenField": "data.access_token"&lt;/code&gt;. No ZAP scripting. No custom code.&lt;/p&gt;

&lt;p&gt;Auth is &lt;strong&gt;validated before scanning&lt;/strong&gt; — if it fails, you know before wasting 5 minutes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[auth] POST /api/v1/auth/user/signin → 200 ✓
[auth] Token extracted: eyJhbGciOi... (length: 189)
[auth] Protected route GET /api/v1/auth/user/me → 200 ✓
[auth] Admin route GET /api/v1/db/meta/projects → 200 ✓
[auth] Auth bootstrap: PASS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4 Proven Auth Adapters
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Adapter&lt;/th&gt;
&lt;th&gt;Auth Flow&lt;/th&gt;
&lt;th&gt;Proven On&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;json-token-login&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;POST JSON → extract Bearer token&lt;/td&gt;
&lt;td&gt;NocoDB, Strapi, Directus, Medusa, Petclinic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;form-cookie-login&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;POST form → session cookie&lt;/td&gt;
&lt;td&gt;Demo app&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;json-session-login&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;POST JSON → session header&lt;/td&gt;
&lt;td&gt;Django Styleguide&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;form-urlencoded-token-login&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;OAuth2-style form → Bearer token&lt;/td&gt;
&lt;td&gt;FastAPI&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Part 3: Tutorial — Your First ZeroDAST Scan (5 Minutes)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;p&gt;Docker (latest stable), Node.js 22+, Git.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Clone and Install
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/AlphaSudo/zerodast.git
&lt;span class="nb"&gt;cd &lt;/span&gt;zerodast
&lt;span class="nb"&gt;cd &lt;/span&gt;demo-app &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd&lt;/span&gt; ..
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Run Your First DAST Scan
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x scripts/run-dast-local.sh
./scripts/run-dast-local.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. Behind the scenes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Docker &lt;code&gt;--internal&lt;/code&gt; network&lt;/strong&gt; isolates app, database, and ZAP from the outside world&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Container hardening&lt;/strong&gt; — app runs with &lt;code&gt;cap-drop ALL&lt;/code&gt;, read-only root, memory/PID limits&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auth bootstrap&lt;/strong&gt; — auto-registers user + admin, logs in, extracts tokens, injects into ZAP&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OpenAPI import&lt;/strong&gt; → Spider → Active Scan through authenticated requests&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Report generation&lt;/strong&gt; — structured findings, remediation guide, API inventory&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Step 3: Read Your Results (~3 min later)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;reports/
├── zap-report.json              # Raw ZAP findings
├── zap-report.html              # Human-readable report
├── environment-manifest.json    # What was scanned and how
├── result-state.json            # Triage state (clean / needs_triage)
├── remediation-guide.md         # Prioritized fix guidance
├── operational-reliability.json # Runtime health metrics
├── api-inventory.json           # Route coverage breakdown
└── api-inventory.md             # Human-readable API inventory
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;strong&gt;remediation guide&lt;/strong&gt; separates findings into:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;New findings&lt;/strong&gt; → fix these first&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Persisting findings&lt;/strong&gt; → decide: fix or accept&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resolved findings&lt;/strong&gt; → guard against regression&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Setting Up CI: The Two-Profile Model
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;PR scans (~3 min):&lt;/strong&gt; When someone opens a PR, ZeroDAST detects changed files via &lt;code&gt;git diff&lt;/code&gt;, extracts API routes from changed controllers, and generates a scoped ZAP config targeting only those routes. Runs in a separate trusted workflow — PR code never gets &lt;code&gt;secrets&lt;/code&gt; access.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PR opens → git diff → route extraction from changed files
    ├── routes changed → scoped ZAP config (targeted seeds only)
    └── core/middleware changed → escalate to FULL scan
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Nightly scans (~5 min):&lt;/strong&gt; Full API surface, every night. Opens a deduplicated triage issue if threshold is exceeded.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why the split matters — it's a security architecture:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Security Control&lt;/th&gt;
&lt;th&gt;Vanilla CI&lt;/th&gt;
&lt;th&gt;ZeroDAST&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;PR code accesses secrets&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;❌ No — &lt;code&gt;workflow_run&lt;/code&gt; isolation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PR code can modify scan config&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;❌ No — artifact handoff only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scanner has internet access&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;❌ No — &lt;code&gt;--internal&lt;/code&gt; network&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Containers are hardened&lt;/td&gt;
&lt;td&gt;❌ No&lt;/td&gt;
&lt;td&gt;✅ Read-only, cap-dropped, memory-limited&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Installing in Your Own Repo
&lt;/h3&gt;

&lt;p&gt;ZeroDAST adds exactly two things to your repository:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Two workflow files (&lt;code&gt;.github/workflows/zerodast-pr.yml&lt;/code&gt; + &lt;code&gt;zerodast-nightly.yml&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;zerodast/&lt;/code&gt; config directory
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;/prototypes/model1-template/install.ps1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-TargetRepoPath&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'C:\path\to\your-repo'&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# Configure zerodast/config.json with your auth/endpoints/seeds&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c"&gt;# Run locally&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;chmod&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;zerodast/run-scan.sh&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;ZERODAST_MODE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;pr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;/zerodast/run-scan.sh&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# Uninstall (clean removal)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;/prototypes/model1-template/uninstall.ps1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-TargetRepoPath&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'C:\path\to\your-repo'&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Full install guide: &lt;strong&gt;&lt;a href="https://github.com/AlphaSudo/zerodast/blob/main/docs/MODEL1_INSTALL_GUIDE.md" rel="noopener noreferrer"&gt;MODEL1_INSTALL_GUIDE.md&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Part 4: Honest Comparison — ZeroDAST vs Checkmarx DAST
&lt;/h2&gt;

&lt;p&gt;If you've ever seen a Checkmarx DAST quote — $600-$1,200/dev/year, $180k-$350k annually for 50-100 devs — you've wondered if there's an open-source alternative. &lt;strong&gt;Here's the honest answer.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Full Capability Matrix
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Capability&lt;/th&gt;
&lt;th&gt;ZeroDAST&lt;/th&gt;
&lt;th&gt;Checkmarx DAST&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cost&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;$0&lt;/strong&gt; — Apache 2.0&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;$180k-$350k/year&lt;/strong&gt; (50-100 devs)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;CI scan speed&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~3 min PR, ~5 min nightly&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;15-60 min typical&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Auth: Token/session REST&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ 4 proven adapters&lt;/td&gt;
&lt;td&gt;✅ Platform wizard&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Auth: SSO/SAML/OIDC/MFA&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;❌ Not supported&lt;/td&gt;
&lt;td&gt;✅ Browser recording + ZEST&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Protocol: REST API&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ Primary focus&lt;/td&gt;
&lt;td&gt;✅ Supported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Protocol: GraphQL&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;❌ Not supported&lt;/td&gt;
&lt;td&gt;✅ Supported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Trusted/untrusted CI split&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ Genuine privilege isolation&lt;/td&gt;
&lt;td&gt;⚠️ Platform-managed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Container hardening&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ cap-drop, read-only, no-new-privileges&lt;/td&gt;
&lt;td&gt;⚠️ Varies&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Delta PR scanning&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ Route-aware git-diff scoping&lt;/td&gt;
&lt;td&gt;⚠️ Incremental varies&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Baseline comparison&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ Diff-aware new/persisting/resolved&lt;/td&gt;
&lt;td&gt;✅ Platform-managed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Remediation guidance&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ Structured priority ordering&lt;/td&gt;
&lt;td&gt;✅ Platform-managed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;API inventory&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ Observed/unobserved/hinted routes&lt;/td&gt;
&lt;td&gt;✅ API Security integration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;PR bot comments&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ Policy-driven&lt;/td&gt;
&lt;td&gt;✅ Platform-managed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Detection depth&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;ZAP standard + tuned rules&lt;/td&gt;
&lt;td&gt;Proprietary + ZAP-level + custom&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Governance/compliance&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;❌ Not supported&lt;/td&gt;
&lt;td&gt;✅ RBAC, permissions, audit trail&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Shadow API discovery&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;❌ Not supported&lt;/td&gt;
&lt;td&gt;✅ API Security product&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Vendor lock-in&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;None&lt;/strong&gt; — fully inspectable&lt;/td&gt;
&lt;td&gt;Yes — proprietary&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Support&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Community (GitHub Issues)&lt;/td&gt;
&lt;td&gt;Commercial SLAs&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Where Checkmarx Wins (Honestly)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Auth breadth&lt;/strong&gt;: SSO/SAML/OIDC/MFA/browser recording — outside ZeroDAST's niche by design&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Detection depth&lt;/strong&gt;: Proprietary rules may catch vulnerability classes ZAP misses — real gap&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Platform features&lt;/strong&gt;: Full governance/compliance/RBAC/ASPM — no contest&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shadow API discovery&lt;/strong&gt;: Production traffic analysis — different product category&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Support&lt;/strong&gt;: SLAs and dedicated teams vs community-only&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Where ZeroDAST Wins
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cost&lt;/strong&gt;: $0 vs $180k+/year — for OSS and small teams, this is the difference between having DAST and not having it&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CI speed&lt;/strong&gt;: 3-minute PR scans vs 15-60 min — ZeroDAST is architecturally faster through delta detection, not just lower budgets&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Transparency&lt;/strong&gt;: Every script, config, and decision is inspectable shell + JS + YAML, not a proprietary black box&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trust architecture&lt;/strong&gt;: Genuine privilege isolation with &lt;code&gt;workflow_run&lt;/code&gt;, &lt;code&gt;--internal&lt;/code&gt; networking, and hardened containers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No lock-in&lt;/strong&gt;: Standard ZAP config, standard GitHub Actions YAML, standard JSON baselines&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Who Should Use What
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Use ZeroDAST if:&lt;/strong&gt; You're an OSS maintainer or small team with a REST API, token-based auth, and no enterprise security budget. You want 3-minute PR scans and full transparency.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use Checkmarx if:&lt;/strong&gt; You need SSO/SAML/MFA auth, GraphQL scanning, enterprise governance, or commercial SLAs. You have the budget and the scale.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use both if:&lt;/strong&gt; You want fast CI-first feedback on PRs (ZeroDAST: 3 min) plus deep periodic enterprise scans (Checkmarx: proprietary rules).&lt;/p&gt;




&lt;h2&gt;
  
  
  What ZeroDAST Doesn't Do (Honest Limitations)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;❌ &lt;strong&gt;Enterprise auth&lt;/strong&gt; — SSO / SAML / OIDC / MFA / browser-recorded login flows&lt;/li&gt;
&lt;li&gt;❌ &lt;strong&gt;Non-REST protocols&lt;/strong&gt; — GraphQL, SOAP, gRPC&lt;/li&gt;
&lt;li&gt;❌ &lt;strong&gt;Shadow API discovery&lt;/strong&gt; — No production traffic analysis&lt;/li&gt;
&lt;li&gt;❌ &lt;strong&gt;Platform features&lt;/strong&gt; — No governance / compliance / RBAC control plane&lt;/li&gt;
&lt;li&gt;❌ &lt;strong&gt;Commercial support&lt;/strong&gt; — Community-only, no SLA&lt;/li&gt;
&lt;li&gt;❌ &lt;strong&gt;Alpha stage&lt;/strong&gt; — Proven on 7 targets, but still early&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;ZeroDAST is purpose-built for a specific niche: &lt;strong&gt;CI-first DAST on documented REST APIs with token-bootstrap-friendly auth.&lt;/strong&gt; Within that niche, it delivers enterprise-grade results at zero cost. Outside that niche, use something else.&lt;/p&gt;




&lt;h2&gt;
  
  
  Key Numbers
&lt;/h2&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;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;PR scan time&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~3 min&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nightly scan time&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~5 min&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Total cost&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$0&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;External targets proven&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;7&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Language stacks&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;3&lt;/strong&gt; (Java, Python, Node.js)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Auth styles proven&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;4&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;More findings vs vanilla ZAP&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;52%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;API URIs vs vanilla&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;48 vs 0&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Combined stars (proven targets)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;100k+&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vendor lock-in&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Every claim is backed by a green CI run:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/AlphaSudo/nocodb/tree/zerodast-install" rel="noopener noreferrer"&gt;NocoDB proof&lt;/a&gt; • &lt;a href="https://github.com/AlphaSudo/strapi/tree/zerodast-install" rel="noopener noreferrer"&gt;Strapi proof&lt;/a&gt; • &lt;a href="https://github.com/AlphaSudo/directus/tree/zerodast-install" rel="noopener noreferrer"&gt;Directus proof&lt;/a&gt; • &lt;a href="https://github.com/AlphaSudo/medusa/tree/zerodast-install" rel="noopener noreferrer"&gt;Medusa proof&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/AlphaSudo/zerodast/actions/workflows/petclinic-t4-scan.yml" rel="noopener noreferrer"&gt;Petclinic T4 scan&lt;/a&gt; • &lt;a href="https://github.com/AlphaSudo/zerodast/actions/workflows/fullstack-fastapi-t4-scan.yml" rel="noopener noreferrer"&gt;FastAPI T4 scan&lt;/a&gt; • &lt;a href="https://github.com/AlphaSudo/zerodast/actions/workflows/dast-nightly.yml" rel="noopener noreferrer"&gt;Nightly&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Try It Now
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/AlphaSudo/zerodast.git
&lt;span class="nb"&gt;cd &lt;/span&gt;zerodast
&lt;span class="nb"&gt;cd &lt;/span&gt;demo-app &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd&lt;/span&gt; ..
&lt;span class="nb"&gt;chmod&lt;/span&gt; +x scripts/run-dast-local.sh
./scripts/run-dast-local.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;⭐ &lt;strong&gt;&lt;a href="https://github.com/AlphaSudo/zerodast" rel="noopener noreferrer"&gt;Star ZeroDAST on GitHub&lt;/a&gt;&lt;/strong&gt; — it helps other developers find it.&lt;/p&gt;

&lt;p&gt;🐛 &lt;strong&gt;Try it on your repo&lt;/strong&gt; — if your API uses token-based auth and has an OpenAPI spec, ZeroDAST can scan it. &lt;strong&gt;&lt;a href="https://github.com/AlphaSudo/zerodast/blob/main/docs/MODEL1_INSTALL_GUIDE.md" rel="noopener noreferrer"&gt;Install guide&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;📊 &lt;strong&gt;&lt;a href="https://github.com/AlphaSudo/zerodast/blob/main/docs/NEAR_LOSSLESS_COMPARISON.md" rel="noopener noreferrer"&gt;Read the full benchmark data&lt;/a&gt;&lt;/strong&gt; — 800+ lines of structured evidence.&lt;/p&gt;

&lt;p&gt;💬 &lt;strong&gt;&lt;a href="https://github.com/AlphaSudo/zerodast/issues" rel="noopener noreferrer"&gt;Open an issue&lt;/a&gt;&lt;/strong&gt; — questions, bugs, and targets that break the adapter model are all welcome.&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;SEO note for self-publishing:&lt;/strong&gt; Publish this article on your own blog/GitHub Pages &lt;strong&gt;first&lt;/strong&gt;. Wait 1-3 days for Google to index it. Then repost to Dev.to using the &lt;strong&gt;canonical URL&lt;/strong&gt; feature (set it in the front matter above). This way your site gets the SEO credit, not Dev.to.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;&lt;em&gt;All benchmark data measured on GitHub-hosted Ubuntu runners with ZAP 2.17.0. Vanilla baselines are executable at &lt;a href="https://github.com/AlphaSudo/zerodast/tree/main/benchmarks/vanilla-baseline" rel="noopener noreferrer"&gt;&lt;code&gt;benchmarks/vanilla-baseline/&lt;/code&gt;&lt;/a&gt;. Checkmarx data sourced from &lt;a href="https://docs.checkmarx.com/en/34965-433898-checkmarx-dast.html" rel="noopener noreferrer"&gt;official public documentation&lt;/a&gt; and &lt;a href="https://checkmarx.com/pricing" rel="noopener noreferrer"&gt;public pricing&lt;/a&gt;. ZeroDAST is Apache 2.0 licensed.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Built with discipline. Proven with evidence. Licensed for freedom.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>devops</category>
      <category>github</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Your AI Agent Will Make CI Supply-Chain Defense Either Too Weak or Too Expensive</title>
      <dc:creator>Ahmed Yasser</dc:creator>
      <pubDate>Fri, 03 Apr 2026 14:36:05 +0000</pubDate>
      <link>https://dev.to/ahmed_yasser_mmo/your-ai-agent-will-make-ci-supply-chain-defense-either-too-weak-or-too-expensive-4b68</link>
      <guid>https://dev.to/ahmed_yasser_mmo/your-ai-agent-will-make-ci-supply-chain-defense-either-too-weak-or-too-expensive-4b68</guid>
      <description>&lt;p&gt;&lt;em&gt;SHA pinning wasn't enough. Enterprise hardening was too much. So I built a benchmark.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;When &lt;code&gt;tj-actions/changed-files&lt;/code&gt; was compromised, the advice spread fast: pin your SHAs, reduce token permissions, review your workflows.&lt;/p&gt;

&lt;p&gt;That advice was not wrong. It was just incomplete.&lt;/p&gt;

&lt;p&gt;What kept bothering me was this: pinning only helps if the thing you pinned is not already malicious. If the pinned code is the attack, or the compromised version is exactly what your workflow is using, then SHA pinning gives you determinism, not safety.&lt;/p&gt;

&lt;p&gt;That turned into a bigger frustration I kept seeing in practice, especially when AI coding agents started generating CI security fixes.&lt;/p&gt;

&lt;p&gt;You build a normal project. You are not trying to become a deep CI security specialist. Then the model suddenly proposes an enterprise-grade answer: hardened runners, attestation, egress controls, multiple extra jobs, maybe cloud federation, maybe more. Some people accept it, but the overhead is high. Some reject it immediately because the performance hit and operational burden feel too big. Then the "smart compromise" is to tell the AI to make the minimal secure fix, and it usually lands on the same answer: SHA pinning plus basic permission scoping.&lt;/p&gt;

&lt;p&gt;That is better than nothing. It is also not enough.&lt;/p&gt;

&lt;p&gt;So I wanted to find the missing middle: a solution stronger than the typical AI-generated "just pin the SHA" workflow, but much lighter than a full enterprise security stack. I wanted something that meaningfully changed the blast radius without adding significant cost.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Question
&lt;/h2&gt;

&lt;p&gt;What CI architecture actually contains a compromised action?&lt;/p&gt;

&lt;p&gt;I did not want an opinion post. I wanted an experiment.&lt;/p&gt;

&lt;p&gt;I built a reproducible benchmark that keeps the repository, source file, malicious action, and GitHub Actions platform constant. The only changing variable is the workflow architecture.&lt;/p&gt;

&lt;p&gt;If the same malicious action runs against four different CI designs, which design actually stops the bad outcomes?&lt;/p&gt;

&lt;h2&gt;
  
  
  The Setup
&lt;/h2&gt;

&lt;p&gt;The repo contains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a tiny deterministic app&lt;/li&gt;
&lt;li&gt;a fake deploy script&lt;/li&gt;
&lt;li&gt;a local composite action that simulates a compromised third-party action&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The app is intentionally trivial because I wanted the benchmark to measure CI trust boundaries, not application complexity.&lt;/p&gt;

&lt;p&gt;The malicious action performs six behaviors:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Environment variable dumping&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GITHUB_TOKEN&lt;/code&gt; permission probing&lt;/li&gt;
&lt;li&gt;Process memory access checks&lt;/li&gt;
&lt;li&gt;Outbound exfiltration attempts&lt;/li&gt;
&lt;li&gt;Artifact poisoning&lt;/li&gt;
&lt;li&gt;Source enumeration&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I ran that same action against four workflow tiers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tier 1: No Security
&lt;/h3&gt;

&lt;p&gt;This is the insecure baseline.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;third-party code runs in the same job as deployment secrets&lt;/li&gt;
&lt;li&gt;release happens from the same compromised workspace&lt;/li&gt;
&lt;li&gt;the artifact upload happens after the malicious action can already modify it&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Tier 2: SHA-Pinned
&lt;/h3&gt;

&lt;p&gt;This is the typical "minimal security fix" AI agents often suggest.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;pin actions by SHA&lt;/li&gt;
&lt;li&gt;reduce workflow permissions&lt;/li&gt;
&lt;li&gt;keep the same workflow shape&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This tier isolates what pinning and token scoping actually buy you when compromised code still executes in the trusted lane.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tier 3: Trusted Release Boundary
&lt;/h3&gt;

&lt;p&gt;This is the candidate solution.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the untrusted lane gets no deployment secrets&lt;/li&gt;
&lt;li&gt;the trusted release lane is separate&lt;/li&gt;
&lt;li&gt;outputs crossing the boundary are validated&lt;/li&gt;
&lt;li&gt;the release lane runs on a fresh runner&lt;/li&gt;
&lt;li&gt;the release lane rebuilds from source instead of consuming artifacts from the untrusted lane&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the core idea I ended up calling the &lt;strong&gt;Trusted Release Boundary&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tier 4: Enterprise
&lt;/h3&gt;

&lt;p&gt;This keeps the Tier 3 boundary model and adds stronger controls:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;hardened runner with egress blocking&lt;/li&gt;
&lt;li&gt;artifact attestation&lt;/li&gt;
&lt;li&gt;more operational complexity&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the upper-bound comparison, not the target solution for most teams.&lt;/p&gt;

&lt;p&gt;Full repo:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/AlphaSudo/sbtr-benchmark" rel="noopener noreferrer"&gt;https://github.com/AlphaSudo/sbtr-benchmark&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Results
&lt;/h2&gt;

&lt;p&gt;Here is the final score table:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tier&lt;/th&gt;
&lt;th&gt;Architecture&lt;/th&gt;
&lt;th&gt;Score&lt;/th&gt;
&lt;th&gt;Annual Cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;No security&lt;/td&gt;
&lt;td&gt;10/100&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;SHA-pinned&lt;/td&gt;
&lt;td&gt;20/100&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Trusted Release Boundary&lt;/td&gt;
&lt;td&gt;75/100&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Enterprise (egress + attestation)&lt;/td&gt;
&lt;td&gt;83/100&lt;/td&gt;
&lt;td&gt;enterprise-style overhead&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The exact score matters less than the shape of the jump.&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%2Fh3p8q5a2puuiufssz05u.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%2Fh3p8q5a2puuiufssz05u.png" alt="Benchmark Comparison Table" width="800" height="517"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Tier 1
&lt;/h3&gt;

&lt;p&gt;Tier 1 got owned exactly the way you would expect:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;secrets were accessible in the same job&lt;/li&gt;
&lt;li&gt;the token could create releases&lt;/li&gt;
&lt;li&gt;outbound exfiltration worked&lt;/li&gt;
&lt;li&gt;the artifact was poisoned and shipped&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is full CI trust collapse.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tier 2
&lt;/h3&gt;

&lt;p&gt;Tier 2 improved one thing: token abuse. The workflow token could no longer create releases.&lt;/p&gt;

&lt;p&gt;But almost everything else stayed bad:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the malicious action still saw secrets&lt;/li&gt;
&lt;li&gt;outbound exfiltration still worked&lt;/li&gt;
&lt;li&gt;the artifact was still poisoned and shipped&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the point of the benchmark. SHA pinning did not fail because pinning is useless. It failed because pinning is not a containment strategy. It protects against mutable references and update drift. It does not protect you when the pinned version is already compromised.&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%2Fm49j7bsc59363rrm0d5o.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%2Fm49j7bsc59363rrm0d5o.png" alt="Tier 2 Security Metrics" width="800" height="387"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Tier 3
&lt;/h3&gt;

&lt;p&gt;Tier 3 is where the architecture changes the outcome.&lt;/p&gt;

&lt;p&gt;The malicious action still runs, but:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the untrusted lane has no deployment secrets&lt;/li&gt;
&lt;li&gt;the token is not write-capable in any meaningful release sense&lt;/li&gt;
&lt;li&gt;outputs cross a validation gate&lt;/li&gt;
&lt;li&gt;the trusted lane rebuilds from source on a fresh runner&lt;/li&gt;
&lt;li&gt;the shipped artifact is clean&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the key result. The malicious action still executes, but it cannot meaningfully corrupt the release path because it never enters the trusted release domain.&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%2Fi437kshxyt82w4by8i5z.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%2Fi437kshxyt82w4by8i5z.png" alt="Tier 3 Security Metrics" width="800" height="419"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Tier 4
&lt;/h3&gt;

&lt;p&gt;Tier 4 was strongest overall:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;no secrets in the untrusted lane&lt;/li&gt;
&lt;li&gt;outbound exfiltration blocked&lt;/li&gt;
&lt;li&gt;validation gate present&lt;/li&gt;
&lt;li&gt;clean artifact rebuilt from source&lt;/li&gt;
&lt;li&gt;successful provenance attestation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This tier also taught a useful lesson. The first attestation attempt failed because the hardened egress policy was too strict and blocked Sigstore endpoints. After allowing &lt;code&gt;fulcio.sigstore.dev&lt;/code&gt; and &lt;code&gt;rekor.sigstore.dev&lt;/code&gt;, the release succeeded.&lt;/p&gt;

&lt;p&gt;That is the reality of enterprise controls: they can be very strong, but they also increase the operational tuning burden.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Climax: Artifact Integrity
&lt;/h3&gt;

&lt;p&gt;The most convincing proof in the benchmark was not the secret count. It was the artifact result.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tier 1 shipped a poisoned artifact&lt;/li&gt;
&lt;li&gt;Tier 2 shipped a poisoned artifact&lt;/li&gt;
&lt;li&gt;Tier 3 shipped a clean artifact&lt;/li&gt;
&lt;li&gt;Tier 4 shipped a clean artifact&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Same repository. Same source file. Same malicious action. Same platform.&lt;/p&gt;

&lt;p&gt;The variable was architecture.&lt;/p&gt;

&lt;p&gt;That is the point where this stops being theoretical. If an attacker can modify the artifact you ship, then the debate about whether a workflow "looked secure" is over.&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%2F4f79ncqwihqv0gxw2h45.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%2F4f79ncqwihqv0gxw2h45.png" alt="Artifact Proof" width="800" height="350"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Framework
&lt;/h2&gt;

&lt;p&gt;The benchmark converged on a six-rule model:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Rule&lt;/th&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;PIN&lt;/td&gt;
&lt;td&gt;Immutable SHA references for all external actions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;QUARANTINE&lt;/td&gt;
&lt;td&gt;Untrusted lane gets no secrets and no write authority&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;ISOLATE&lt;/td&gt;
&lt;td&gt;Trusted lane is separate and first-party only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;REBUILD&lt;/td&gt;
&lt;td&gt;Trusted lane rebuilds from source on a fresh runner&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;ARTIFACT QUARANTINE&lt;/td&gt;
&lt;td&gt;Only metadata crosses the boundary, never untrusted binaries&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;VALIDATE&lt;/td&gt;
&lt;td&gt;Outputs crossing the boundary are explicitly sanitized&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Why this matters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;PIN&lt;/code&gt; is still necessary, but it is not the whole answer&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;QUARANTINE&lt;/code&gt; and &lt;code&gt;ISOLATE&lt;/code&gt; stop same-job trust collapse&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;REBUILD&lt;/code&gt; and &lt;code&gt;ARTIFACT QUARANTINE&lt;/code&gt; stop poisoned binaries from riding the release path&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;VALIDATE&lt;/code&gt; closes the quieter gap where untrusted outputs become trusted control inputs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That combination is what makes Tier 3 work.&lt;/p&gt;

&lt;p&gt;And that is why I think this is a useful practical framework: it gives teams a real middle path:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;stronger than "just pin the SHA"&lt;/li&gt;
&lt;li&gt;far lighter than "full enterprise security stack"&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Honest Limitations
&lt;/h2&gt;

&lt;p&gt;A few caveats matter.&lt;/p&gt;

&lt;p&gt;First, this benchmark repo is public. That means source exposure is less severe than it would be in a private repository.&lt;/p&gt;

&lt;p&gt;Second, the malicious action is simulated. It is designed to be reproducible, not stealthy. A real attacker would likely be quieter.&lt;/p&gt;

&lt;p&gt;Third, there was a line-ending caveat. Local Windows hashing used &lt;code&gt;CRLF&lt;/code&gt;, while GitHub-hosted Linux runners built artifacts from &lt;code&gt;LF&lt;/code&gt; checkouts. So clean artifact comparisons had to be anchored to the normalized Linux source hash.&lt;/p&gt;

&lt;p&gt;Fourth, Tier 3 still leaves outbound network access open. That is one reason Tier 4 scores higher. TRB is the biggest low-cost jump, not the final possible hardening state.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I Think This Matters
&lt;/h2&gt;

&lt;p&gt;What I wanted out of this project was not a perfect security architecture for every team. I wanted a better answer to a very common failure mode:&lt;/p&gt;

&lt;p&gt;someone wants more than SHA pinning, but does not want enterprise-grade overhead.&lt;/p&gt;

&lt;p&gt;That is exactly where a lot of real teams live.&lt;/p&gt;

&lt;p&gt;They are not ignoring security. They just cannot justify a dramatic performance hit, a large operations burden, or a full platform-engineering project because an AI assistant suggested the maximum possible hardening pattern.&lt;/p&gt;

&lt;p&gt;The benchmark result suggests there is a much better default answer:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;build a trusted release boundary.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That is the smallest change in this experiment that materially changed the attack outcome.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try To Break It
&lt;/h2&gt;

&lt;p&gt;If you want to reproduce the benchmark, the repository includes the workflows, malicious action, evidence, and reproduction guide.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Repo: &lt;a href="https://github.com/AlphaSudo/sbtr-benchmark" rel="noopener noreferrer"&gt;https://github.com/AlphaSudo/sbtr-benchmark&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Reproduction guide: &lt;a href="//REPRODUCE.md"&gt;REPRODUCE.md&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Results: &lt;a href="//RESULTS.md"&gt;RESULTS.md&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I genuinely want feedback on where this model breaks, where the validation boundary is too weak, or where the assumptions stop holding under more realistic CI environments.&lt;/p&gt;

&lt;p&gt;Because the goal here is not to claim "problem solved."&lt;/p&gt;

&lt;p&gt;The goal is to offer a better alternative than:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;overkill enterprise security that many teams will reject&lt;/li&gt;
&lt;li&gt;minimal SHA-only hardening that still leaves the release path too exposed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If that middle path is useful, then this benchmark did its job.&lt;/p&gt;

&lt;p&gt;If you've seen CI patterns that break this model, leave them in the comments. I'm especially interested in where Tier 3 fails in real-world workflows.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>security</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
