Tools: DevSecOps in Practice: Tools That Actually Catch Vulnerabilities - Part 6: The Full Pipeline (2026)
If you've followed along from Part 1, we have built five separate scanningworkflows. This final part replaces them with a single unified pipeline —one YAML file, one run, everything in the right order. The pipeline structureThe five individual workflow files are deleted and replaced with one:.github/workflows/devsecops-pipeline.yml The logic is deliberate: What the pipeline run looks like Secret Scanning passed — no leaks detected. SAST, SCA, and IaC all failedbecause 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 catchesAcross the five parts of this series, the pipeline found: 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 The repoEverything built across this series is athttps://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. Templates let you quickly answer FAQs or store snippets for re-use. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse
$ 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: -weight: 500;">pip -weight: 500;">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 - -weight: 500;">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 -weight: 500;">pip-audit run: -weight: 500;">pip -weight: 500;">install -weight: 500;">pip-audit - name: Run -weight: 500;">pip-audit run: -weight: 500;">pip-audit -r requirements.txt -f json -o -weight: 500;">pip-audit-report.json - name: Upload Report uses: actions/upload-artifact@v4 if: always() with: name: -weight: 500;">pip-audit-report path: -weight: 500;">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: -weight: 500;">pip -weight: 500;">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: -weight: 500;">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
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: -weight: 500;">pip -weight: 500;">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 - -weight: 500;">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 -weight: 500;">pip-audit run: -weight: 500;">pip -weight: 500;">install -weight: 500;">pip-audit - name: Run -weight: 500;">pip-audit run: -weight: 500;">pip-audit -r requirements.txt -f json -o -weight: 500;">pip-audit-report.json - name: Upload Report uses: actions/upload-artifact@v4 if: always() with: name: -weight: 500;">pip-audit-report path: -weight: 500;">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: -weight: 500;">pip -weight: 500;">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: -weight: 500;">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
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: -weight: 500;">pip -weight: 500;">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 - -weight: 500;">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 -weight: 500;">pip-audit run: -weight: 500;">pip -weight: 500;">install -weight: 500;">pip-audit - name: Run -weight: 500;">pip-audit run: -weight: 500;">pip-audit -r requirements.txt -f json -o -weight: 500;">pip-audit-report.json - name: Upload Report uses: actions/upload-artifact@v4 if: always() with: name: -weight: 500;">pip-audit-report path: -weight: 500;">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: -weight: 500;">pip -weight: 500;">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: -weight: 500;">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 - 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.
- 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.
- 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. - Secrets — AWS access keys hardcoded in app.py, caught before commit
and at push time
- Code vulnerabilities — SQL injection, eval() on user input, debug=True in Flask, all flagged by Bandit
- Vulnerable dependencies — 37 known CVEs across 6 packages in requirements.txt, caught by -weight: 500;">pip-audit
- Infrastructure misconfigurations — 18 failed Checkov checks including
a public S3 bucket, unencrypted EBS, and no IMDSv2 enforcement
- Container CVEs — 1,747 vulnerabilities in the Docker image, 185 of
them CRITICAL, caught by Trivy - DAST — Dynamic Application Security Testing scans a running application
for vulnerabilities. Nothing here does that. Tools like OWASP ZAP fill
this gap.
- Runtime security — once the container is running in production,
nothing here monitors it. Tools like Falco watch for suspicious behaviour
at runtime.
- Secrets in -weight: 500;">git history — Gitleaks scans current files and recent
commits. A secret committed years ago and deleted may still be in history.
- Logic flaws — no static analysis tool catches business logic vulnerabilities. Those require manual review.