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
- A project hosted on GitHub, GitLab, or Bitbucket.
- Basic familiarity with YAML for pipeline configuration.
- A basic understanding of the DAST vs SAST vs IAST comparison so you know which tool to use when.
- Docker installed on your local machine for testing containers.
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.