DevSecOps: Integrating Security into CI/CD

Photo by Unsplash

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/action-baseline@v0.12.0
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.