DEV Community

Hariharan
Hariharan

Posted on

DevSecOps in Practice: Tools That Actually Catch Vulnerabilities - Part 6: The Full Pipeline

If you've followed along from Part 1, we have built five separate scanning
workflows. This final part replaces them with a single unified pipeline —
one YAML file, one run, everything in the right order.

The pipeline structure
The five individual workflow files are deleted and replaced with one:
.github/workflows/devsecops-pipeline.yml

name: DevSecOps Pipeline

on:
  push:
    branches: ["**"]
  pull_request:
    branches: ["**"]

jobs:

  secret-scan:
    name: Secret Scanning - Gitleaks
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - name: Run Gitleaks
        uses: gitleaks/gitleaks-action@v2
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

  sast:
    name: SAST - Bandit
    runs-on: ubuntu-latest
    needs: secret-scan
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: '3.11'
      - name: Install Bandit
        run: pip install bandit
      - name: Run Bandit
        run: bandit -r app.py --severity-level high -f json -o bandit-report.json
      - name: Upload Report
        uses: actions/upload-artifact@v4
        if: always()
        with:
          name: bandit-report
          path: bandit-report.json

  sca:
    name: SCA - pip-audit
    runs-on: ubuntu-latest
    needs: secret-scan
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: '3.11'
      - name: Install pip-audit
        run: pip install pip-audit
      - name: Run pip-audit
        run: pip-audit -r requirements.txt -f json -o pip-audit-report.json
      - name: Upload Report
        uses: actions/upload-artifact@v4
        if: always()
        with:
          name: pip-audit-report
          path: pip-audit-report.json

  iac:
    name: IaC - Checkov
    runs-on: ubuntu-latest
    needs: secret-scan
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: '3.11'
      - name: Install Checkov
        run: pip install checkov
      - name: Run Checkov
        run: checkov -d terraform/ -o json > checkov-report.json
      - name: Upload Report
        uses: actions/upload-artifact@v4
        if: always()
        with:
          name: checkov-report
          path: checkov-report.json

  container-scan:
    name: Container Scan - Trivy
    runs-on: ubuntu-latest
    needs: [sast, sca, iac]
    steps:
      - uses: actions/checkout@v4
      - name: Build Docker image
        run: docker build -t devsecops-demo:${{ github.sha }} .
      - name: Run Trivy
        uses: aquasecurity/trivy-action@master
        with:
          image-ref: devsecops-demo:${{ github.sha }}
          format: json
          output: trivy-report.json
          severity: CRITICAL,HIGH
          exit-code: 1
      - name: Upload Report
        uses: actions/upload-artifact@v4
        if: always()
        with:
          name: trivy-report
          path: trivy-report.json
Enter fullscreen mode Exit fullscreen mode

The logic is deliberate:

  1. Secret scanning runs first. If credentials are found in the code, nothing else runs. There's no value in scanning code that's already compromised.
  2. SAST, SCA, and IaC run in parallel after secrets pass. These are independent checks — no reason to run them sequentially. Running in parallel keeps the pipeline fast.
  3. Container scanning runs last. It only runs if the three parallel scans all pass. If the code has known vulnerabilities, there's no point building and scanning the image.

What the pipeline run looks like

Secret Scanning passed — no leaks detected. SAST, SCA, and IaC all failed
because the demo app is deliberately broken. Container Scan was skipped.
That skipped Trivy stage is worth explaining. It's not a failure — it's the pipeline working correctly. GitHub Actions skips a job when its dependencies fail. Trivy needed Bandit, pip-audit, and Checkov to all pass before it would run. They didn't, so it didn't. There's no point scanning a container image built from code you already know is vulnerable.
In a real project where the code is clean, all five stages would run and the pipeline would either pass completely or fail at Trivy if the image has CVEs.

What this pipeline catches
Across the five parts of this series, the pipeline found:

  1. Secrets — AWS access keys hardcoded in app.py, caught before commit and at push time
  2. Code vulnerabilities — SQL injection, eval() on user input, debug=True in Flask, all flagged by Bandit
  3. Vulnerable dependencies — 37 known CVEs across 6 packages in requirements.txt, caught by pip-audit
  4. Infrastructure misconfigurations — 18 failed Checkov checks including a public S3 bucket, unencrypted EBS, and no IMDSv2 enforcement
  5. Container CVEs — 1,747 vulnerabilities in the Docker image, 185 of them CRITICAL, caught by Trivy

None of this required a security expert. Each tool is open source, free,
and wired into a standard GitHub Actions workflow.

What this pipeline doesn't catch

  1. DAST — Dynamic Application Security Testing scans a running application for vulnerabilities. Nothing here does that. Tools like OWASP ZAP fill this gap.
  2. Runtime security — once the container is running in production, nothing here monitors it. Tools like Falco watch for suspicious behaviour at runtime.
  3. Secrets in git history — Gitleaks scans current files and recent commits. A secret committed years ago and deleted may still be in history.
  4. Logic flaws — no static analysis tool catches business logic vulnerabilities. Those require manual review.

The repo
Everything built across this series is at
https://github.com/pkkht/devsecops-demo — the vulnerable Flask app, the Terraform, the Dockerfile, and all the GitHub Actions workflows.
Clone it, run the pipeline, break things deliberately, and see what gets caught.

Top comments (0)