Skip to content

Security Scanning (DevSecOps)

First PublishedByAtif Alam

DevSecOps integrates security into every stage of the CI/CD pipeline — catching vulnerabilities early (“shift left”) instead of discovering them in production. This page covers the scanning tools and techniques that make security a first-class citizen in your pipeline.

Traditional:
Code ──► Build ──► Test ──► Deploy ──► Security audit (months later)
expensive to fix
Shift Left (DevSecOps):
Code ──► SAST ──► Build ──► SCA ──► Container scan ──► DAST ──► Deploy
▲ ▲ ▲ ▲
cheap to fix cheap to fix cheap to fix still cheaper

Finding a vulnerability in development costs 10x less to fix than in production. Security scanning in CI catches issues before they reach users.

TypeWhat It ScansWhen It RunsFinds
SASTSource codeBefore/during buildSQL injection, XSS, hardcoded secrets, insecure patterns
SCADependenciesAfter dependency installKnown CVEs in packages
Container ScanningDocker imagesAfter image buildOS-level and package CVEs in the image
IaC ScanningTerraform, CloudFormation, K8s YAMLBefore apply/deployMisconfigurations, insecure defaults
Secret DetectionSource code and git historyPre-commit or CIAPI keys, passwords, tokens committed to code
DASTRunning applicationAfter deployment (staging)Runtime vulnerabilities (XSS, CSRF, auth issues)
License ScanningDependenciesDuring buildIncompatible open-source licenses

SAST (Static Application Security Testing)

Section titled “SAST (Static Application Security Testing)”

SAST analyzes source code without running the application — looking for insecure patterns, injection flaws, and coding mistakes.

ToolLanguagesOpen SourceNotes
Semgrep30+ languagesYesRule-based, fast, easy custom rules
CodeQLC/C++, C#, Go, Java, JS, Python, RubyFree on GitHubDeep dataflow analysis, GitHub-native
SonarQube30+ languagesCommunity + CommercialCode quality + security, dashboards
BanditPythonYesPython-specific security linter
BrakemanRuby (Rails)YesRails security scanner
gosecGoYesGo security linter
ESLint security pluginsJavaScript/TypeScriptYeseslint-plugin-security, eslint-plugin-no-secrets
security-sast:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: returntocorp/semgrep-action@v1
with:
config: >-
p/default
p/owasp-top-ten
p/python
security-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/autobuild@v3
- uses: github/codeql-action/analyze@v3

CodeQL results appear in GitHub’s Security tabCode scanning alerts.

include:
- template: Security/SAST.gitlab-ci.yml
# GitLab auto-detects languages and runs appropriate scanners
# Results appear in the MR security widget

SCA scans your dependencies for known vulnerabilities (CVEs) using public databases like the National Vulnerability Database (NVD).

ToolTypeNotes
DependabotGitHub-nativeAuto-creates PRs to update vulnerable deps
RenovateMulti-platformAuto-update PRs, highly configurable
SnykSaaS + CLIDeep SCA + container scanning, fix suggestions
TrivyCLI (open source)SCA + container + IaC scanning (all-in-one)
OWASP Dependency-CheckCLI (open source)Java/Maven focused, broad language support
npm auditBuilt-in (Node.js)npm audit / npm audit fix
pip-auditBuilt-in (Python)pip-audit
cargo auditBuilt-in (Rust)cargo audit
security-sca:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: aquasecurity/trivy-action@master
with:
scan-type: fs
scan-ref: .
severity: CRITICAL,HIGH
exit-code: 1 # Fail the pipeline on critical/high
.github/dependabot.yml
version: 2
updates:
- package-ecosystem: npm
directory: /
schedule:
interval: weekly
open-pull-requests-limit: 10
labels: [dependencies]
reviewers: [myorg/security-team]
- package-ecosystem: docker
directory: /
schedule:
interval: weekly
- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly

Dependabot automatically opens PRs when a dependency has a known vulnerability.

