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.
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 |
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.
When asked "how do you secure CI/CD?", answer with categories + placement + SARIF aggregation + severity gates—not a single vendor name.
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
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
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
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
SAST false positives erode trust. Start in audit mode for two sprints, then enable blocking on CRITICAL only.
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
- Define severity policy document—what CRITICAL means for your org.
- Run tool in advisory mode for one sprint; export baseline finding count.
- Enable SARIF upload; wire Slack/PagerDuty on new CRITICAL in main.
- Review false positive rate monthly; tune rulesets or suppressions with expiry dates.
- 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
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
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"
Pinning major versions without Renovate creates security debt—teams fear the 200-PR backlog. Automate small incremental updates weekly.
Log4Shell (CVE-2021-44228) proved SCA alone is insufficient: you need runtime WAF rules and emergency patch SLAs alongside dependency scanning.
Operational checklist
- Define severity policy document—what CRITICAL means for your org.
- Run tool in advisory mode for one sprint; export baseline finding count.
- Enable SARIF upload; wire Slack/PagerDuty on new CRITICAL in main.
- Review false positive rate monthly; tune rulesets or suppressions with expiry dates.
- 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
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
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
If a secret is committed, rotate immediately—scan clean does not mean attackers did not clone the repo during the exposure window.
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
- Define severity policy document—what CRITICAL means for your org.
- Run tool in advisory mode for one sprint; export baseline finding count.
- Enable SARIF upload; wire Slack/PagerDuty on new CRITICAL in main.
- Review false positive rate monthly; tune rulesets or suppressions with expiry dates.
- 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
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
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
Distroless and minimal base images reduce CVE count but do not eliminate scanning—glibc and OpenSSL still ship in the chain.
Cache Trivy DB with aquasecurity/trivy-action cache-dir—cold DB download adds 30–90s per job.
Operational checklist
- Define severity policy document—what CRITICAL means for your org.
- Run tool in advisory mode for one sprint; export baseline finding count.
- Enable SARIF upload; wire Slack/PagerDuty on new CRITICAL in main.
- Review false positive rate monthly; tune rulesets or suppressions with expiry dates.
- 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
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
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]
Authenticated DAST requires session cookies or OAuth flows—budget engineering time for test users and cleanup, not just tool licenses.
Running active DAST against production without scope approval causes incidents. Always scope to staging; use read-only test data.
Operational checklist
- Define severity policy document—what CRITICAL means for your org.
- Run tool in advisory mode for one sprint; export baseline finding count.
- Enable SARIF upload; wire Slack/PagerDuty on new CRITICAL in main.
- Review false positive rate monthly; tune rulesets or suppressions with expiry dates.
- 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
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/
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]
Separate plan-time policy (Checkov on PR) from runtime admission (Kyverno in cluster)—defense in depth for drift and emergency hotfixes.
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
- Define severity policy document—what CRITICAL means for your org.
- Run tool in advisory mode for one sprint; export baseline finding count.
- Enable SARIF upload; wire Slack/PagerDuty on new CRITICAL in main.
- Review false positive rate monthly; tune rulesets or suppressions with expiry dates.
- 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
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 }}
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
Legal teams care about distribution method—SaaS using AGPL libraries triggers different obligations than on-prem shrink-wrap.
Explain the difference between permissive (MIT, Apache-2.0) and copyleft (GPL, AGPL) and how SBOM SPDX fields feed license policy automation.
Operational checklist
- Define severity policy document—what CRITICAL means for your org.
- Run tool in advisory mode for one sprint; export baseline finding count.
- Enable SARIF upload; wire Slack/PagerDuty on new CRITICAL in main.
- Review false positive rate monthly; tune rulesets or suppressions with expiry dates.
- 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
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 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
Dedup across tools is hard—same CVE may appear in SCA and container scan. Use SARIF fingerprints or DefectDojo hashcode algorithms.
GitHub Code Scanning shows findings inline in PRs when SARIF includes valid partialFingerprints and file paths relative to repo root.
Operational checklist
- Define severity policy document—what CRITICAL means for your org.
- Run tool in advisory mode for one sprint; export baseline finding count.
- Enable SARIF upload; wire Slack/PagerDuty on new CRITICAL in main.
- Review false positive rate monthly; tune rulesets or suppressions with expiry dates.
- 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.