Education › Security › Stage 3: Cloud & pipeline

DevSecOps: security in the pipeline

SAST, DAST, secret scanning, dependency and container scanning, and how to gate without blocking everyone.

Intermediate ~30 min read Module 11 of 16

Security that happens once a year, in a document, after the code has shipped, is a report about the past. DevSecOps moves the checks into the place where code actually changes: the pull request, the build, the deploy. Done badly it is a wall of red findings that everyone learns to ignore. Done well it is a set of fast, precise gates that catch the specific bug classes that matter, plus a steady stream of low-severity findings handled like any other technical debt. This module shows which scanners belong at which stage, how to tune them so that a failing check means something, and how to measure whether the whole thing is working.

After this module you can
  • Place SAST, secret scanning, dependency scanning, container scanning, IaC scanning and DAST at the right stage of the pipeline
  • Configure gates that block on high-confidence, high-severity findings and report the rest
  • Manage findings as work: triage, ownership, suppression with reasons, expiry
  • Keep pipelines fast enough that security checks are not skipped
  • Track the metrics that show whether the program reduces risk

The right check at the right stage

Each class of scanner sees a different kind of problem and costs a different amount of time, so each belongs at a specific point. Fast, precise checks run on every commit; slower or noisier ones run on merge or on a schedule; the slowest run against a deployed environment.

StageCheckCatches
Pre-commit / PRSecret scanning, linting, SAST on the diffLeaked keys, injection patterns, insecure APIs
PRDependency audit, IaC scanning, licence checkKnown-vulnerable packages, public buckets, open ports
Build on mergeContainer scanning, SBOM, signing, provenanceVulnerable base images, unsigned artifacts
DeployAdmission policy, config validationUnsigned or non-compliant workloads
Scheduled (nightly/weekly)Full SAST, DAST against staging, re-scan stored SBOMsNewly published vulnerabilities, runtime-only bugs

SAST (static analysis) reads code for dangerous patterns: string-built queries, unsafe deserialisation, hard-coded credentials. DAST (dynamic analysis) attacks a running application over HTTP and finds what only shows at runtime: missing headers, reflected input, authentication issues. SCA (software composition analysis) is dependency scanning. IaC scanners read Terraform and Kubernetes manifests for misconfiguration. Each has blind spots; together they cover most of the OWASP categories before a human reviewer looks.

Note

The DevOps and supply chain modules already put secret scanning, dependency audit, image scanning and signing into the pipeline. This module is about running all of it as a system without slowing delivery down.

Gates that mean something

A check that fails on every pull request trains people to click past it. Block only on findings that are both high severity and high confidence: a critical vulnerability with a fix available, a verified secret, a public bucket, a SQL injection pattern with a taint path from a request parameter. Everything else is reported in the pull request as a comment or in a dashboard, tracked as a ticket with an owner, and fixed on a schedule. Severity thresholds should be explicit in the pipeline configuration, not in someone's head.

A PR security job: fast checks that block, slower ones that only report. Each blocking step has an explicit threshold.
yaml
name: security

on:
  pull_request:

permissions:
  contents: read
  security-events: write

jobs:
  gate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - name: Secrets (blocks)
        uses: gitleaks/gitleaks-action@v2
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      - name: SAST on OWASP rules (blocks on ERROR severity only)
        run: |
          pip install semgrep
          semgrep scan --config p/owasp-top-ten --severity ERROR --error .
      - name: Dependencies (blocks on fixable high/critical)
        run: |
          pip install pip-audit
          pip-audit -r requirements.txt --strict --ignore-vuln GHSA-xxxx-accepted-until-2026-12
      - name: IaC (blocks on the misconfiguration set we agreed)
        run: |
          pip install checkov
          checkov -d infra/ --framework terraform --check CKV_AWS_18,CKV_AWS_19,CKV_AWS_23,CKV_AWS_53,CKV_AWS_54,CKV_AWS_55,CKV_AWS_56 --compact

  report:
    runs-on: ubuntu-latest
    continue-on-error: true
    steps:
      - uses: actions/checkout@v4
      - name: Full SAST, uploaded as code scanning alerts (does not block)
        run: |
          pip install semgrep
          semgrep scan --config auto --sarif --output semgrep.sarif . || true
      - uses: github/codeql-action/upload-sarif@v3
        with:
          sarif_file: semgrep.sarif

Note the split: the gate job fails the pull request; the report job never does, and its findings appear as code scanning alerts where they can be triaged. As the backlog shrinks, thresholds tighten. This is the ratchet: start permissive enough that the pipeline is green today, and never let it get looser.

Findings are work, not noise

Every finding gets one of three outcomes: fix (with a pull request), accept (with a reason, an owner and an expiry date), or false positive (with a suppression that names the rule and why). Suppressions live in code next to the finding, are reviewed like code, and expire so that they are reconsidered. A finding with no outcome after its SLA is a process failure that the metrics should show.

Inline suppression with a reason. The rule id, the justification and the reviewer are all in the diff.
python
import subprocess

# nosemgrep: python.lang.security.audit.subprocess-shell-true
# Justification: `cmd` is a constant from config, never user input; reviewed by @security 2026-06.
result = subprocess.run(cmd, shell=True, check=True, capture_output=True)
SeverityFix SLAWhere it blocks
Critical (exploitable, exposed)24 hoursPR gate and deploy
High7 daysPR gate if fix available
Medium30 daysReport only; tracked
Low / informationalBacklogReport only
Tip

Give every service a security owner and a dashboard of its open findings by age. The conversation changes from "security says" to "our service has three highs older than a week".

Keep it fast