Scans Docker images for vulnerabilities in OS packages and application dependencies embedded in the image.

ToolOpen SourceNotes
TrivyYesFast, comprehensive, supports OS + language deps
GrypeYes (Anchore)Fast, pairs with Syft for SBOM
Snyk ContainerCommercial + free tierFix suggestions, base image recommendations
AWS ECR ScanningAWS-nativeBasic (Clair) or enhanced (Inspector)
Azure Defender for ContainersAzure-nativeScans ACR images
container-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: docker build -t myapp:${{ github.sha }} .
- uses: aquasecurity/trivy-action@master
with:
image-ref: myapp:${{ github.sha }}
severity: CRITICAL,HIGH
exit-code: 1
format: sarif
output: trivy-results.sarif
- uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: trivy-results.sarif # Results appear in GitHub Security tab
include:
- template: Security/Container-Scanning.gitlab-ci.yml
container_scanning:
variables:
CS_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
PracticeImpact
Use minimal base imagesalpine, distroless, scratch — fewer packages = fewer CVEs
Multi-stage buildsOnly copy build artifacts into the final image (no build tools)
Pin base image versionsnode:20.11-alpine not node:latest
Update base images regularlyRebuild images weekly to pick up OS security patches
Don’t run as rootUSER nonroot in Dockerfile

Scans Infrastructure as Code (Terraform, CloudFormation, Kubernetes YAML, Dockerfiles) for security misconfigurations.

ToolScansOpen SourceNotes
CheckovTerraform, CloudFormation, K8s, DockerYes1000+ built-in rules
tfsec (now part of Trivy)TerraformYesTerraform-specific, fast
KICSTerraform, K8s, Docker, AnsibleYesMulti-IaC scanner
TrivyTerraform, CloudFormation, K8s, DockerYesAll-in-one (IaC + SCA + container)
OPA / ConftestAny (Rego policies)YesCustom policy engine
iac-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: bridgecrewio/checkov-action@v12
with:
directory: infra/
framework: terraform
soft_fail: false # Fail pipeline on violations
output_format: sarif
output_file_path: checkov.sarif
- uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: checkov.sarif
FindingRisk
S3 bucket with public accessData leak
Security group allowing 0.0.0.0/0 on port 22Unauthorized SSH access
RDS without encryption at restData exposure
Kubernetes pod running as rootContainer breakout
Azure storage account without HTTPS enforcementData interception
IAM policy with * resource and * actionOver-privileged access

Scans source code and git history for accidentally committed secrets — API keys, passwords, tokens, private keys.

ToolOpen SourceNotes
gitleaksYesScans git history, fast, regex + entropy
truffleHogYesDeep git history scanning, verified secrets
detect-secrets (Yelp)YesPre-commit hook + baseline file
GitHub Secret ScanningGitHub-nativeAutomatic for public repos, alerts on 200+ secret types
GitLab Secret DetectionGitLab-nativeBuilt-in CI template

Pre-Commit Hook (Prevent Secrets from Being Committed)

Section titled “Pre-Commit Hook (Prevent Secrets from Being Committed)”
.pre-commit-config.yaml
repos:
- repo: https://github.com/gitleaks/gitleaks
rev: v8.18.0
hooks:
- id: gitleaks
secret-detection:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # Full history for scanning
- uses: gitleaks/gitleaks-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
  1. Rotate the secret immediately — assume it’s compromised.
  2. Remove from code — don’t just delete; rewrite git history with git filter-repo or BFG Repo Cleaner.
  3. Add to .gitignore — prevent re-committing.
  4. Add a pre-commit hook — prevent future leaks.
  5. Audit usage — check if the secret was used maliciously.

DAST (Dynamic Application Security Testing)

Section titled “DAST (Dynamic Application Security Testing)”

DAST tests a running application by sending crafted requests and analyzing responses — like an automated penetration test.

