For years, security was the ‘final boss’ of the software development lifecycle. You’d build the feature, test it, and then hand it over to a security team who would return a 50-page PDF of vulnerabilities two weeks later. By then, the original developer had already forgotten how the code even worked. This is why I’m a huge advocate for ‘shifting left.’

When you learn how to automate security testing in CI/CD pipeline, you stop treating security as a gate and start treating it as a continuous quality metric. In my experience, integrating these tools early reduces the cost of fixing bugs by an order of magnitude. In this tutorial, I’ll walk you through building a robust DevSecOps pipeline that catches secrets, vulnerable dependencies, and runtime flaws automatically.

Prerequisites

Step 1: Implement Static Analysis (SAST)

Static Application Security Testing (SAST) examines your source code without executing it. It’s the fastest way to catch common mistakes like SQL injection or hardcoded credentials. I typically start with Semgrep because it’s fast and highly customizable.

# Example GitHub Action for Semgrep
- name: Semgrep
  uses: returntocorp/semgrep-action@v1
  with:
    semgrep_config: p/default
    # Fail the build if high-severity issues are found
    fail_on_severity: ERROR

By adding this to your .github/workflows/main.yml, you ensure that no code is merged into your main branch if it contains obvious security holes.

Step 2: Automate Software Composition Analysis (SCA)

Modern apps are 80% dependencies and 20% original code. Most vulnerabilities actually enter your system through a third-party library. SCA tools scan your package-lock.json or requirements.txt against databases of known vulnerabilities (CVEs).

I recommend using Snyk or GitHub Dependabot. For a pipeline-integrated approach, Snyk provides a powerful CLI:

# Adding Snyk to your pipeline
- name: Snyk Security Scan
  run: |
    npm install -g snyk
    snyk auth ${{ secrets.SNYK_TOKEN }}
    snyk test --severity-threshold=high

Step 3: Dynamic Analysis (DAST) in Staging

Unlike SAST, Dynamic Application Security Testing (DAST) attacks your running application. It finds issues that only appear at runtime, such as misconfigured HTTP headers or broken authentication. This must happen after the app is deployed to a staging environment.

The industry standard here is OWASP ZAP. If you’re using GitHub Actions, you can leverage the official ZAP Docker image. For a detailed walkthrough, check out my OWASP ZAP GitHub Actions tutorial.

# Running a ZAP Baseline Scan
- name: ZAP Scan
  uses: zaproxy/action-baseline@v0.12.0
  with:
    target: 'https://staging.myapp.com'

As shown in the image below, you’ll want to ensure the DAST scan triggers only after the ‘Deploy to Staging’ step is successful.

GitHub Actions workflow visualization showing the sequential flow from Build -> Deploy to Staging -> DAST Scan” loading=”lazy” /><figcaption>GitHub Actions workflow visualization showing the sequential flow from Build -> Deploy to Staging -> DAST Scan</figcaption></figure>
<h2>Step 4: Secret Scanning</h2>
<p>There is nothing more terrifying than finding an AWS Secret Key in a public Git history. Automating secret scanning prevents the commit from ever happening. I use <strong>gitleaks</strong> for this. You can run it as a pre-commit hook or as a pipeline step.</p>
<pre><code># Gitleaks pipeline integration
- name: Gitleaks
  uses: gitleaks/gitleaks-action@v2
  env:
    GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
</code></pre>
<h2>Pro Tips for a Frictionless Pipeline</h2>
<ul>
<li><strong>Avoid ‘Build Fatigue’:</strong> Don’t fail the build for ‘Low’ or ‘Informational’ alerts. Only break the pipeline for ‘High’ or ‘Critical’ issues.</li>
<li><strong>Baseline Your Project:</strong> If you’re adding security to an old project, you’ll get 1,000 errors. Create a ‘baseline’ file to ignore existing issues and only alert on <em>new</em> vulnerabilities.</li>
<li><strong>Caching:</strong> Cache your SCA scan results to speed up the pipeline.</li>
<li><strong>Security as Code:</strong> Keep your security configurations in the repo, not hidden in a UI, so they can be peer-reviewed.</li>
</ul>
<h2>Troubleshooting Common Issues</h2>
<h3>The Pipeline is Too Slow</h3>
<p>Security scans can be heavy. To fix this, run SAST and SCA on every commit, but run heavy DAST scans only on merges to the <code>develop</code> or <code>main</code> branches.</p>
<h3>Too Many False Positives</h3>
<p>No tool is perfect. When you find a false positive, don’t ignore it manually every time. Use the tool’s <code>.ignore</code> or <code>.yml</code> configuration to permanently whitelist that specific code block.</p>
<h2>What’s Next?</h2>
<p>Once you’ve mastered the basics of <strong>how to automate security testing in CI/CD pipeline</strong>, the next step is implementing <strong>Infrastructure as Code (IaC) scanning</strong> using tools like Checkov or Tfsec to ensure your cloud environment is as secure as your code.</p>
<p>Want to dive deeper into the theoretical differences between these tools? Read my <a href=comparison guide to refine your strategy.