<?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: prateeks007</title>
    <description>The latest articles on DEV Community by prateeks007 (@prateeks007).</description>
    <link>https://dev.to/prateeks007</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%2F526877%2F1c570c4b-3d6d-4b2e-853f-e9d717786f68.png</url>
      <title>DEV Community: prateeks007</title>
      <link>https://dev.to/prateeks007</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/prateeks007"/>
    <language>en</language>
    <item>
      <title>The Case of the 40-Second Logins: Debugging an ALB Gone Wrong</title>
      <dc:creator>prateeks007</dc:creator>
      <pubDate>Sun, 30 Nov 2025 19:47:45 +0000</pubDate>
      <link>https://dev.to/prateeks007/the-case-of-the-40-second-logins-debugging-an-alb-gone-wrong-2m4o</link>
      <guid>https://dev.to/prateeks007/the-case-of-the-40-second-logins-debugging-an-alb-gone-wrong-2m4o</guid>
      <description>&lt;p&gt;It was supposed to be a smooth EKS migration. Instead, a handful of users started complaining about painfully slow logins — 20 to 40 seconds long. Oddly, others saw no issue at all. What followed was a three-hour debugging marathon that took us through Cloudflare cache rabbit holes, pod benchmarking, and finally, an AWS ALB subnet misconfiguration.&lt;/p&gt;

&lt;p&gt;What makes this story worth telling is not just the root cause (a bad ALB IP), but the &lt;strong&gt;full forensic process&lt;/strong&gt; — every command, every false lead, and the eventual breakthrough. This is an engineering playbook, shared so you don’t waste the same hours the next time you face an intermittent latency mystery.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Summary&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
After an EKS + ALB migration we saw intermittent 20–40s delays for users hitting SSO and API XHR endpoints. Some users were unaffected. Host- and pod-level checks looked healthy. The root cause turned out to be &lt;em&gt;one ALB node (an Availability Zone / subnet) that was misbehaving&lt;/em&gt; — traffic routed to that node would hang. DNS round-robin / Cloudflare was returning the bad ALB IP to a subset of users, causing the intermittent slow behavior. Removing the faulty subnet from the ALB fixed the problem.&lt;/p&gt;

&lt;p&gt;This post walks the entire forensic process: the commands we ran (copy/paste-ready, with safe placeholders), the exact timings and outputs we observed (sensitive values redacted), the conclusions at each step, the mistakes and detours, and the final mitigation + runbook you can reuse.&lt;/p&gt;


&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Service migrated to a new EKS cluster fronted by &lt;strong&gt;ALB + Cloudflare&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Immediately after migration: some users saw &lt;strong&gt;smooth logins&lt;/strong&gt;, others saw &lt;strong&gt;20–50s hangs&lt;/strong&gt; during login/API fetch.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;HAR logs from affected users: static JS/CSS fast, &lt;strong&gt;XHR requests (API)&lt;/strong&gt; stuck.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Classic symptom of &lt;strong&gt;multi-node load balancer inconsistency&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  Step 1: First suspicion — Cloudflare caching (our biggest detour)
&lt;/h2&gt;

&lt;p&gt;Because HAR logs from affected users showed &lt;strong&gt;long waits&lt;/strong&gt; on API/XHR calls (while static JS/CSS looked fine), our first instinct was: &lt;em&gt;maybe Cloudflare is the culprit&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Specifically, we thought Cloudflare might be mishandling cache rules or proxying traffic inconsistently.&lt;/p&gt;
&lt;h3&gt;
  
  
  What we tried
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Checked response headers for static assets&lt;/strong&gt; to confirm whether caching was working as expected:&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-I&lt;/span&gt; &lt;span class="s2"&gt;"https://&amp;lt;MY_DOMAIN&amp;gt;/main.css"&lt;/span&gt; | egrep &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"CF-Cache-Status|Age|Cache-Control|Content-Encoding"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;Example output:&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CF-Cache-Status: HIT
Cache-Control: max-age=31536000
Age: 1284
&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;This showed caching was behaving normally — assets were cached and HITs increased over time.&lt;/p&gt;


&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;strong&gt;Toggled DNS/Proxy settings in Cloudflare Dashboard&lt;/strong&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;
We experimented with switching the API and frontend DNS records between:&lt;/p&gt;&lt;/li&gt;

&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;* **Proxied (orange cloud)** → traffic routed through Cloudflare’s edge.

* **DNS-only (grey cloud)** → traffic went directly to the ALB.


