08 - DevSecOps for Developers: SAST, DAST and Security in the CI/CD Pipeline
In February 2024, a security researcher demonstrated how the compromise of tj-actions/changed-files, a GitHub Action used by over 23,000 repositories, allowed the exfiltration of secrets from CI/CD pipelines across thousands of organizations. The problem was not in the application code, not in the dependencies — it was in the pipeline itself: the very tool meant to protect software had become the attack vector.
This scenario reflects an uncomfortable truth: most teams invest in application code security but neglect the security of the CI/CD infrastructure that builds, tests, and deploys it. DevSecOps is not just integrating a few scanning tools into the pipeline — it is a fundamental shift in approach: security must be an integral part of every phase of the development lifecycle, not a final checkpoint before release.
According to the DORA 2025 report, teams that adopt complete DevSecOps practices report a Mean Time to Remediate (MTTR) for critical vulnerabilities 4x lower than teams running security tests only in staging or production. The cost of fixing a security bug in production is on average 100 times higher than fixing it during development: the "shift-left" principle has a solid economic justification, not just a technical one.
What You'll Learn
- Shift-left security: embedding security from the start of the development cycle
- SAST with Semgrep and CodeQL: static analysis of source code
- DAST with OWASP ZAP: dynamic testing on running applications
- SCA with Snyk and Trivy: Software Composition Analysis for dependencies
- Secrets scanning with Gitleaks and TruffleHog: preventing credential leaks
- IaC security with Checkov: scanning Terraform and Kubernetes manifests
- Complete GitHub Actions pipeline with security gates
- Metrics and KPIs for measuring DevSecOps maturity
Shift-Left Security: The Fundamental Principle
The term "shift-left" comes from the visual representation of the development lifecycle as a timeline from left (planning, development) to right (testing, production). Moving security to the left means bringing security controls as close as possible to the moment code is written: in the developer's IDE, in the pre-commit hook, in the pull request — not just in the staging environment.
The levels of shift-left security, from most reactive to most proactive, are:
- Level 0 - Production: vulnerability scanning only in prod. Late, expensive.
- Level 1 - Staging/Pre-prod: DAST in test environment. Better, but still slow.
- Level 2 - CI/CD Pipeline: SAST, SCA, secrets scanning on every push. DevSecOps standard.
- Level 3 - Pull Request: automated security review on every PR. Fast and contextual.
- Level 4 - Pre-commit: local controls before commit. Immediate feedback.
- Level 5 - IDE: security plugins in the editor (Snyk IDE, CodeQL extension). Real-time.
The 45% Paradox
Research from Veracode and GitLab shows that 45% of code generated by AI tools (GitHub Copilot, Cursor, Claude Code) contains security vulnerabilities that would fail basic security tests. Not because AI is inherently dangerous, but because it replicates insecure code patterns present in training datasets. This makes automated controls in CI/CD even more critical in the era of vibe coding and AI-assisted development. See the Vibe Coding series to explore this topic further.
SAST: Static Application Security Testing
SAST analyzes source code, bytecode, or binary without executing the application, looking for vulnerable code patterns, misconfigurations, and security anti-patterns. It is the fastest control (executable in seconds or minutes) and can be integrated directly into the IDE, pre-commit hook, and CI/CD pipeline.
The main advantages of SAST over other approaches: it runs on source code (no running application instance required), identifies vulnerabilities before deployment, and can analyze 100% of code including rarely-executed code paths. The main drawback is the false positive rate, which requires a triage and rule-tuning process.
Semgrep: Fast and Open Source SAST
Semgrep is probably the most widely used SAST tool in the open source ecosystem in 2025.
Its strengths lie in the simple rule syntax (structure nearly identical to the code it
analyzes), execution speed, and native support for TypeScript, JavaScript, Python, Go,
Java, and many other languages. It maintains thousands of security rules in the official
semgrep.dev registry.
# Install Semgrep
pip install semgrep
# or with Homebrew
brew install semgrep
# Basic scan with OWASP ruleset
semgrep --config p/owasp-top-ten .
# Scan with JavaScript/TypeScript-specific rules
semgrep --config p/javascript .
semgrep --config p/typescript .
# Rules for Angular/Node.js
semgrep --config p/nodejs-express-security .
semgrep --config p/jwt .
# Output in SARIF format for GitHub Security tab
semgrep --config p/owasp-top-ten --sarif --output semgrep.sarif .
# JSON output for automated processing
semgrep --config p/owasp-top-ten --json --output semgrep.json .
# Exclude irrelevant directories
semgrep --config p/owasp-top-ten \
--exclude="node_modules,dist,coverage,*.min.js" .
# Custom Semgrep rule to find JWT without signature verification
# .semgrep/jwt-insecure.yaml
# rules:
# - id: jwt-decode-without-verify
# patterns:
# - pattern: jwt.decode($TOKEN, ...)
# - pattern-not: jwt.verify($TOKEN, ...)
# message: "Using jwt.decode without verification: use jwt.verify instead"
# severity: ERROR
# languages: [javascript, typescript]
CodeQL: Deep Semantic Analysis
CodeQL, developed by GitHub, performs deeper analysis than Semgrep: it builds a code database and allows writing SQL-like queries to find vulnerabilities across multiple function call levels (taint analysis). LinkedIn announced in February 2026 that they use both CodeQL and Semgrep in a complementary way for optimal codebase coverage. CodeQL is natively integrated into GitHub Advanced Security and available for free on public repositories.
# .github/workflows/codeql.yml
name: CodeQL Security Analysis
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
schedule:
# Weekly analysis (Sunday at 2:00 UTC)
- cron: '0 2 * * 0'
permissions:
contents: read
security-events: write
actions: read
jobs:
codeql-analysis:
name: CodeQL Analysis
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
language: ['javascript-typescript']
steps:
- name: Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Initialize CodeQL
uses: github/codeql-action/init@dd746615b1a4b1e1c5d3b87432fe040f4c04082 # v3.28.0
with:
languages: {{ matrix.language }}
# Query suite: default, extended, security-extended
queries: security-extended
config: |
paths-ignore:
- node_modules
- dist
- '**/*.test.ts'
- '**/*.spec.ts'
- name: Autobuild
uses: github/codeql-action/autobuild@dd746615b1a4b1e1c5d3b87432fe040f4c04082 # v3.28.0
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@dd746615b1a4b1e1c5d3b87432fe040f4c04082 # v3.28.0
with:
category: "/language:{{ matrix.language }}"
upload: true
SAST: False Positives and Triage
The main problem with SAST is the number of false positives: an aggressive ruleset can generate hundreds of findings per commit, making the process unsustainable. The recommended strategy is to start with a high severity profile (CRITICAL/HIGH only), tune the rules for your specific codebase, and progressively increase coverage as the team becomes familiar with the tool. Never block the build for LOW/MEDIUM findings without a consolidated triage process in place.
DAST: Dynamic Application Security Testing
While SAST analyzes code without running it, DAST tests the application while it is running, simulating real attacks from the outside as an attacker would. DAST finds vulnerabilities that SAST cannot see: server configuration issues, runtime behaviors, REST API vulnerabilities, and authentication problems that only emerge with real HTTP requests.
DAST is complementary to SAST, not a replacement: SAST finds 70-80% of vulnerabilities in source code, DAST finds the remaining 20-30% that only emerges at runtime. For complete coverage, both are necessary.
OWASP ZAP: DAST Standard in the Open Source Ecosystem
OWASP ZAP (Zed Attack Proxy) is the most widely used DAST tool in the open source world, with native GitHub Actions integration. It offers three main modes for CI/CD: baseline scan (passive, non-invasive, ideal for every PR), full scan (active, uses real attack payloads, only in isolated environments), and API scan (specialized for OpenAPI/Swagger, ideal for microservices).
# .github/workflows/dast-zap.yml
name: DAST Security Scan (OWASP ZAP)
on:
push:
branches: [main]
workflow_dispatch:
inputs:
target_url:
description: 'Target URL for DAST scan'
required: true
default: 'https://staging.myapp.com'
permissions:
contents: read
issues: write # ZAP creates GitHub issues for found vulnerabilities
jobs:
zap-baseline-scan:
name: OWASP ZAP Baseline Scan
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
# Baseline scan: PASSIVE scan (does not send attack payloads)
# Ideal for every PR/push - fast and non-invasive
- name: ZAP Baseline Scan
uses: zaproxy/action-baseline@v0.14.0
with:
target: 'https://staging.myapp.com'
rules_file_name: '.zap/rules.tsv'
fail_action: true
cmd_options: '-a -j -l WARN'
# Full scan: ACTIVE scan (sends real attack payloads)
# Only in isolated test environments, NEVER against production!
# - name: ZAP Full Scan (isolated staging only)
# uses: zaproxy/action-full-scan@v0.11.0
# with:
# target: 'https://staging-isolated.myapp.com'
# API Scan: specialized for REST APIs with OpenAPI spec
# - name: ZAP API Scan
# uses: zaproxy/action-api-scan@v0.9.0
# with:
# target: 'https://api.staging.myapp.com/openapi.json'
- name: Upload ZAP Report
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.0
if: always()
with:
name: zap-report
path: report_html.html
# .zap/rules.tsv - ZAP rule customization
# ID ACTION PARAM
# 10015 WARN # Incomplete or No Cache-control Header Set
# 10038 IGNORE # CSP Header Not Set (managed separately by server)
# 10096 WARN # Timestamp Disclosure
# 40014 IGNORE # CSRF (if CSRF token is implemented at app level)
For Angular projects with SSR, DAST must run against a reachable instance of the application. An effective strategy is deploying to an ephemeral staging environment (e.g., Firebase Hosting Preview Channel) as part of the pipeline, running DAST, and only proceeding with production deployment if DAST passes.
# Ephemeral staging deployment + DAST in Angular pipeline
# .github/workflows/angular-devsecops.yml (excerpt)
jobs:
deploy-staging:
runs-on: ubuntu-latest
outputs:
staging_url: {{ steps.deploy.outputs.details_url }}
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
- run: npm ci --ignore-scripts
- run: npm run build --configuration=staging
- name: Deploy to Firebase Hosting preview channel
id: deploy
uses: FirebaseExtended/action-hosting-deploy@v0
with:
repoToken: {{ secrets.GITHUB_TOKEN }}
firebaseServiceAccount: {{ secrets.FIREBASE_SERVICE_ACCOUNT }}
channelId: staging-{{ github.run_id }}
expires: 1d
dast-scan:
needs: deploy-staging
runs-on: ubuntu-latest
permissions:
contents: read
issues: write
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
- name: ZAP Baseline Scan on ephemeral staging
uses: zaproxy/action-baseline@v0.14.0
with:
target: {{ needs.deploy-staging.outputs.staging_url }}
fail_action: true
rules_file_name: '.zap/rules.tsv'
SCA: Software Composition Analysis
SCA analyzes an application's open source dependencies to identify known vulnerabilities (CVEs), license issues, and outdated packages. The previous article in this series (Supply Chain Security: npm audit and SBOM) covers npm audit, Snyk, Dependabot, and SBOM generation in detail. Here we focus on Trivy, which offers both SCA and IaC scanning in a single tool — ideal for unified DevSecOps pipelines.
# Trivy: SCA + container + IaC in one tool
# Installation
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh \
| sh -s -- -b /usr/local/bin
# Scan npm dependencies (SCA)
trivy fs --scanners vuln --pkg-types library .
# Only HIGH and CRITICAL vulnerabilities (for security gate)
trivy fs --scanners vuln --severity HIGH,CRITICAL \
--pkg-types library --exit-code 1 .
# SARIF output for GitHub Security tab
trivy fs --scanners vuln --format sarif \
--output trivy-results.sarif .
# Scan with SBOM output (CycloneDX)
trivy fs --format cyclonedx --output sbom.json .
# .trivyignore - accepted CVEs (mandatory reason required!)
# CVE-2023-44270 # postcss: impacts only CLI usage, not build output - review 2026-06-01
# Full scan: vuln + secrets + misconfigurations
trivy fs --scanners vuln,secret,misconfig .
# GitHub Actions with official Trivy Action
# - name: Run Trivy vulnerability scanner
# uses: aquasecurity/trivy-action@d43c1f16c00cfd3978dde6c07f4bbcf9eb6993ca
# with:
# scan-type: 'fs'
# scan-ref: '.'
# scanners: 'vuln,secret'
# severity: 'HIGH,CRITICAL'
# exit-code: '1'
# format: 'sarif'
# output: 'trivy-results.sarif'
Secrets Scanning: Preventing Credential Leaks
Every week, thousands of secrets are accidentally exposed on public GitHub: API keys, OAuth tokens, database passwords, private certificates. GitGuardian, which monitors public repositories, reported that over 12 million secrets were exposed in public repositories in 2024, a 28% increase from the previous year. Once a secret is in a public repository (even for just a few minutes before removal), it must be considered compromised and rotated immediately.
Gitleaks and TruffleHog are the two most widely used tools for secrets detection, with complementary approaches: Gitleaks is fast and excellent for CI/CD; TruffleHog actively verifies whether found secrets are still valid (significantly reducing false positives).
Gitleaks: Configuration and Integration
# Install Gitleaks
brew install gitleaks
# or download binary from GitHub Releases
# https://github.com/gitleaks/gitleaks/releases
# Scan the entire repository (all commits)
gitleaks detect --source . --verbose
# Scan only staged files (for pre-commit hook)
gitleaks protect --staged --verbose
# Scan a specific commit range
gitleaks detect --source . --log-opts HEAD~1..HEAD
# JSON output
gitleaks detect --source . --report-format json \
--report-path gitleaks-report.json
# Pre-commit framework configuration
# .pre-commit-config.yaml
# repos:
# - repo: https://github.com/gitleaks/gitleaks
# rev: v8.24.2
# hooks:
# - id: gitleaks
# Custom .gitleaks.toml configuration
# [extend]
# useDefault = true
#
# [[rules]]
# id = "internal-api-token"
# description = "MyCompany internal API token"
# regex = '''mycompany_token_[0-9a-zA-Z]{32}'''
# tags = ["token", "internal"]
#
# [allowlist]
# description = "Test fixtures and safe placeholders"
# regexTarget = "line"
# regexes = [
# '''EXAMPLE_TOKEN_PLACEHOLDER''',
# '''test_secret_[a-z0-9]{8}'''
# ]
# paths = [
# '''.*_test\.ts''',
# '''.*\.spec\.ts''',
# '''.*\.fixture\.ts'''
# ]
TruffleHog: Active Secret Verification
# Install TruffleHog
curl -sSfL https://raw.githubusercontent.com/trufflesecurity/trufflehog/main/scripts/install.sh \
| sh -s -- -b /usr/local/bin
# Scan with active verification (reduces false positives)
trufflehog git file://. --only-verified
# Scan a remote repository
trufflehog github --repo https://github.com/myorg/myrepo --only-verified
# JSON output for processing
trufflehog git file://. --json --no-verification
# GitHub Actions: secrets scanning on every push/PR
# .github/workflows/secrets-scan.yml
# - name: TruffleHog secrets scan
# uses: trufflesecurity/trufflehog@main
# with:
# path: ./
# base: $GITHUB_BASE_REF
# head: HEAD
# extra_args: --only-verified
# Key difference: Gitleaks vs TruffleHog
# - Gitleaks: fast, pattern matching, great for pre-commit and CI
# - TruffleHog: active verification (tests secret against real API)
# Result: fewer false positives, but slower and more intrusive
# Recommendation: use both (Gitleaks for speed, TruffleHog for verified findings)
Secrets Are Not Removed with git rm
If a secret is accidentally committed, removing the file with git rm
or adding it to .gitignore is not enough: the secret is still visible
in git history. To truly remove it, use git filter-repo (officially
recommended by GitHub), then force-push on all branches, and consider the secret
compromised — rotate it immediately. GitHub also offers the native "Secret scanning"
tool in repository settings that continuously monitors commit history for 200+ known
token types.
IaC Security: Checkov for Terraform and Kubernetes
Infrastructure as Code (IaC) has democratized infrastructure management, but has also introduced a new attack vector: misconfigurations in Terraform files, Kubernetes manifests, Docker Compose, and AWS CloudFormation that can expose the entire infrastructure. Checkov (Bridgecrew/Palo Alto Networks) statically scans these files with over 1,000 built-in security policies, mapped to CIS Benchmarks, NIST, and other standards.
Note: tfsec, the original Aqua Security IaC scanning tool, has been integrated into Trivy. For new projects, Trivy is recommended for IaC scanning, or Checkov for broader coverage.
# Install Checkov
pip install checkov
# or with Homebrew
brew install checkov
# Scan Terraform directory
checkov -d ./terraform
# Scan Kubernetes manifests
checkov -d ./k8s --framework kubernetes
# Scan Dockerfile
checkov -f Dockerfile --framework dockerfile
# SARIF output for GitHub Security
checkov -d ./terraform --output sarif --output-file checkov.sarif
# Skip specific checks (with mandatory justification comment!)
checkov -d ./terraform --skip-check CKV_AWS_18,CKV_AWS_21
# Common AWS Terraform violations Checkov detects:
# CKV_AWS_18: S3 bucket - access logging disabled
# CKV_AWS_57: S3 bucket - public ACL (never in production!)
# CKV_AWS_87: Lambda - not in VPC
# CKV_AWS_135: EC2 - IMDSv1 enabled (use IMDSv2)
# INSECURE Kubernetes manifest (Checkov violations):
# apiVersion: v1
# kind: Pod
# spec:
# containers:
# - name: app
# image: myapp:latest # CKV_K8S_14: use specific tag, not :latest
# securityContext:
# runAsRoot: true # CKV_K8S_6: don't run as root
# privileged: true # CKV_K8S_16: don't use privileged mode
# resources: {} # CKV_K8S_11: define resource limits
# SECURE Kubernetes manifest (passes Checkov):
# spec:
# containers:
# - name: app
# image: myapp:1.2.3
# securityContext:
# runAsNonRoot: true
# runAsUser: 1000
# allowPrivilegeEscalation: false
# readOnlyRootFilesystem: true
# resources:
# limits:
# memory: "256Mi"
# cpu: "500m"
# requests:
# memory: "128Mi"
# cpu: "250m"
Complete GitHub Actions Pipeline: The Definitive Security Gate
Integrating all these tools into a coherent pipeline requires careful planning: not all jobs should block the build, some scans are more appropriate for PRs while others for pushes to main, and parallelization is essential to avoid slowing down development flow. The following pipeline illustrates a complete configuration with 6 parallel and sequential jobs.
# .github/workflows/devsecops-complete.yml
name: DevSecOps Security Pipeline
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
schedule:
- cron: '0 3 * * 1' # Every Monday at 3:00 UTC (full scan)
# Minimum permissions at workflow level (principle of least privilege)
permissions:
contents: read
jobs:
# ===== JOB 1: SECRETS SCANNING (first - fail fast) =====
secrets-scan:
name: Secrets Scanning
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
fetch-depth: 0 # Full git history needed for Gitleaks
- name: Gitleaks secrets detection
uses: gitleaks/gitleaks-action@v2
env:
GITHUB_TOKEN: {{ secrets.GITHUB_TOKEN }}
# ===== JOB 2: SCA - Dependency Vulnerabilities =====
sca-scan:
name: SCA Dependency Audit
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
with:
node-version: '22'
cache: 'npm'
- run: npm ci --ignore-scripts
- name: npm audit (production deps only)
run: npm audit --audit-level=high --omit=dev
- name: Trivy SCA scan
uses: aquasecurity/trivy-action@d43c1f16c00cfd3978dde6c07f4bbcf9eb6993ca
with:
scan-type: 'fs'
scan-ref: '.'
scanners: 'vuln'
severity: 'HIGH,CRITICAL'
exit-code: '1'
format: 'sarif'
output: 'trivy-vuln.sarif'
- name: Upload Trivy results
uses: github/codeql-action/upload-sarif@dd746615b1a4b1e1c5d3b87432fe040f4c04082
if: always()
with:
sarif_file: 'trivy-vuln.sarif'
# ===== JOB 3: SAST - Static Code Analysis =====
sast-scan:
name: SAST Static Analysis
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write
actions: read
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
- name: Initialize CodeQL
uses: github/codeql-action/init@dd746615b1a4b1e1c5d3b87432fe040f4c04082
with:
languages: 'javascript-typescript'
queries: security-extended
- name: Autobuild
uses: github/codeql-action/autobuild@dd746615b1a4b1e1c5d3b87432fe040f4c04082
- name: CodeQL Analysis
uses: github/codeql-action/analyze@dd746615b1a4b1e1c5d3b87432fe040f4c04082
with:
category: "/language:javascript-typescript"
- name: Semgrep SAST
uses: semgrep/semgrep-action@v1
with:
config: >-
p/owasp-top-ten
p/javascript
p/typescript
p/jwt
env:
SEMGREP_APP_TOKEN: {{ secrets.SEMGREP_APP_TOKEN }}
# ===== JOB 4: IaC Security =====
iac-scan:
name: IaC Security Scan
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
- name: Trivy config scan
uses: aquasecurity/trivy-action@d43c1f16c00cfd3978dde6c07f4bbcf9eb6993ca
with:
scan-type: 'config'
scan-ref: '.'
severity: 'HIGH,CRITICAL'
exit-code: '1'
- name: Checkov IaC scan
uses: bridgecrewio/checkov-action@v12
with:
directory: .
framework: all
soft_fail_on: MEDIUM
hard_fail_on: HIGH,CRITICAL
# ===== JOB 5: DAST (only on push to main) =====
dast-scan:
name: DAST ZAP Baseline
runs-on: ubuntu-latest
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
needs: [secrets-scan, sca-scan, sast-scan]
permissions:
contents: read
issues: write
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
- name: ZAP Baseline Scan
uses: zaproxy/action-baseline@v0.14.0
with:
target: {{ secrets.STAGING_URL }}
fail_action: true
- name: Upload ZAP Report
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1
if: always()
with:
name: zap-report-{{ github.sha }}
path: report_html.html
retention-days: 30
# ===== JOB 6: SBOM (on push to main, after all checks) =====
sbom-generate:
name: Generate SBOM
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
needs: [sca-scan, sast-scan, iac-scan]
permissions:
contents: write
id-token: write
attestations: write
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af
with:
node-version: '22'
- run: npm ci --ignore-scripts
- name: Generate SBOM (CycloneDX)
run: |
npm install -g @cyclonedx/cyclonedx-npm
cyclonedx-npm --omit dev \
--output-format json \
--output-file sbom-{{ github.sha }}.json
- name: Attest SBOM
uses: actions/attest-build-provenance@v1
with:
subject-path: sbom-{{ github.sha }}.json
- name: Upload SBOM
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1
with:
name: sbom-{{ github.sha }}
path: sbom-{{ github.sha }}.json
retention-days: 365
Security Gates: What Blocks the Build and What Doesn't
An effective security gate does not block every single warning but distinguishes between problems requiring immediate attention and problems that can be managed over time. The basic rule is: only block what is clearly dangerous and actionable. Blocking the build for too many false positives or LOW findings leads teams to disable the controls entirely, nullifying the entire investment.
Recommended Security Gates Matrix
- ALWAYS BLOCK: Secrets in code, CRITICAL vulnerabilities with available fix, SAST injection/RCE findings, CRITICAL IaC misconfigurations (public S3 bucket, security group with 0.0.0.0/0 on SSH ingress)
- BLOCK on main (not on PR): HIGH vulnerabilities with available fix, DAST findings set to FAIL, incompatible license dependencies (GPL in commercial product)
- WARNING (notify but don't block): HIGH vulnerabilities without available fix, MEDIUM in SAST, HIGH IaC with documented workaround in exceptions file, DAST findings set to WARN
- IGNORE (with documentation): LOW/INFO vulnerabilities, false positives documented in exceptions files (.trivyignore, .gitleaks.toml allowlist), CVEs with no real impact on the specific application context
# Documented exceptions in configuration files
# .trivyignore - accepted CVEs with mandatory justification
# Format: CVE-ID # [date] Justification and review plan
CVE-2023-44270 # [2026-02-01] postcss: only impacts CLI usage, not build output. Review 2026-06
# .gitleaks.toml - allowlist for non-dangerous patterns
# [allowlist]
# description = "Test fixtures, placeholders, and example values"
# regexes = [
# '''EXAMPLE_API_KEY_[A-Z0-9]{16}''',
# '''test_secret_placeholder''',
# '''YOUR_TOKEN_HERE'''
# ]
# paths = [
# '''.*\.spec\.ts''',
# '''.*\.fixture\.ts''',
# '''README\.md'''
# ]
# Semgrep inline suppression in source code
# const regex = new RegExp(userPattern); // nosemgrep: javascript.lang.security.audit.unsafe-regex
# Checkov inline suppression in Terraform
# resource "aws_s3_bucket" "cdn_assets" {
# bucket = "my-public-cdn-assets"
# #checkov:skip=CKV_AWS_57: CDN bucket for public static assets - intentionally public
# }
Angular DevSecOps Checklist
Angular projects have specific DevSecOps considerations, related to the toolchain (Angular CLI, esbuild, ng-packagr), the use of HTML templates with dynamic binding, and SSR with Node.js Express server.
# Angular DevSecOps Checklist
# 1. SAST: Angular-specific Semgrep rules
# Dangerous patterns to detect:
# - DomSanitizer.bypassSecurityTrustHtml(userInput) without sanitization
# - [innerHTML]="untrustedData" (unsafe HTML binding)
# - eval() or new Function() in Angular services
# - HttpClient without authentication interceptor
# 2. SECRETS: environment files must NOT contain real values
# WRONG - environment.ts committed with real secret:
# export const environment = {
# apiKey: 'sk-prod-real-key-12345', # Blocked by Gitleaks!
# googleMapsKey: 'AIza...' # Blocked by Gitleaks!
# };
# CORRECT - placeholders in committed files, values injected in CI:
# export const environment = {
# apiKey: '', // injected at build time by CI/CD pipeline
# };
# In pipeline: sed -i "s/apiKey: ''/apiKey: '{{ secrets.API_KEY }}'/" src/environments/environment.prod.ts
# 3. CSP HEADER in Angular SSR server (Express)
# server.ts
# import { randomBytes } from 'crypto';
# const generateNonce = () => randomBytes(16).toString('base64');
#
# app.use((req, res, next) => {
# const nonce = generateNonce();
# res.locals['nonce'] = nonce;
# res.setHeader('Content-Security-Policy', [
# "default-src 'self'",
# `script-src 'self' 'nonce-






