---
title: "What&#39;s the Deal with Shift-Left Security, and Why Should You Care?"
lang: "en"
author: "Mohammad Abu Mattar"
canonical: https://mkabumattar.com/post/shift-left-security-sast-dast-sca-cicd
---

![Blog post image for What's the Deal with Shift-Left Security, and Why Should You Care? - How to implement shift-left security with SAST, DAST, and SCA in your CI/CD pipeline to cut costs and catch issues earlier, with tool examples like SonarQube, Trivy, and OWASP ZAP.](/_astro/hero.xI_32nOg_1a8j9r.webp)

[Home](/)›[Blog](/blog)›[All Categories](/blog/categories)›[CI/CD](/blog/categories/cicd)

Blog

[Prev in CI/CDHow to Install Jenkins on AWS EC2 Instance](/blog/post/install-jenkins-on-aws-ec2-instance)[Next in CI/CDTaming the Chaos: Let's Sort Out Those Flaky CI/CD Pipelines](/blog/post/troubleshooting-flaky-ci-cd-pipelines)

[CI/CD](/blog/categories/cicd)[DevSecOps](/blog/categories/devsecops)[Security](/blog/categories/security)[Automation](/blog/categories/automation)[Application Security](/blog/categories/application-security)

# What's the Deal with Shift-Left Security, and Why Should You Care?

[Mohammad Abu Mattar](/authors/mohammad-abu-mattar)Published: 24 Jan 202634 Mins read33 Mins listen

[Markdown for AI(opens in a new tab)](/post/shift-left-security-sast-dast-sca-cicd/index.md "Open the plain-Markdown version of this page, for pasting into an AI tool")

TL;DR

How to implement shift-left security with SAST, DAST, and SCA in your CI/CD pipeline to cut costs and catch issues earlier, with tool examples like SonarQube, Trivy, and OWASP ZAP.

Series

[Container Security & DevSecOps](/series/container-security--devsecops)5/6

[PreviousContainer Image Signing with Cosign: A Hands-On Guide to Secure Your Supply Chain](/blog/post/container-image-signing-cosign-guide)[NextQuenchWorks: A Zero-CVE, Built-From-Source Replacement for the Bitnami Catalog](/blog/post/quenchworks-zero-cve-bitnami-alternative-wolfi)

All posts in this series (6)

Blog6

1.  [Policy as Code with Open Policy Agent: A Technical and Governance Perspective](/blog/post/policy-as-code-opa-guide)
2.  [HashiCorp Vault vs. AWS Secrets Manager vs. SOPS: Which One Fits Your Setup](/blog/post/secrets-management-vault-secrets-manager-sops)
3.  [The Democratization of Container Security: Docker Hardened Images](/blog/post/democratization-docker-hardened-images-container-security)
4.  [Container Image Signing with Cosign: A Hands-On Guide to Secure Your Supply Chain](/blog/post/container-image-signing-cosign-guide)
5.  [What's the Deal with Shift-Left Security, and Why Should You Care?You are here](/blog/post/shift-left-security-sast-dast-sca-cicd)
6.  [QuenchWorks: A Zero-CVE, Built-From-Source Replacement for the Bitnami Catalog](/blog/post/quenchworks-zero-cve-bitnami-alternative-wolfi)

### What's the Deal with Shift-Left Security, and Why Should You Care?

Contents

[SAST, DAST, and SCA: your early warning system](#sast-dast-and-sca-your-early-warning-system)[How can SAST help you find problems early on?](#how-can-sast-help-you-find-problems-early-on)[What's DAST, and why is testing a running application so important?](#whats-dast-and-why-is-testing-a-running-application-so-important)[What's SCA, and why should you keep an eye on your dependencies?](#whats-sca-and-why-should-you-keep-an-eye-on-your-dependencies)[Making security part of your pipeline: integrating SAST, DAST, and SCA in CI/CD](#making-security-part-of-your-pipeline-integrating-sast-dast-and-sca-in-cicd)[How can you integrate SAST into your CI/CD workflow?](#how-can-you-integrate-sast-into-your-cicd-workflow)[What's the best way to add DAST to your CI/CD pipeline?](#whats-the-best-way-to-add-dast-to-your-cicd-pipeline)[Integrating SCA into your CI/CD: keeping dependencies secure](#integrating-sca-into-your-cicd-keeping-dependencies-secure)[Putting it all together: tools in action](#putting-it-all-together-tools-in-action)[How can SonarQube help with shift-left in CI/CD?](#how-can-sonarqube-help-with-shift-left-in-cicd)[Using OWASP ZAP for dynamic security testing in your pipeline](#using-owasp-zap-for-dynamic-security-testing-in-your-pipeline)[Using Trivy for vulnerability scanning in CI/CD](#using-trivy-for-vulnerability-scanning-in-cicd)[Scan Results Summary](#scan-results-summary)[Challenges and best practices](#challenges-and-best-practices)[What are some common challenges when implementing shift-left security?](#what-are-some-common-challenges-when-implementing-shift-left-security)[What are the best practices for a shift-left implementation in CI/CD?](#what-are-the-best-practices-for-a-shift-left-implementation-in-cicd)[Frequently Asked Questions About Shift-Left Security and CI/CD Integration](#frequently-asked-questions-about-shift-left-security-and-cicd-integration)[Conclusion](#conclusion)[References](#references)

Let’s be honest: security can’t be an afterthought. If you’re still waiting until the end of your development cycle to think about vulnerabilities, you’re doing it wrong. That’s where **shift-left security** comes in. Instead of treating security like a final checkpoint before release, we’re moving it way earlier in the process, right from the start. Think of it as catching problems before they become problems.

This approach fits perfectly with DevOps and DevSecOps practices. You’re already automating things and working cross-functionally, so why not bake security into that workflow? When you start thinking about security during the planning phase (yes, that early), you build stronger, more resilient applications. Developers catch issues when they’re still easy and cheap to fix. No more panic-mode security patches right before launch. Shift-left security means you’re proactive instead of reactive, and that saves you time, money, and a ton of headaches.

So why does this matter so much? Three reasons. First, it’s way cheaper to fix a security bug during development than in production. We’re talking orders of magnitude cheaper. Second, when security checks run alongside your normal development workflow, you actually ship faster because you’re not scrambling at the last minute. Third, applications built with security in mind from day one are just more secure, period. They’re harder to exploit and less likely to leak data.

But there’s more. Shift-left security gets your dev, security, and ops teams actually talking to each other. Everyone understands the security requirements from the beginning, and developers become more security-aware over time. This approach also makes compliance easier (who doesn’t want that?) and builds trust with your users. Bottom line: you catch risks early, reduce technical debt, ship faster, and spend less time fixing broken things.

## [SAST, DAST, and SCA: your early warning system](#sast-dast-and-sca-your-early-warning-system)

### [How can SAST help you find problems early on?](#how-can-sast-help-you-find-problems-early-on)

Static Application Security Testing (**SAST**) is your first line of defense in shift-left security. Think of it as a spell-checker, but for security vulnerabilities. SAST tools analyze your source code without actually running it, looking for potential security issues. This is what we call “white box” testing because the tool can see everything inside your application.

Here’s how it works: the tool parses your code to understand its structure, then analyzes it to understand what it does and how different parts connect. It’s looking for patterns that match known vulnerabilities and bad coding practices. SAST tools also track data flow through your application to spot places where untrusted input could cause problems.

SAST catches a wide range of issues: SQL injection, Cross-Site Scripting (XSS), buffer overflows, hardcoded secrets, weak cryptography, poor error handling, dead code, code duplication, and resource leaks. The best part? It gives you immediate feedback. You get the exact file, location, and line number where the problem is, plus guidance on how to fix it.

You can integrate SAST directly into your IDE so developers see issues as they code, or run it in your CI/CD pipeline for automated checks. It’s scalable, repeatable, and can even analyze compiled code like binaries and bytecode. By catching vulnerabilities before they reach production, SAST makes your applications more secure and helps developers learn to write better code.

### [What’s DAST, and why is testing a running application so important?](#whats-dast-and-why-is-testing-a-running-application-so-important)

Dynamic Application Security Testing (**DAST**) takes a different approach. Instead of analyzing static code, DAST tests your running application by simulating real attacks. This is “black box” testing because the tool doesn’t know anything about your application’s internals. It’s testing from an attacker’s perspective, which is exactly what you want.

DAST finds issues that only show up at runtime, things SAST can’t catch. It tests how your app handles requests and responses, how it interacts with other services, how it manages sessions and authentication. You get a real-world view of your security posture. DAST excels at finding input validation issues, server misconfigurations, and authentication bypasses. It can test your entire application flow, including complex multi-step processes and business logic vulnerabilities. It’s also perfect for testing third-party applications where you don’t have source code access.

DAST tools launch automated attacks (SQL injection, XSS, etc.) and watch how your app responds. They generate detailed reports with actionable findings and remediation guidance. You can integrate DAST into your CI/CD pipeline for continuous testing, run it against staging environments, or even use it on production (carefully). It helps you prioritize fixes based on actual risk and is essential for compliance requirements. Since it tests the running app, DAST catches runtime issues and client-side vulnerabilities that static analysis misses.

### [What’s SCA, and why should you keep an eye on your dependencies?](#whats-sca-and-why-should-you-keep-an-eye-on-your-dependencies)

Software Composition Analysis (**SCA**) is your dependency watchdog. Modern applications use tons of open-source libraries and third-party components. SCA tools automatically discover all these dependencies by scanning package managers, manifest files, source code, binaries, and container images. They create a Software Bill of Materials (SBOM), basically an inventory of everything your app depends on. Then they cross-reference this against vulnerability databases like the National Vulnerability Database (NVD) to flag any known security issues.

Why does this matter? Because open-source components come with licensing requirements and security risks that are hard to track manually. SCA helps you manage these risks by identifying vulnerable dependencies, checking license compliance, and monitoring your software supply chain for malicious or compromised packages. It also flags outdated components and gives you visibility into your entire dependency tree. In the DevSecOps world, SCA is essential for shift-left security.

SCA tools identify vulnerabilities and recommend fixes or updates. Some can assess the health of open-source projects. They automate dependency scanning and can alert you or even block builds if they find policy violations. Many integrate directly into your IDE to warn developers as they add new packages. SCA scans aren’t limited to your code; they also check your cloud infrastructure and runtime environments. They help you manage SBOMs, track transitive dependencies (the dependencies of your dependencies), and meet compliance requirements.

## [Making security part of your pipeline: integrating SAST, DAST, and SCA in CI/CD](#making-security-part-of-your-pipeline-integrating-sast-dast-and-sca-in-cicd)

### [How can you integrate SAST into your CI/CD workflow?](#how-can-you-integrate-sast-into-your-cicd-workflow)

Integrating SAST into your CI/CD pipeline is how you catch vulnerable code before it reaches production. This automates security checks with every code change, giving developers immediate feedback so they can fix issues quickly.

Here’s how to make it work. Start by defining your security requirements clearly. Pick a SAST tool that supports your programming languages and tech stack. Popular options include SonarQube, Fortify, Checkmarx, and Semgrep. Connect it to your version control system (like Git) so scans trigger automatically on code changes. Configure the tool for your specific needs to reduce false positives and improve accuracy. Then integrate it into your CI/CD pipeline using Jenkins, GitLab CI, GitHub Actions, or Azure Pipelines.

Run an initial scan of your existing codebase to establish a baseline. Connect the findings to your issue tracking system (like Jira) so developers get notified immediately. Many teams configure their pipeline to fail builds if critical security issues are detected. Here’s a practical example using GitHub Actions with SonarQube:

sast-scan.yml

```
1name: SAST Security Scan2
3# Security: Restrict workflow triggers to prevent unauthorized runs4on:5  push:6    branches: [main, develop]7  pull_request:8    branches: [main]9  workflow_dispatch: # Allow manual triggers for security audits10
11# Security: Define minimum permissions (OIDC best practice)12permissions:13  contents: read14  security-events: write15  pull-requests: write16
17jobs:18  validate-inputs:19    name: Validate Configuration20    runs-on: ubuntu-latest21    steps:22      # Security: Validate required secrets are present23      - name: Validate Required Secrets24        run: |25          set -euo pipefail  # Exit on error, undefined vars, pipe failures26
27          if [ -z "${{ secrets.SONAR_TOKEN }}" ]; then28            echo "::error::SONAR_TOKEN secret is not configured"29            exit 130          fi31
32          if [ -z "${{ secrets.SONAR_HOST_URL }}" ]; then33            echo "::error::SONAR_HOST_URL secret is not configured"34            exit 135          fi36
37          echo "✓ All required secrets are configured"38
39  sonarqube:40    name: SonarQube SAST Analysis41    runs-on: ubuntu-latest42    needs: validate-inputs43
44    # Security: Set timeout to prevent resource exhaustion45    timeout-minutes: 3046
47    steps:48      # Security: Use specific version tags, not @latest49      - name: Checkout Code50        uses: actions/checkout@v451        with:52          fetch-depth: 0 # Required for blame information53          persist-credentials: false # Security: Don't persist Git credentials54
55      # Security: Use specific LTS Java version56      - name: Set up JDK 1757        uses: actions/setup-java@v458        with:59          java-version: '17'60          distribution: 'temurin'61          cache: 'maven' # Built-in Maven cache62
63      # Security: Verify Maven wrapper integrity64      - name: Validate Maven Wrapper65        run: |66          set -euo pipefail67
68          if [ -f "mvnw" ]; then69            # Check if Maven wrapper jar exists and verify checksum70            if [ ! -f ".mvn/wrapper/maven-wrapper.jar" ]; then71              echo "::error::Maven wrapper jar not found"72              exit 173            fi74            echo "✓ Maven wrapper validated"75          fi76
77      # Security: Run dependency vulnerability check before analysis78      - name: Check Dependencies79        run: |80          set -euo pipefail81          mvn dependency:tree -DoutputFile=dependency-tree.txt82          mvn org.owasp:dependency-check-maven:check \83            -DfailBuildOnCVSS=7 \84            -DsuppressionFile=dependency-check-suppressions.xml || true85
86      # Security: Run SonarQube with strict settings87      - name: Run SonarQube Analysis88        env:89          SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}90          SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}91          # Security: Prevent token leakage in logs92          SONAR_SCANNER_OPTS: '-Dsonar.log.level=INFO'93        run: |94          set -euo pipefail95
96          # Validate URL format (prevent injection)97          if ! echo "$SONAR_HOST_URL" | grep -E '^https?://[a-zA-Z0-9.-]+'; then98            echo "::error::Invalid SONAR_HOST_URL format"99            exit 1100          fi101
102          # Run analysis with security-focused settings103          mvn clean verify sonar:sonar \104            -Dsonar.projectKey=my-project \105            -Dsonar.qualitygate.wait=true \106            -Dsonar.qualitygate.timeout=300 \107            -Dsonar.exclusions="**/test/**,**/node_modules/**" \108            -Dsonar.coverage.exclusions="**/test/**" \109            -B  # Batch mode for CI110
111      # Security: Verify quality gate status112      - name: Check Quality Gate Status113        if: always()114        run: |115          set -euo pipefail116          echo "✓ SonarQube analysis completed"117          echo "View detailed report at: ${{ secrets.SONAR_HOST_URL }}"118
119      # Security: Upload analysis artifacts (sanitized)120      - name: Upload Analysis Report121        if: always()122        uses: actions/upload-artifact@v4123        with:124          name: sonarqube-analysis125          path: |126            target/sonar/report-task.txt127            dependency-tree.txt128          retention-days: 30129          if-no-files-found: warn130
131      # Security: Notify on failure132      - name: Notify on Failure133        if: failure()134        run: |135          echo "::error::SAST scan failed - review security findings before merge"136          exit 1
```

Platforms like GitLab often have built-in CI/CD templates that make SAST integration even easier. Use incremental analysis features when available to speed up scans by only checking changed code. Some SAST tools include training features to help developers learn secure coding practices. Keep your tools updated and regularly review your security policies. Create dashboards to visualize SAST results and track security trends over time.

The trick is making SAST work smoothly with CI/CD, so everyone on the team takes ownership of security. Your tool also needs to support the languages you’re actually using.

### [What’s the best way to add DAST to your CI/CD pipeline?](#whats-the-best-way-to-add-dast-to-your-cicd-pipeline)

DAST is essential for finding security vulnerabilities by simulating real attacks on your running application. Unlike SAST, you need your app deployed to a test or staging environment before DAST can do its thing.

Here’s how to make it work. Automate DAST scans to run with every build or deployment. Choose tools designed for your specific application types (web apps, APIs, etc.). Popular options include OWASP ZAP, Acunetix, Burp Suite, and GitLab DAST. These integrate with CI/CD platforms like GitLab, CircleCI, and Azure DevOps through plugins or extensions.

For authenticated applications, configure your DAST tool with proper credentials so it can test protected areas. After scans complete, review the reports carefully, prioritize findings, and use the insights to improve your security. Run DAST early and often. Connect it to your bug tracking system for smoother remediation workflows.

Here’s a practical example using OWASP ZAP in a GitLab CI pipeline:

dast-scan.yml

```
1# Security: Define execution stages2stages:3  - validate4  - deploy5  - security-scan6  - report7
8# Security: Global variables with safe defaults9variables:10  # Prevent credential leakage in logs11  GIT_STRATEGY: fetch12  GIT_DEPTH: 113  # Security: Set scan timeout14  ZAP_SCAN_TIMEOUT: '10'15  # Security: Define acceptable risk threshold16  ZAP_FAIL_ON_SEVERITY: 'HIGH'17
18# Security: Validate configuration before deployment19validate_config:20  stage: validate21  image: alpine:latest22  script:23    - |24      set -euo pipefail25
26      # Validate required variables27      if [ -z "${STAGING_URL:-}" ]; then28        echo "ERROR: STAGING_URL not configured"29        exit 130      fi31
32      # Security: Validate URL format (prevent injection)33      if ! echo "$STAGING_URL" | grep -E '^https://[a-zA-Z0-9.-]+'; then34        echo "ERROR: STAGING_URL must use HTTPS and valid hostname"35        exit 136      fi37
38      # Security: Check for secure protocol39      if echo "$STAGING_URL" | grep -q '^http://'; then40        echo "WARNING: Non-HTTPS URL detected - security scan may be unreliable"41      fi42
43      echo "✓ Configuration validated successfully"44  only:45    - branches46
47# Security: Deploy to isolated staging environment48deploy_staging:49  stage: deploy50  image: alpine:latest51  needs: [validate_config]52
53  # Security: Set deployment timeout54  timeout: 15 minutes55
56  script:57    - |58      set -euo pipefail59
60      echo "Deploying to staging environment..."61
62      # Security: Validate deployment script exists and is executable63      if [ ! -f "./deploy-staging.sh" ]; then64        echo "ERROR: Deployment script not found"65        exit 166      fi67
68      if [ ! -x "./deploy-staging.sh" ]; then69        echo "ERROR: Deployment script is not executable"70        exit 171      fi72
73      # Security: Run deployment with strict error handling74      ./deploy-staging.sh75
76      # Security: Verify deployment health77      echo "Waiting for application to be ready..."78      for i in {1..30}; do79        if wget --spider --timeout=5 "${STAGING_URL}/health" 2>/dev/null; then80          echo "✓ Application is healthy"81          exit 082        fi83        sleep 1084      done85
86      echo "ERROR: Application health check failed"87      exit 188
89  environment:90    name: staging91    url: $STAGING_URL92    on_stop: cleanup_staging93
94  only:95    - branches96
97# Security: Run DAST scan with comprehensive checks98dast_scan:99  stage: security-scan100  # Security: Use specific version tag101  image: owasp/zap2docker-stable:latest102  needs: [deploy_staging]103
104  # Security: Set scan timeout105  timeout: 30 minutes106
107  variables:108    # Security: Restrict ZAP memory usage109    ZAP_JAVA_OPTS: '-Xmx2048m'110
111  script:112    - |113      set -euo pipefail114
115      # Security: Create working directory with proper permissions116      mkdir -p /zap/wrk117      chmod 700 /zap/wrk118
119      # Security: Validate target URL120      TARGET_URL="${STAGING_URL}"121      if ! echo "$TARGET_URL" | grep -E '^https://[a-zA-Z0-9.-]+'; then122        echo "ERROR: Invalid target URL format"123        exit 1124      fi125
126      echo "Starting DAST scan on: $TARGET_URL"127
128      # Security: Create ZAP configuration with safe settings129      cat > /zap/wrk/zap-config.conf << 'EOF'130      # Rate limiting to prevent DoS on target131      rules.config.script.passive.enabled=true132      scanner.threadPerHost=2133      connection.timeoutInSecs=30134      EOF135
136      # Security: Run baseline scan with strict settings137      zap-baseline.py \138        -t "$TARGET_URL" \139        -c /zap/wrk/zap-config.conf \140        -r dast-report.html \141        -w dast-report.md \142        -J dast-report.json \143        -x dast-report.xml \144        -d \145        -T "${ZAP_SCAN_TIMEOUT}" \146        -z "-config api.disablekey=true" || SCAN_EXIT_CODE=$?147
148      # Security: Parse results and determine severity149      if [ -f "dast-report.json" ]; then150        HIGH_COUNT=$(jq '[.site[].alerts[] | select(.riskdesc | startswith("High"))] | length' dast-report.json || echo "0")151        MEDIUM_COUNT=$(jq '[.site[].alerts[] | select(.riskdesc | startswith("Medium"))] | length' dast-report.json || echo "0")152
153        echo "Security Scan Results:"154        echo "  High Severity Issues: $HIGH_COUNT"155        echo "  Medium Severity Issues: $MEDIUM_COUNT"156
157        # Security: Fail on high severity issues158        if [ "$HIGH_COUNT" -gt 0 ]; then159          echo "ERROR: High severity vulnerabilities detected!"160          exit 1161        fi162      fi163
164      echo "✓ DAST scan completed successfully"165
166  # Security: Preserve scan results for audit167  artifacts:168    when: always169    paths:170      - dast-report.html171      - dast-report.json172      - dast-report.xml173      - dast-report.md174    reports:175      junit: dast-report.xml176    expire_in: 90 days177
178  # Security: Don't allow failure in production pipeline179  allow_failure: false180
181  only:182    - branches183
184# Security: Cleanup staging environment185cleanup_staging:186  stage: report187  image: alpine:latest188  script:189    - echo "Cleaning up staging environment..."190    - ./cleanup-staging.sh || true191  when: manual192  environment:193    name: staging194    action: stop
```

For faster feedback, use OWASP ZAP’s baseline scan mode for quick passive checks. Focus scans on specific parts of your application to speed things up. For API testing, OWASP ZAP works great for checking OWASP Top 10 API Security Risks. Use DAST alongside SAST for broader coverage.

Some teams even run DAST scans on production in non-intrusive mode to catch issues in the real environment. The key is automating these attacks so you find vulnerabilities before the bad guys do.

## [Integrating SCA into your CI/CD: keeping dependencies secure](#integrating-sca-into-your-cicd-keeping-dependencies-secure)

SCA in your CI/CD pipeline keeps your dependencies secure. These tools discover open-source components and third-party libraries, then flag known vulnerabilities and licensing issues.

Here’s the approach: automate scans and generate detailed reports. Trigger scans at scheduled intervals or on specific events like new commits. Running SCA directly in your CI/CD pipeline gives you continuous monitoring. Popular tools include OWASP Dependency-Check, Snyk, Black Duck, and Trivy. Some platforms offer combined solutions like Mend CLI and Veracode SCA.

Scan early in your pipeline to catch vulnerable dependencies before they cause problems. SCA tools generate SBOMs (Software Bill of Materials), giving you a complete inventory of your components. Use automated policies to block risky dependencies. Integrate with IDEs and version control systems for real-time developer alerts.

Look for tools that continuously discover and prioritize vulnerabilities by risk so your team can focus on what matters most. They should constantly monitor for newly reported vulnerabilities and automatically check license compliance. Configure your pipeline to fail builds that don’t meet your security policies and track vulnerability trends across builds.

Here’s a practical example using Trivy in GitHub Actions:

sca-dependency-scan.yml

```
1name: SCA Dependency Scan2
3# Security: Controlled trigger conditions4on:5  push:6    branches: [main, develop]7  pull_request:8    branches: [main]9    types: [opened, synchronize, reopened]10  schedule:11    # Security: Daily scan at 2 AM UTC for new CVEs12    - cron: '0 2 * * *'13  workflow_dispatch: # Manual security audits14
15# Security: Minimal required permissions16permissions:17  contents: read18  security-events: write19  pull-requests: write20  issues: write21
22jobs:23  validate-environment:24    name: Validate Scan Environment25    runs-on: ubuntu-latest26    timeout-minutes: 527
28    steps:29      - name: Validate Configuration30        run: |31          set -euo pipefail32
33          echo "Validating scan environment..."34
35          # Security: Check runner environment36          if [ -z "${GITHUB_WORKSPACE:-}" ]; then37            echo "::error::Invalid GitHub workspace"38            exit 139          fi40
41          # Security: Verify Trivy is available42          echo "✓ Environment validated"43
44  trivy_filesystem_scan:45    name: Scan Dependencies (Filesystem)46    runs-on: ubuntu-latest47    needs: validate-environment48    timeout-minutes: 2049
50    steps:51      # Security: Checkout with minimal permissions52      - name: Checkout Code53        uses: actions/checkout@v454        with:55          persist-credentials: false56          fetch-depth: 157
58      # Security: Use specific Trivy version for reproducibility59      - name: Run Trivy Vulnerability Scanner60        uses: aquasecurity/trivy-action@0.16.161        with:62          scan-type: 'fs'63          scan-ref: '.'64          format: 'sarif'65          output: 'trivy-results.sarif'66          severity: 'CRITICAL,HIGH'67          # Security: Scan for vulnerabilities and misconfigurations68          scanners: 'vuln,secret,config'69          # Security: Include license checks70          license-full: true71          # Security: Timeout protection72          timeout: '10m'73        env:74          # Security: Disable analytics/telemetry75          TRIVY_DISABLE_VEX_NOTICE: 'true'76
77      # Security: Validate SARIF output before upload78      - name: Validate SARIF Report79        run: |80          set -euo pipefail81
82          if [ ! -f "trivy-results.sarif" ]; then83            echo "::error::SARIF report not generated"84            exit 185          fi86
87          # Check if file is valid JSON88          if ! jq empty trivy-results.sarif 2>/dev/null; then89            echo "::error::Invalid SARIF format"90            exit 191          fi92
93          # Count vulnerabilities94          VULN_COUNT=$(jq '[.runs[].results[]] | length' trivy-results.sarif)95          echo "Found $VULN_COUNT security findings"96
97      # Security: Upload to GitHub Security tab98      - name: Upload to GitHub Security99        uses: github/codeql-action/upload-sarif@v3100        if: always()101        with:102          sarif_file: 'trivy-results.sarif'103          category: 'trivy-filesystem'104
105      # Security: Generate human-readable report106      - name: Generate Detailed Report107        uses: aquasecurity/trivy-action@0.16.1108        with:109          scan-type: 'fs'110          scan-ref: '.'111          format: 'table'112          output: 'trivy-report.txt'113          severity: 'CRITICAL,HIGH,MEDIUM'114          scanners: 'vuln,secret,config'115
116      # Security: Create sanitized report for artifact upload117      - name: Sanitize Report118        if: always()119        run: |120          set -euo pipefail121
122          # Remove potential secrets from report123          if [ -f "trivy-report.txt" ]; then124            # Mask potential sensitive patterns125            sed -i 's/[A-Za-z0-9]\{32,\}/***REDACTED***/g' trivy-report.txt126          fi127
128      # Security: Upload artifacts with retention policy129      - name: Upload Scan Results130        uses: actions/upload-artifact@v4131        if: always()132        with:133          name: trivy-scan-results-${{ github.run_number }}134          path: |135            trivy-results.sarif136            trivy-report.txt137          retention-days: 90138          if-no-files-found: error139
140      # Security: Enforce vulnerability threshold141      - name: Check Vulnerability Threshold142        uses: aquasecurity/trivy-action@0.16.1143        with:144          scan-type: 'fs'145          scan-ref: '.'146          exit-code: '1'147          severity: 'CRITICAL,HIGH'148          scanners: 'vuln'149          # Security: Ignore unfixed vulnerabilities in dev dependencies150          ignore-unfixed: true151
152      # Security: Create GitHub issue for critical findings153      - name: Create Security Issue154        if: failure()155        uses: actions/github-script@v7156        with:157          script: |158            const fs = require('fs');159            const report = fs.readFileSync('trivy-report.txt', 'utf8');160
161            const issue = await github.rest.issues.create({162              owner: context.repo.owner,163              repo: context.repo.repo,164              title: `🚨 Security: Critical vulnerabilities detected in ${context.ref}`,165              body: `## Security Scan Results\n\n` +166                    `**Workflow Run:** ${context.runId}\n` +167                    `**Commit:** ${context.sha}\n\n` +168                    `### Findings\n\n\`\`\`\n${report}\n\`\`\`\n\n` +169                    `**Action Required:** Review and remediate these vulnerabilities before merging.`,170              labels: ['security', 'vulnerability']171            });172
173            console.log(`Created issue #${issue.data.number}`);
```

This scans your filesystem for vulnerable dependencies, uploads findings to the GitHub Security tab, and fails the build if critical or high-severity vulnerabilities are found. You can also scan Docker images:

sca-docker-scan.yml

```
1# Security: Build Docker image with security controls2- name: Set up Docker Buildx3  uses: docker/setup-buildx-action@v34  with:5    # Security: Enable BuildKit security features6    driver-opts: |7      image=moby/buildkit:latest8      network=host9
10# Security: Build with security scanner integration11- name: Build Docker Image12  run: |13    set -euo pipefail14
15    # Security: Validate Dockerfile exists16    if [ ! -f "Dockerfile" ]; then17      echo "::error::Dockerfile not found"18      exit 119    fi20
21    # Security: Scan Dockerfile for issues before building22    docker run --rm -i hadolint/hadolint < Dockerfile || true23
24    # Security: Build with security labels and no cache for reproducibility25    docker build \26      --no-cache \27      --pull \28      --label "org.opencontainers.image.created=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" \29      --label "org.opencontainers.image.revision=${{ github.sha }}" \30      --label "org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }}" \31      -t myapp:${{ github.sha }} \32      -t myapp:scan-candidate \33      .34
35    echo "✓ Docker image built successfully"36
37# Security: Comprehensive image vulnerability scan38- name: Scan Docker Image for Vulnerabilities39  uses: aquasecurity/trivy-action@0.16.140  with:41    image-ref: 'myapp:${{ github.sha }}'42    format: 'sarif'43    output: 'trivy-image-results.sarif'44    severity: 'CRITICAL,HIGH,MEDIUM'45    # Security: Scan all layers and dependencies46    scanners: 'vuln,secret,config'47    # Security: Include OS packages and application dependencies48    vuln-type: 'os,library'49    # Security: Timeout protection50    timeout: '15m'51  env:52    TRIVY_DISABLE_VEX_NOTICE: 'true'53
54# Security: Validate and upload results55- name: Upload Image Scan Results56  uses: github/codeql-action/upload-sarif@v357  if: always()58  with:59    sarif_file: 'trivy-image-results.sarif'60    category: 'trivy-container'61
62# Security: Generate SBOM for supply chain security63- name: Generate SBOM64  run: |65    set -euo pipefail66
67    docker run --rm \68      -v /var/run/docker.sock:/var/run/docker.sock \69      aquasec/trivy image \70      --format cyclonedx \71      --output sbom.json \72      myapp:${{ github.sha }}73
74    # Validate SBOM is valid JSON75    if ! jq empty sbom.json 2>/dev/null; then76      echo "::error::Invalid SBOM format"77      exit 178    fi79
80    echo "✓ SBOM generated successfully"81
82# Security: Fail build on critical vulnerabilities83- name: Check Image Security Threshold84  run: |85    set -euo pipefail86
87    # Run scan with exit code check88    docker run --rm \89      -v /var/run/docker.sock:/var/run/docker.sock \90      aquasec/trivy image \91      --exit-code 1 \92      --severity CRITICAL,HIGH \93      --ignore-unfixed \94      myapp:${{ github.sha }}
```

The best SCA tools provide actionable remediation advice, not just a list of problems. They tell you exactly which version to upgrade to or how to work around the issue.

## [Putting it all together: tools in action](#putting-it-all-together-tools-in-action)

### [How can SonarQube help with shift-left in CI/CD?](#how-can-sonarqube-help-with-shift-left-in-cicd)

SonarQube is one of the most popular open-source platforms for continuous code quality and security analysis. It supports over 20 programming languages out of the box (with more available via plugins). SonarQube automatically scans your code for bugs, security vulnerabilities, and code smells, giving developers actionable feedback on code quality and security.

It integrates directly with major CI/CD platforms: GitHub Actions, GitLab CI/CD, Azure Pipelines, and Jenkins. The killer feature is the Quality Gate, which lets you define specific quality and security thresholds. If your code doesn’t meet these standards, the pipeline fails. No bad code gets through.

SonarQube also provides pull request decoration, showing developers feedback directly in their PRs. It integrates with IDEs for consistent analysis everywhere developers work. You get code coverage metrics and detailed reports on quality and security. SonarQube helps enforce coding standards and regulatory compliance, and its fast, specific feedback keeps developers moving instead of waiting.

You can deploy it on your own servers, use the cloud version, or run it in Docker and Kubernetes. Higher editions support branch analysis and PR/MR scanning. For Jenkins users, there’s a dedicated SonarQube Scanner plugin. GitLab integration lets you import projects and see quality gate status right in merge requests. Azure DevOps has an extension for easy pipeline integration.

Here’s a simple Jenkins pipeline with SonarQube:

Jenkinsfile

```
1// Security: Define pipeline with strict error handling2pipeline {3    agent any4
5    // Security: Set global timeout to prevent resource exhaustion6    options {7        timeout(time: 1, unit: 'HOURS')8        timestamps()9        disableConcurrentBuilds()10        buildDiscarder(logRotator(numToKeepStr: '10'))11    }12
13    // Security: Define required tools with specific versions14    tools {15        maven 'Maven-3.9.6'16        jdk 'JDK-17'17    }18
19    // Security: Define environment variables with validation20    environment {21        // Security: Mask sensitive values in logs22        SONAR_TOKEN = credentials('sonarqube-token')23        SONAR_HOST_URL = credentials('sonarqube-url')24        // Security: Set Maven options for secure builds25        MAVEN_OPTS = '-Xmx2048m -Dmaven.wagon.http.ssl.insecure=false -Dmaven.wagon.http.ssl.allowall=false'26    }27
28    stages {29        // Security: Validate environment before starting30        stage('Validate Environment') {31            steps {32                script {33                    // Security: Check required tools are available34                    sh '''35                        set -euo pipefail36
37                        echo "Validating build environment..."38
39                        # Check Maven version40                        mvn --version || { echo "ERROR: Maven not found"; exit 1; }41
42                        # Check Java version43                        java -version || { echo "ERROR: Java not found"; exit 1; }44
45                        # Validate credentials are set46                        if [ -z "$SONAR_TOKEN" ]; then47                            echo "ERROR: SonarQube token not configured"48                            exit 149                        fi50
51                        echo "✓ Environment validated successfully"52                    '''53                }54            }55        }56
57        // Security: Checkout with validation58        stage('Checkout') {59            steps {60                script {61                    // Security: Clean workspace before checkout62                    deleteDir()63
64                    // Security: Checkout with specific branch validation65                    checkout([66                        $class: 'GitSCM',67                        branches: [[name: '*/main']],68                        extensions: [69                            [$class: 'CleanCheckout'],70                            [$class: 'CloneOption', depth: 0, noTags: false, reference: '', shallow: false]71                        ],72                        userRemoteConfigs: [[73                            url: 'https://github.com/yourorg/yourrepo.git',74                            credentialsId: 'github-credentials'75                        ]]76                    ])77
78                    // Security: Verify workspace integrity79                    sh '''80                        set -euo pipefail81
82                        if [ ! -f "pom.xml" ]; then83                            echo "ERROR: pom.xml not found - invalid project structure"84                            exit 185                        fi86
87                        echo "✓ Workspace validated"88                    '''89                }90            }91        }92
93        // Security: Dependency vulnerability check94        stage('Security: Dependency Check') {95            steps {96                script {97                    sh '''98                        set -euo pipefail99
100                        echo "Checking dependencies for known vulnerabilities..."101
102                        # Run OWASP Dependency Check103                        mvn org.owasp:dependency-check-maven:check \104                            -DfailBuildOnCVSS=7 \105                            -DskipTestScope=true \106                            -DsuppressionFile=dependency-check-suppressions.xml \107                            -Dformats=HTML,JSON,JUNIT || {108                                echo "WARNING: Vulnerabilities found in dependencies"109                                exit 0110                            }111
112                        echo "✓ Dependency check completed"113                    '''114                }115            }116            post {117                always {118                    // Security: Archive dependency check report119                    publishHTML([120                        allowMissing: true,121                        alwaysLinkToLastBuild: true,122                        keepAll: true,123                        reportDir: 'target',124                        reportFiles: 'dependency-check-report.html',125                        reportName: 'OWASP Dependency Check'126                    ])127                }128            }129        }130
131        // Security: Secure build process132        stage('Build') {133            steps {134                script {135                    sh '''136                        set -euo pipefail137
138                        echo "Building project with security checks..."139
140                        # Security: Build with strict compiler warnings141                        mvn clean package \142                            -DskipTests=false \143                            -Dmaven.test.failure.ignore=false \144                            -Dcheckstyle.failOnViolation=true \145                            -Denforcer.fail=true \146                            -B  # Batch mode for CI147
148                        # Security: Verify build artifacts149                        if [ ! -f "target/*.jar" ]; then150                            echo "ERROR: Build artifact not found"151                            exit 1152                        fi153
154                        echo "✓ Build completed successfully"155                    '''156                }157            }158            post {159                success {160                    // Security: Archive build artifacts with checksum161                    script {162                        sh 'sha256sum target/*.jar > target/checksums.txt'163                        archiveArtifacts artifacts: 'target/*.jar,target/checksums.txt', fingerprint: true164                    }165                }166            }167        }168
169        // Security: SonarQube SAST analysis170        stage('SonarQube Analysis') {171            steps {172                script {173                    def scannerHome = tool 'SonarQubeScanner'174
175                    // Security: Run SonarQube with strict quality profiles176                    withSonarQubeEnv('SonarQube') {177                        sh """178                            set -euo pipefail179
180                            echo "Running SonarQube security analysis..."181
182                            # Security: Validate SonarQube URL183                            if ! echo "\$SONAR_HOST_URL" | grep -E '^https://[a-zA-Z0-9.-]+'; then184                                echo "ERROR: Invalid SonarQube URL"185                                exit 1186                            fi187
188                            # Run analysis with security-focused settings189                            ${scannerHome}/bin/sonar-scanner \190                                -Dsonar.projectKey=my-project \191                                -Dsonar.sources=src/main \192                                -Dsonar.tests=src/test \193                                -Dsonar.java.binaries=target/classes \194                                -Dsonar.java.test.binaries=target/test-classes \195                                -Dsonar.coverage.jacoco.xmlReportPaths=target/site/jacoco/jacoco.xml \196                                -Dsonar.exclusions='**/test/**,**/node_modules/**' \197                                -Dsonar.log.level=INFO198
199                            echo "✓ SonarQube analysis completed"200                        """201                    }202                }203            }204        }205
206        // Security: Enforce quality gate207        stage('Quality Gate') {208            steps {209                script {210                    // Security: Wait for quality gate with timeout211                    timeout(time: 10, unit: 'MINUTES') {212                        def qg = waitForQualityGate()213
214                        if (qg.status != 'OK') {215                            // Security: Log quality gate failure216                            echo "ERROR: Quality Gate failed with status: ${qg.status}"217                            echo "Review security findings at: ${env.SONAR_HOST_URL}"218
219                            // Security: Fail the build220                            error "Quality Gate failed - security standards not met"221                        }222
223                        echo "✓ Quality Gate passed"224                    }225                }226            }227        }228
229        // Security: Conditional deployment230        stage('Deploy') {231            when {232                allOf {233                    branch 'main'234                    expression { currentBuild.result == null || currentBuild.result == 'SUCCESS' }235                }236            }237            steps {238                script {239                    sh '''240                        set -euo pipefail241
242                        echo "Deploying application with security verification..."243
244                        # Security: Verify artifact integrity before deployment245                        if ! sha256sum -c target/checksums.txt; then246                            echo "ERROR: Artifact integrity check failed"247                            exit 1248                        fi249
250                        # Your secure deployment steps here251                        echo "✓ Deployment completed successfully"252                    '''253                }254            }255        }256    }257
258    // Security: Post-build actions with notifications259    post {260        always {261            // Security: Clean workspace after build262            cleanWs()263        }264
265        success {266            echo 'Pipeline completed successfully with all security checks passed'267        }268
269        failure {270            script {271                // Security: Send notification on failure272                emailext(273                    subject: "SECURITY ALERT: Pipeline Failed - ${currentBuild.fullDisplayName}",274                    body: """275                        Build failed with security issues:276
277                        Project: ${env.JOB_NAME}278                        Build Number: ${env.BUILD_NUMBER}279                        Build URL: ${env.BUILD_URL}280
281                        Action Required: Review security findings before proceeding.282                    """,283                    to: 'security-team@example.com',284                    mimeType: 'text/plain'285                )286            }287        }288
289        unstable {290            echo 'Build unstable - review security warnings'291        }292    }293}
```

This pipeline builds your code, runs SonarQube analysis, waits for the quality gate result, and only deploys if everything passes. If the quality gate fails, the pipeline stops and notifies your team.

### [Using OWASP ZAP for dynamic security testing in your pipeline](#using-owasp-zap-for-dynamic-security-testing-in-your-pipeline)

OWASP ZAP (Zed Attack Proxy) is one of the most popular open-source tools for web application security testing, maintained by the Open Web Application Security Project. It works as a man-in-the-middle proxy, intercepting traffic between your browser and application to find security vulnerabilities.

OWASP ZAP automatically detects common vulnerabilities like SQL injection and XSS. You can integrate it into CI/CD pipelines using official Docker images for automated security testing. The CLI makes automation easy, and the API lets you script custom security tests.

It offers different scan modes: quick passive scans and thorough active scans. Reports come in multiple formats (HTML, XML, JSON, Markdown) for easy sharing. Jenkins users get a dedicated plugin. You can also use it with GitLab CI via Docker and custom scripts, and with Azure DevOps through marketplace extensions.

For faster CI/CD feedback, use the baseline scan for quick passive checks. It supports authenticated scans so you can test protected areas of your app. It’s also excellent for API security testing.

Here’s an example Docker Compose setup for local DAST testing:

compose.yml

```
1services:2  # Application under test3  app:4    image: myapp:latest5    container_name: dast-target-app6
7    # Security: Network isolation8    networks:9      - dast-network10
11    # Security: Resource limits to prevent DoS12    deploy:13      resources:14        limits:15          cpus: '1.0'16          memory: 1G17        reservations:18          cpus: '0.5'19          memory: 512M20
21    # Security: Port mapping (localhost only)22    ports:23      - '127.0.0.1:3000:3000'24
25    # Security: Environment variables (use .env file for secrets)26    environment:27      - NODE_ENV=test28      - LOG_LEVEL=info29      # Security: Disable debug modes in test30      - DEBUG=false31
32    # Security: Health check for readiness33    healthcheck:34      test:35        [36          'CMD',37          'wget',38          '--quiet',39          '--tries=1',40          '--spider',41          'http://localhost:3000/health',42        ]43      interval: 10s44      timeout: 5s45      retries: 546      start_period: 30s47
48    # Security: Read-only root filesystem where possible49    read_only: true50
51    # Security: Drop unnecessary capabilities52    cap_drop:53      - ALL54    cap_add:55      - NET_BIND_SERVICE56
57    # Security: Run as non-root user58    user: '1000:1000'59
60    # Security: Temporary volumes for writable paths61    tmpfs:62      - /tmp:noexec,nosuid,size=100M63      - /var/tmp:noexec,nosuid,size=100M64
65  # OWASP ZAP scanner66  zap:67    image: owasp/zap2docker-stable:latest68    container_name: dast-zap-scanner69
70    # Security: Network isolation71    networks:72      - dast-network73
74    # Security: Resource limits75    deploy:76      resources:77        limits:78          cpus: '2.0'79          memory: 2G80        reservations:81          cpus: '1.0'82          memory: 1G83
84    # Security: Wait for app to be healthy before scanning85    depends_on:86      app:87        condition: service_healthy88
89    # Security: Custom entrypoint for enhanced scanning90    entrypoint: ['/bin/bash', '-c']91    command:92      - |93        set -euo pipefail94
95        echo "Starting DAST security scan..."96
97        # Security: Wait for app to be fully ready98        sleep 1099
100        # Security: Create secure working directory101        mkdir -p /zap/wrk102        chmod 700 /zap/wrk103
104        # Security: Configure ZAP with safe settings105        cat > /zap/wrk/zap-options.prop << 'EOF'106        # Rate limiting to prevent DoS107        scanner.threadPerHost=2108        connection.timeoutInSecs=30109        # Disable risky attack vectors in test environment110        scanner.attackMode=standard111        EOF112
113        # Security: Run baseline scan with comprehensive reporting114        zap-baseline.py \115          -t http://app:3000 \116          -c /zap/wrk/zap-options.prop \117          -r /zap/wrk/dast-report.html \118          -w /zap/wrk/dast-report.md \119          -J /zap/wrk/dast-report.json \120          -x /zap/wrk/dast-report.xml \121          -d \122          -T 15 \123          -z "-config api.disablekey=true" || true124
125        # Security: Generate summary126        if [ -f "/zap/wrk/dast-report.json" ]; then127          echo "Scan completed. Analyzing results..."128
129          # Parse JSON for vulnerabilities130          HIGH_COUNT=$$(jq '[.site[].alerts[] | select(.riskdesc | startswith("High"))] | length' /zap/wrk/dast-report.json 2>/dev/null || echo "0")131          MEDIUM_COUNT=$$(jq '[.site[].alerts[] | select(.riskdesc | startswith("Medium"))] | length' /zap/wrk/dast-report.json 2>/dev/null || echo "0")132          LOW_COUNT=$$(jq '[.site[].alerts[] | select(.riskdesc | startswith("Low"))] | length' /zap/wrk/dast-report.json 2>/dev/null || echo "0")133
134          echo "========================================"135          echo "DAST Scan Results:"136          echo "  High Severity:   $$HIGH_COUNT"137          echo "  Medium Severity: $$MEDIUM_COUNT"138          echo "  Low Severity:    $$LOW_COUNT"139          echo "========================================"140
141          # Security: Set exit code based on findings142          if [ "$$HIGH_COUNT" -gt 0 ]; then143            echo "CRITICAL: High severity vulnerabilities detected!"144            exit 1145          fi146        else147          echo "ERROR: Scan report not generated"148          exit 1149        fi150
151        echo "DAST scan completed successfully"152
153    # Security: Mount reports directory with restricted permissions154    volumes:155      - type: bind156        source: ./reports157        target: /zap/wrk158        read_only: false159
160    # Security: Run as non-root user161    user: '1000:1000'162
163    # Security: Drop unnecessary capabilities164    cap_drop:165      - ALL166
167# Security: Isolated network for testing168networks:169  dast-network:170    driver: bridge171    internal: false172    ipam:173      config:174        - subnet: 172.28.0.0/16
```

And here’s a more advanced Azure DevOps pipeline with authenticated scanning:

azure-pipelines-dast.yml

```
1# Security: Azure DevOps DAST Pipeline with Comprehensive Controls2trigger:3  branches:4    include:5      - main6      - develop7  paths:8    exclude:9      - docs/*10      - '*.md'11
12# Security: Scheduled security scans13schedules:14  - cron: '0 2 * * 0' # Weekly scan on Sunday at 2 AM15    displayName: 'Weekly Security Scan'16    branches:17      include:18        - main19    always: true20
21# Security: Define variables with validation22variables:23  - name: stagingUrl24    value: 'https://staging.example.com'25  - name: zapTimeout26    value: '15'27  - name: maxScanDuration28    value: '30'29  # Security: Use variable groups for secrets30  - group: 'security-credentials'31
32pool:33  vmImage: 'ubuntu-22.04' # Use specific version34
35stages:36  - stage: Validate37    displayName: 'Validate Configuration'38    jobs:39      - job: ValidateEnvironment40        displayName: 'Validate Security Environment'41        timeoutInMinutes: 1042        steps:43          - bash: |44              set -euo pipefail45
46              echo "Validating security scan configuration..."47
48              # Security: Validate staging URL format49              if ! echo "$(stagingUrl)" | grep -E '^https://[a-zA-Z0-9.-]+'; then50                echo "##vso[task.logissue type=error]Invalid staging URL format - must use HTTPS"51                exit 152              fi53
54              # Security: Check for required secrets55              if [ -z "$(stagingUsername)" ] || [ -z "$(stagingPassword)" ]; then56                echo "##vso[task.logissue type=error]Authentication credentials not configured"57                exit 158              fi59
60              echo "✓ Configuration validated successfully"61            displayName: 'Validate Configuration'62
63  - stage: SecurityTest64    displayName: 'DAST Security Testing'65    dependsOn: Validate66    condition: succeeded()67    jobs:68      - job: PrepareScan69        displayName: 'Prepare Security Scan'70        timeoutInMinutes: 1071        steps:72          # Security: Create secure working directory73          - bash: |74              set -euo pipefail75
76              mkdir -p $(Build.ArtifactStagingDirectory)/zap-config77              mkdir -p $(Build.ArtifactStagingDirectory)/zap-reports78              chmod 700 $(Build.ArtifactStagingDirectory)/zap-config79
80              echo "✓ Scan directories prepared"81            displayName: 'Prepare Scan Environment'82
83      - job: DASTScan84        displayName: 'Run DAST Security Scan'85        dependsOn: PrepareScan86        timeoutInMinutes: $(maxScanDuration)87
88        steps:89          # Security: Pull specific Docker image version90          - task: Docker@291            displayName: 'Pull OWASP ZAP Image'92            inputs:93              command: 'pull'94              arguments: 'owasp/zap2docker-stable:2.14.0'95            retryCountOnTaskFailure: 396
97          # Security: Verify target availability before scanning98          - bash: |99              set -euo pipefail100
101              echo "Verifying target application is accessible..."102
103              MAX_RETRIES=10104              RETRY_COUNT=0105
106              while [ $RETRY_COUNT -lt $MAX_RETRIES ]; do107                if curl -sSf -m 10 "$(stagingUrl)/health" > /dev/null 2>&1; then108                  echo "✓ Target application is healthy"109                  exit 0110                fi111
112                RETRY_COUNT=$((RETRY_COUNT + 1))113                echo "Attempt $RETRY_COUNT/$MAX_RETRIES failed, retrying..."114                sleep 10115              done116
117              echo "##vso[task.logissue type=error]Target application not accessible"118              exit 1119            displayName: 'Verify Target Availability'120
121          # Security: Create authenticated scan configuration122          - bash: |123              set -euo pipefail124
125              echo "Creating secure ZAP configuration..."126
127              # Security: Create authentication context with validation128              cat > $(Build.ArtifactStagingDirectory)/zap-config/auth-config.yaml << EOF129              ---130              env:131                contexts:132                  - name: "Staging Environment"133                    urls:134                      - "$(stagingUrl)"135                    authentication:136                      method: "form"137                      parameters:138                        loginUrl: "$(stagingUrl)/login"139                        loginRequestData: "username={%username%}&password={%password%}"140                      verification:141                        method: "response"142                        loggedInRegex: "\\QLogout\\E"143                        loggedOutRegex: "\\QLogin\\E"144                    sessionManagement:145                      method: "cookie"146                users:147                  - name: "test-user"148                    credentials:149                      username: "\$(TEST_USERNAME)"150                      password: "\$(TEST_PASSWORD)"151              EOF152
153              # Security: Validate configuration file154              if [ ! -s "$(Build.ArtifactStagingDirectory)/zap-config/auth-config.yaml" ]; then155                echo "##vso[task.logissue type=error]Failed to create auth configuration"156                exit 1157              fi158
159              echo "✓ Authentication configuration created"160            displayName: 'Create Auth Configuration'161            env:162              TEST_USERNAME: $(stagingUsername)163              TEST_PASSWORD: $(stagingPassword)164
165          # Security: Run comprehensive DAST scan166          - bash: |167              set -euo pipefail168
169              echo "Starting DAST security scan..."170              echo "Target: $(stagingUrl)"171              echo "Timeout: $(zapTimeout) minutes"172
173              # Security: Run ZAP with resource limits and timeout174              docker run --rm \175                --name zap-scanner-$(Build.BuildId) \176                --memory=2g \177                --cpus=2 \178                -v $(Build.ArtifactStagingDirectory)/zap-config:/zap/wrk/config:ro \179                -v $(Build.ArtifactStagingDirectory)/zap-reports:/zap/wrk/reports:rw \180                -e "ZAP_JAVA_OPTS=-Xmx1536m" \181                owasp/zap2docker-stable:2.14.0 \182                zap-full-scan.py \183                  -t "$(stagingUrl)" \184                  -n /zap/wrk/config/auth-config.yaml \185                  -U test-user \186                  -r /zap/wrk/reports/dast-report.html \187                  -w /zap/wrk/reports/dast-report.md \188                  -J /zap/wrk/reports/dast-report.json \189                  -x /zap/wrk/reports/dast-report.xml \190                  -d \191                  -T $(zapTimeout) \192                  -z "-config api.disablekey=true -config spider.maxDuration=$(zapTimeout) -config scanner.threadPerHost=2" \193                  || SCAN_EXIT_CODE=$?194
195              # Security: Analyze scan results196              if [ -f "$(Build.ArtifactStagingDirectory)/zap-reports/dast-report.json" ]; then197                echo "Analyzing scan results..."198
199                # Parse vulnerability counts200                HIGH_COUNT=$(jq '[.site[].alerts[] | select(.riskdesc | startswith("High"))] | length' \201                  $(Build.ArtifactStagingDirectory)/zap-reports/dast-report.json 2>/dev/null || echo "0")202                MEDIUM_COUNT=$(jq '[.site[].alerts[] | select(.riskdesc | startswith("Medium"))] | length' \203                  $(Build.ArtifactStagingDirectory)/zap-reports/dast-report.json 2>/dev/null || echo "0")204                LOW_COUNT=$(jq '[.site[].alerts[] | select(.riskdesc | startswith("Low"))] | length' \205                  $(Build.ArtifactStagingDirectory)/zap-reports/dast-report.json 2>/dev/null || echo "0")206
207                echo "========================================"208                echo "DAST Scan Results Summary:"209                echo "  High Severity:   $HIGH_COUNT"210                echo "  Medium Severity: $MEDIUM_COUNT"211                echo "  Low Severity:    $LOW_COUNT"212                echo "========================================"213
214                # Security: Set pipeline variables for reporting215                echo "##vso[task.setvariable variable=highVulnCount;isOutput=true]$HIGH_COUNT"216                echo "##vso[task.setvariable variable=mediumVulnCount;isOutput=true]$MEDIUM_COUNT"217
218                # Security: Fail on high severity findings219                if [ "$HIGH_COUNT" -gt 0 ]; then220                  echo "##vso[task.logissue type=error]Critical vulnerabilities detected: $HIGH_COUNT high severity issues"221                  exit 1222                fi223
224                echo "✓ DAST scan completed successfully"225              else226                echo "##vso[task.logissue type=error]Scan report not generated"227                exit 1228              fi229            displayName: 'Run OWASP ZAP Scan'230            timeoutInMinutes: $(maxScanDuration)231
232          # Security: Sanitize reports before publishing233          - bash: |234              set -euo pipefail235
236              echo "Sanitizing scan reports..."237
238              # Security: Remove potential credentials from reports239              find $(Build.ArtifactStagingDirectory)/zap-reports -type f -exec \240                sed -i 's/$(stagingUsername)/***REDACTED***/g' {} \;241
242              # Security: Generate report summary243              cat > $(Build.ArtifactStagingDirectory)/zap-reports/scan-summary.txt << EOF244              DAST Security Scan Summary245              ==========================246              Build ID: $(Build.BuildId)247              Target URL: $(stagingUrl)248              Scan Date: $(date -u +"%Y-%m-%d %H:%M:%S UTC")249              Pipeline: $(Build.DefinitionName)250
251              Reports generated:252              - HTML: dast-report.html253              - JSON: dast-report.json254              - XML: dast-report.xml255              - Markdown: dast-report.md256              EOF257
258              echo "✓ Reports sanitized"259            displayName: 'Sanitize Reports'260            condition: always()261
262          # Security: Publish scan artifacts263          - task: PublishBuildArtifacts@1264            displayName: 'Publish DAST Reports'265            inputs:266              PathtoPublish: '$(Build.ArtifactStagingDirectory)/zap-reports'267              ArtifactName: 'dast-security-reports-$(Build.BuildId)'268              publishLocation: 'Container'269            condition: always()270
271          # Security: Publish test results272          - task: PublishTestResults@2273            displayName: 'Publish Test Results'274            inputs:275              testResultsFormat: 'JUnit'276              testResultsFiles: '$(Build.ArtifactStagingDirectory)/zap-reports/dast-report.xml'277              testRunTitle: 'DAST Security Scan'278              mergeTestResults: true279              failTaskOnFailedTests: true280            condition: always()281
282  - stage: Report283    displayName: 'Security Reporting'284    dependsOn: SecurityTest285    condition: always()286    jobs:287      - job: GenerateReport288        displayName: 'Generate Security Report'289        steps:290          # Security: Send notification on findings291          - bash: |292              set -euo pipefail293
294              echo "Generating security report summary..."295
296              # Add custom reporting logic here297              echo "✓ Security scan completed"298              echo "Review detailed findings in published artifacts"299            displayName: 'Generate Summary'
```

This setup tests authenticated areas of your app, which is where most interesting vulnerabilities hide. The scan results are published as artifacts you can review after the pipeline runs.

### [Using Trivy for vulnerability scanning in CI/CD](#using-trivy-for-vulnerability-scanning-in-cicd)

Trivy is a fast, full-featured open-source vulnerability scanner from Aqua Security. It’s designed to be simple and fast, making it perfect for CI/CD pipelines. Trivy scans containers, filesystems, and Git repositories for security issues. The best part? It’s incredibly easy to install and use, usually requiring zero configuration (no database setup needed).

Trivy’s speed makes it ideal for fast-paced CI/CD workflows. Its design fits the DevSecOps philosophy, letting security teams easily add vulnerability scanning to existing CI pipelines. You can use it as a CLI tool or Docker container, giving you flexibility.

It checks for OS package vulnerabilities, application dependency vulnerabilities, and other known security issues. Beyond vulnerabilities, Trivy scans Dockerfiles and Kubernetes manifests for misconfigurations. It outputs results in multiple formats (JSON, HTML) for easy integration with reporting tools.

In CI/CD, Trivy can fail your build if it finds critical issues, preventing insecure code or containers from being deployed. It has specific integrations for GitHub Actions, Azure DevOps, CircleCI, GitLab CI, and Jenkins. Trivy generates SBOMs for detailed component visibility. There’s a Jenkins plugin available, and GitLab has built-in container scanning templates using Trivy.

Here’s a full example for a Node.js application in GitLab CI:

gitlab-ci-trivy.yml

```
1# Security: Comprehensive GitLab CI Pipeline with Trivy2stages:3  - validate4  - build5  - scan6  - report7  - deploy8
9# Security: Global variables with safe defaults10variables:11  # Docker image settings12  IMAGE_NAME: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA13  LATEST_IMAGE: $CI_REGISTRY_IMAGE:latest14  # Security: Enable BuildKit for better security15  DOCKER_BUILDKIT: '1'16  # Security: Set scan timeouts17  TRIVY_TIMEOUT: '15m'18  # Security: Fail on high/critical vulnerabilities19  FAIL_ON_SEVERITY: 'CRITICAL,HIGH'20
21# Security: Reusable configuration22.security_template:23  retry:24    max: 225    when:26      - runner_system_failure27      - stuck_or_timeout_failure28
29# Security: Validate pipeline configuration30validate_config:31  stage: validate32  image: alpine:3.1933  script:34    - |35      set -euo pipefail36
37      echo "Validating pipeline configuration..."38
39      # Security: Check required variables40      if [ -z "${CI_REGISTRY_IMAGE:-}" ]; then41        echo "ERROR: CI_REGISTRY_IMAGE not set"42        exit 143      fi44
45      if [ -z "${CI_COMMIT_SHORT_SHA:-}" ]; then46        echo "ERROR: CI_COMMIT_SHORT_SHA not set"47        exit 148      fi49
50      echo "✓ Configuration validated"51  only:52    - branches53    - tags54
55# Security: Build Docker image with controls56build:57  stage: build58  image: docker:24-cli59  services:60    - docker:24-dind61
62  # Security: Set resource limits63  timeout: 30 minutes64
65  variables:66    # Security: DinD configuration67    DOCKER_TLS_CERTDIR: '/certs'68    DOCKER_DRIVER: overlay269
70  before_script:71    - |72      set -euo pipefail73
74      # Security: Validate Docker daemon75      if ! docker info >/dev/null 2>&1; then76        echo "ERROR: Docker daemon not available"77        exit 178      fi79
80      # Security: Login to registry81      echo "$CI_REGISTRY_PASSWORD" | docker login -u "$CI_REGISTRY_USER" --password-stdin "$CI_REGISTRY"82
83  script:84    - |85      set -euo pipefail86
87      echo "Building Docker image: $IMAGE_NAME"88
89      # Security: Validate Dockerfile90      if [ ! -f "Dockerfile" ]; then91        echo "ERROR: Dockerfile not found"92        exit 193      fi94
95      # Security: Scan Dockerfile for issues96      docker run --rm -i hadolint/hadolint:latest < Dockerfile || true97
98      # Security: Build with security best practices99      docker build \100        --no-cache \101        --pull \102        --label "org.opencontainers.image.created=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" \103        --label "org.opencontainers.image.revision=$CI_COMMIT_SHA" \104        --label "org.opencontainers.image.source=$CI_PROJECT_URL" \105        --label "org.opencontainers.image.version=$CI_COMMIT_SHORT_SHA" \106        --tag "$IMAGE_NAME" \107        --tag "$LATEST_IMAGE" \108        .109
110      # Security: Push image111      docker push "$IMAGE_NAME"112      docker push "$LATEST_IMAGE"113
114      echo "✓ Image built and pushed successfully"115
116  only:117    - branches118    - tags119
120# Security: Scan filesystem for vulnerabilities121trivy_fs_scan:122  stage: scan123  image:124    name: aquasec/trivy:0.48.3125    entrypoint: ['']126
127  timeout: 20 minutes128
129  extends: .security_template130
131  script:132    - |133      set -euo pipefail134
135      echo "Scanning filesystem for vulnerabilities..."136
137      # Security: Comprehensive filesystem scan138      trivy fs \139        --exit-code 0 \140        --severity "${FAIL_ON_SEVERITY},MEDIUM" \141        --format json \142        --output trivy-fs-report.json \143        --scanners vuln,secret,config \144        --timeout "${TRIVY_TIMEOUT}" \145        .146
147      # Security: Generate human-readable report148      trivy fs \149        --severity "${FAIL_ON_SEVERITY},MEDIUM,LOW" \150        --format table \151        --output trivy-fs-report.txt \152        .153
154      # Security: Parse results155      if [ -f "trivy-fs-report.json" ]; then156        CRITICAL_COUNT=$(jq '[.Results[].Vulnerabilities[]? | select(.Severity=="CRITICAL")] | length' trivy-fs-report.json)157        HIGH_COUNT=$(jq '[.Results[].Vulnerabilities[]? | select(.Severity=="HIGH")] | length' trivy-fs-report.json)158
159        echo "Filesystem Scan Results:"160        echo "  Critical: $CRITICAL_COUNT"161        echo "  High: $HIGH_COUNT"162      fi163
164      # Security: Fail on critical vulnerabilities165      trivy fs \166        --exit-code 1 \167        --severity CRITICAL \168        --format table \169        . || {170          echo "ERROR: Critical vulnerabilities detected in filesystem"171          exit 1172        }173
174      echo "✓ Filesystem scan completed"175
176  artifacts:177    name: 'trivy-fs-scan-$CI_COMMIT_SHORT_SHA'178    reports:179      dependency_scanning: trivy-fs-report.json180    paths:181      - trivy-fs-report.json182      - trivy-fs-report.txt183    expire_in: 90 days184    when: always185
186  allow_failure: false187
188  only:189    - branches190    - tags191
192# Security: Scan Docker image for vulnerabilities193trivy_image_scan:194  stage: scan195  image:196    name: aquasec/trivy:0.48.3197    entrypoint: ['']198  services:199    - docker:24-dind200
201  timeout: 25 minutes202
203  extends: .security_template204
205  dependencies:206    - build207
208  variables:209    DOCKER_TLS_CERTDIR: '/certs'210
211  before_script:212    - |213      set -euo pipefail214
215      # Security: Verify Docker connectivity216      if ! docker info >/dev/null 2>&1; then217        echo "ERROR: Cannot connect to Docker daemon"218        exit 1219      fi220
221      # Security: Login to registry222      echo "$CI_REGISTRY_PASSWORD" | docker login -u "$CI_REGISTRY_USER" --password-stdin "$CI_REGISTRY"223
224  script:225    - |226      set -euo pipefail227
228      echo "Scanning Docker image: $IMAGE_NAME"229
230      # Security: Comprehensive image scan231      trivy image \232        --exit-code 0 \233        --severity "${FAIL_ON_SEVERITY},MEDIUM" \234        --format json \235        --output trivy-image-report.json \236        --scanners vuln,secret,config \237        --timeout "${TRIVY_TIMEOUT}" \238        "$IMAGE_NAME"239
240      # Security: Generate table report241      trivy image \242        --severity "${FAIL_ON_SEVERITY},MEDIUM" \243        --format table \244        --output trivy-image-report.txt \245        "$IMAGE_NAME"246
247      # Security: Generate SBOM248      echo "Generating SBOM..."249      trivy image \250        --format cyclonedx \251        --output sbom.json \252        "$IMAGE_NAME"253
254      # Security: Validate SBOM255      if ! jq empty sbom.json 2>/dev/null; then256        echo "WARNING: Invalid SBOM format"257      fi258
259      # Security: Parse and report findings260      if [ -f "trivy-image-report.json" ]; then261        CRITICAL_COUNT=$(jq '[.Results[].Vulnerabilities[]? | select(.Severity=="CRITICAL")] | length' trivy-image-report.json)262        HIGH_COUNT=$(jq '[.Results[].Vulnerabilities[]? | select(.Severity=="HIGH")] | length' trivy-image-report.json)263        MEDIUM_COUNT=$(jq '[.Results[].Vulnerabilities[]? | select(.Severity=="MEDIUM")] | length' trivy-image-report.json)264
265        echo "========================================"266        echo "Image Scan Results:"267        echo "  Critical: $CRITICAL_COUNT"268        echo "  High: $HIGH_COUNT"269        echo "  Medium: $MEDIUM_COUNT"270        echo "========================================"271      fi272
273      # Security: Fail on critical/high vulnerabilities274      trivy image \275        --exit-code 1 \276        --severity "${FAIL_ON_SEVERITY}" \277        --format table \278        "$IMAGE_NAME" || {279          echo "ERROR: Critical/High vulnerabilities detected in image"280          exit 1281        }282
283      echo "✓ Image scan completed successfully"284
285  artifacts:286    name: 'trivy-image-scan-$CI_COMMIT_SHORT_SHA'287    reports:288      container_scanning: trivy-image-report.json289    paths:290      - trivy-image-report.json291      - trivy-image-report.txt292      - sbom.json293    expire_in: 90 days294    when: always295
296  allow_failure: false297
298  only:299    - branches300    - tags301
302# Security: Scan Kubernetes manifests303trivy_config_scan:304  stage: scan305  image:306    name: aquasec/trivy:0.48.3307    entrypoint: ['']308
309  timeout: 15 minutes310
311  extends: .security_template312
313  script:314    - |315      set -euo pipefail316
317      # Security: Check if k8s directory exists318      if [ ! -d "./k8s" ]; then319        echo "INFO: No Kubernetes manifests found, skipping config scan"320        exit 0321      fi322
323      echo "Scanning Kubernetes configurations..."324
325      # Security: Scan configurations326      trivy config \327        --exit-code 0 \328        --severity "${FAIL_ON_SEVERITY},MEDIUM" \329        --format json \330        --output trivy-config-report.json \331        --timeout "${TRIVY_TIMEOUT}" \332        ./k8s333
334      # Security: Generate table report335      trivy config \336        --severity "${FAIL_ON_SEVERITY},MEDIUM,LOW" \337        --format table \338        --output trivy-config-report.txt \339        ./k8s340
341      # Security: Fail on high severity misconfigurations342      trivy config \343        --exit-code 1 \344        --severity "${FAIL_ON_SEVERITY}" \345        --format table \346        ./k8s || {347          echo "WARNING: High severity misconfigurations detected"348          exit 0  # Don't block on config issues349        }350
351      echo "✓ Configuration scan completed"352
353  artifacts:354    name: 'trivy-config-scan-$CI_COMMIT_SHORT_SHA'355    paths:356      - trivy-config-report.json357      - trivy-config-report.txt358    expire_in: 90 days359    when: always360
361  allow_failure: true # Config warnings shouldn't block deployment362
363  only:364    - branches365    - tags366
367# Security: Generate consolidated report368generate_report:369  stage: report370  image: alpine:3.19371
372  dependencies:373    - trivy_fs_scan374    - trivy_image_scan375    - trivy_config_scan376
377  before_script:378    - apk add --no-cache jq379
380  script:381    - |382      set -euo pipefail383
384      echo "Generating consolidated security report..."385
386      cat > security-report.md << EOF387      # Security Scan Report388
389      **Project:** $CI_PROJECT_NAME390      **Commit:** $CI_COMMIT_SHORT_SHA391      **Pipeline:** $CI_PIPELINE_ID392      **Date:** $(date -u +"%Y-%m-%d %H:%M:%S UTC")393
394      ## Scan Results Summary395
396      EOF397
398      # Add filesystem scan results399      if [ -f "trivy-fs-report.txt" ]; then400        echo "### Filesystem Scan" >> security-report.md401        echo '```' >> security-report.md402        head -n 50 trivy-fs-report.txt >> security-report.md403        echo '```' >> security-report.md404        echo "" >> security-report.md405      fi406
407      # Add image scan results408      if [ -f "trivy-image-report.txt" ]; then409        echo "### Container Image Scan" >> security-report.md410        echo '```' >> security-report.md411        head -n 50 trivy-image-report.txt >> security-report.md412        echo '```' >> security-report.md413        echo "" >> security-report.md414      fi415
416      echo "✓ Consolidated report generated"417
418  artifacts:419    name: 'security-report-$CI_COMMIT_SHORT_SHA'420    paths:421      - security-report.md422    expire_in: 90 days423
424  only:425    - branches426    - tags427
428# Security: Deploy only if scans pass429deploy:430  stage: deploy431  image: alpine:3.19432
433  dependencies:434    - build435    - trivy_fs_scan436    - trivy_image_scan437
438  script:439    - |440      set -euo pipefail441
442      echo "Deploying $IMAGE_NAME..."443
444      # Security: Verify all scans passed445      echo "✓ All security scans passed"446      echo "✓ Deployment authorized"447
448      # Your deployment commands here449
450  only:451    - main452    - tags453
454  when: manual # Require manual approval for production
```

This pipeline scans three things: your filesystem for vulnerable dependencies, your Docker image for OS and app vulnerabilities, and your Kubernetes configs for misconfigurations. It generates an SBOM and fails the build if critical or high-severity issues are found. You get broad security coverage without slowing down your pipeline significantly.

## [Challenges and best practices](#challenges-and-best-practices)

### [What are some common challenges when implementing shift-left security?](#what-are-some-common-challenges-when-implementing-shift-left-security)

Let’s be real: implementing shift-left security isn’t always smooth sailing. You’ll face resistance to change. Development, security, and ops teams might push back against new practices and tools. Getting everyone on board takes time and patience.

Knowledge gaps are another big hurdle. If your teams lack security expertise, making shift-left work is tough. Often, security tools don’t play nicely with development tools, causing visibility issues and friction. Then there’s alert fatigue. Automated security tools can flood developers with alerts and false positives, leading them to ignore important warnings. You’re also trying to balance thorough security testing with the pressure to ship fast.

Other headaches include incomplete testing, failing to maintain the pipeline, underestimating scalability needs, and struggling with configuration management across different environments. Debugging and reporting within CI/CD pipelines can be tricky. Version conflicts might pop up when introducing new security checks.

You might not have fully automated security tools that fit well into CI/CD, or you might lack consistent security practices across your infrastructure. There’s the constant worry that security testing will slow down development. Developer adoption can be the biggest challenge, especially if they see security as extra work or if they’re only measured on feature velocity, not code security.

Sometimes security tools don’t match your organization’s specific needs. Scaling shift-left across large, complex organizations is a massive undertaking. Early in development, you can’t always predict runtime issues. And there’s a risk of overloading developers with security responsibilities, expecting them to become security experts on top of everything else they do.

### [What are the best practices for a shift-left implementation in CI/CD?](#what-are-the-best-practices-for-a-shift-left-implementation-in-cicd)

Making shift-left work requires clear security policies that everyone understands. No ambiguity. Automate security testing (SAST, DAST, and SCA) throughout your CI/CD pipeline to catch issues early without slowing things down. Train developers on secure coding and security tool usage. This isn’t optional. It’s essential for making security a priority.

Build collaboration between dev, security, and ops teams (that’s the DevSecOps mindset). When everyone works together and shares responsibility, security goals become clearer. Prioritize findings so teams know what to fix first. Not all vulnerabilities are created equal.

Keep your security tools and dependencies updated to stay ahead of new threats. Have clear remediation workflows so vulnerabilities get fixed quickly. Configure tools to reduce false positives so developers can focus on real issues. Make security everyone’s job and create a culture where everyone contributes to building secure software.

Continuously monitor and improve your security practices based on feedback and emerging threats. Start by thinking about security requirements early and add security checks to code reviews. Use tools that automate security and enable collaboration. Track security metrics throughout development and maintain current security documentation.

First, assess your current development process to find where you can effectively shift left. Create a new shift-left security strategy with clear goals and responsibilities. Implement gradually, starting with easy wins. Try to replicate your production environment early in the process.

Encourage early and frequent testing. Use automation for continuous integration and delivery. Carefully evaluate and monitor third-party software. Build a strong security awareness culture among developers. Give them the right tools and knowledge. Always monitor and improve your security policies to keep up with new threats.

Here’s a practical example of a full security pipeline that combines all three approaches:

complete-security-pipeline.yml

```
1# Security: Complete Shift-Left Security Pipeline with All Controls2name: Complete Security Pipeline3
4# Security: Controlled trigger conditions5on:6  push:7    branches: [main, develop]8    paths-ignore:9      - '**.md'10      - 'docs/**'11  pull_request:12    branches: [main]13    types: [opened, synchronize, reopened]14  schedule:15    # Security: Weekly comprehensive scan16    - cron: '0 3 * * 0'17  workflow_dispatch: # Manual security audits18
19# Security: Minimal required permissions (OIDC best practice)20permissions:21  contents: read22  security-events: write23  pull-requests: write24  deployments: write25  statuses: write26
27# Security: Environment variables28env:29  # Security: Set global timeout30  GLOBAL_TIMEOUT: 12031  # Security: Enable fail-fast on critical issues32  FAIL_ON_CRITICAL: 'true'33
34jobs:35  # Security: Pre-flight validation36  validate:37    name: Validate Pipeline Configuration38    runs-on: ubuntu-latest39    timeout-minutes: 540    steps:41      - name: Validate Environment42        run: |43          set -euo pipefail44
45          echo "Validating pipeline environment..."46
47          # Security: Verify required secrets48          REQUIRED_SECRETS=("SONAR_TOKEN" "SONAR_HOST_URL" "STAGING_URL")49          for secret in "${REQUIRED_SECRETS[@]}"; do50            if [ -z "${!secret:-}" ]; then51              echo "::error::Required secret $secret not configured"52              exit 153            fi54          done55
56          echo "✓ Environment validated"57
58  # Security: Static Analysis (SAST) - Fast feedback59  sast:60    name: Static Analysis (SAST)61    runs-on: ubuntu-latest62    needs: validate63    timeout-minutes: 3064
65    steps:66      - name: Checkout Code67        uses: actions/checkout@v468        with:69          fetch-depth: 070          persist-credentials: false71
72      - name: Set up JDK73        uses: actions/setup-java@v474        with:75          java-version: '17'76          distribution: 'temurin'77          cache: 'maven'78
79      # Security: Run SonarQube with strict settings80      - name: Run SonarQube Scan81        uses: sonarsource/sonarqube-scan-action@v282        env:83          SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}84          SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}85        with:86          args: >87            -Dsonar.qualitygate.wait=true88            -Dsonar.qualitygate.timeout=30089            -Dsonar.exclusions=**/test/**,**/node_modules/**90
91      # Security: Enforce quality gate92      - name: Check Quality Gate93        uses: sonarsource/sonarqube-quality-gate-action@v194        timeout-minutes: 595        env:96          SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}97
98      - name: Upload SAST Results99        if: always()100        uses: actions/upload-artifact@v4101        with:102          name: sast-results-${{ github.run_number }}103          path: .scannerwork/104          retention-days: 90105
106  # Security: Software Composition Analysis (SCA) - Parallel with SAST107  sca:108    name: Dependency Scan (SCA)109    runs-on: ubuntu-latest110    needs: validate111    timeout-minutes: 20112
113    steps:114      - name: Checkout Code115        uses: actions/checkout@v4116        with:117          persist-credentials: false118
119      # Security: Comprehensive filesystem scan120      - name: Run Trivy Filesystem Scan121        uses: aquasecurity/trivy-action@0.16.1122        with:123          scan-type: 'fs'124          scan-ref: '.'125          format: 'sarif'126          output: 'trivy-fs-results.sarif'127          severity: 'CRITICAL,HIGH'128          scanners: 'vuln,secret,config'129          timeout: '15m'130
131      - name: Upload to GitHub Security132        uses: github/codeql-action/upload-sarif@v3133        if: always()134        with:135          sarif_file: 'trivy-fs-results.sarif'136          category: 'sca-filesystem'137
138      # Security: Fail on critical vulnerabilities139      - name: Check Vulnerability Threshold140        uses: aquasecurity/trivy-action@0.16.1141        with:142          scan-type: 'fs'143          scan-ref: '.'144          exit-code: '1'145          severity: 'CRITICAL,HIGH'146          scanners: 'vuln'147          ignore-unfixed: true148
149  # Security: Build and scan container150  build_and_scan:151    name: Build & Scan Container152    runs-on: ubuntu-latest153    needs: [sast, sca]154    timeout-minutes: 40155
156    steps:157      - name: Checkout Code158        uses: actions/checkout@v4159        with:160          persist-credentials: false161
162      # Security: Set up Docker Buildx163      - name: Set up Docker Buildx164        uses: docker/setup-buildx-action@v3165
166      # Security: Build with security labels167      - name: Build Docker Image168        run: |169          set -euo pipefail170
171          # Security: Validate Dockerfile172          if [ ! -f "Dockerfile" ]; then173            echo "::error::Dockerfile not found"174            exit 1175          fi176
177          # Security: Lint Dockerfile178          docker run --rm -i hadolint/hadolint < Dockerfile179
180          # Security: Build with metadata181          docker build \182            --no-cache \183            --pull \184            --label "org.opencontainers.image.created=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" \185            --label "org.opencontainers.image.revision=${{ github.sha }}" \186            --label "org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }}" \187            -t myapp:${{ github.sha }} \188            .189
190      # Security: Scan container image191      - name: Scan Container Image192        uses: aquasecurity/trivy-action@0.16.1193        with:194          image-ref: 'myapp:${{ github.sha }}'195          format: 'sarif'196          output: 'trivy-image-results.sarif'197          severity: 'CRITICAL,HIGH,MEDIUM'198          scanners: 'vuln,secret,config'199
200      - name: Upload Image Scan Results201        uses: github/codeql-action/upload-sarif@v3202        if: always()203        with:204          sarif_file: 'trivy-image-results.sarif'205          category: 'container-image'206
207      # Security: Generate SBOM208      - name: Generate SBOM209        run: |210          set -euo pipefail211
212          docker run --rm \213            -v /var/run/docker.sock:/var/run/docker.sock \214            aquasec/trivy image \215            --format cyclonedx \216            --output sbom.json \217            myapp:${{ github.sha }}218
219          # Validate SBOM220          if ! jq empty sbom.json; then221            echo "::error::Invalid SBOM format"222            exit 1223          fi224
225      - name: Upload SBOM226        uses: actions/upload-artifact@v4227        with:228          name: sbom-${{ github.run_number }}229          path: sbom.json230          retention-days: 90231
232      # Security: Save image for deployment233      - name: Save Docker Image234        run: |235          docker save myapp:${{ github.sha }} | gzip > myapp.tar.gz236
237      - name: Upload Image Artifact238        uses: actions/upload-artifact@v4239        with:240          name: docker-image-${{ github.run_number }}241          path: myapp.tar.gz242          retention-days: 7243
244  # Security: Deploy to staging with health checks245  deploy_staging:246    name: Deploy to Staging247    runs-on: ubuntu-latest248    needs: build_and_scan249    timeout-minutes: 20250    environment:251      name: staging252      url: ${{ secrets.STAGING_URL }}253
254    steps:255      - name: Download Docker Image256        uses: actions/download-artifact@v4257        with:258          name: docker-image-${{ github.run_number }}259
260      - name: Load and Deploy Image261        run: |262          set -euo pipefail263
264          # Load image265          docker load < myapp.tar.gz266
267          echo "Deploying to staging..."268          # Your deployment commands here269
270          # Security: Wait for health check271          MAX_RETRIES=30272          RETRY_COUNT=0273
274          while [ $RETRY_COUNT -lt $MAX_RETRIES ]; do275            if curl -sSf "${{ secrets.STAGING_URL }}/health"; then276              echo "✓ Staging deployment healthy"277              exit 0278            fi279            RETRY_COUNT=$((RETRY_COUNT + 1))280            sleep 10281          done282
283          echo "::error::Staging deployment health check failed"284          exit 1285
286  # Security: Dynamic Analysis (DAST) against staging287  dast:288    name: Dynamic Analysis (DAST)289    runs-on: ubuntu-latest290    needs: deploy_staging291    timeout-minutes: 45292
293    steps:294      - name: Checkout Code295        uses: actions/checkout@v4296
297      # Security: Run OWASP ZAP scan298      - name: OWASP ZAP Baseline Scan299        uses: zaproxy/action-baseline@v0.10.0300        with:301          target: ${{ secrets.STAGING_URL }}302          rules_file_name: '.zap/rules.tsv'303          cmd_options: '-a -j -T 20'304          fail_action: true305
306      - name: Upload DAST Report307        if: always()308        uses: actions/upload-artifact@v4309        with:310          name: dast-report-${{ github.run_number }}311          path: |312            report_html.html313            report_json.json314          retention-days: 90315
316  # Security: Security gate before production317  security_gate:318    name: Security Gate Review319    runs-on: ubuntu-latest320    needs: [sast, sca, build_and_scan, dast]321    if: always()322    timeout-minutes: 10323
324    steps:325      - name: Check All Security Scans326        run: |327          set -euo pipefail328
329          echo "Reviewing all security scan results..."330
331          # Security: Verify all jobs passed332          NEEDS_CONTEXT='${{ toJson(needs) }}'333          echo "$NEEDS_CONTEXT" | jq -e 'all(.result == "success")' || {334            echo "::error::One or more security scans failed"335            exit 1336          }337
338          echo "✓ All security gates passed"339
340  # Security: Production deployment (manual approval)341  deploy_production:342    name: Deploy to Production343    runs-on: ubuntu-latest344    needs: security_gate345    if: github.ref == 'refs/heads/main'346    timeout-minutes: 30347    environment:348      name: production349      url: https://example.com350
351    steps:352      - name: Download Docker Image353        uses: actions/download-artifact@v4354        with:355          name: docker-image-${{ github.run_number }}356
357      - name: Load and Deploy Image358        run: |359          set -euo pipefail360
361          docker load < myapp.tar.gz362
363          echo "Deploying to production..."364          # Your production deployment commands here365
366          echo "✓ Production deployment completed"367
368      # Security: Post-deployment verification369      - name: Verify Production Deployment370        run: |371          set -euo pipefail372
373          echo "Verifying production deployment..."374
375          MAX_RETRIES=30376          for i in $(seq 1 $MAX_RETRIES); do377            if curl -sSf "https://example.com/health"; then378              echo "✓ Production deployment verified"379              exit 0380            fi381            sleep 10382          done383
384          echo "::error::Production verification failed"385          exit 1386
387  # Security: Notification and cleanup388  notify:389    name: Notify Security Team390    runs-on: ubuntu-latest391    needs: [deploy_production]392    if: always()393
394    steps:395      - name: Send Notification396        run: |397          echo "Security pipeline completed"398          echo "Status: ${{ job.status }}"399          # Add notification logic (Slack, email, etc.)
```

This pipeline runs SAST and SCA in parallel first (fast feedback), then builds and scans the container, deploys to staging, runs DAST, and only deploys to production if everything passes. It’s a complete shift-left security implementation.

## [Frequently Asked Questions About Shift-Left Security and CI/CD Integration](#frequently-asked-questions-about-shift-left-security-and-cicd-integration)

Shift-left testing is about moving all types of testing (functional, performance, etc.) earlier in the development cycle to catch bugs sooner and improve software quality. Shift-left security specifically focuses on integrating security practices and testing (SAST, DAST, SCA) from the earliest stages of development. While shift-left testing aims to improve overall quality, shift-left security targets security vulnerabilities specifically. The key difference? Security becomes a first-class concern from day one, not something you bolt on at the end.

As far left as possible. Ideally, security should be part of every stage of your software development lifecycle, starting from initial planning and requirements gathering. This includes defining security requirements upfront, considering security in your design, using secure coding practices during development, and continuously testing throughout the build and release process. The goal is to make security a fundamental part of how you build software, not an afterthought.

Shift-left security boils down to a few core principles: integrate security into every SDLC stage starting with design; automate security testing as much as possible, especially in CI/CD pipelines; build collaboration and shared responsibility between dev, security, and ops teams (DevSecOps); give developers the training and tools to write secure code; continuously monitor and get feedback to improve security; and establish clear security policies so developers know what’s expected. The main goal is making security proactive and built-in, not reactive and tacked-on.

Integrating security testing (SAST, DAST, SCA) into CI/CD gives you tons of benefits. You catch vulnerabilities early when they’re cheap and easy to fix. Automation keeps security checks consistent with every code change, reducing human error and preventing security from becoming a bottleneck. Early feedback helps developers learn secure coding practices and improves overall code quality. It speeds up releases by preventing last-minute security fire drills. You get a more secure software supply chain by identifying third-party component vulnerabilities. Automated checks help meet compliance requirements. Continuous monitoring with immediate feedback makes your applications more secure overall.

Integrating security into CI/CD pipelines comes with challenges. It can make your pipeline more complex as you add different security checks. There’s a risk of slowing down release velocity if security testing isn’t optimized. Full application security means adding various test types, which adds complexity. Sometimes security tools don’t integrate well with each other or your CI/CD platform. Managing the increased volume of alerts and false positives can overwhelm developers. Keeping security practices consistent across different environments (dev, staging, prod) is hard. You might have gaps from incomplete testing or outdated pipelines. Scaling the pipeline to handle additional security checks without performance degradation requires planning. And you’ll face developer resistance if they perceive it as slowing them down.

It depends on your development velocity, application criticality, and risk tolerance. SAST and SCA should run in your CI process, ideally on every commit or merge to give developers immediate feedback. Daily scans or scans with every significant code change catch issues early. DAST scans need a running application, so they typically run after deployment to staging or test environments. How often you run DAST varies, but common practice is with each staging deployment or on a regular schedule (weekly or nightly). For production apps, occasional DAST scans are good, but be careful not to disrupt availability. SCA should run frequently, ideally with every build or dependency update, because new vulnerabilities are constantly discovered in third-party libraries. Continuous monitoring for new vulnerabilities in your dependencies is a best practice so you can react quickly to emerging threats.

Best practices include establishing clear security policies; automating all three security testing types in your pipeline; providing developers with training and resources on secure coding and tool usage; building collaboration between dev, security, and ops teams; prioritizing and remediating findings based on risk; keeping tools and dependencies updated; and having clear remediation workflows. Configure tools to minimize false positives, make security a shared responsibility, and continuously monitor and improve your practices. Start with security requirements early and add security to code reviews.

Overcoming developer resistance requires training and resources so they understand security best practices and know how to use the tools. Integrate security tools directly into their existing workflows (IDEs, version control) to minimize disruption. Configure tools to reduce false positives and provide clear, actionable remediation guidance instead of just dumping alerts on them. Emphasize that security is a shared responsibility and build collaboration between dev, security, and ops so developers feel supported rather than burdened. Highlight the benefits: less rework, faster releases, better code quality. When developers see the value, they’re more likely to embrace it.

Tackling alert fatigue matters for shift-left success. Configure security tools to cut false positives and low-priority alerts. Prioritize alerts by severity so developers focus on critical issues first, not a massive list. Provide clear, actionable remediation guidance directly in the tools developers use. Consolidate security tools and dashboards for a unified view of findings. Use AI-powered tools that intelligently filter and prioritize based on context and impact. Establish clear SLAs for vulnerability remediation and give developers time and resources to address issues, preventing alert backlog and the feeling of being overwhelmed.

## [Conclusion](#conclusion)

Integrating shift-left security into your development workflow, especially by using SAST, DAST, and SCA in CI/CD pipelines, isn’t optional anymore. With today’s threats, it’s essential. By thinking about security from the start, you’ll save money, ship faster, build more secure applications, and develop security-aware teams.

Yes, implementation has challenges: resistance to change, alert fatigue, tooling complexity. But the best practices we’ve covered (automation, developer training, cross-team collaboration) will get you there. Tools like SonarQube, OWASP ZAP, and Trivy make security a natural part of how you build software, not a painful add-on. The result? Safer, more reliable applications and fewer 3 AM security incidents.

## [References](#references)

1.  [What is Shift Left? Security, Testing & More Explained | CrowdStrike](https://www.crowdstrike.com/en-us/cybersecurity-101/cloud-security/shift-left-security/)
2.  [Shift Left Security Explained: Key Concepts and Benefits - Check Point Software](https://www.checkpoint.com/cyber-hub/cloud-security/what-is-shift-left-security/)
3.  [What is Shift Left Security? | Perforce Software](https://www.perforce.com/blog/sca/what-is-shift-left-security)
4.  [Shifting Security Left: Benefits & Implementation Tips - StackHawk](https://www.stackhawk.com/blog/why-shift-security-left/)
5.  [Shift Left Security: A Complete Guide - Codacy | Blog](https://blog.codacy.com/what-is-shift-left-security)
6.  [The AppSec Guide to Shift-Left Security: How to Integrate Security Earlier in the SDLC - StackHawk](https://www.stackhawk.com/blog/the-appsec-guide-to-shift-left-security-how-to-integrate-security-earlier-in-the-sdlc/)
7.  [What is Shift-left Security? | Harness](https://www.harness.io/harness-devops-academy/what-is-shift-left-security)
8.  [Implementing Shift Left Security Effectively - Snyk](https://snyk.io/articles/shift-left-security/)
9.  [What is Software Composition Analysis (SCA)? | Black Duck](https://www.blackduck.com/glossary/what-is-software-composition-analysis.html)
10.  [Source Code Analysis Tools - OWASP Foundation](https://owasp.org/www-community/Source_Code_Analysis_Tools)
11.  [Dynamic Application Security Testing (DAST) - OWASP DevSecOps Guideline](https://owasp.org/www-project-devsecops-guideline/latest/02b-Dynamic-Application-Security-Testing)
12.  [CI/CD Integrations - Trivy](http://trivy.dev/v0.40/ecosystem/cicd/)
13.  [A Step-by-Step Guide to Integrating SonarQube into Your CI/CD Workflow - Appcircle Blog](https://appcircle.io/blog/how-to-integrate-sonarqube-into-your-ci-cd-workflow)
14.  [Ultimate Guide to OWASP ZAP: Understanding the Architecture, Implementing CI/CD, and Best Practices - Clovin Security](https://www.clovinsec.com/post/ultimate-guide-to-owasp-zap-understanding-the-architecture-implementing-ci-cd-and-best-practices)
15.  [Shift Left Security: Principles and Best Practices | Digital.ai](https://digital.ai/glossary/shift-left-security/)

Was this useful?

## Tags

[#Shift Left Security](/blog/tags/shift-left-security)[#SAST](/blog/tags/sast)[#DAST](/blog/tags/dast)[#SCA](/blog/tags/sca)[#Vulnerability Scanning](/blog/tags/vulnerability-scanning)[#SonarQube](/blog/tags/sonarqube)[#OWASP ZAP](/blog/tags/owasp-zap)[#Trivy](/blog/tags/trivy)[#Secure SDLC](/blog/tags/secure-sdlc)[#Pipeline Security](/blog/tags/pipeline-security)

## Share

[Facebook](https://facebook.com/sharer/sharer.php?u=https%3A%2F%2Fmkabumattar.com%2Fblog%2Fpost%2Fshift-left-security-sast-dast-sca-cicd "Share on Facebook")[Twitter](https://twitter.com/intent/tweet/?text=What's%20the%20Deal%20with%20Shift-Left%20Security%2C%20and%20Why%20Should%20You%20Care%3F&url=https%3A%2F%2Fmkabumattar.com%2Fblog%2Fpost%2Fshift-left-security-sast-dast-sca-cicd "Share on Twitter")[LinkedIn](https://www.linkedin.com/shareArticle?mini=true&url=https%3A%2F%2Fmkabumattar.com%2Fblog%2Fpost%2Fshift-left-security-sast-dast-sca-cicd&title=What's%20the%20Deal%20with%20Shift-Left%20Security%2C%20and%20Why%20Should%20You%20Care%3F&summary=How%20to%20implement%20shift-left%20security%20with%20SAST%2C%20DAST%2C%20and%20SCA%20in%20your%20CI%2FCD%20pipeline%20to%20cut%20costs%20and%20catch%20issues%20earlier%2C%20with%20tool%20examples%20like%20SonarQube%2C%20Trivy%2C%20and%20OWASP%20ZAP.&source=https://mkabumattar.com "Share on LinkedIn")[WhatsApp](https://wa.me/?text=What's%20the%20Deal%20with%20Shift-Left%20Security%2C%20and%20Why%20Should%20You%20Care%3F%20https%3A%2F%2Fmkabumattar.com%2Fblog%2Fpost%2Fshift-left-security-sast-dast-sca-cicd "Share on WhatsApp")[Telegram](https://t.me/share/url?url=https%3A%2F%2Fmkabumattar.com%2Fblog%2Fpost%2Fshift-left-security-sast-dast-sca-cicd&text=What's%20the%20Deal%20with%20Shift-Left%20Security%2C%20and%20Why%20Should%20You%20Care%3F "Share on Telegram")[Reddit](https://www.reddit.com/submit?url=https%3A%2F%2Fmkabumattar.com%2Fblog%2Fpost%2Fshift-left-security-sast-dast-sca-cicd&title=What's%20the%20Deal%20with%20Shift-Left%20Security%2C%20and%20Why%20Should%20You%20Care%3F "Share on Reddit")[Hacker News](http://news.ycombinator.com/submitlink?u=https%3A%2F%2Fmkabumattar.com%2Fblog%2Fpost%2Fshift-left-security-sast-dast-sca-cicd&t=What's%20the%20Deal%20with%20Shift-Left%20Security%2C%20and%20Why%20Should%20You%20Care%3F "Share on Hacker News")[Pinterest](https://pinterest.com/pin/create/button/?url=https%3A%2F%2Fmkabumattar.com%2Fblog%2Fpost%2Fshift-left-security-sast-dast-sca-cicd&media=&description=How%20to%20implement%20shift-left%20security%20with%20SAST%2C%20DAST%2C%20and%20SCA%20in%20your%20CI%2FCD%20pipeline%20to%20cut%20costs%20and%20catch%20issues%20earlier%2C%20with%20tool%20examples%20like%20SonarQube%2C%20Trivy%2C%20and%20OWASP%20ZAP. "Share on Pinterest")[Email](<mailto:?subject=What's%20the%20Deal%20with%20Shift-Left%20Security%2C%20and%20Why%20Should%20You%20Care%3F&body=Check out this article: https%3A%2F%2Fmkabumattar.com%2Fblog%2Fpost%2Fshift-left-security-sast-dast-sca-cicd>)

## Comments

## You might also enjoy

More posts on similar topics

[![Container Image Signing with Cosign: A Hands-On Guide to Secure Your Supply Chain](/_astro/hero.eolo66hS_Z1Eh5SE.webp)](/blog/post/container-image-signing-cosign-guide)

## [Container Image Signing with Cosign: A Hands-On Guide to Secure Your Supply Chain](/blog/post/container-image-signing-cosign-guide)

-   [Mohammad Abu Mattar](/authors/mohammad-abu-mattar)
-   [Security](/blog/categories/security)
-   [Supply Chain Security](/blog/categories/supply-chain-security)
-   [Containers](/blog/categories/containers)
-   [DevSecOps](/blog/categories/devsecops)

Container images make packaging and running apps consistent across environments, which is genuinely useful. But that consistency also brings new security questions. We need to make sure the images run

[#Cosign](/blog/tags/cosign)[#Sigstore](/blog/tags/sigstore)[#Container Signing](/blog/tags/container-signing)+4 tags

[read more](/blog/post/container-image-signing-cosign-guide)

[![The Democratization of Container Security: Docker Hardened Images](/_astro/hero.CPmb6ixV_1X6DeU.webp)](/blog/post/democratization-docker-hardened-images-container-security)

## [The Democratization of Container Security: Docker Hardened Images](/blog/post/democratization-docker-hardened-images-container-security)

-   [Mohammad Abu Mattar](/authors/mohammad-abu-mattar)
-   [Docker](/blog/categories/docker)
-   [Container Security](/blog/categories/container-security)
-   [DevSecOps](/blog/categories/devsecops)
-   [Supply Chain Security](/blog/categories/supply-chain-security)

On December 17, 2025, the world of container security changed in a big way. Docker decided to open up its entire catalog of over 1,000 Docker Hardened Images (DHI) to everyone under the Apache 2.0 lic

[#Docker Hardened Images](/blog/tags/docker-hardened-images)[#Distroless](/blog/tags/distroless)[#SBOM](/blog/tags/sbom)+5 tags

[read more](/blog/post/democratization-docker-hardened-images-container-security)

[![Policy as Code with Open Policy Agent: A Technical and Governance Perspective](/_astro/hero.FMyPom55_ZBRCR.webp)](/blog/post/policy-as-code-opa-guide)

## [Policy as Code with Open Policy Agent: A Technical and Governance Perspective](/blog/post/policy-as-code-opa-guide)

-   [Mohammad Abu Mattar](/authors/mohammad-abu-mattar)
-   [DevOps](/blog/categories/devops)
-   [Security](/blog/categories/security)
-   [Cloud Governance](/blog/categories/cloud-governance)
-   [Policy as Code](/blog/categories/policy-as-code)

Think about how much stuff modern organizations have running in the cloud these days. It's a lot, right? All those servers, applications, and connections can get pretty complicated to manage. Just cli

[#Open Policy Agent](/blog/tags/open-policy-agent)[#OPA](/blog/tags/opa)[#Policy as Code](/blog/tags/policy-as-code)+9 tags

[read more](/blog/post/policy-as-code-opa-guide)

[![HashiCorp Vault vs. AWS Secrets Manager vs. SOPS: Which One Fits Your Setup](/_astro/hero.BxpvzHJN_Z2hLAEl.webp)](/blog/post/secrets-management-vault-secrets-manager-sops)

## [HashiCorp Vault vs. AWS Secrets Manager vs. SOPS: Which One Fits Your Setup](/blog/post/secrets-management-vault-secrets-manager-sops)

-   [Mohammad Abu Mattar](/authors/mohammad-abu-mattar)
-   [DevOps](/blog/categories/devops)
-   [Security](/blog/categories/security)
-   [Cloud Computing](/blog/categories/cloud-computing)

Let's face it, keeping sensitive info safe, we're talking about those digital keys like API keys and passwords, is a big deal. They're what let you into important systems and data. But with everything

[#Secrets Management](/blog/tags/secrets-management)[#HashiCorp Vault](/blog/tags/hashicorp-vault)[#AWS Secrets Manager](/blog/tags/aws-secrets-manager)+4 tags

[read more](/blog/post/secrets-management-vault-secrets-manager-sops)

[![QuenchWorks: A Zero-CVE, Built-From-Source Replacement for the Bitnami Catalog](/_astro/hero.DloumiN1_Z1RwvV3.webp)](/blog/post/quenchworks-zero-cve-bitnami-alternative-wolfi)

## [QuenchWorks: A Zero-CVE, Built-From-Source Replacement for the Bitnami Catalog](/blog/post/quenchworks-zero-cve-bitnami-alternative-wolfi)

-   [Mohammad Abu Mattar](/authors/mohammad-abu-mattar)
-   [DevOps](/blog/categories/devops)
-   [Containers](/blog/categories/containers)
-   [Supply Chain Security](/blog/categories/supply-chain-security)
-   [Kubernetes](/blog/categories/kubernetes)
-   [Open Source](/blog/categories/open-source)

If you run anything on Kubernetes, there's a good chance you were pulling Bitnami images without even thinking about it. bitnami/postgresql, bitnami/redis, bitnami/nginx, the whole Helm charts l

[#Wolfi](/blog/tags/wolfi)[#Apko](/blog/tags/apko)[#Melange](/blog/tags/melange)+9 tags

[read more](/blog/post/quenchworks-zero-cve-bitnami-alternative-wolfi)

[![Taming the Chaos: Let's Sort Out Those Flaky CI/CD Pipelines](/_astro/hero.CNGGank__Zjy3fe.webp)](/blog/post/troubleshooting-flaky-ci-cd-pipelines)

## [Taming the Chaos: Let's Sort Out Those Flaky CI/CD Pipelines](/blog/post/troubleshooting-flaky-ci-cd-pipelines)

-   [Mohammad Abu Mattar](/authors/mohammad-abu-mattar)
-   [CI/CD](/blog/categories/cicd)
-   [Testing](/blog/categories/testing)
-   [DevOps](/blog/categories/devops)
-   [Automation](/blog/categories/automation)
-   [Pipeline Reliability](/blog/categories/pipeline-reliability)

Ever get super frustrated with your CI/CD pipeline? You know, the one that sometimes works perfectly and other times just throws a random tantrum? You push your code, the pipeline starts doing its thi

[#Flaky Tests](/blog/tags/flaky-tests)[#CI/CD Pipelines](/blog/tags/cicd-pipelines)[#Test Automation](/blog/tags/test-automation)+5 tags

[read more](/blog/post/troubleshooting-flaky-ci-cd-pipelines)

6 related posts