Security checks compete with test suites for a developer's patience. Keep the blocking path under a few minutes: scan only the diff in SAST on pull requests and the whole codebase nightly; cache scanner databases and dependency downloads; run independent scanners in parallel jobs; scan container images once per build by digest rather than per environment. Put DAST, which takes long and needs a deployed target, on a schedule against staging or as a post-deploy check, never in the pull request path.

Diff-scoped SAST: only files changed in the pull request, against the base branch.
bash
git fetch origin "$BASE_BRANCH" --depth=1
CHANGED=$(git diff --name-only "origin/$BASE_BRANCH"...HEAD -- '*.py' '*.js' '*.ts' '*.go')
if [ -n "$CHANGED" ]; then
  # shellcheck disable=SC2086
  semgrep scan --config p/owasp-top-ten --severity ERROR --error $CHANGED
else
  echo "no source files changed"
fi

Developer experience decides whether the program survives. Findings should appear where the developer already is (as pull request annotations, not in a separate portal), say what to change (a fix suggestion beats a CWE number), and be reproducible locally with the same command the pipeline runs. A pre-commit hook for the fastest checks catches most issues before a pull request exists.

Measure the program

Counting findings measures scanner output, not risk. Measure outcomes instead: mean time to remediate by severity, percentage of services with the full gate enabled, age of the oldest open critical, escaped vulnerabilities (found in production or by a pen test that the pipeline should have caught), and pipeline duration for the security stage. Rising remediation time or a growing pile of accepted risks are early warnings; a falling escape rate is the number that proves the investment.

  • Coverage: which repositories and images have which checks. Gaps are usually the oldest, most important services.
  • Signal quality: the ratio of findings marked false positive. Above a third, tune the rules or change tools.
  • Ratchet progress: thresholds tightened per quarter, suppressions expired and re-evaluated.
  • Escapes: every production vulnerability gets a retrospective question — which stage should have caught it?

The end state is unremarkable: security checks are simply part of the definition of done, the same as tests and linting, owned by the teams that own the code, with a small central group maintaining the tooling and the thresholds. That is what DevSecOps means when the buzzword is removed.

Hands-on practice

Build a two-tier security workflow

  1. Add the security.yml workflow from the lesson to a repository with Python code and a Terraform folder (the zero-to-production project fits). Run it on a pull request and read the output of each step.
  2. Introduce a deliberate SQL string-format bug and a fake AWS key in a pull request; confirm the gate fails and identify which step caught which.
  3. Suppress one Semgrep finding inline with a justification comment, then confirm the gate passes and the suppression is visible in the diff.
  4. Move full-codebase SAST to a scheduled workflow (on: schedule) and confirm PR runs take under three minutes.
  5. Run OWASP ZAP's baseline scan (zap-baseline.py -t URL) against your staging URL from a scheduled job and upload the report as an artifact.
  6. Create a one-page dashboard (a spreadsheet is fine) with: services covered, open findings by severity and age, and time to remediate for the last five fixes.
Cheat sheet

DevSecOps: security in the pipeline — at a glance

Main things to focus on

  • Fast precise checks on every PR; slow or noisy ones on merge, on a schedule, or against staging
  • Block only on high-severity, high-confidence findings with explicit thresholds; report the rest
  • Every finding: fix, accept (reason, owner, expiry) or false positive (suppression in code)
  • Ratchet: never loosen thresholds; tighten as the backlog shrinks
  • Keep the blocking path minutes long: diff-scoped SAST, caching, parallel jobs, scan by digest once
  • Measure remediation time, coverage, escapes and false-positive ratio, not raw finding counts

Scanner by stage

gitleaks (pre-commit + PR)Secrets; always blocks
semgrep --config p/owasp-top-ten --severity ERROR --errorSAST gate on high-confidence rules
pip-audit --strict / npm audit --audit-level=highDependency gate on fixable highs
checkov -d infra/ --check ...IaC gate on an agreed misconfiguration set
trivy image --exit-code 1 --severity CRITICAL,HIGH --ignore-unfixedContainer gate at build
zap-baseline.py -t URL (scheduled)DAST against staging, never in the PR path

Gate design

gate job fails; report job continue-on-errorTwo tiers in one workflow
upload SARIF -> code scanning alertsFindings where developers already look
thresholds in config, not in headsExplicit and reviewable
diff-scoped on PR, full on scheduleSpeed without losing coverage

Findings management

# nosemgrep: RULE + justification + reviewer + dateSuppression in code, reviewed
--ignore-vuln ID (with expiry in the name/comment)Accepted dependency risk
SLA: critical 24h, high 7d, medium 30dOwnership by severity
owner per service + findings-by-age dashboardMakes the backlog visible

Metrics

mean time to remediate (by severity)Are fixes happening?
coverage: % repos/images with the full gateWhere are the gaps?
escaped vulnerabilitiesFound in prod or pen test; which stage missed it?
false-positive ratioAbove ~1/3: tune rules
security stage durationKeep it minutes

Common pitfalls

  • Turning on every rule at ERROR severity on day one; the pipeline goes red and stays red until people stop looking.
  • Suppressing findings in a central config nobody reviews, without reasons or expiry.
  • Running DAST in the pull request path and doubling build times.
  • Measuring the program by findings count, which rewards noisy tools.
  • One security team triaging findings for fifty services instead of owners doing it for their own.
  • Scanning the same image in every environment instead of once by digest.
Quiz

Check your understanding

5 questions · 4 to pass · answers are explained as you go. Your best score is saved on this device only.

Progress and quiz scores are saved in this browser only. Back up or restore on the hub.

Was this lesson useful? Tell me what to improve →