Every engineering team has experienced the friction: a release is ready, the sprint is closing, and then a security review lands a list of findings that sends the team back to the drawing board. Deadlines slip. Developers grow resentful of security. Security teams grow frustrated with developers who treat their findings as optional. The result is an adversarial relationship that serves nobody — least of all the customers whose data is at risk.
DevSecOps is the practice of embedding security into every phase of the software delivery lifecycle, from the first line of code to production monitoring. It is not a tool, a team, or a certification. It is an operating model that makes security a shared responsibility across development, operations, and security — continuous, automated, and fast enough to keep pace with modern delivery cadences.
This guide walks through how to design security gates at every stage of your CI/CD pipeline: what to scan, when to scan it, how to define pass/fail thresholds, and how to measure whether your security program is actually making your software safer without destroying your team's velocity.
The core principle: Security gates should provide fast, actionable feedback at the earliest possible moment. A vulnerability caught in a developer's IDE costs minutes to fix. The same vulnerability caught in production costs weeks, reputation, and potentially millions in breach response.
What Is DevSecOps and Why "Shift Left" Matters
The term "shift left" refers to moving security activities earlier in the software development lifecycle (SDLC). In traditional models, security testing happened at the end — a manual penetration test before release, an audit before go-live. This approach has three fatal flaws:
- Cost escalation: The later a vulnerability is discovered, the more expensive it is to remediate. Studies from the National Institute of Standards and Technology (NIST) consistently show that fixing a defect in production costs 30x more than fixing it during design, and 6x more than fixing it during development.
- Bottleneck creation: When security is a single gate at the end, it becomes a chokepoint. Teams queue for reviews. Reviewers are overwhelmed. The security team becomes the department of "no."
- Context loss: By the time a penetration test reveals a design flaw, the developer who wrote the code has moved on to three other features. Remediation requires re-learning context, re-testing adjacent functionality, and coordinating across teams.
DevSecOps eliminates these problems by distributing security checks across the entire pipeline. Instead of one massive gate at the end, you get many small, fast gates throughout. Each gate catches a specific class of issues at the moment when the team has the most context and the fix is cheapest.
DevSecOps vs. Traditional Security
Traditional security is a phase. DevSecOps is a property of the system. In a mature DevSecOps organization, asking "when does security happen?" is like asking "when does quality happen?" — it happens everywhere, all the time, automatically.
The shift-left model does not mean security teams are no longer needed. On the contrary, their role evolves from gatekeepers to enablers. Security engineers define the policies, curate the rulesets, tune the thresholds, and build the automation that empowers developers to self-serve secure code. They move from reviewing every pull request to building the systems that review every pull request at machine speed.
The Problem: Security as a Bottleneck vs. Security as an Enabler
Most organizations that attempt to add security to their pipelines make the same mistake: they bolt on security tools without thinking about the developer experience. The result is predictable and devastating to the security program itself.
The Bottleneck Pattern
Consider what happens when a team adds a SAST scanner to their CI pipeline without preparation:
- The scanner runs on every pull request and takes 12 minutes to complete
- It produces 847 findings on the first run, most of them false positives or low-severity noise
- Developers cannot merge until findings are resolved
- Nobody knows which findings matter and which are noise
- Developers start adding
// noseccomments to suppress everything - Within a month, the team creates a bypass process that effectively disables the scanner
- Security loses credibility and influence
This is not a failure of the scanner. It is a failure of implementation design. The scanner was deployed without severity thresholds, without baseline suppression, without incremental scanning, and without a plan for triaging the initial backlog.
The Enabler Pattern
Now consider the alternative — a team that deploys the same scanner with a deliberate strategy:
- The scanner is configured to run incrementally, analyzing only changed files on pull requests (full scans run nightly)
- Only Critical and High severity findings block the build; Medium findings generate warnings; Low findings are logged for trending
- The existing codebase is baselined: known findings in existing code are tracked separately from new findings introduced in pull requests
- Each finding includes a remediation suggestion with a code fix the developer can apply
- Scan results appear as inline comments on the pull request, right next to the vulnerable line of code
- The scanner completes in under 90 seconds because it only scans the delta
The 90-second rule: If a security gate takes longer than 90 seconds on a typical pull request, developers will find ways to circumvent it. Speed is not a nice-to-have — it is a prerequisite for adoption. Design every gate to be fast by default, with deeper scans running asynchronously.
The difference between these two approaches is not the tooling — it is the policy design. The same scanner, configured thoughtfully, transforms from a bottleneck into an enabler that developers actually appreciate because it catches their mistakes before peer reviewers do.
Security Gates Across the Pipeline
A mature DevSecOps pipeline has security gates at five distinct stages. Each stage targets different vulnerability classes and operates at different speeds. The goal is to catch as much as possible as early as possible, with progressively deeper (and slower) analysis at later stages.
Gate 1: Pre-Commit — Secrets Scanning and Linting
The pre-commit gate runs on the developer's local machine before code ever reaches the repository. This is the fastest feedback loop in the entire pipeline: the developer gets results in seconds, while the code is still fresh in their mind.
What to scan:
- Secrets detection: API keys, database credentials, private keys, tokens, and passwords that should never be committed to version control. Even in private repositories, committed secrets end up in git history forever and are regularly exfiltrated through compromised CI systems, exposed
.gitdirectories, or repository access misconfiguration. - Security linting: Language-specific rules that catch common security mistakes at the syntax level. Examples include flagging
eval()in JavaScript,os.system()with string concatenation in Python, or SQL string interpolation in any language. - Commit message validation: Ensuring commits that reference tickets, PRs, or security fixes are properly tagged for traceability.
# These should NEVER appear in your codebase AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY DATABASE_URL=postgres://admin:P@ssw0rd123@db.internal:5432/production STRIPE_SECRET_KEY=sk_live_4eC39HqLyjWDarjtT1zdp7dc GITHUB_TOKEN=ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx # Hardcoded in application code const API_KEY = "AIzaSyD-9tSrke72PouQMnMX-a7eZSW0jkFMBWY"; private static final String JWT_SECRET = "mySuper$ecretKey2026!";
# Environment variables loaded at runtime (never committed)
AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}
DATABASE_URL=${DATABASE_URL}
# Application code references environment or secrets manager
const API_KEY = process.env.GOOGLE_API_KEY;
String jwtSecret = SecretsManager.getSecret("jwt-signing-key");
# .gitignore properly configured
.env
.env.local
*.pem
*.key
credentials.json
Critical insight: Pre-commit hooks are a first line of defense, not a guarantee. Developers can skip hooks with --no-verify, and hooks don't run in all environments. Always back up pre-commit secrets scanning with server-side scanning in your CI pipeline. Defense in depth applies to your pipeline, not just your application.
Gate 2: Build — SAST, SCA, and License Compliance
The build gate runs in your CI system every time code is pushed to a pull request or merged to a protected branch. This is where the heaviest automated analysis happens, and it covers three distinct domains.
Static Application Security Testing (SAST)
SAST analyzes source code without executing it, tracing data flows from untrusted inputs (HTTP parameters, file uploads, database results) to dangerous operations (SQL queries, OS commands, HTML rendering). The core technique is taint analysis: marking data as "tainted" when it enters from an untrusted source and flagging it when it reaches a "sink" without passing through a sanitization function.
SAST is most effective at catching:
- SQL injection, NoSQL injection, and command injection
- Cross-site scripting (XSS) via unsanitized output
- Path traversal and file inclusion
- Insecure cryptographic usage (weak algorithms, hardcoded keys)
- Authentication and session management flaws
# SAST will flag this: user input flows directly to SQL query
def get_user(username):
query = f"SELECT * FROM users WHERE name = '{username}'"
return db.execute(query)
# SAST will flag this: unsanitized data rendered in HTML
@app.route('/search')
def search():
term = request.args.get('q')
return f"<h1>Results for: {term}</h1>"
# Parameterized query: input is treated as data, not code
def get_user(username):
query = "SELECT * FROM users WHERE name = %s"
return db.execute(query, (username,))
# Output encoding: user input is escaped before rendering
@app.route('/search')
def search():
term = escape(request.args.get('q'))
return render_template('search.html', term=term)
Software Composition Analysis (SCA)
SCA scans your dependency manifests (package.json, requirements.txt, go.mod, pom.xml, Gemfile.lock) and builds a complete dependency graph including transitive dependencies. It then cross-references every package version against vulnerability databases (NVD, GitHub Advisory Database, OSV) to identify known CVEs.
Modern SCA goes beyond simple version matching:
- Reachability analysis: Determines whether your code actually calls the vulnerable function in a dependency, dramatically reducing false positives
- Exploit prediction: Prioritizes CVEs that have known exploits in the wild over theoretical vulnerabilities
- Fix guidance: Identifies the minimum version upgrade that resolves the vulnerability without breaking compatibility
- Transitive dependency mapping: Shows exactly which direct dependency introduced the vulnerable transitive dependency
License Compliance
SCA tools also analyze the licenses of your dependencies. This is a legal and business risk that is frequently overlooked in security pipelines but can have severe consequences. Common policy violations include:
- GPL-licensed dependencies in proprietary commercial software (copyleft infection)
- AGPL dependencies in SaaS products (network copyleft)
- Dependencies with no declared license (legal uncertainty)
- Dependencies with licenses incompatible with your distribution model
License risk is real: A single AGPL dependency in your SaaS product could theoretically require you to open-source your entire application. License scanning should be a blocking gate with the same severity as critical vulnerability detection.
Gate 3: Test — DAST and Container Scanning
The test gate runs against a deployed instance of your application in a staging or ephemeral environment. Unlike SAST, which analyzes code without running it, DAST interacts with the running application from the outside — simulating how an attacker would probe your system.
Dynamic Application Security Testing (DAST)
DAST crawls your application, discovers endpoints, and sends malicious payloads to test for vulnerabilities. It excels at finding issues that are difficult or impossible to detect in source code alone:
- Authentication and session issues: Weak session tokens, missing secure flags on cookies, session fixation
- Security header misconfiguration: Missing Content-Security-Policy, X-Frame-Options, HSTS
- Server-Side Request Forgery (SSRF): Applications that fetch user-provided URLs without validation
- CORS misconfiguration: Overly permissive cross-origin resource sharing policies
- Information disclosure: Verbose error messages, exposed debug endpoints, directory listing
DAST is inherently slower than SAST because it requires a running application and performs network-based testing. In a DevSecOps pipeline, you have two options for managing this:
- Targeted DAST: Run a focused scan against only the endpoints modified in the current change (fast, suitable for PR gates)
- Full DAST: Run a comprehensive crawl and attack simulation on a nightly or weekly schedule (thorough, results feed into the backlog)
Container Image Scanning
If your application is containerized, the test stage should also scan your container images. Container scanning examines the base image and all installed packages for known vulnerabilities. A common and dangerous pattern is building on top of full OS images that contain hundreds of unnecessary packages, each expanding your attack surface.
# Dockerfile using full OS image with unnecessary packages
FROM ubuntu:latest
RUN apt-get update && apt-get install -y \
python3 python3-pip curl wget vim net-tools
COPY . /app
RUN pip3 install -r requirements.txt
# Running as root (default)
CMD ["python3", "/app/main.py"]
# Problems:
# - "latest" tag is unpinnable and unpredictable
# - Full OS image has hundreds of unnecessary packages
# - Tools like wget, curl, vim aid post-exploitation
# - Running as root inside the container
# Minimal, pinned, non-root container
FROM python:3.12-slim@sha256:a1b2c3d4e5f6... AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
FROM python:3.12-slim@sha256:a1b2c3d4e5f6...
RUN groupadd -r appuser && useradd -r -g appuser appuser
WORKDIR /app
COPY --from=builder /usr/local/lib/python3.12 /usr/local/lib/python3.12
COPY --from=builder /usr/local/bin /usr/local/bin
COPY . .
USER appuser
HEALTHCHECK CMD ["python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8080/health')"]
CMD ["python", "main.py"]
# Improvements:
# - Slim base image with minimal packages
# - Digest-pinned for reproducibility
# - Multi-stage build reduces final image size
# - Non-root user limits container escape impact
# - No unnecessary tools installed
Gate 4: Deploy — IaC Scanning and Config Validation
The deploy gate analyzes the infrastructure definitions and deployment configurations that determine how your application runs in production. Even if your application code is perfectly secure, insecure infrastructure can expose it to catastrophic risk.
Infrastructure-as-Code (IaC) Scanning
IaC scanners analyze Terraform, CloudFormation, Kubernetes manifests, Helm charts, and ARM templates for security misconfigurations. Common findings include:
- S3 buckets or storage accounts with public access enabled
- Security groups or network policies allowing unrestricted ingress (0.0.0.0/0)
- Databases without encryption at rest or in transit
- IAM roles with overly permissive policies (wildcards in actions or resources)
- Kubernetes pods running as privileged or with host network access
- Missing logging and audit trail configuration
# Terraform: Insecure S3 bucket configuration
resource "aws_s3_bucket" "data" {
bucket = "company-sensitive-data"
# No versioning, no encryption, no access logging
}
resource "aws_s3_bucket_policy" "data_policy" {
bucket = aws_s3_bucket.data.id
policy = jsonencode({
Statement = [{
Effect = "Allow"
Principal = "*" # PUBLIC ACCESS!
Action = "s3:GetObject"
Resource = "${aws_s3_bucket.data.arn}/*"
}]
})
}
resource "aws_security_group" "web" {
ingress {
from_port = 0
to_port = 65535
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"] # All ports open to the world
}
}
# Terraform: Hardened S3 bucket configuration
resource "aws_s3_bucket" "data" {
bucket = "company-sensitive-data"
}
resource "aws_s3_bucket_versioning" "data" {
bucket = aws_s3_bucket.data.id
versioning_configuration { status = "Enabled" }
}
resource "aws_s3_bucket_server_side_encryption_configuration" "data" {
bucket = aws_s3_bucket.data.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "aws:kms"
kms_master_key_id = aws_kms_key.data_key.arn
}
}
}
resource "aws_s3_bucket_public_access_block" "data" {
bucket = aws_s3_bucket.data.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
resource "aws_security_group" "web" {
ingress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["10.0.0.0/8"] # Internal network only
}
}
Configuration Validation
Beyond IaC scanning, the deploy gate should validate runtime configuration: environment variable completeness, TLS certificate validity and expiration, database connection encryption settings, and feature flag states. Catching a missing TLS certificate before deployment is infinitely preferable to discovering it when users see browser warnings.
Gate 5: Runtime — Monitoring and Log Intelligence
Security does not end at deployment. The runtime gate provides continuous monitoring of your production environment, detecting attacks in progress, anomalous behavior, and configuration drift.
What to monitor:
- Application logs: Failed authentication attempts, authorization failures, input validation errors, and exception rates that may indicate active exploitation
- Infrastructure metrics: Unusual network traffic patterns, unexpected outbound connections, CPU or memory spikes that may indicate cryptomining
- Configuration drift: Changes to security groups, IAM policies, or application settings that deviate from the declared IaC state
- Dependency monitoring: Newly published CVEs affecting your production dependencies, triggering automated alerts and remediation workflows
SENTINEL and runtime security: AI-powered log intelligence platforms can correlate events across application, infrastructure, and network layers to detect attack patterns that individual log entries would miss. Instead of writing static rules for every attack pattern, machine learning models establish behavioral baselines and alert on deviations — catching novel attacks that no rule anticipated.
Runtime monitoring closes the feedback loop. Findings from production monitoring flow back into the development process: if a new attack vector is observed in the wild, the SAST and DAST rulesets are updated, the security gate thresholds are adjusted, and the next pipeline run catches the pattern before it ships again.
Designing Quality Gates: Severity Thresholds and Break vs. Warn Policies
The most critical design decision in any DevSecOps pipeline is not which tools to use — it is how to configure the quality gates. A quality gate defines what happens when a security scanner produces findings: does the build break, does the team get a warning, or does the finding get silently logged?
The Three-Tier Policy Model
Effective quality gates use a three-tier model based on finding severity:
| Policy | Severity | Action | Example |
|---|---|---|---|
| Break | Critical, High | Pipeline fails, merge blocked until resolved | SQL injection, hardcoded secrets, RCE vulnerabilities |
| Warn | Medium | Pipeline passes with warning; finding tracked in backlog | Missing security headers, weak TLS versions, info disclosure |
| Log | Low, Informational | Finding recorded for trend analysis; no developer notification | Code quality suggestions, best-practice deviations |
Baseline and Delta Scanning
One of the most important concepts for maintaining developer productivity is baseline separation. When you first introduce a security scanner to an existing codebase, it will likely produce hundreds or thousands of findings in legacy code. These findings are real, but they should not block current development.
The solution is to establish a baseline: snapshot all existing findings and track them separately. The quality gate then applies only to net-new findings introduced in the current pull request. This ensures that:
- New code is held to the security standard immediately
- Legacy findings are tracked, prioritized, and remediated through a dedicated backlog
- Developers are never punished for pre-existing debt they did not create
- The total finding count trends downward over time as both new gates and backlog remediation take effect
# Example quality gate configuration
quality_gate:
sast:
break_on:
- severity: critical
- severity: high
categories: [injection, broken-auth, ssrf]
warn_on:
- severity: high
categories: [misconfiguration]
- severity: medium
ignore:
- severity: low
- severity: informational
baseline: .security/sast-baseline.json
scan_mode: delta # Only new findings block the build
sca:
break_on:
- cvss_score: ">= 9.0"
- exploit_available: true
cvss_score: ">= 7.0"
warn_on:
- cvss_score: ">= 4.0"
license_policy:
deny: [AGPL-3.0, GPL-3.0, SSPL-1.0]
warn: [GPL-2.0, LGPL-3.0]
allow_override: true # With security team approval
secrets:
break_on: all # Any detected secret blocks the build
allow_test_values: true # Ignore known test/example values
Grace Periods and SLA-Based Policies
For medium-severity findings that generate warnings, define clear SLA timelines:
- Critical: Must be resolved before merge (zero grace period)
- High: Must be resolved within the same sprint (1-2 weeks)
- Medium: Must be resolved within 30 days; auto-escalates to High after SLA breach
- Low: Tracked in backlog; reviewed quarterly
The key is that these SLAs are enforced automatically. When a Medium finding exceeds its 30-day window, the system automatically reclassifies it as High, and the next pipeline run will break on it. This prevents the common pattern where warnings accumulate indefinitely and are never addressed.
Pipeline Configuration Examples
Below are concrete, production-ready pipeline configurations for the two most widely used CI/CD platforms. These examples demonstrate how to wire security gates into your existing workflows with minimal friction.
GitHub Actions: Full DevSecOps Pipeline
name: DevSecOps Pipeline
on:
pull_request:
branches: [main, develop]
push:
branches: [main]
permissions:
contents: read
security-events: write
pull-requests: write
jobs:
# ---- Gate 1: Secrets & Lint ----
secrets-scan:
name: Secrets Detection
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Scan for secrets
uses: trufflesecurity/trufflehog@main
with:
extra_args: --only-verified --results=verified
- name: Security linting
run: |
pip install semgrep
semgrep scan --config=auto --error \
--severity=ERROR \
--json --output=semgrep-lint.json
# ---- Gate 2: SAST + SCA ----
sast:
name: Static Analysis (SAST)
runs-on: ubuntu-latest
needs: secrets-scan
steps:
- uses: actions/checkout@v4
- name: Run SAST scan
run: |
semgrep scan --config=p/owasp-top-ten \
--config=p/cwe-top-25 \
--error --severity=ERROR \
--json --output=sast-results.json \
--baseline-commit=${{ github.event.pull_request.base.sha }}
- name: Upload SAST results
if: always()
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: sast-results.sarif
sca:
name: Dependency Scan (SCA)
runs-on: ubuntu-latest
needs: secrets-scan
steps:
- uses: actions/checkout@v4
- name: Run SCA scan
run: |
# Scan dependencies for known CVEs
osv-scanner scan --lockfile=package-lock.json \
--format=json --output=sca-results.json
- name: Check license compliance
run: |
license-checker --production \
--failOn="AGPL-3.0;GPL-3.0;SSPL-1.0" \
--json --out=license-report.json
- name: Generate SBOM
run: |
syft . -o spdx-json > sbom.spdx.json
# Store SBOM as build artifact
- uses: actions/upload-artifact@v4
with:
name: sbom
path: sbom.spdx.json
# ---- Gate 3: DAST + Container Scan ----
container-scan:
name: Container Image Scan
runs-on: ubuntu-latest
needs: [sast, sca]
steps:
- uses: actions/checkout@v4
- name: Build container image
run: docker build -t app:${{ github.sha }} .
- name: Scan container image
run: |
trivy image --severity HIGH,CRITICAL \
--exit-code 1 \
--format sarif --output trivy-results.sarif \
app:${{ github.sha }}
- name: Upload container scan results
if: always()
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: trivy-results.sarif
dast:
name: Dynamic Analysis (DAST)
runs-on: ubuntu-latest
needs: container-scan
if: github.event_name == 'push' # Full DAST on merge only
steps:
- name: Deploy to staging
run: |
# Deploy ephemeral environment for DAST testing
echo "Deploying to staging..."
- name: Run DAST scan
run: |
zap-cli quick-scan --self-contained \
--start-options="-config api.disablekey=true" \
-l Medium \
https://staging.example.com
# ---- Gate 4: IaC Scan ----
iac-scan:
name: Infrastructure Scan (IaC)
runs-on: ubuntu-latest
needs: secrets-scan
steps:
- uses: actions/checkout@v4
- name: Scan Terraform configurations
run: |
tfsec . --format sarif --out tfsec-results.sarif \
--minimum-severity HIGH
- name: Scan Kubernetes manifests
run: |
kubesec scan k8s/*.yaml | jq '.[].score'
# Fail if any manifest scores below threshold
kubesec scan k8s/*.yaml | \
jq -e '.[].score >= 5' > /dev/null
# ---- Security Summary ----
security-gate:
name: Security Gate Decision
runs-on: ubuntu-latest
needs: [secrets-scan, sast, sca, container-scan, iac-scan]
if: always()
steps:
- name: Evaluate gate results
run: |
echo "Evaluating all security gate results..."
# Aggregate results from all gates
# Apply break/warn/log policies
# Post summary comment to PR
- name: Post PR comment
if: github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
script: |
github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: '## Security Gate Summary\n' +
'| Gate | Status |\n' +
'|------|--------|\n' +
'| Secrets | Passed |\n' +
'| SAST | Passed |\n' +
'| SCA | Passed |\n' +
'| Container | Passed |\n' +
'| IaC | Passed |'
})
Azure DevOps: Multi-Stage Security Pipeline
trigger:
branches:
include:
- main
- develop
pr:
branches:
include:
- main
pool:
vmImage: 'ubuntu-latest'
variables:
- group: security-config
- name: breakOnHighSeverity
value: true
stages:
# ---- Stage 1: Security Scanning ----
- stage: SecurityScanning
displayName: 'Security Gates'
jobs:
- job: SecretsDetection
displayName: 'Secrets Scanning'
steps:
- task: Bash@3
displayName: 'Detect hardcoded secrets'
inputs:
targetType: inline
script: |
pip install detect-secrets
detect-secrets scan --baseline .secrets.baseline \
--exclude-files 'test/.*' \
--exclude-secrets 'EXAMPLE|PLACEHOLDER'
detect-secrets audit .secrets.baseline --report
- job: StaticAnalysis
displayName: 'SAST Scan'
dependsOn: SecretsDetection
steps:
- task: Bash@3
displayName: 'Run SAST'
inputs:
targetType: inline
script: |
semgrep scan \
--config=p/owasp-top-ten \
--config=p/security-audit \
--error --severity=ERROR \
--sarif --output=$(Build.ArtifactStagingDirectory)/sast.sarif
- task: PublishBuildArtifacts@1
displayName: 'Publish SAST Results'
inputs:
PathtoPublish: '$(Build.ArtifactStagingDirectory)/sast.sarif'
ArtifactName: 'security-reports'
- job: DependencyScan
displayName: 'SCA + License Check'
dependsOn: SecretsDetection
steps:
- task: Bash@3
displayName: 'Scan dependencies'
inputs:
targetType: inline
script: |
# Vulnerability scanning
osv-scanner scan --lockfile=package-lock.json \
--format=json > sca-results.json
# License compliance
license-checker --production \
--failOn="AGPL-3.0;GPL-3.0;SSPL-1.0" \
--json > license-report.json
# SBOM generation
syft . -o spdx-json > sbom.spdx.json
- task: PublishBuildArtifacts@1
displayName: 'Publish SBOM'
inputs:
PathtoPublish: 'sbom.spdx.json'
ArtifactName: 'sbom'
- job: InfrastructureScan
displayName: 'IaC Scanning'
steps:
- task: Bash@3
displayName: 'Scan Terraform and K8s'
inputs:
targetType: inline
script: |
# Terraform scanning
tfsec ./infra --minimum-severity HIGH --format json \
> tfsec-results.json
# Kubernetes manifest scanning
checkov -d ./k8s --framework kubernetes \
--check HIGH --output json \
> checkov-results.json
# ---- Stage 2: Build and Container Scan ----
- stage: BuildAndScan
displayName: 'Build & Container Security'
dependsOn: SecurityScanning
jobs:
- job: BuildAndScanImage
displayName: 'Build & Scan Container'
steps:
- task: Docker@2
displayName: 'Build image'
inputs:
command: build
Dockerfile: Dockerfile
tags: '$(Build.BuildId)'
- task: Bash@3
displayName: 'Scan container image'
inputs:
targetType: inline
script: |
trivy image --severity HIGH,CRITICAL \
--exit-code 1 \
--format json --output trivy-results.json \
app:$(Build.BuildId)
# ---- Stage 3: DAST (on merge to main only) ----
- stage: DynamicAnalysis
displayName: 'DAST Scan'
dependsOn: BuildAndScan
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
jobs:
- job: DAST
displayName: 'Dynamic Application Security Testing'
steps:
- task: Bash@3
displayName: 'Deploy and scan staging'
inputs:
targetType: inline
script: |
# Deploy to ephemeral staging environment
# Run targeted DAST against modified endpoints
zap-cli quick-scan -l Medium \
https://staging.example.com
# ---- Security Gate Decision ----
- stage: SecurityGate
displayName: 'Security Gate Evaluation'
dependsOn:
- SecurityScanning
- BuildAndScan
jobs:
- job: EvaluateGates
displayName: 'Aggregate & Decide'
steps:
- task: Bash@3
displayName: 'Evaluate security posture'
inputs:
targetType: inline
script: |
echo "Aggregating results from all security gates..."
echo "Applying severity thresholds and policies..."
echo "Generating security posture report..."
# Aggregate all scan results
# Apply break/warn/log policies
# Generate unified security report
Pipeline design principle: Notice that secrets scanning and IaC scanning run in parallel with SAST and SCA. Security gates should be parallelized wherever possible to minimize total pipeline duration. Only stages with true dependencies (e.g., container scanning depends on the image being built) should be sequential.
SBOM Generation in CI/CD
A Software Bill of Materials (SBOM) is a formal, machine-readable inventory of every component, library, and module in your software. If SCA tells you what vulnerabilities exist today, the SBOM enables you to answer the question "are we affected?" when a new vulnerability is disclosed tomorrow.
The urgency of SBOM adoption has been driven by regulatory mandates. Executive Order 14028 in the United States requires SBOMs for software sold to the federal government. The EU Cyber Resilience Act similarly mandates SBOM generation for products sold in the European market. Even without regulatory pressure, SBOMs are rapidly becoming a baseline expectation in enterprise procurement.
SBOM Formats
Two formats dominate the landscape:
- SPDX (Software Package Data Exchange): An ISO standard (ISO/IEC 5962:2021) backed by the Linux Foundation. Preferred in open-source ecosystems and government procurement.
- CycloneDX: An OWASP standard optimized for security use cases. Supports vulnerability references, service dependencies, and composition analysis natively.
Generating SBOMs in Your Pipeline
# Generate SBOM during the build stage # Method 1: From source code and manifests syft . -o spdx-json > sbom-source.spdx.json syft . -o cyclonedx-json > sbom-source.cdx.json # Method 2: From container image (captures OS packages too) syft app:latest -o spdx-json > sbom-container.spdx.json # Method 3: From a running container syft docker:running-container -o cyclonedx-json > sbom-runtime.cdx.json # Validate SBOM completeness sbom-scorecard score sbom-source.spdx.json # Scan SBOM against vulnerability databases grype sbom:sbom-source.spdx.json --output json > vulns.json # Sign the SBOM for integrity verification cosign sign-blob --key cosign.key sbom-source.spdx.json \ --output-signature sbom.sig
Best practices for SBOM management in CI/CD:
- Generate on every build: SBOMs should be created as build artifacts alongside binaries, not as a one-time exercise
- Store immutably: Archive SBOMs in an artifact repository alongside the release they describe, enabling point-in-time auditing
- Monitor continuously: Re-scan stored SBOMs against updated vulnerability databases daily — a dependency that was safe at build time may have a CVE published tomorrow
- Include transitive dependencies: Your SBOM is incomplete if it only lists direct dependencies; transitive (indirect) dependencies account for the majority of actual vulnerabilities
- Sign for integrity: Use cryptographic signing to ensure SBOMs have not been tampered with between generation and consumption
SBOM as incident response accelerator: When Log4Shell was disclosed, organizations with SBOM inventories answered "are we affected?" in minutes. Organizations without SBOMs spent days or weeks manually auditing every application. In the next zero-day event, your SBOM inventory is the difference between a 30-minute response and a 30-day scramble.
Measuring Security Velocity
You cannot improve what you do not measure. A DevSecOps program needs metrics that track both security posture and engineering velocity to ensure that security gates are making the software safer without making the team slower.
Key Metrics
| Metric | What It Measures | Target |
|---|---|---|
| Mean Time to Remediate (MTTR) | Average time from finding discovery to fix deployment | Critical: < 48h, High: < 7d, Medium: < 30d |
| Vulnerability Density | Findings per 1,000 lines of code (or per application) | Trending downward quarter-over-quarter |
| Scan Coverage | Percentage of repositories with all security gates enabled | 100% of production repositories |
| False Positive Rate | Percentage of findings dismissed as false positives | < 15% (indicates well-tuned scanners) |
| Pipeline Pass Rate | Percentage of builds that pass security gates on first attempt | > 85% (indicates developers are writing secure code) |
| Gate Duration | Time added to the pipeline by security scanning | < 5 minutes total for PR-triggered gates |
| SLA Compliance | Percentage of findings remediated within their severity SLA | > 90% compliance across all severities |
| Dependency Freshness | Percentage of dependencies within one major version of latest | > 80% of direct dependencies current |
Vulnerability Density Trends
The single most important long-term metric is vulnerability density trend. Plot the number of findings per 1,000 lines of code (or per application) over time. A healthy DevSecOps program shows a consistent downward trend as:
- Developers learn from security gate feedback and stop introducing the same vulnerability classes
- Legacy backlog is systematically remediated
- Scanner rulesets are tuned to reduce false positives
- Secure coding training addresses the most common finding categories
If vulnerability density is flat or increasing despite active security gates, investigate root causes: are developers suppressing findings instead of fixing them? Are new vulnerability classes being introduced faster than old ones are remediated? Is the team growing faster than the security culture can scale?
MTTR Segmentation
Do not measure MTTR as a single number across all severities. Segment MTTR by severity, by scanner type, and by team. This reveals specific areas of friction:
- MTTR by severity: If critical MTTR is good but medium MTTR is measured in months, your warning-tier findings are being ignored
- MTTR by scanner: If SAST MTTR is fast but SCA MTTR is slow, teams may struggle with dependency upgrades (suggesting a need for better tooling or training around breaking changes)
- MTTR by team: Outlier teams with significantly higher MTTR may need additional support, training, or resource allocation
The velocity trap: Never measure security success by the number of findings produced. A scanner that generates 10,000 findings is not ten times more valuable than one that generates 1,000. What matters is the number of true positives remediated within SLA. Quality of signal outweighs quantity every time.
The Role of ASPM in Pipeline Orchestration
Application Security Posture Management (ASPM) is the emerging discipline that unifies all the security gates, scanners, and findings into a single control plane. If individual scanners are the instruments, ASPM is the conductor that orchestrates them into a coherent program.
Why Individual Scanners Are Not Enough
A typical enterprise runs five to ten different security scanners across their pipeline. Each scanner produces findings in its own format, with its own severity scales, its own dashboards, and its own notification channels. This creates several problems:
- Duplicate findings: SAST and DAST may both report the same SQL injection vulnerability. Without deduplication, teams waste time investigating the same issue twice.
- Inconsistent severity: A CVSS 7.5 from one scanner might be rated "High" while another scanner rates the same score as "Critical." Without normalization, quality gate thresholds are meaningless.
- No cross-scanner correlation: A medium-severity SAST finding combined with a medium-severity IaC finding might represent a critical attack chain. Individual scanners cannot detect these compound risks.
- Visibility gaps: Security leaders cannot answer basic questions like "what is our riskiest application?" or "which team has the most overdue findings?" without manually aggregating data from multiple tools.
- Policy fragmentation: Quality gate policies are defined separately in each scanner, making it impossible to enforce consistent standards organization-wide.
What ASPM Provides
An ASPM platform sits above your individual scanners and provides:
- Unified finding inventory: All findings from all scanners, across all repositories, deduplicated and normalized into a single prioritized view
- Risk-based prioritization: Findings ranked not just by CVSS score, but by business context (is this application internet-facing? does it handle payment data? is the vulnerable function reachable?)
- Policy-as-code: Centralized quality gate definitions that are applied consistently across all repositories and all scanner types
- Compliance mapping: Automatic mapping of findings to compliance frameworks (OWASP Top 10, PCI DSS, HIPAA, SOC 2) with evidence generation for auditors
- Remediation orchestration: Automated ticket creation, assignment to the appropriate team, SLA tracking, and escalation workflows
- Executive dashboards: Real-time visibility into organizational security posture, trends, and risk hotspots
ASPM as the Security Operating System
Think of ASPM as the operating system for your application security program. Individual scanners are applications that run on it. Your quality gate policies are the configuration files. Your findings inventory is the filesystem. And your dashboards are the user interface. Without an operating system, individual applications cannot communicate, share resources, or be managed centrally.
ASPM and AI-Powered Triage
The next frontier in ASPM is AI-powered triage and remediation. Rather than requiring security engineers to manually review every finding, AI agents can:
- Auto-classify false positives: Machine learning models trained on historical triage decisions can predict with high accuracy whether a new finding is a true positive or false positive, dramatically reducing review burden
- Generate fix suggestions: AI models can analyze the vulnerable code, understand the vulnerability class, and generate a concrete code fix that the developer can review and apply
- Predict exploitability: Given the vulnerability type, the application's architecture, and the network topology, AI can assess the likelihood that a vulnerability can be exploited in your specific environment
- Correlate across signals: AI can identify attack chains that span multiple vulnerability types and multiple applications, surfacing compound risks that no individual scanner would flag
The combination of automated scanning, ASPM orchestration, and AI-powered intelligence creates a security program that scales with your engineering organization. Whether you have 10 repositories or 10,000, the same policies, gates, and intelligence apply consistently — without requiring a proportional increase in security headcount.
Getting Started: A Pragmatic Rollout Plan
If you are starting from zero, do not attempt to implement all five gates simultaneously. Roll out incrementally, proving value at each stage before adding complexity:
- Week 1-2: Secrets scanning. This is the highest-impact, lowest-friction gate. It produces very few false positives, prevents a category of risk that is genuinely catastrophic (credential exposure), and runs in under 10 seconds. Deploy it as a pre-commit hook and as a CI gate on every repository.
- Week 3-4: SCA scanning. Dependency vulnerability scanning is the second-highest-impact gate. It is fast, produces actionable results, and addresses a risk category (supply chain attacks) that is growing exponentially. Include license compliance from day one.
- Month 2: SAST scanning. Deploy SAST in warn-only mode first. Establish a baseline of existing findings. Spend two weeks tuning rules and suppressing false positives. Then switch to break-on-critical mode once the false positive rate is below 15%.
- Month 3: IaC scanning and container scanning. If your applications are containerized or use infrastructure-as-code, add these gates to catch infrastructure-level risks. These scanners are fast and produce few false positives.
- Month 4+: DAST and runtime monitoring. DAST requires a running application and more complex setup. Deploy it as a nightly job first, then optimize for PR-level targeted scanning. Integrate runtime monitoring with your log intelligence platform.
At each stage, measure the metrics described above. Track developer satisfaction alongside security posture. If developers hate the security gates, the gates will eventually be circumvented. If the gates are fast, accurate, and actionable, developers will come to rely on them — and your codebase will be measurably safer for it.
Final thought: The best DevSecOps pipeline is not the one with the most scanners or the strictest policies. It is the one that developers trust, security teams can manage, and leadership can measure. Build for trust first. Everything else follows.
Automate Your DevSecOps Pipeline Today
Security Factor 365 provides SAST, SCA, DAST, secrets scanning, IaC analysis, and SBOM generation in a unified ASPM platform — with AI-powered triage that eliminates the noise.
Explore the Platform