Overview
A secure pipeline treats security checks as automated build gates, not after-the-fact reports: SAST scans an application's own source code early, before any artifact exists, SCA scans third-party dependencies against known-vulnerability databases, since most shipped code is dependencies rather than code the team wrote, and DAST exercises a running instance of the application from the outside, closer to release. OWASP SAMM frames the maturity ladder for this plainly, at its highest level, a non-compliant artifact simply fails to build, which is what actually makes "shift-left" a control instead of a slogan; a scan result nobody is required to act on doesn't gate anything. For a real pipeline built around this exact gating idea, see Your GitHub Actions Cache Hit Rate Is Worse Than You Think and, for the deployment side once an artifact passes its gates, Production-Ready AKS GitOps with Terraform and ArgoCD.
Quick Reference
| Check | Analyzes | Runs against | Typical pipeline stage |
|---|---|---|---|
| SAST | The application's own source code | Static code, no execution | Early build, before an artifact exists |
| SCA | Third-party dependencies | Known-vulnerability databases | Build, alongside dependency resolution |
| DAST | A running instance of the application | Live HTTP traffic, black-box | Later, against a deployed staging environment |
Syntax
# A build stage that fails the pipeline on a high-severity SCA
# finding, rather than only recording it for later review.
security-scan:
script:
- sca-scan --fail-on=high ./Examples
# Gate the build on SAST findings the same way; the pipeline
# stops here, it doesn't continue and just flag the result.
sast-scan:
stage: build
script:
- sast-scan --source . --fail-on-severity=high
allow_failure: falseA finding that doesn't block the build is a suggestion, not a control
OWASP SAMM's highest build-process maturity level is explicit: define mandatory checks and make a non-compliant artifact actually fail to build. A scan that only produces a report for later review isn't shifting security left, it's just adding a dashboard.
A real GitHub Actions job wires this up with an actual scanner action rather than a placeholder script, for example Trivy for SCA/container scanning and CodeQL for SAST:
- name: Scan dependencies and image with Trivy
uses: aquasecurity/trivy-action@0.30.0 # pin to a released tag, never @master, in a gate that can fail a build
with:
scan-type: fs
severity: HIGH,CRITICAL
exit-code: '1' # non-zero exit is what actually fails the jobThat exit-code: '1' is the entire gate. Trivy (and most scanners) default to reporting without failing; without an explicit non-zero exit-code setting, the step goes green regardless of what it finds.
Visual Diagram
Common Mistakes
- Running SAST/SCA/DAST scans but configuring them to only report findings, never actually failing the build, so nothing is really gated.
- Scanning only first-party source code and treating third-party dependencies as out of scope, when most of a modern application's code is dependencies.
- Running DAST only against production, instead of a staging environment, turning a security test into a live-fire exercise against real users.
- Adding every available scanner at once with default severity thresholds, generating enough noise that the team starts routinely overriding or ignoring failures.
- Setting
continue-on-error: trueon a scanner step "temporarily" to unblock a release, then never removing it. GitHub Actions marks the step green regardless of its exit code from that point on, so the gate silently stops gating while every dashboard still shows the scan as passing.
Performance
- SAST and SCA run against source/dependency manifests and add build time roughly proportional to codebase and dependency-tree size; incremental/cached scanning keeps this bounded on repeat runs.
- DAST exercises a live running application and is meaningfully slower than SAST/SCA, which is part of why it typically runs later and less frequently in the pipeline, not on every single commit.
Best Practices
- Configure every security check to actually fail the build on findings above an agreed severity threshold, not merely to report them.
- Apply the same rigor to third-party dependency scanning (SCA) as to first-party source code (SAST); most shipped code isn't code the team wrote.
- I run DAST against a staging deployment that mirrors production, not against production itself.
- Tune severity thresholds deliberately so failures stay meaningful; a pipeline the team routinely overrides has stopped functioning as a gate.
- Audit workflow YAML for
continue-on-error: trueandallow_failure: trueon security steps periodically; both are legitimate for genuinely non-blocking steps, but silently defeat a scanner when left on one. - Pin third-party scanner actions to a specific released tag (e.g.
aquasecurity/trivy-action@0.30.0), not a floating@masteror@latest, in any workflow that has the authority to fail a build.