Observation: regardless of proxy setting, users still hit intermittent 20–40s delays. This suggested Cloudflare wasn’t the true bottleneck.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Played with Page Rules &amp;amp; Cache Rules&lt;/strong&gt;
We tried temporarily bypassing cache for &lt;code&gt;/api/*&lt;/code&gt; paths (setting “Cache Level: Bypass” in rules).
API requests still hung for some clients — proving cache wasn’t the issue.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Why we wasted time here
&lt;/h3&gt;

&lt;p&gt;HAR logs are notorious for being misleading when “Waiting (TTFB)” dominates. At first glance it looked like a cache miss problem (especially since static assets behaved differently from API calls).&lt;/p&gt;

&lt;p&gt;We spent valuable time tweaking Cloudflare rules, proxy toggles, and headers — only to realize later that the problem lay &lt;strong&gt;downstream in the ALB node itself&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson:&lt;/strong&gt; when HAR shows long &lt;em&gt;wait&lt;/em&gt; (StartTransfer), always double-check server-side timings (pods, ingress) before diving too deep into CDN/cache layers.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2: Pod-level health checks
&lt;/h2&gt;

&lt;p&gt;We needed to confirm whether the &lt;strong&gt;pods themselves&lt;/strong&gt; were slow.&lt;/p&gt;

&lt;h3&gt;
  
  
  ApacheBench inside API pods
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What is ApacheBench (&lt;/strong&gt;&lt;code&gt;ab&lt;/code&gt;)?&lt;br&gt;&lt;br&gt;
&lt;code&gt;ab&lt;/code&gt; is a lightweight command-line HTTP benchmarking tool that fires a specified number of HTTP requests at a server with a specified concurrency. It measures requests per second, average latency, and distribution of response times. We use it for quick sanity-checks of basic HTTP responsiveness from inside the pod.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Commands we ran:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &amp;lt;NAMESPACE&amp;gt; &amp;lt;API_POD_1&amp;gt; &lt;span class="nt"&gt;--&lt;/span&gt; ab &lt;span class="nt"&gt;-n&lt;/span&gt; 10 &lt;span class="nt"&gt;-c&lt;/span&gt; 2 http://localhost/validatetoken
kubectl &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &amp;lt;NAMESPACE&amp;gt; &amp;lt;API_POD_2&amp;gt; &lt;span class="nt"&gt;--&lt;/span&gt; ab &lt;span class="nt"&gt;-n&lt;/span&gt; 10 &lt;span class="nt"&gt;-c&lt;/span&gt; 2 http://localhost/validatetoken
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Representative output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;Requests per second:    ~9 [#/sec] (mean)
Time per request:       ~220 ms (mean)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both pods responded in &lt;strong&gt;&amp;lt;250ms&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Test from ingress
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; ingress-nginx &amp;lt;INGRESS_POD&amp;gt; &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  curl &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;Connect:%{time_connect} StartTransfer:%{time_starttransfer} Total:%{time_total}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-o&lt;/span&gt; /dev/null &lt;span class="nt"&gt;-s&lt;/span&gt; http://api-service.&amp;lt;NAMESPACE&amp;gt;.svc.cluster.local/validatetoken
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;Connect:0.005s StartTransfer:0.136s Total:0.136s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Conclusion:&lt;/strong&gt; pods and ingress path were fine.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3: End-to-end curl timings from different clients
&lt;/h2&gt;

&lt;p&gt;We asked three clients: &lt;strong&gt;Investigator&lt;/strong&gt;, &lt;strong&gt;User A&lt;/strong&gt; (slow), and &lt;strong&gt;User B&lt;/strong&gt; (slow) to run this command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s2"&gt;"DNS:%{time_namelookup} Connect:%{time_connect} SSL:%{time_appconnect} StartTransfer:%{time_starttransfer} Total:%{time_total}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-o&lt;/span&gt; /dev/null &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"https://&amp;lt;API_DOMAIN&amp;gt;/validatetoken"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Outputs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Investigator (fast):&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;DNS:0.013 Connect:0.022 SSL:0.152 StartTransfer:0.629 Total:0.629
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;User A (slow):&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;DNS:0.103 Connect:0.206 SSL:0.523 StartTransfer:20.995 Total:20.996
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;User B (slow): similar ~20s waits.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Observation:&lt;/strong&gt; DNS and SSL were fine. The &lt;strong&gt;delay was in StartTransfer&lt;/strong&gt; (server response).&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 4: Investigating ALB IPs
&lt;/h2&gt;

&lt;p&gt;The API domain resolved to multiple ALB IPs. We checked with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dig +short &amp;lt;API_DOMAIN&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;ALB_IP_1&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;ALB_IP_2&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 5: Per-IP forced testing
&lt;/h2&gt;

&lt;p&gt;We forced curl to each IP with &lt;code&gt;--resolve&lt;/code&gt;:&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;# Test ALB IP 1&lt;/span&gt;
curl &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s2"&gt;"Connect:%{time_connect} SSL:%{time_appconnect} StartTransfer:%{time_starttransfer} Total:%{time_total}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-o&lt;/span&gt; /dev/null &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;--resolve&lt;/span&gt; &amp;lt;API_DOMAIN&amp;gt;:443:&amp;lt;ALB_IP_1&amp;gt; https://&amp;lt;API_DOMAIN&amp;gt;/validatetoken

&lt;span class="c"&gt;# Test ALB IP 2&lt;/span&gt;
curl &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s2"&gt;"Connect:%{time_connect} SSL:%{time_appconnect} StartTransfer:%{time_starttransfer} Total:%{time_total}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-o&lt;/span&gt; /dev/null &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;--resolve&lt;/span&gt; &amp;lt;API_DOMAIN&amp;gt;:443:&amp;lt;ALB_IP_2&amp;gt; https://&amp;lt;API_DOMAIN&amp;gt;/validatetoken
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Results:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;&amp;lt;ALB_IP_1&amp;gt;&lt;/code&gt; → fast (1s total)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;&amp;lt;ALB_IP_2&amp;gt;&lt;/code&gt; → hung (20–120s total, sometimes timeout)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We now had a smoking gun: &lt;strong&gt;one ALB IP (node/AZ) was bad&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 6: Automating per-IP checks (&lt;a href="http://conncheck.sh" rel="noopener noreferrer"&gt;&lt;code&gt;conncheck.sh&lt;/code&gt;&lt;/a&gt;)
&lt;/h2&gt;

&lt;p&gt;To be sure, we scripted it:&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;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="c"&gt;# conncheck.sh&lt;/span&gt;
&lt;span class="nv"&gt;DOMAIN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;API_DOMAIN&amp;gt;"&lt;/span&gt;
&lt;span class="nv"&gt;IPS&lt;/span&gt;&lt;span class="o"&gt;=(&lt;/span&gt;&amp;lt;ALB_IP_1&amp;gt; &amp;lt;ALB_IP_2&amp;gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;ip &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;IPS&lt;/span&gt;&lt;span class="p"&gt;[@]&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"--- Testing &lt;/span&gt;&lt;span class="nv"&gt;$ip&lt;/span&gt;&lt;span class="s2"&gt; ---"&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;i &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;1..10&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
    &lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /dev/null &lt;span class="nt"&gt;--resolve&lt;/span&gt; &lt;span class="nv"&gt;$DOMAIN&lt;/span&gt;:443:&lt;span class="nv"&gt;$ip&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
      &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s2"&gt;"Run:&lt;/span&gt;&lt;span class="nv"&gt;$i&lt;/span&gt;&lt;span class="s2"&gt; Connect:%{time_connect} SSL:%{time_appconnect} StartTransfer:%{time_starttransfer} Total:%{time_total}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
      https://&lt;span class="nv"&gt;$DOMAIN&lt;/span&gt;/validatetoken
  &lt;span class="k"&gt;done
done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output (redacted):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;--- Testing &lt;span class="nt"&gt;&amp;lt;ALB_IP_1&amp;gt;&lt;/span&gt; ---
Run:1 Connect:0.305 SSL:0.799 StartTransfer:1.187 Total:1.187
...

--- Testing &lt;span class="nt"&gt;&amp;lt;ALB_IP_2&amp;gt;&lt;/span&gt; ---
Run:1 Connect:0.000 SSL:0.000 StartTransfer:0.000 Total:132.969
Run:2 Connect:0.000 SSL:0.000 StartTransfer:0.000 Total:133.002
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every run confirmed: &lt;code&gt;&amp;lt;ALB_IP_2&amp;gt;&lt;/code&gt; was consistently bad.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 7: Mapping the bad IP in AWS — how we actually did it
&lt;/h2&gt;

&lt;p&gt;We needed to find which AZ/subnet the bad IP belonged to.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1) Tried the CLI mapping (it failed to return info for us):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;aws ec2 describe-network-interfaces &lt;span class="err"&gt;\&lt;/span&gt;
  --region &lt;span class="nt"&gt;&amp;lt;REGION&amp;gt;&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;
  --filters "Name=association.public-ip,Values=&lt;span class="nt"&gt;&amp;lt;ALB_IP_BAD&amp;gt;&lt;/span&gt;" &lt;span class="err"&gt;\&lt;/span&gt;
  --query "NetworkInterfaces[&lt;span class="err"&gt;*&lt;/span&gt;].{ENI:NetworkInterfaceId,Subnet:SubnetId,AZ:AvailabilityZone}" &lt;span class="err"&gt;\&lt;/span&gt;
  --output table
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;In our case this returned no rows. This can happen depending on how front-end ALB IPs are represented in the account or due to IAM/console differences or environment configurations.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;2) Used the AWS Console to find the ENI / subnet / AZ (this is what actually worked for us):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Open &lt;strong&gt;EC2&lt;/strong&gt; in the AWS Console.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Go to &lt;strong&gt;Network Interfaces&lt;/strong&gt; (EC2 → Network Interfaces).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Search for the bad public IP &lt;code&gt;&amp;lt;ALB_IP_BAD&amp;gt;&lt;/code&gt; in the Network Interfaces list (or inspect the ALB → Description → Subnets to see which subnets are attached).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;When you find the ENI that has that Public IP, open it — the console shows &lt;code&gt;Subnet ID&lt;/code&gt; and &lt;code&gt;Availability Zone&lt;/code&gt; (e.g., &lt;code&gt;us-east-2c&lt;/code&gt;).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Cross-check by looking at &lt;strong&gt;ALB → Description → Subnets&lt;/strong&gt; and the public IPs that the ALB resolved to (from &lt;code&gt;dig&lt;/code&gt;) to be confident which subnet/ENI mapped to the bad IP.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What we saw in the Console:&lt;/strong&gt; one subnet looked different (the properties / mapping in the console visually stood out when compared to the other two). That subnet corresponded to &lt;code&gt;&amp;lt;ALB_IP_BAD&amp;gt;&lt;/code&gt; and the ENI attached to it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3) Action:&lt;/strong&gt; remove that subnet from the ALB (ALB → Edit subnets) (or from the target group) and let ALB reprovision across remaining subnets.&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%2Fs98vfcrpvo94wuzp6qmz.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%2Fs98vfcrpvo94wuzp6qmz.png" alt=" " width="800" height="216"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Quick notes&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;What is an ENI?&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
An &lt;strong&gt;ENI (Elastic Network Interface)&lt;/strong&gt; is the AWS virtual network interface attached to resources (ALB nodes, EC2 instances, etc.). An ENI has a Public/Private IP, belongs to a particular subnet, and therefore an AZ. Finding which ENI has a public IP lets you see the subnet and AZ where a problematic node lives.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is an AZ?&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
An &lt;strong&gt;Availability Zone (AZ)&lt;/strong&gt; is a discrete data center within an AWS region (for example &lt;code&gt;us-east-2a&lt;/code&gt;, &lt;code&gt;us-east-2b&lt;/code&gt;, &lt;code&gt;us-east-2c&lt;/code&gt;). An ALB typically spans multiple AZs and exposes one IP per node; if one AZ’s node or subnet is misconfigured, only that node (and clients routed to its IP) will suffer.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 8: Fix
&lt;/h2&gt;

&lt;p&gt;We removed the bad subnet from the ALB (console action).&lt;/p&gt;

&lt;p&gt;After that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;dig +short &amp;lt;API_DOMAIN&amp;gt;&lt;/code&gt; → only returned healthy IPs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Re-ran &lt;a href="http://conncheck.sh" rel="noopener noreferrer"&gt;&lt;code&gt;conncheck.sh&lt;/code&gt;&lt;/a&gt; → all IPs fast (~1s).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;User A and User B confirmed API requests were instant again.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Root cause
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Cause:&lt;/strong&gt; ALB node in one AZ was unhealthy/misconfigured. Any client that got that ALB IP suffered 20–40s hangs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Why only some users?&lt;/strong&gt; DNS round-robin gave different IPs to different clients. If you got the bad IP → you suffered.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Why the subnet mismatch broke the ALB (public vs private)&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;During post-mortem verification we discovered the real infra mismatch: one of the ALB’s node subnets (AZ &lt;code&gt;2c&lt;/code&gt;) was a &lt;strong&gt;private&lt;/strong&gt; subnet that routed outbound via a NAT gateway, while the other two ALB subnets were &lt;strong&gt;public&lt;/strong&gt; and routed directly to the Internet Gateway (IGW).&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%2F4k12dot2edi0xr0cb7ot.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%2F4k12dot2edi0xr0cb7ot.png" alt=" " width="800" height="169"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Key facts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;An &lt;strong&gt;internet-facing ALB&lt;/strong&gt; must place its load-balancer nodes (the ALB front-end ENIs) in &lt;strong&gt;public subnets&lt;/strong&gt; — that is, subnets whose route table has &lt;code&gt;0.0.0.0/0&lt;/code&gt; → an &lt;strong&gt;Internet Gateway (IGW)&lt;/strong&gt;. This gives the ALB node a public path to receive and respond to client traffic.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A &lt;strong&gt;private subnet&lt;/strong&gt; normally uses a NAT gateway for outbound traffic (&lt;code&gt;0.0.0.0/0&lt;/code&gt; → NAT). NAT gateways forward outbound connections from private hosts to the internet, but they do &lt;strong&gt;not&lt;/strong&gt; provide a stable inbound path that an internet-facing ALB needs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In our case the ALB got a node into the &lt;strong&gt;private&lt;/strong&gt; subnet in AZ &lt;code&gt;2c&lt;/code&gt; (the subnet had &lt;code&gt;0.0.0.0/0 → nat-…&lt;/code&gt;). Any client that resolved DNS to that ALB IP hit a node that could not service incoming internet requests properly, producing the 20–40s hangs.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;DNS / Cloudflare / recursive resolvers will hand out different ALB public IPs to different clients. If one of those public IPs corresponds to an ALB node that lives in a private subnet (no IGW), those clients experience timeouts/hangs while other clients are fine — exactly the intermittent, user-specific behavior we saw.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Remediation and best practice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;For internet-facing ALBs, ensure you configure &lt;strong&gt;public subnets (with IGW route)&lt;/strong&gt; in every AZ you attach to the ALB. If you need HA across three AZs, create public subnets in all three AZs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If you require internal-only load balancing (no direct internet access), use an &lt;strong&gt;internal ALB&lt;/strong&gt; (scheme &lt;code&gt;internal&lt;/code&gt;) and place it in private subnets — but then it won’t be reachable from the public internet.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If you accidentally attach a private subnet to an internet ALB, detach it or replace it with a properly routed public subnet. Tag subnets clearly (&lt;code&gt;public-&amp;lt;az&amp;gt;&lt;/code&gt;, &lt;code&gt;private-&amp;lt;az&amp;gt;&lt;/code&gt;) to avoid future mistakes.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the precise mismatch that caused the outage: the ALB node in &lt;code&gt;us-east-2c&lt;/code&gt; was in a private subnet (NAT only), not a public subnet with an IGW, so it could not reliably serve external client requests.&lt;/p&gt;




&lt;h2&gt;
  
  
  Lessons learned
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;False start:&lt;/strong&gt; We wasted time chasing Cloudflare cache headers. Static files were never the problem — API responses were.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Best tool:&lt;/strong&gt; &lt;code&gt;curl --resolve&lt;/code&gt; is the single best way to isolate per-IP ALB issues.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Automation:&lt;/strong&gt; A simple script like &lt;a href="http://conncheck.sh" rel="noopener noreferrer"&gt;&lt;code&gt;conncheck.sh&lt;/code&gt;&lt;/a&gt; can catch bad nodes quickly.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;AWS mapping:&lt;/strong&gt; If CLI doesn’t show subnet info, check the console. Visual inspection caught the misconfigured subnet.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Checklist item:&lt;/strong&gt; Never assume a reused ALB is healthy. Always test each AZ/IP after migration.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Suggested runbook
&lt;/h2&gt;

&lt;p&gt;If you hit intermittent API latency after ALB migration:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Collect HARs — look for long &lt;em&gt;wait&lt;/em&gt; times.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Pod-level check with &lt;code&gt;ab&lt;/code&gt; — confirm pods are fast.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Run &lt;code&gt;curl -w&lt;/code&gt; from multiple clients.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use &lt;code&gt;dig +short&lt;/code&gt; to list ALB IPs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use &lt;code&gt;curl --resolve&lt;/code&gt; or &lt;a href="http://conncheck.sh" rel="noopener noreferrer"&gt;&lt;code&gt;conncheck.sh&lt;/code&gt;&lt;/a&gt; to test each IP.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Identify bad IP → map in AWS console → remove offending subnet/AZ.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Re-test and confirm.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;This was a &lt;strong&gt;wild debugging ride&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;We started with Cloudflare cache rules.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Dug through pod and ingress latency.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Only by comparing outputs from multiple clients did we realize it was &lt;strong&gt;external only&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Finally isolated a single bad ALB IP and removed its subnet.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Three hours later, we had a fix — and a playbook to never fall into the same trap again.&lt;/p&gt;




&lt;h2&gt;
  
  
  Join the Conversation 🚀
&lt;/h2&gt;

&lt;p&gt;I’d love to hear from you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Have you ever chased a bug that turned out to be an &lt;strong&gt;infra misconfiguration&lt;/strong&gt;?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Any &lt;strong&gt;war stories with ALBs, NATs, or subnets&lt;/strong&gt; that ate up hours of your life?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Would you like more deep-dive posts like this (commands, outputs, root cause), or shorter “just the fix” write-ups?&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Drop your thoughts in the comments — I’m excited to learn from this community and share more DevOps/infra stories in the future.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>aws</category>
      <category>kubernetes</category>
      <category>networking</category>
    </item>
    <item>
      <title>CI/CD Security Architecture: End-to-End Guide for SAST, SCA, DAST, and Automated Triage</title>
      <dc:creator>prateeks007</dc:creator>
      <pubDate>Sun, 30 Nov 2025 17:04:33 +0000</pubDate>
      <link>https://dev.to/prateeks007/building-a-security-assessment-architecture-that-actually-works-1bk3</link>
      <guid>https://dev.to/prateeks007/building-a-security-assessment-architecture-that-actually-works-1bk3</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; Scanners that run automatically, findings that aggregate in one place, reports that don't make stakeholders' eyes glaze over. For small-to-medium engineering teams who need real security without hiring a dedicated AppSec team.&lt;/p&gt;

&lt;p&gt;Who is this guide for?&lt;br&gt;
Teams without a dedicated AppSec function, platform engineers, or DevOps teams who want a practical, tool-agnostic blueprint for continuous security in CI/CD.&lt;/p&gt;



&lt;p&gt;Security scanners are cheap. &lt;strong&gt;Security architecture that developers don't hate&lt;/strong&gt; is expensive.&lt;/p&gt;

&lt;p&gt;Most teams end up with a mess: SAST runs somewhere in Jenkins, Snyk emails get ignored, and pentest reports live in Google Drive where findings go to die. Developers don't ignore security findings because they're lazy—they ignore them because findings arrive in 47 different places with zero context.&lt;/p&gt;

&lt;p&gt;This is the architecture I built and actually use. Not a vendor pitch. Not enterprise theater. Just the stack that works when you need security that scales.&lt;/p&gt;
&lt;h2&gt;
  
  
  The End-to-End Flow
&lt;/h2&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%2F2r92vs3ylkug82qtwlzb.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%2F2r92vs3ylkug82qtwlzb.png" alt=" " width="800" height="412"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Everything flows into &lt;strong&gt;Faraday&lt;/strong&gt; for deduplication and triage. Developers see findings in their PRs. Security team manages everything from one dashboard.&lt;/p&gt;


&lt;h2&gt;
  
  
  Design Philosophy
&lt;/h2&gt;

&lt;p&gt;Before throwing tools at the problem, get the fundamentals right:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Shift-left without becoming a bottleneck&lt;/strong&gt; — Security runs in CI/CD, not as a gate that developers circumvent with "temporary" bypasses that become permanent.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Single source of truth&lt;/strong&gt; — One dashboard to rule them all. No more "where did I see that SQL injection again?"&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Humans still matter&lt;/strong&gt; — Automated scanners catch the obvious. Manual testing finds the business logic flaws that actually get exploited.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Actionable by default&lt;/strong&gt; — Every finding needs an owner, a severity that makes sense, and remediation guidance that isn't "fix it."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tool-agnostic&lt;/strong&gt; — Your architecture shouldn't implode when you swap one scanner for another.&lt;/p&gt;


&lt;h2&gt;
  
  
  The Stack (Recommended + Alternatives)
&lt;/h2&gt;
&lt;h3&gt;
  
  
  SAST (Static Analysis)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Recommended: Semgrep&lt;/strong&gt; — Fast, multilingual, doesn't make PRs take 10 minutes. Free tier is generous.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Alternative: SonarQube&lt;/strong&gt; — Consider if you need code quality metrics beyond security or have 50+ microservices needing centralized quality gates.&lt;/p&gt;
&lt;h3&gt;
  
  
  SCA (Dependency Scanning)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Recommended: Snyk&lt;/strong&gt; — Free tier is excellent. GitHub integration is native. Dashboard is actually usable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Alternative: Trivy&lt;/strong&gt; — Great for container/IaC scanning. Better for cost-conscious teams scaling up.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Critical detail:&lt;/strong&gt; For most teams, the Snyk web dashboard is sufficient—just connect your repos in their UI. If you want findings in GitHub Security tab, add the optional SARIF upload workflow (shown below), but it's not required.&lt;/p&gt;
&lt;h3&gt;
  
  
  DAST (Dynamic Analysis)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Recommended: OWASP ZAP&lt;/strong&gt; — Industry standard, great for CI/CD, active community.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Alternative: Nuclei&lt;/strong&gt; — Template-based scanning. Faster for API-focused testing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Manual: Burp Suite Professional&lt;/strong&gt; — Non-negotiable for auth bypasses, IDOR, race conditions, and business logic bugs that scanners miss entirely.&lt;/p&gt;
&lt;h3&gt;
  
  
  Infrastructure Visibility
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Recommended: Nmap&lt;/strong&gt; — Know what ports you've exposed before attackers do.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Alternative: Masscan + Naabu&lt;/strong&gt; — Faster for large IP ranges, but Nmap's service detection is unmatched.&lt;/p&gt;
&lt;h3&gt;
  
  
  Aggregation
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Recommended: Faraday&lt;/strong&gt; — I tried DefectDojo. The UI made me sad. Faraday's API is cleaner and the workflow makes more sense for my team.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Alternative: DefectDojo&lt;/strong&gt; — Broader importer ecosystem. Better if you're integrating 15+ tools.&lt;/p&gt;
&lt;h3&gt;
  
  
  Reporting
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Recommended: SysReptor&lt;/strong&gt; — Write in Markdown, design with HTML/CSS. Most flexible.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Alternative: PwnDoc&lt;/strong&gt; — Simpler templates, less customization. Good for teams who just want "done."&lt;/p&gt;


&lt;h2&gt;
  
  
  Implementation: Week by Week
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Week 1: The Foundation
&lt;/h3&gt;

&lt;p&gt;Start with the highest ROI, lowest friction changes.&lt;/p&gt;
&lt;h4&gt;
  
  
  1. SAST on Every PR
&lt;/h4&gt;

&lt;p&gt;Semgrep catches SQL injection, XSS, and crypto misuse before code merges:&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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Semgrep Security Scan&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;main&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;develop&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;semgrep&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;container&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;returntocorp/semgrep&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&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;Run Semgrep&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;semgrep ci --sarif --output=semgrep.sarif&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;SEMGREP_APP_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.SEMGREP_APP_TOKEN }}&lt;/span&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;Upload to GitHub Security&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github/codeql-action/upload-sarif@v3&lt;/span&gt;
        &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always()&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;sarif_file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;semgrep.sarif&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Findings show up inline in PR reviews. Developers actually see them. That's the whole point.&lt;/p&gt;

&lt;h4&gt;
  
  
  2. Dependency Scanning (The Easy Win)
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Don't overthink this.&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Go to snyk.io&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Sign up with GitHub OAuth&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click "Add Projects"&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Select your repos&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Done&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Snyk will scan on every PR automatically. Their dashboard shows you everything. You don't need a GitHub Action for this unless you want SARIF uploads to GitHub Security tab (which is nice but optional).&lt;/p&gt;

&lt;p&gt;If you DO want the GitHub integration for SARIF uploads (optional):&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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Snyk Security&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;snyk&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;security-events&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;
      &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;read&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&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;Run Snyk&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;snyk/actions/node@master&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;SNYK_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.SNYK_TOKEN }}&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;--sarif-file-output=snyk.sarif&lt;/span&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;Upload SARIF to GitHub Security tab&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github/codeql-action/upload-sarif@v3&lt;/span&gt;
        &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always()&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;sarif_file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;snyk.sarif&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Real talk:&lt;/strong&gt; The Snyk dashboard already shows everything. This workflow just puts findings in GitHub's Security tab too. Nice to have, not required.&lt;/p&gt;

&lt;h4&gt;
  
  
  3. Nightly DAST Baseline
&lt;/h4&gt;

&lt;p&gt;Run passive scans against staging without auth complexity:&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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ZAP Baseline Scan&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;schedule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;cron&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;2&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*'&lt;/span&gt;  &lt;span class="c1"&gt;# 2 AM daily&lt;/span&gt;
  &lt;span class="na"&gt;workflow_dispatch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;zap&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&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;ZAP Baseline Scan&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;zaproxy/action-baseline@v0.12.0&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;https://staging.example.com'&lt;/span&gt;
          &lt;span class="na"&gt;rules_file_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.zap/rules.tsv'&lt;/span&gt;
          &lt;span class="na"&gt;cmd_options&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;-a'&lt;/span&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;Upload Report&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/upload-artifact@v4&lt;/span&gt;
        &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always()&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&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;zap-report&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;report_html.html&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Start with baseline mode.&lt;/strong&gt; It's passive, won't break anything, and you can tune false positives before enabling active attacks.&lt;/p&gt;

&lt;h4&gt;
  
  
  4. Know Your Attack Surface
&lt;/h4&gt;

&lt;p&gt;Weekly infrastructure scans:&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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Nmap Perimeter Scan&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;schedule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;cron&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;4&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;0'&lt;/span&gt;  &lt;span class="c1"&gt;# Sunday 4 AM&lt;/span&gt;
  &lt;span class="na"&gt;workflow_dispatch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;nmap&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&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;Install Nmap&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sudo apt-get update &amp;amp;&amp;amp; sudo apt-get install -y nmap&lt;/span&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;Scan Production&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;nmap -sV --top-ports 1000 \&lt;/span&gt;
               &lt;span class="s"&gt;-oX nmap-scan.xml \&lt;/span&gt;
               &lt;span class="s"&gt;prod.example.com api.example.com&lt;/span&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;Upload Results&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/upload-artifact@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&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;nmap-report&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nmap-scan.xml&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Establish a baseline of expected ports. Alert on changes. New port 3306 exposed? Someone probably didn't mean to do that.&lt;/p&gt;




&lt;h3&gt;
  
  
  Week 2-4: Centralization
&lt;/h3&gt;

&lt;p&gt;You're now collecting findings from 4+ sources. Time to aggregate them before you drown in noise.&lt;/p&gt;

&lt;h4&gt;
  
  
  Setting Up Faraday
&lt;/h4&gt;

&lt;p&gt;Faraday gives you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;API-first design (easy to script imports)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Workspace management per project/client&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Web UI that doesn't make you want to quit security&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Deduplication that actually works&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Docker setup:&lt;/strong&gt;&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;-d&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; faraday &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; 5985:5985 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;PGSQL_HOST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;postgres &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;PGSQL_USER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;faraday &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;PGSQL_PASSWD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;changeme &lt;span class="se"&gt;\&lt;/span&gt;
  faradaysec/faraday
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Access at &lt;code&gt;http://localhost:5985&lt;/code&gt; (default creds: &lt;code&gt;faraday/changeme&lt;/code&gt;—change them immediately).&lt;/p&gt;

&lt;h4&gt;
  
  
  Automated Imports
&lt;/h4&gt;

&lt;p&gt;Push findings from CI into Faraday:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;

&lt;span class="n"&gt;FARADAY_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://faraday.example.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;API_TOKEN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;your-api-token&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;upload_to_faraday&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;workspace&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tool_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;report_file&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Import scan results into Faraday workspace.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Authorization&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Token &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;API_TOKEN&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Content-Type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;application/json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;report_file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;report_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# Faraday auto-detects report format
&lt;/span&gt;    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;FARADAY_URL&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/api/v3/ws/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;workspace&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/upload_report&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;file&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;report_file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;report_data&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;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;✓ Uploaded &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;tool_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; scan successfully&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;✗ Upload failed: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

&lt;span class="c1"&gt;# Usage in CI:
# upload_to_faraday('project-alpha', 'ZAP', 'zap-report.xml')
# upload_to_faraday('project-alpha', 'Semgrep', 'semgrep.sarif')
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;GitHub Action integration:&lt;/strong&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="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;Upload to Faraday&lt;/span&gt;
  &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;FARADAY_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.FARADAY_URL }}&lt;/span&gt;
    &lt;span class="na"&gt;FARADAY_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.FARADAY_TOKEN }}&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;python scripts/faraday_upload.py \&lt;/span&gt;
      &lt;span class="s"&gt;--workspace ${{ github.event.repository.name }} \&lt;/span&gt;
      &lt;span class="s"&gt;--tool ZAP \&lt;/span&gt;
      &lt;span class="s"&gt;--file zap-report.xml&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Handling False Positives
&lt;/h4&gt;

&lt;p&gt;Build a suppression system or you'll go insane:&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="c1"&gt;# .security-exceptions.yml&lt;/span&gt;
&lt;span class="na"&gt;suppressions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;tool&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;semgrep&lt;/span&gt;
    &lt;span class="na"&gt;rule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;javascript.lang.security.audit.xss.template-string"&lt;/span&gt;
    &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;src/__tests__/*"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;src/fixtures/*"&lt;/span&gt;
    &lt;span class="na"&gt;reason&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Test&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;fixtures&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;intentionally&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;use&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;unsafe&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;patterns"&lt;/span&gt;
    &lt;span class="na"&gt;approved_by&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;security-team@example.com"&lt;/span&gt;
    &lt;span class="na"&gt;expires&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2025-12-31"&lt;/span&gt;

  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;tool&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;snyk&lt;/span&gt;
    &lt;span class="na"&gt;cve&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CVE-2023-12345"&lt;/span&gt;
    &lt;span class="na"&gt;reason&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;False&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;positive&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;we&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;don't&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;use&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;vulnerable&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;code&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;path"&lt;/span&gt;
    &lt;span class="na"&gt;ticket&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SEC-456"&lt;/span&gt;
    &lt;span class="na"&gt;approved_by&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;security-team@example.com"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Filter findings in CI before uploading:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# filter_findings.py
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;yaml&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;should_suppress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;finding&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;suppressions&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Check if finding matches suppression rules.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;rule&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;suppressions&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;rule&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;tool&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;finding&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;tool&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;rule&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;rule&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;rule&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;rule&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;finding&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;rule_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;paths&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;rule&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;finding&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;file&lt;/span&gt;&lt;span class="sh"&gt;'&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;path&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;rule&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;paths&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]):&lt;/span&gt;
                    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
            &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;cve&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;rule&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;rule&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;cve&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;finding&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;cve&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;

&lt;span class="c1"&gt;# Use in GitHub Actions:
# python filter_findings.py --input semgrep.json --output filtered.json
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;In your upload script:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.security-exceptions.yml&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;suppressions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;yaml&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;safe_load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;suppressions&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;filtered_findings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;findings&lt;/span&gt; 
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;should_suppress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;suppressions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="nf"&gt;upload_to_faraday&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;workspace&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Semgrep&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;filtered_findings&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This keeps false positives out of Faraday entirely. Much cleaner than triaging the same noise every week.&lt;/p&gt;




&lt;h3&gt;
  
  
  Ongoing: Enhancement
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Authenticated DAST
&lt;/h4&gt;

&lt;p&gt;Baseline scans only cover logged-out pages. Upgrade to scan the 80% of your app behind authentication:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ZAP Authentication Context&lt;/strong&gt; (create &lt;code&gt;.zap/auth-context.conf&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# Form-based authentication example&lt;/span&gt;
env:
  contexts:
&lt;span class="p"&gt;    -&lt;/span&gt; name: "staging-auth"
      urls:
&lt;span class="p"&gt;        -&lt;/span&gt; "https://staging.example.com.&lt;span class="err"&gt;*&lt;/span&gt;"
      authentication:
        method: "formBasedAuthentication"
        parameters:
          loginUrl: "https://staging.example.com/login"
          loginRequestData: "username={%username%}&amp;amp;password={%password%}"
        verification:
          method: "response"
          loggedInRegex: "&lt;span class="se"&gt;\\&lt;/span&gt;Qlogout&lt;span class="se"&gt;\\&lt;/span&gt;E"
          loggedOutRegex: "&lt;span class="se"&gt;\\&lt;/span&gt;Qlogin&lt;span class="se"&gt;\\&lt;/span&gt;E"
      users:
&lt;span class="p"&gt;        -&lt;/span&gt; name: "test-user"
          credentials:
            username: "${TEST_USER}"
            password: "${TEST_PASS}"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;GitHub Action with auth:&lt;/strong&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="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;ZAP Full Scan with Auth&lt;/span&gt;
  &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;zaproxy/action-full-scan@v0.10.0&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;https://staging.example.com'&lt;/span&gt;
    &lt;span class="na"&gt;cmd_options&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;-a&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-j&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-n&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;auth-context.conf'&lt;/span&gt;
  &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;TEST_USER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.TEST_USER }}&lt;/span&gt;
    &lt;span class="na"&gt;TEST_PASS&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.TEST_PASS }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Real example:&lt;/strong&gt; We caught a path traversal vulnerability in our file download API that only manifested for authenticated users. ZAP's authenticated scan found it; baseline mode never would have.&lt;/p&gt;

&lt;h4&gt;
  
  
  Manual Testing (The Important Part)
&lt;/h4&gt;

&lt;p&gt;Quarterly security sprints for high-risk features:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Week 1: Scope&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;New payment integration? Test it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Admin panel rewrite? Test it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Multi-tenant feature? Definitely test it.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Week 2-3: Test with Burp&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;IDOR across tenant boundaries&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Race conditions in payment flows&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Session handling edge cases&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Auth bypasses through state manipulation&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Week 4: Document &amp;amp; Track&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Log findings in Faraday with PoCs&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create Jira tickets with severity labels&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Generate stakeholder report&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No scanner will catch:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;User A accessing User B's data through ID manipulation&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Payment bypass by canceling during webhook processing&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Admin access through malformed OAuth state&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;SSRF chains that require manual exploitation&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Real examples:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Prototype pollution from Semgrep:&lt;/strong&gt; Caught unsafe &lt;code&gt;Object.assign()&lt;/code&gt; usage in our webhook handler that could've let attackers pollute global object properties. Fixed before merge.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Path traversal from ZAP:&lt;/strong&gt; Authenticated scan found a &lt;code&gt;../&lt;/code&gt; injection in our file download API that let users access arbitrary files. Would've missed this without auth context enabled.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;IDOR from manual Burp testing:&lt;/strong&gt; Found that changing &lt;code&gt;user_id&lt;/code&gt; parameter in profile update endpoint let users modify other accounts. Simple, devastating, and scanners never flagged it because the API response looked identical for authorized/unauthorized attempts.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's why you still need humans.&lt;/p&gt;

&lt;h4&gt;
  
  
  Reporting That Doesn't Suck
&lt;/h4&gt;

&lt;p&gt;Export from Faraday and generate proper reports:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option 1: SysReptor&lt;/strong&gt; (my recommendation)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Write findings in Markdown&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Customize templates with HTML/CSS&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Generate PDF with one click&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Self-hosted or cloud&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Option 2: PwnDoc&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Simpler, less customization&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Good default templates&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Vulnerability database built-in&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Export from Faraday&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; GET &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;FARADAY_URL&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/api/v3/ws/project-alpha/vulns"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
     &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Token &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;API_TOKEN&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
     &lt;span class="nt"&gt;-o&lt;/span&gt; findings.json

&lt;span class="c"&gt;# Import to SysReptor or PwnDoc&lt;/span&gt;
&lt;span class="c"&gt;# Generate quarterly report PDF&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The Glue Code That Matters
&lt;/h2&gt;

&lt;p&gt;Success lives in the 5-10 scripts that bridge tools:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. SARIF Normalizer
&lt;/h3&gt;

&lt;p&gt;Different tools output SARIF differently. Normalize it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;normalize_sarif&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tool_name&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Fix SARIF inconsistencies.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;sarif&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&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;run&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;sarif&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;runs&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]):&lt;/span&gt;
        &lt;span class="c1"&gt;# Ensure tool name is set
&lt;/span&gt;        &lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;tool&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;driver&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tool_name&lt;/span&gt;

        &lt;span class="c1"&gt;# Fix relative paths
&lt;/span&gt;        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;results&lt;/span&gt;&lt;span class="sh"&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;for&lt;/span&gt; &lt;span class="n"&gt;loc&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;locations&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]):&lt;/span&gt;
                &lt;span class="n"&gt;uri&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;loc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;physicalLocation&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;artifactLocation&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;uri&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;file://&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;https://&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
                    &lt;span class="n"&gt;loc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;physicalLocation&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;artifactLocation&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;uri&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;file://&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;uri&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;

    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;w&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dump&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sarif&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;indent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Slack Alerts for Critical Findings
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;notify_slack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;finding&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Alert on High/Critical findings.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;finding&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;severity&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Critical&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;High&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;

    &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;🚨 &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;finding&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;severity&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; Security Finding&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;blocks&lt;/span&gt;&lt;span class="sh"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;section&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mrkdwn&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;finding&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;title&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;finding&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;description&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;section&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fields&lt;/span&gt;&lt;span class="sh"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mrkdwn&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*Tool:*&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;finding&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;tool&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
                    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mrkdwn&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*Severity:*&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;finding&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;severity&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
                    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mrkdwn&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*Location:*&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;`&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;finding&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;file&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;finding&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;line&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;`&lt;/span&gt;&lt;span class="sh"&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;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;actions&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;elements&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;button&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;plain_text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;View in Faraday&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;url&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;finding&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;faraday_url&lt;/span&gt;&lt;span class="sh"&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;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SLACK_WEBHOOK_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Metrics That Actually Matter
&lt;/h2&gt;

&lt;p&gt;Track these in Faraday custom fields and Jira labels:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mean Time to Remediate (MTTR):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Critical: &amp;lt; 7 days (Faraday field: &lt;code&gt;remediation_deadline&lt;/code&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;High: &amp;lt; 30 days&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Medium: &amp;lt; 90 days&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Track in Faraday using custom field &lt;code&gt;date_closed - date_created&lt;/code&gt;. Alert on SLA breaches via Slack webhook.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Coverage:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;% of repos with SAST enabled (GitHub API: count repos with &lt;code&gt;.github/workflows/semgrep.yml&lt;/code&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;% of deployments scanned by DAST (track in deployment metadata)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Manual testing hours per quarter (Jira issue time tracking: sum hours on tickets tagged &lt;code&gt;security-sprint&lt;/code&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Trends (Faraday dashboard queries):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;New vulnerabilities per sprint: &lt;code&gt;GET /api/v3/ws/{workspace}/vulns?created_after=2024-01-01&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Remediation velocity: Count of &lt;code&gt;status=closed&lt;/code&gt; grouped by week&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Most common CWE: &lt;code&gt;GROUP BY cwe&lt;/code&gt; to identify patterns worth fixing upstream&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Developer Experience:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;False positive rate: &lt;code&gt;(marked_as_false_positive / total_findings) × 100&lt;/code&gt; (should be &amp;lt;20%)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Time to triage: Track via Faraday field &lt;code&gt;date_triaged - date_created&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Average PR scan duration: GitHub Actions metrics (should be &amp;lt;3 min)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Dashboard query example:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Weekly remediation report
&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;FARADAY_URL&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/api/v3/ws/project-alpha/vulns&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;closed&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;closed_after&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;2024-11-01&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;group_by&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;severity&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Authorization&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Token &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;API_KEY&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Closed this month: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If MTTR is trending up, you're creating more findings than your team can fix. Scale back scanning frequency or prioritize better.&lt;/p&gt;




&lt;h2&gt;
  
  
  What NOT to Do
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Running multiple dashboards&lt;/strong&gt; — Pick Faraday OR DefectDojo. Not both. Tool sprawl kills adoption faster than false positives.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Emailing PDF reports&lt;/strong&gt; — Evidence belongs with findings in your aggregation platform. Email is where information goes to die.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Treating all severities equally&lt;/strong&gt; — A critical SQL injection in production auth is not the same as a medium XSS in your 404 page. Prioritize by (exploitability × impact × exposure).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Unauthenticated DAST only&lt;/strong&gt; — 80% of your attack surface is behind login. Scan it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No suppression workflow&lt;/strong&gt; — 30% false positive rate = developers ignore your tools. Build a clear process for accepting/dismissing findings.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Forgetting APIs&lt;/strong&gt; — Modern apps are API-first. Don't just scan the web UI. Use Postman collections or OpenAPI specs with ZAP.&lt;/p&gt;




&lt;h2&gt;
  
  
  What This Actually Looks Like
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Monday morning:&lt;/strong&gt; Developer opens PR. Semgrep flags a SQL injection in inline comments. They fix it before requesting review.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tuesday afternoon:&lt;/strong&gt; You review Faraday dashboard, triage 8 new ZAP findings, mark 3 as false positives, create Jira tickets for 5.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Wednesday:&lt;/strong&gt; Product asks about security for the upcoming release. You filter Faraday by "High+ findings added this sprint" and export a CSV. Takes 30 seconds.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;End of quarter:&lt;/strong&gt; Security sprint finds an IDOR that would've let users access other accounts. Document in Faraday, create emergency Jira ticket, generate SysReptor report for leadership with PoC screenshots.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Compliance audit:&lt;/strong&gt; Auditor wants proof of regular scanning. Export 6 months of scan history from Faraday. They're happy. You're happy.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Practical Closing Thought
&lt;/h2&gt;

&lt;p&gt;You don’t need to implement everything at once. Start with whatever gives your team the most visibility — SAST, SCA, DAST, infra scans — and evolve the rest over time. The architecture I shared is just what worked for me; the best security setup is the one your developers actually stick to. Build it incrementally, tune aggressively, and keep the feedback loop tight.&lt;/p&gt;




&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://semgrep.dev/docs/deployment/add-semgrep-to-ci" rel="noopener noreferrer"&gt;Semgrep CI Docs&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://docs.snyk.io/developer-tools/snyk-ci-cd-integrations/github-actions-for-snyk-setup-and-checking-for-vulnerabilities" rel="noopener noreferrer"&gt;Snyk GitHub Integration&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.zaproxy.org/docs/docker/" rel="noopener noreferrer"&gt;OWASP ZAP Docker&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://docs.faradaysec.com/" rel="noopener noreferrer"&gt;Faraday Documentation&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/Syslifters/sysreptor" rel="noopener noreferrer"&gt;SysReptor&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/pwndoc/pwndoc" rel="noopener noreferrer"&gt;PwnDoc&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://portswigger.net/burp/documentation/dast/user-guide/ci-cd" rel="noopener noreferrer"&gt;Burp Suite CI/CD&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Questions about implementing this? Already running something similar? Drop your thoughts in the comments.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>cicd</category>
      <category>devops</category>
      <category>security</category>
      <category>architecture</category>
    </item>
  </channel>
</rss>