ToolOpen SourceNotes
OWASP ZAPYesMost popular open-source DAST
NucleiYesTemplate-based, fast, community templates
Burp SuiteCommercialIndustry standard for pen testing
GitLab DASTGitLab-nativeBuilt-in CI template
dast:
runs-on: ubuntu-latest
needs: deploy-staging # Only run after staging deployment
steps:
- uses: zaproxy/action-full-scan@v0.10.0
with:
target: https://staging.myapp.com
rules_file_name: .zap-rules.tsv
cmd_options: '-a'

DAST runs after deployment to staging — it needs a live application to test against.

A recommended ordering for security scans in your pipeline:

push ──► SAST + Secret Detection ──► Build ──► SCA ──► Container Scan ──► Deploy Staging ──► DAST
(source code) │ (deps) (image) │ (running app)
│ │
└── IaC Scan (Terraform/K8s) ───────┘
name: Security
on:
push:
branches: [main]
pull_request:
jobs:
secrets:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: gitleaks/gitleaks-action@v2
sast:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: returntocorp/semgrep-action@v1
with:
config: p/default
sca:
runs-on: ubuntu-latest
needs: [secrets, sast]
steps:
- uses: actions/checkout@v4
- run: npm ci
- uses: aquasecurity/trivy-action@master
with:
scan-type: fs
severity: CRITICAL,HIGH
exit-code: 1
container-scan:
runs-on: ubuntu-latest
needs: sca
steps:
- uses: actions/checkout@v4
- run: docker build -t myapp:${{ github.sha }} .
- uses: aquasecurity/trivy-action@master
with:
image-ref: myapp:${{ github.sha }}
severity: CRITICAL,HIGH
exit-code: 1
iac-scan:
runs-on: ubuntu-latest
if: hashFiles('infra/**') != ''
steps:
- uses: actions/checkout@v4
- uses: bridgecrewio/checkov-action@v12
with:
directory: infra/

An SBOM is a complete list of components in your software — like an ingredient list. It’s increasingly required for compliance and supply chain security.

ToolFormatWhat It Scans
Syft (Anchore)SPDX, CycloneDXContainer images, filesystems
TrivySPDX, CycloneDXContainer images, filesystems
npm sbomSPDX, CycloneDXNode.js packages
GitHub Dependency GraphGitHub-nativeAuto-generated for supported ecosystems
Terminal window
# Generate SBOM with Syft
syft myapp:latest -o spdx-json > sbom.spdx.json
# Generate SBOM with Trivy
trivy image --format spdx-json -o sbom.spdx.json myapp:latest

Use cosign (Sigstore) to sign images and verify provenance:

Terminal window
# Sign an image
cosign sign --key cosign.key myregistry/myapp:v1.2.3
# Verify a signature
cosign verify --key cosign.pub myregistry/myapp:v1.2.3

Not all vulnerabilities are equal. Define a policy for how to handle each severity:

SeverityPipeline ActionSLA to Fix
CriticalBlock merge / deploymentWithin 24 hours
HighBlock merge / deploymentWithin 1 week
MediumWarn (allow merge)Within 1 month
LowInformationalBest effort
# Trivy: fail only on critical and high
- uses: aquasecurity/trivy-action@master
with:
severity: CRITICAL,HIGH
exit-code: 1 # Fail the pipeline
  • Shift left — run security scans early in the pipeline (SAST, secret detection) to catch issues cheaply.
  • SAST (Semgrep, CodeQL) scans source code; SCA (Trivy, Snyk, Dependabot) scans dependencies; Container scanning (Trivy, Grype) scans images.
  • IaC scanning (Checkov, tfsec) catches misconfigurations in Terraform and Kubernetes manifests.
  • Secret detection (gitleaks, pre-commit hooks) prevents API keys from reaching the repository.
  • DAST (ZAP, Nuclei) tests the running application after deployment to staging.
  • SBOM provides a component inventory for supply chain transparency.
  • Define a severity policy — block on critical/high, warn on medium, inform on low.
  • Use Dependabot or Renovate to auto-update vulnerable dependencies.