Discovering security vulnerabilities late in the software development life cycle (SDLC) is a primary driver of technical debt and project delays. When a critical flaw is found during a manual penetration test just days before a release, the cost of remediation is exponentially higher than if it were caught during the initial pull request. Shifting security to the left means bringing automated testing directly into the developer workflow. By integrating Static Application Security Testing (SAST) and Dynamic Application Security Testing (DAST) natively into GitHub Actions, you ensure that every line of code is scanned for vulnerabilities before it ever touches your production environment.
This guide provides a technical roadmap for building a DevSecOps pipeline using GitHub Actions. We will implement CodeQL for SAST and OWASP ZAP for DAST, creating a multi-layered defense strategy that identifies both coding errors and runtime configuration flaws.
TL;DR — Use GitHub CodeQL for native SAST and the OWASP ZAP Action for containerized DAST. Automate these in .github/workflows to block insecure pull requests and upload results to the GitHub Security tab via SARIF files.
Table of Contents
Understanding SAST vs DAST in CI/CD
💡 Analogy: Think of SAST like a building inspector examining the architectural blueprints for structural flaws before construction begins. Think of DAST like a security guard trying to kick down the doors and pick the locks once the building is actually standing and open for business.
Static Application Security Testing (SAST) examines your source code, bytecode, or binaries without executing the program. It looks for patterns that indicate vulnerabilities, such as SQL injection, cross-site scripting (XSS), or insecure cryptographic implementations. Because SAST has access to the "inside" of the application, it can point developers to the exact file and line number where a bug exists. In GitHub Actions, CodeQL is the industry standard for this, as it treats code like data that can be queried for known vulnerability patterns.
Dynamic Application Security Testing (DAST) takes the opposite approach. It interacts with your application while it is running, typically in a staging or ephemeral environment. DAST does not see the source code; it acts like an external attacker, sending malicious payloads to headers, form fields, and API endpoints to see how the system responds. This is essential for catching environment-specific issues, such as insecure SSL/TLS configurations, mismanaged headers, or broken authentication flows that SAST might miss.
Combining both tools creates a "defense in depth" model. SAST catches logic errors early in the coding phase, while DAST ensures that the integrated components—database, web server, and application logic—work together securely in a live state. For a modern DevSecOps pipeline, the goal is to automate both so that security becomes a silent partner in the development process rather than a manual gatekeeper.
When to Use Automated Scanning
Not every security scan belongs in every part of the pipeline. If you run a full DAST scan on every tiny commit, you will slow down your development team and invite "alert fatigue." Effective DevSecOps requires a tiered approach to scanning frequency and depth.
Pull Request Scans (SAST): Every time a developer opens a pull request, a "delta" SAST scan should run. This only analyzes the changed code. This provides immediate feedback to the developer, allowing them to fix an insecure eval() or an unparameterized query before a peer even starts the code review. This is the core of the shift-left philosophy.
Scheduled Scans (DAST & Full SAST): DAST typically requires a running environment, which can be expensive and slow to spin up. Many teams run a "Baseline" DAST scan on every merge to the main branch and a "Full" DAST scan weekly. Weekly scans are also ideal for deep SAST analysis of the entire codebase to catch vulnerabilities introduced by outdated dependencies or changing security definitions in tools like CodeQL or SonarQube.
Deployment Gates: For organizations with high compliance requirements (like HIPAA or PCI-DSS), passing a security scan is a hard requirement for deployment. In GitHub Actions, you can configure environment protection rules that prevent a deployment to production unless the security-scan job completes successfully with zero high-severity findings.
Step-by-Step Implementation
Step 1: Implementing Native SAST with CodeQL
GitHub provides CodeQL for free for public repositories and as part of GitHub Advanced Security for private ones. To set this up, create a file at .github/workflows/codeql.yml. This configuration uses CodeQL v3 to analyze a JavaScript/TypeScript project.
name: "CodeQL Analysis"
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
schedule:
- cron: '30 1 * * 0'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
security-events: write
actions: read
contents: read
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: javascript
- name: Autobuild
uses: github/codeql-action/autobuild@v3
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
Step 2: Implementing DAST with OWASP ZAP
For DAST, we use the OWASP ZAP Baseline Action. This is ideal for CI/CD because it finds common security misconfigurations quickly without performing a deep, time-consuming crawl. It is important to run this against a test URL. If you are using GitHub Actions, you can use a tool like vercel/preview-url or simply point it at a persistent staging environment.
name: "DAST Scan"
on:
workflow_dispatch:
schedule:
- cron: '0 0 * * 1'
jobs:
zap_scan:
runs-on: ubuntu-latest
name: Scan the web application
steps:
- name: Checkout
uses: actions/checkout@v4
- name: ZAP Scan
uses: zaproxy/action-baseline@v0.12.0
with:
target: 'https://staging.your-app.com'
rules_file_name: '.zap/rules.tsv'
fail_action: true
Step 3: Centralizing Results in GitHub Security Tab
One of the best features of GitHub Actions is the ability to upload scan results in the Static Analysis Results Interchange Format (SARIF). This allows developers to view security alerts in the same place they view code—the "Security" tab of the repository. While CodeQL does this automatically, third-party tools like OWASP ZAP or Snyk require an explicit upload step.
- name: Upload SARIF file
uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: 'results.sarif'
By centralizing these alerts, you reduce tool-switching. Developers don't need to log into a separate SonarQube or ZAP dashboard. Instead, they see the vulnerability, the line of code responsible, and the remediation advice directly within the GitHub UI, which drastically speeds up the fix rate.
Common Pitfalls and Troubleshooting
⚠️ Common Mistake: Failing to handle "False Positives" in automated pipelines. If a security tool blocks a PR for a non-existent issue, developers will eventually ignore or disable the scans entirely.
1. Performance Bottlenecks: SAST and DAST are resource-intensive. Running a full CodeQL scan on a massive Java monolith can take 30+ minutes. If this happens, your CI/CD pipeline becomes a bottleneck. To fix this, use incremental scanning and ensure you are using the autobuild feature correctly. For DAST, avoid full scans on pull requests; use the baseline scan which limits the crawl time to 1-2 minutes.
2. Authentication Hurdles in DAST: Many DAST tools fail because they cannot get past the login screen of your application. When I implemented ZAP for a React-based dashboard, the scan initially returned 401 Unauthorized errors for every page. To solve this, you must provide the scanner with session tokens or authentication scripts. In GitHub Actions, store these as GitHub Secrets and pass them into the ZAP action configuration.
3. Outdated Rule Sets: Security threats evolve. Using an old version of a scanning action might mean you miss new vulnerabilities like Log4Shell or specific zero-day exploits. Always pin your GitHub Actions to a major version (e.g., v3) but ensure you have a process (like Dependabot) to update those versions regularly. Outdated scanning tools provide a false sense of security.
Best Practices for DevSecOps Scale
To move from a basic setup to a mature DevSecOps pipeline, you need to think about metrics and governance. It is not enough to find bugs; you must ensure they are fixed. Based on enterprise implementations, here are three tips to improve your security posture.
Implement a Security Severity Threshold: Use the fail-on or severity flags in your actions. I recommend setting the pipeline to "Warn" for Low/Medium issues but "Fail" for High/Critical issues. This prevents major leaks without stopping development for minor issues like "Missing Security Header."
Use SARIF for Cross-Tool Integration: Standardizing on SARIF allows you to swap tools without changing your reporting logic. If you decide to move from CodeQL to Snyk or Checkmarx, as long as the tool supports SARIF output, your GitHub Security dashboard remains the "single source of truth." This makes your pipeline modular and future-proof.
Infrastructure as Code (IaC) Scanning: Don't stop at application code. If your GitHub Actions deploy to AWS or Azure, integrate an IaC scanner like Trivy or Checkov. These tools scan your Terraform or CloudFormation files for misconfigured S3 buckets or open security groups. Adding this to your workflow completes the DevSecOps circle by securing the infrastructure alongside the application.
📌 Key Takeaways
- SAST finds logic flaws in code (inside-out); DAST finds runtime flaws in apps (outside-in).
- GitHub CodeQL is the easiest starting point for native SAST integration.
- Always upload results via SARIF to the Security tab for better developer experience.
- Schedule deep scans weekly to avoid slowing down daily pull requests.
- Use GitHub Secrets to manage authentication for DAST scanners.
Frequently Asked Questions
Q. Is SAST better than DAST for DevSecOps?
A. Neither is "better" as they find different types of flaws. SAST is better for immediate developer feedback in PRs because it points to specific code lines. DAST is better for finding issues that only appear when the application is integrated with its environment, like database connection errors or SSL issues.
Q. How do I reduce false positives in GitHub Actions security scans?
A. Use a configuration file (like codeql-config.yml) to exclude specific paths (like test folders or documentation) and ignore specific rules that aren't relevant to your threat model. Regularly reviewing and "dismissing" alerts in the GitHub UI also helps the tool learn what is a legitimate threat.
Q. Can I use SAST/DAST in private repositories for free?
A. GitHub CodeQL requires a GitHub Advanced Security (GHAS) license for private repositories. However, you can use third-party open-source tools like SonarQube Community Edition or OWASP ZAP in private repos without extra GitHub licensing costs, though you may need to manage the integration manually.
Post a Comment