Security Scanning

Shift-left security is a matrix of scan types—not a single checkbox. This chapter maps SAST, SCA, secrets, containers, DAST, IaC, and license scanning to pipeline stages, severity gates, SARIF aggregation, and dual-platform YAML you can paste into production.

developer devops security SARIF SLSA OIDC

Scan categories & pipeline placement

Security scanning is not one tool—it is a layered defense across the SDLC. Each category inspects a different attack surface: source code, dependencies, secrets, containers, running apps, and infrastructure definitions. World-class pipelines run fast checks on every PR and deeper scans on merge to main.

The eight scan families

Map each family to a pipeline stage. Running everything on every commit creates 45-minute PR feedback loops; running nothing until release ships CVEs. Elite teams use tiered execution: PR = fast + blocking, main = comprehensive + attestations.

Tool Category When to run Default gate
Semgrep / CodeQL SAST Every PR HIGH gate typical
Dependabot / Snyk / Trivy FS SCA Every PR CRITICAL gate typical
Gitleaks / TruffleHog Secret scan Pre-commit + PR CRITICAL gate typical
Trivy / Grype Container After image build CRITICAL gate typical
OWASP ZAP / Nuclei DAST Staging deploy HIGH gate typical
Checkov / tfsec / KICS IaC Terraform PR HIGH gate typical
FOSSA / License Finder License Release branch MEDIUM gate typical
SARIF upload Aggregation After each scan LOW gate typical
flowchart LR
  PR["Pull request"] --> SAST["SAST
Semgrep"]
  PR --> SCA["SCA
lockfile"]
  PR --> SEC["Secret scan
Gitleaks"]
  PR --> IAC["IaC scan
Checkov"]
  MERGE["Merge to main"] --> BUILD["Build image"]
  BUILD --> CS["Container scan
Trivy"]
  CS --> SBOM["SBOM + SARIF"]
  DEPLOY["Deploy staging"] --> DAST["DAST
ZAP baseline"]
  SBOM --> GATE["Policy gate
CRITICAL block"]
Severity Examples Typical gate policy
CRITICAL RCE, auth bypass, exposed secrets in prod Block merge & deploy immediately
HIGH SQLi, SSRF, privilege escalation paths Block within SLA (24–72h)
MEDIUM XSS, misconfigurations with limited blast radius Track in backlog; fix before next release
LOW Info leaks, best-practice deviations Accept risk or fix opportunistically
⚖️ Trade-off

Parallel scan jobs cut wall-clock time but multiply runner cost. Cache tool databases (Trivy, Grype) and use paths-filter to skip scans when only docs change.

🎯 Interview Tip

When asked "how do you secure CI/CD?", answer with categories + placement + SARIF aggregation + severity gates—not a single vendor name.

.github/workflows/security-matrix.yml
name: Security matrix
on:
  pull_request:
  push:
    branches: [main]
permissions:
  contents: read
  security-events: write
jobs:
  changes:
    runs-on: ubuntu-latest
    outputs:
      app: ${{ steps.filter.outputs.app }}
    steps:
      - uses: actions/checkout@v4
      - uses: dorny/paths-filter@v3
        id: filter
        with:
          filters: |
            app:
              - 'src/**'
              - 'package*.json'
              - 'Dockerfile'
  sast:
    needs: changes
    if: needs.changes.outputs.app == 'true'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: pip install semgrep
      - run: semgrep scan --config=auto --sarif -o semgrep.sarif
      - uses: github/codeql-action/upload-sarif@v3
        with:
          sarif_file: semgrep.sarif
.gitlab-ci.yml
stages: [detect, scan, report]

changes:
  stage: detect
  script:
    - apk add --no-cache git
    - |
      if git diff --name-only $CI_MERGE_REQUEST_DIFF_BASE_SHA...$CI_COMMIT_SHA | grep -qE '^(src/|package|Dockerfile)'; then
        echo "APP_CHANGED=true" >> scan.env
      fi
  artifacts:
    reports:
      dotenv: scan.env

sast:
  stage: scan
  needs: [changes]
  rules:
    - if: $APP_CHANGED == "true"
  script:
    - semgrep scan --config=auto --sarif -o gl-sast-report.sarif
  artifacts:
    reports:
      sast: gl-sast-report.sarif

SAST — static application security testing

SAST analyzes source code and bytecode without executing the app. It finds injection flaws, unsafe deserialization, hardcoded credentials in code paths, and framework misuses. Run on every PR; tune rules to reduce false positives before blocking merges.

Tooling landscape

Tool Category When to run Default gate
Semgrep Multi-language, fast, custom rules PR gate HIGH gate typical
CodeQL Deep data-flow, GitHub native PR + scheduled HIGH gate typical
SonarQube Quality + security rules PR decoration MEDIUM gate typical
Bandit Python-specific PR HIGH gate typical

What to block vs warn

Severity Examples Typical gate policy
CRITICAL RCE, auth bypass, exposed secrets in prod Block merge & deploy immediately
HIGH SQLi, SSRF, privilege escalation paths Block within SLA (24–72h)
MEDIUM XSS, misconfigurations with limited blast radius Track in backlog; fix before next release
LOW Info leaks, best-practice deviations Accept risk or fix opportunistically

Pipeline integration

.github/workflows/sast.yml
name: SAST
on: [pull_request]
jobs:
  semgrep:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: returntocorp/semgrep-action@v1
        with:
          config: p/ci
      - run: semgrep scan --config=auto --sarif -o semgrep.sarif
      - uses: github/codeql-action/upload-sarif@v3
        with:
          sarif_file: semgrep.sarif
  codeql:
    runs-on: ubuntu-latest
    permissions:
      security-events: write
    steps:
      - uses: actions/checkout@v4
      - uses: github/codeql-action/init@v3
        with:
          languages: javascript, python
      - uses: github/codeql-action/analyze@v3
.gitlab-ci.yml
sast-semgrep:
  stage: test
  image: returntocorp/semgrep
  script:
    - semgrep scan --config=auto --sarif -o gl-sast-report.sarif
  artifacts:
    reports:
      sast: gl-sast-report.sarif
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"

codeql:
  stage: test
  image: registry.gitlab.com/gitlab-org/security-products/analyzers/codeql:latest
  script:
    - /analyzer run
  artifacts:
    reports:
      sast: gl-sast-report.sarif
🔬 Under the Hood

SAST false positives erode trust. Start in audit mode for two sprints, then enable blocking on CRITICAL only.

💡 Pro Tip

Semgrep matches AST patterns; CodeQL builds a queryable database and runs inter-procedural taint tracking. CodeQL is slower but catches flows Semgrep rules miss.

Operational checklist

  1. Define severity policy document—what CRITICAL means for your org.
  2. Run tool in advisory mode for one sprint; export baseline finding count.
  3. Enable SARIF upload; wire Slack/PagerDuty on new CRITICAL in main.
  4. Review false positive rate monthly; tune rulesets or suppressions with expiry dates.
  5. Measure mean time to remediate per category—SAST vs SCA vs container often differ by 10×.

Common failure modes

  • Scanning only default branch—feature branches ship vulns via cherry-pick.
  • No lockfile—SCA reports stale or empty.
  • Ignoring unfixed upstream CVEs without compensating WAF/network controls.
  • Developers disable checks locally because PR feedback exceeds 30 minutes.

SCA — software composition analysis

SCA inspects third-party dependencies—npm, pip, Maven, Go modules—against CVE databases. Transitive dependencies cause 80%+ of dependency CVEs. Lockfiles are mandatory; scanning without them is theater.

Tooling landscape

Tool Category When to run Default gate
Dependabot / Renovate PR-based updates Continuous MEDIUM gate typical
Snyk Deep reachability PR gate CRITICAL gate typical
Trivy filesystem OS + language CVEs PR CRITICAL gate typical
OSV-Scanner Google OSV API CI HIGH gate typical

What to block vs warn

Severity Examples Typical gate policy
CRITICAL RCE, auth bypass, exposed secrets in prod Block merge & deploy immediately
HIGH SQLi, SSRF, privilege escalation paths Block within SLA (24–72h)
MEDIUM XSS, misconfigurations with limited blast radius Track in backlog; fix before next release
LOW Info leaks, best-practice deviations Accept risk or fix opportunistically

Pipeline integration

.github/workflows/sca.yml
name: SCA
on: [pull_request]
jobs:
  dependency-review:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/dependency-review-action@v4
        with:
          fail-on-severity: high
  trivy-fs:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: aquasecurity/trivy-action@master
        with:
          scan-type: fs
          scan-ref: .
          format: sarif
          output: trivy-fs.sarif
          severity: CRITICAL,HIGH
      - uses: github/codeql-action/upload-sarif@v3
        with:
          sarif_file: trivy-fs.sarif
.gitlab-ci.yml
dependency-scan:
  stage: test
  image:
    name: aquasec/trivy:latest
    entrypoint: [""]
  script:
    - trivy fs --format template --template @/contrib/sarif.tpl
        --output gl-dependency-scanning-report.sarif .
        --severity HIGH,CRITICAL
  artifacts:
    reports:
      dependency_scanning: gl-dependency-scanning-report.sarif
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"

renovate:
  stage: maintenance
  script:
    - echo "Renovate bot opens update MRs on schedule — see renovate.json"
📦 Real World

Pinning major versions without Renovate creates security debt—teams fear the 200-PR backlog. Automate small incremental updates weekly.

🔬 Under the Hood

Log4Shell (CVE-2021-44228) proved SCA alone is insufficient: you need runtime WAF rules and emergency patch SLAs alongside dependency scanning.

Operational checklist

  1. Define severity policy document—what CRITICAL means for your org.
  2. Run tool in advisory mode for one sprint; export baseline finding count.
  3. Enable SARIF upload; wire Slack/PagerDuty on new CRITICAL in main.
  4. Review false positive rate monthly; tune rulesets or suppressions with expiry dates.
  5. Measure mean time to remediate per category—SAST vs SCA vs container often differ by 10×.

Common failure modes

  • Scanning only default branch—feature branches ship vulns via cherry-pick.
  • No lockfile—SCA reports stale or empty.
  • Ignoring unfixed upstream CVEs without compensating WAF/network controls.
  • Developers disable checks locally because PR feedback exceeds 30 minutes.

Secret scanning

Secrets in git history are forever unless you rotate and purge. Pre-commit hooks catch mistakes before push; CI scans full tree including history on main. Never rely on .gitignore for secrets.

Tooling landscape

Tool Category When to run Default gate
Gitleaks Fast regex + entropy Pre-commit + CI CRITICAL gate typical
TruffleHog Verified secrets CI CRITICAL gate typical
GitHub push protection Blocks push to remote Pre-push CRITICAL gate typical
detect-secrets Baseline workflow Pre-commit HIGH gate typical

What to block vs warn

Severity Examples Typical gate policy
CRITICAL RCE, auth bypass, exposed secrets in prod Block merge & deploy immediately
HIGH SQLi, SSRF, privilege escalation paths Block within SLA (24–72h)
MEDIUM XSS, misconfigurations with limited blast radius Track in backlog; fix before next release
LOW Info leaks, best-practice deviations Accept risk or fix opportunistically

Pipeline integration

.github/workflows/secret-scanning.yml
name: Secret scan
on: [pull_request, push]
jobs:
  gitleaks:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - uses: gitleaks/gitleaks-action@v2
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      - name: TruffleHog verified
        run: |
          docker run --rm -v $PWD:/pwd trufflesecurity/trufflehog:latest \
            git file:///pwd --only-verified --fail
.gitlab-ci.yml
secret-detection:
  stage: test
  image:
    name: zricethezav/gitleaks:latest
    entrypoint: [""]
  script:
    - gitleaks detect --source . --verbose --redact
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"

trufflehog:
  stage: test
  image: trufflesecurity/trufflehog:latest
  script:
    - trufflehog git file://. --only-verified --fail
  allow_failure: false
🔒 Security

If a secret is committed, rotate immediately—scan clean does not mean attackers did not clone the repo during the exposure window.

🔬 Under the Hood

AWS access keys in public repos are harvested by bots within minutes. Push protection and org-level secret scanning are non-negotiable for production orgs.

Operational checklist

  1. Define severity policy document—what CRITICAL means for your org.
  2. Run tool in advisory mode for one sprint; export baseline finding count.
  3. Enable SARIF upload; wire Slack/PagerDuty on new CRITICAL in main.
  4. Review false positive rate monthly; tune rulesets or suppressions with expiry dates.
  5. Measure mean time to remediate per category—SAST vs SCA vs container often differ by 10×.

Common failure modes

  • Scanning only default branch—feature branches ship vulns via cherry-pick.
  • No lockfile—SCA reports stale or empty.
  • Ignoring unfixed upstream CVEs without compensating WAF/network controls.
  • Developers disable checks locally because PR feedback exceeds 30 minutes.

Container image scanning

Container scans inspect OS packages and application layers in built images. Filesystem SCA misses packages installed via apt in Dockerfile. Scan the digest you deploy—not a tag that can be mutated.

Tooling landscape

Tool Category When to run Default gate
Trivy OS + app CVEs, SBOM Post-build CRITICAL gate typical
Grype Anchore ecosystem Post-build CRITICAL gate typical
Snyk Container Policy dashboards Post-build HIGH gate typical
Clair Quay-native Registry HIGH gate typical

What to block vs warn

Severity Examples Typical gate policy
CRITICAL RCE, auth bypass, exposed secrets in prod Block merge & deploy immediately
HIGH SQLi, SSRF, privilege escalation paths Block within SLA (24–72h)
MEDIUM XSS, misconfigurations with limited blast radius Track in backlog; fix before next release
LOW Info leaks, best-practice deviations Accept risk or fix opportunistically

Pipeline integration

.github/workflows/container-scanning.yml
name: Container scan
on:
  push:
    branches: [main]
jobs:
  build-scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Build
        run: docker build -t app:${{ github.sha }} .
      - uses: aquasecurity/trivy-action@master
        with:
          image-ref: app:${{ github.sha }}
          format: sarif
          output: trivy-image.sarif
          severity: CRITICAL,HIGH
          exit-code: 1
      - uses: github/codeql-action/upload-sarif@v3
        with:
          sarif_file: trivy-image.sarif
.gitlab-ci.yml
container-scan:
  stage: scan
  image: docker:24
  services:
    - docker:24-dind
  variables:
    DOCKER_TLS_CERTDIR: "/certs"
  script:
    - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
    - trivy image --exit-code 1 --severity CRITICAL,HIGH
        --format template --template @/contrib/sarif.tpl
        --output gl-container-scanning-report.sarif
        $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
  artifacts:
    reports:
      container_scanning: gl-container-scanning-report.sarif
💡 Pro Tip

Distroless and minimal base images reduce CVE count but do not eliminate scanning—glibc and OpenSSL still ship in the chain.

🔬 Under the Hood

Cache Trivy DB with aquasecurity/trivy-action cache-dir—cold DB download adds 30–90s per job.

Operational checklist

  1. Define severity policy document—what CRITICAL means for your org.
  2. Run tool in advisory mode for one sprint; export baseline finding count.
  3. Enable SARIF upload; wire Slack/PagerDuty on new CRITICAL in main.
  4. Review false positive rate monthly; tune rulesets or suppressions with expiry dates.
  5. Measure mean time to remediate per category—SAST vs SCA vs container often differ by 10×.

Common failure modes

  • Scanning only default branch—feature branches ship vulns via cherry-pick.
  • No lockfile—SCA reports stale or empty.
  • Ignoring unfixed upstream CVEs without compensating WAF/network controls.
  • Developers disable checks locally because PR feedback exceeds 30 minutes.

DAST — dynamic application security testing

DAST probes a running application like an attacker—SQLi, XSS, auth bypass, exposed admin panels. Too early in CI (unit test stage) wastes time; run against staging after deploy with realistic auth fixtures.

Tooling landscape

Tool Category When to run Default gate
OWASP ZAP baseline Passive + spider Staging MEDIUM gate typical
OWASP ZAP full Active attack Nightly HIGH gate typical
Nuclei Template-based Staging HIGH gate typical
Burp CI Enterprise DAST Release HIGH gate typical

What to block vs warn

Severity Examples Typical gate policy
CRITICAL RCE, auth bypass, exposed secrets in prod Block merge & deploy immediately
HIGH SQLi, SSRF, privilege escalation paths Block within SLA (24–72h)
MEDIUM XSS, misconfigurations with limited blast radius Track in backlog; fix before next release
LOW Info leaks, best-practice deviations Accept risk or fix opportunistically

Pipeline integration

.github/workflows/dast.yml
name: DAST
on:
  workflow_dispatch:
  schedule:
    - cron: '0 3 * * 1'
jobs:
  zap-baseline:
    runs-on: ubuntu-latest
    steps:
      - uses: zaproxy/[email protected]
        with:
          target: https://staging.example.com
          rules_file_name: .zap/rules.tsv
          cmd_options: -a
      - uses: github/codeql-action/upload-sarif@v3
        if: always()
        with:
          sarif_file: report_json.sarif
.gitlab-ci.yml
dast-zap:
  stage: dast
  image: owasp/zap2docker-stable
  variables:
    STAGING_URL: https://staging.example.com
  script:
    - zap-baseline.py -t $STAGING_URL -J gl-dast-report.json
        -c .zap/rules.tsv -a
  artifacts:
    reports:
      dast: gl-dast-report.json
  rules:
    - if: $CI_COMMIT_BRANCH == "main"
      when: manual

nuclei:
  stage: dast
  image: projectdiscovery/nuclei:latest
  script:
    - nuclei -u $STAGING_URL -severity critical,high -json-export nuclei.json
  artifacts:
    paths: [nuclei.json]
⚠️ Pitfall

Authenticated DAST requires session cookies or OAuth flows—budget engineering time for test users and cleanup, not just tool licenses.

🔬 Under the Hood

Running active DAST against production without scope approval causes incidents. Always scope to staging; use read-only test data.

Operational checklist

  1. Define severity policy document—what CRITICAL means for your org.
  2. Run tool in advisory mode for one sprint; export baseline finding count.
  3. Enable SARIF upload; wire Slack/PagerDuty on new CRITICAL in main.
  4. Review false positive rate monthly; tune rulesets or suppressions with expiry dates.
  5. Measure mean time to remediate per category—SAST vs SCA vs container often differ by 10×.

Common failure modes

  • Scanning only default branch—feature branches ship vulns via cherry-pick.
  • No lockfile—SCA reports stale or empty.
  • Ignoring unfixed upstream CVEs without compensating WAF/network controls.
  • Developers disable checks locally because PR feedback exceeds 30 minutes.

IaC scanning — infrastructure as code

Terraform, Kubernetes manifests, Helm charts, and CloudFormation templates define production blast radius before a single pod runs. IaC scanners catch public S3 buckets, overly permissive security groups, and privileged pods at PR time.

Tooling landscape

Tool Category When to run Default gate
Checkov Multi-framework Terraform PR HIGH gate typical
tfsec Terraform-focused Terraform PR HIGH gate typical
KICS Broad IaC coverage PR HIGH gate typical
Kyverno / OPA K8s admission Deploy CRITICAL gate typical

What to block vs warn

Severity Examples Typical gate policy
CRITICAL RCE, auth bypass, exposed secrets in prod Block merge & deploy immediately
HIGH SQLi, SSRF, privilege escalation paths Block within SLA (24–72h)
MEDIUM XSS, misconfigurations with limited blast radius Track in backlog; fix before next release
LOW Info leaks, best-practice deviations Accept risk or fix opportunistically

Pipeline integration

.github/workflows/iac-scanning.yml
name: IaC scan
on:
  pull_request:
    paths: ['terraform/**', 'k8s/**', 'helm/**']
jobs:
  checkov:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: bridgecrewio/checkov-action@master
        with:
          directory: terraform/
          framework: terraform
          output_format: sarif
          output_file_path: checkov.sarif
      - uses: github/codeql-action/upload-sarif@v3
        with:
          sarif_file: checkov.sarif
  kubeconform:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: |
          curl -sL https://github.com/yannh/kubeconform/releases/latest/download/kubeconform-linux-amd64.tar.gz | tar xz
          ./kubeconform -summary k8s/
.gitlab-ci.yml
iac-sast:
  stage: test
  image:
    name: bridgecrew/checkov:latest
    entrypoint: [""]
  script:
    - checkov -d terraform/ --framework terraform
        --output sarif --output-file-path checkov.sarif
  artifacts:
    reports:
      sast: checkov.sarif
  rules:
    - changes:
        - terraform/**/*
        - k8s/**/*

kics:
  stage: test
  image: checkmarx/kics:latest
  script:
    - kics scan -p . -o results --report-formats sarif
  artifacts:
    paths: [results/results.sarif]
🔬 Under the Hood

Separate plan-time policy (Checkov on PR) from runtime admission (Kyverno in cluster)—defense in depth for drift and emergency hotfixes.

💡 Pro Tip

Terraform plan shows intent; Checkov scans HCL statically. OPA Conftest can evaluate rendered JSON/YAML from terraform show -json for plan-aware policy.

Operational checklist

  1. Define severity policy document—what CRITICAL means for your org.
  2. Run tool in advisory mode for one sprint; export baseline finding count.
  3. Enable SARIF upload; wire Slack/PagerDuty on new CRITICAL in main.
  4. Review false positive rate monthly; tune rulesets or suppressions with expiry dates.
  5. Measure mean time to remediate per category—SAST vs SCA vs container often differ by 10×.

Common failure modes

  • Scanning only default branch—feature branches ship vulns via cherry-pick.
  • No lockfile—SCA reports stale or empty.
  • Ignoring unfixed upstream CVEs without compensating WAF/network controls.
  • Developers disable checks locally because PR feedback exceeds 30 minutes.

License compliance scanning

Open-source licenses are legal contracts. GPL in a statically linked mobile SDK, or AGPL in a SaaS backend, can force disclosure or licensing fees. License scanners map dependencies to SPDX identifiers and flag policy violations.

Tooling landscape

Tool Category When to run Default gate
FOSSA Policy engine + legal Release MEDIUM gate typical
License Finder Ruby gem, adaptable CI MEDIUM gate typical
Trivy license SPDX in SBOM Build LOW gate typical
Scancode Deep audit Quarterly LOW gate typical

What to block vs warn

Severity Examples Typical gate policy
CRITICAL RCE, auth bypass, exposed secrets in prod Block merge & deploy immediately
HIGH SQLi, SSRF, privilege escalation paths Block within SLA (24–72h)
MEDIUM XSS, misconfigurations with limited blast radius Track in backlog; fix before next release
LOW Info leaks, best-practice deviations Accept risk or fix opportunistically

Pipeline integration

.github/workflows/license-scanning.yml
name: License scan
on:
  pull_request:
  push:
    tags: ['v*']
jobs:
  license-check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
      - run: npm ci
      - run: npx license-checker --production --summary --failOn GPL
      - uses: fossas/fossa-action@v1
        with:
          api-key: ${{ secrets.FOSSA_API_KEY }}
.gitlab-ci.yml
license-scan:
  stage: compliance
  image: node:20
  script:
    - npm ci
    - npx license-checker --production --summary --failOn GPL
  rules:
    - if: $CI_COMMIT_TAG

fossa:
  stage: compliance
  image: fossa/fossa-cli:latest
  script:
    - fossa analyze
    - fossa test
  rules:
    - if: $CI_COMMIT_BRANCH == "main"
  allow_failure: false
🎯 Interview Tip

Legal teams care about distribution method—SaaS using AGPL libraries triggers different obligations than on-prem shrink-wrap.

🔬 Under the Hood

Explain the difference between permissive (MIT, Apache-2.0) and copyleft (GPL, AGPL) and how SBOM SPDX fields feed license policy automation.

Operational checklist

  1. Define severity policy document—what CRITICAL means for your org.
  2. Run tool in advisory mode for one sprint; export baseline finding count.
  3. Enable SARIF upload; wire Slack/PagerDuty on new CRITICAL in main.
  4. Review false positive rate monthly; tune rulesets or suppressions with expiry dates.
  5. Measure mean time to remediate per category—SAST vs SCA vs container often differ by 10×.

Common failure modes

  • Scanning only default branch—feature branches ship vulns via cherry-pick.
  • No lockfile—SCA reports stale or empty.
  • Ignoring unfixed upstream CVEs without compensating WAF/network controls.
  • Developers disable checks locally because PR feedback exceeds 30 minutes.

SARIF aggregation & security dashboards

SARIF (Static Analysis Results Interchange Format) is the lingua franca of security findings. Upload SARIF from Semgrep, Trivy, Checkov into GitHub Advanced Security or DefectDojo for unified triage, deduplication, and SLA tracking.

Tooling landscape

Tool Category When to run Default gate
GitHub code scanning Native SARIF ingest Per repo LOW gate typical
GitLab security dashboard Built-in reports Per project LOW gate typical
DefectDojo Multi-tool aggregator Org-wide MEDIUM gate typical
Azure DevOps SARIF ADO integration Enterprise LOW gate typical

What to block vs warn

Severity Examples Typical gate policy
CRITICAL RCE, auth bypass, exposed secrets in prod Block merge & deploy immediately
HIGH SQLi, SSRF, privilege escalation paths Block within SLA (24–72h)
MEDIUM XSS, misconfigurations with limited blast radius Track in backlog; fix before next release
LOW Info leaks, best-practice deviations Accept risk or fix opportunistically

Pipeline integration

.github/workflows/sarif.yml
name: SARIF upload
on:
  workflow_run:
    workflows: [SAST, SCA, Container scan, IaC scan]
    types: [completed]
jobs:
  aggregate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/download-artifact@v4
      - run: |
          for f in *.sarif; do
            echo "Uploading $f"
            gh api repos/${{ github.repository }}/code-scanning/sarifs \
              -f sarif=@$f -f ref=refs/heads/main
          done
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
.gitlab-ci.yml
# GitLab auto-ingests when artifacts.reports.sast/container_scanning/dependency_scanning are set
include:
  - template: Security/SAST.gitlab-ci.yml
  - template: Security/Dependency-Scanning.gitlab-ci.yml
  - template: Security/Container-Scanning.gitlab-ci.yml

variables:
  SECURE_ANALYZERS_PREFIX: "registry.gitlab.com/gitlab-org/security-products/analyzers"
  SAST_EXCLUDED_PATHS: "spec, test, tmp"

security-dashboard:
  stage: report
  script:
    - echo "Findings visible in Security & Compliance > Vulnerability Report"
  needs:
    - sast
    - dependency_scanning
    - container_scanning
💡 Pro Tip

Dedup across tools is hard—same CVE may appear in SCA and container scan. Use SARIF fingerprints or DefectDojo hashcode algorithms.

🔬 Under the Hood

GitHub Code Scanning shows findings inline in PRs when SARIF includes valid partialFingerprints and file paths relative to repo root.

Operational checklist

  1. Define severity policy document—what CRITICAL means for your org.
  2. Run tool in advisory mode for one sprint; export baseline finding count.
  3. Enable SARIF upload; wire Slack/PagerDuty on new CRITICAL in main.
  4. Review false positive rate monthly; tune rulesets or suppressions with expiry dates.
  5. Measure mean time to remediate per category—SAST vs SCA vs container often differ by 10×.

Common failure modes

  • Scanning only default branch—feature branches ship vulns via cherry-pick.
  • No lockfile—SCA reports stale or empty.
  • Ignoring unfixed upstream CVEs without compensating WAF/network controls.
  • Developers disable checks locally because PR feedback exceeds 30 minutes.