DevSecOps: Integrating Security into CI/CD

Photo by Unsplash
The pipeline uses Semgrep for SAST (scanning source code for vulnerabilities like SQL injection and XSS), Trivy for both dependency scanning (npm, pip, go.mod) and container image scanning against CVE databases, Gitleaks for secrets detection across the full Git history, and OWASP ZAP for DAST against a staging environment. Each tool targets a different attack surface, so running them together gives broad security coverage on every pull request.
SAST (Static Application Security Testing) analyzes source code without running the application, catching issues like hardcoded secrets and insecure API usage. DAST (Dynamic Application Security Testing) sends crafted HTTP requests to a running application to find vulnerabilities like broken authentication, insecure direct object references, and misconfigured CORS headers that SAST cannot detect from code alone. Using both ensures coverage at compile time and at runtime.
Yes. If Trivy finds critical or high-severity vulnerabilities during the container image scan, the GitHub Actions step exits with code 1, which blocks the push to the registry. The SARIF upload step uses if: always() so that findings are recorded even when the build fails, giving security teams a permanent audit trail of what was blocked and why.
The post recommends starting with high-confidence rules only and tuning thresholds conservatively — flagging only CRITICAL and HIGH findings, not MEDIUM. It also suggests establishing a clear process for accepting false positives with written justification. Using a policy-as-code file (.security/policy.yml) to track accepted CVE IDs with expiry dates and justifications creates an auditable trail and forces periodic review of waivers.
Gitleaks scans the full Git history (using fetch-depth: 0) to catch accidentally committed credentials such as AWS keys, GitHub tokens, and JWT secrets. Additionally, the post recommends generating and publishing a Software Bill of Materials (SBOM) using Syft or Trivy's SBOM generation mode, which documents every dependency in the container image for compliance auditing and incident response.

Photo by Unsplash
DevSecOps integrates security testing directly into the software delivery lifecycle rather than treating it as a separate phase after development is complete. The 'shift-left' approach means every pull request is automatically scanned for vulnerabilities, exposed secrets, and insecure dependencies — catching issues when they're cheapest to fix: before they reach production. This article walks through building a practical DevSecOps pipeline using GitHub Actions, Semgrep, Trivy, Gitleaks, and OWASP ZAP.
A mature DevSecOps CI/CD pipeline runs multiple security gates in parallel: Static Application Security Testing (SAST) analyzes source code for vulnerabilities, Software Composition Analysis (SCA) scans dependencies for known CVEs, secrets detection scans for accidentally committed credentials, and container image scanning ensures your Docker images don't carry known vulnerabilities. Each gate has a clear pass/fail criterion and blocks deployment on critical findings.
Semgrep is an open-source SAST tool with hundreds of OWASP-aligned rules. It scans source code for SQL injection, XSS, hardcoded secrets, and insecure API usage. Trivy is a versatile scanner that handles both filesystem dependency scanning (npm, pip, go.mod) and container image scanning. Running them together on every pull request gives you immediate feedback on security regressions introduced by a change.
# .github/workflows/devsecops.yml — Security gates in CI/CD
name: DevSecOps Pipeline
on: [push, pull_request]
jobs:
sast:
name: Static Analysis (SAST)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Semgrep SAST
uses: semgrep/semgrep-action@v1
with:
config: p/owasp-top-ten
dependency-scan:
name: Dependency Vulnerability Scan
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Trivy on dependencies
uses: aquasecurity/trivy-action@master
with:
scan-type: fs
scan-ref: .
severity: CRITICAL,HIGH
exit-code: 1 # Fail build on critical/high vulns
secrets-detection:
name: Secrets Detection
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: gitleaks/gitleaks-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}Container images are a common attack surface — base images like ubuntu:latest or node:18 frequently have known CVEs in their installed packages. Trivy image scanning checks the image manifest against the CVE databases (NVD, GitHub Advisory Database) and reports vulnerabilities by severity. Integrating SARIF output with GitHub's security tab gives you a permanent record of all findings across your repositories.
The security gate runs after the Docker build step but before pushing to the registry. If Trivy finds critical or high-severity vulnerabilities, the GitHub Actions step exits with code 1, blocking the push. The SARIF upload step uses if: always() to ensure findings are recorded even when the build fails — giving security teams visibility into what was blocked and why.
# Container image scanning with Trivy
container-scan:
name: Container Image Scan
runs-on: ubuntu-latest
needs: [sast]
steps:
- uses: actions/checkout@v4
- name: Build Docker image
run: docker build -t myapp:${{ github.sha }} .
- name: Scan image with Trivy
uses: aquasecurity/trivy-action@master
with:
image-ref: myapp:${{ github.sha }}
format: sarif
output: trivy-results.sarif
severity: CRITICAL,HIGH
exit-code: 1
- name: Upload SARIF to GitHub Security
uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: trivy-results.sarifUse policy-as-code for vulnerability exceptions. Rather than hardcoding severity thresholds, maintain a .security/policy.yml file that specifies which CVE IDs are accepted with expiry dates and justifications. This creates an auditable trail of accepted risk decisions and auto-expires waivers so they get reviewed regularly.
DAST tests your running application by sending crafted HTTP requests to find security vulnerabilities that only appear at runtime — things like broken authentication, insecure direct object references, and misconfigured CORS headers that SAST can't detect by looking at code alone. OWASP ZAP is the most widely-used open-source DAST tool, and its GitHub Action makes integration straightforward.
The ZAP baseline scan is a passive scan that doesn't actively attack the application — it crawls the site, observes responses, and flags security issues without sending malicious payloads. It's safe to run against a staging environment in every CI/CD run. For more thorough testing, the ZAP full scan includes active attack rules but should be run less frequently and in an isolated environment.
# DAST with OWASP ZAP against a staging environment
dast:
name: Dynamic Analysis (DAST)
runs-on: ubuntu-latest
needs: [container-scan]
steps:
- name: ZAP Baseline Scan
uses: zaproxy/[email protected]
with:
target: https://staging.myapp.com
rules_file_name: .zap/rules.tsv
cmd_options: >
-I # ignore warnings (only fail on alerts)
-j # output JSON report
-a # include alpha active scan rulesA DevSecOps pipeline that blocks every build with false positives will quickly be bypassed or disabled by frustrated developers. Start with high-confidence rules only, tune thresholds conservatively (CRITICAL and HIGH only, not MEDIUM), and establish a clear process for accepting false positives with justification. Security tooling should help developers, not antagonize them.
Gitleaks scans the full Git history for accidentally committed credentials — AWS keys, GitHub tokens, database passwords, JWT secrets. Running it with fetch-depth: 0 scans every commit in the repository, not just the latest. For supply chain security, generate and publish a Software Bill of Materials (SBOM) using Syft or Trivy's SBOM generation mode — this documents every dependency in your container image for compliance and incident response.
Essential DevSecOps terminology you'll encounter in the field: SAST, DAST, SCA, CVE, shift-left security, and SBOM.
DevSecOps is a cultural shift as much as a technical one. Start small: add Gitleaks to prevent secrets from entering the repository, then add Trivy dependency scanning, then container scanning. Celebrate wins when the pipeline catches real vulnerabilities. Invest in developer security training so the team understands why the gates exist — teams that understand security context write more secure code proactively.