100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
DevSecOps & Site Reliability Engineering
45 minadvanced

Foundations Practice — Audit a Pipeline

What You'll Build

In this exercise you will audit an intentionally insecure CI/CD pipeline configuration, identify security weaknesses using the STRIDE framework, implement fixes for the three highest-priority findings, and add a minimal set of supply chain security gates to a GitHub Actions workflow. By the end, the pipeline will block builds with HIGH/CRITICAL CVEs, prevent secret commits, and generate a signed SBOM.

Analogy🏏Cricket
🏏 Think of it like cricket: This is a full-match simulation in the nets — a structured practice match with umpires, scorers, and a target, played under match conditions so the team builds muscle memory for the real thing. The coach throws curveballs to test the team's response to unexpected events. After the simulation, the team watches the footage and identifies improvements for the next real match.

Prerequisites

  • Lessons 1, 2, and 3 completed.
  • A GitHub repository with a basic Python FastAPI project and a GitHub Actions CI workflow.
  • Docker installed locally.
  • Python 3.11+ with pip installed.
  • GitHub CLI (gh) or access to GitHub Actions settings in the repository.

Step 1 — Audit the Insecure Pipeline

Start with the intentionally insecure workflow below. Read through it carefully and identify every security weakness. Use the STRIDE threat categories from Lesson 2 to classify each finding. You should find at least six distinct issues before comparing with the audit report in Step 2.

Analogy🏏Cricket
🏏 Think of it like cricket: A new batting coach watches a training session video before providing feedback. They observe quietly, noting every technical flaw — footwork, grip, head position, shot selection — before speaking. Your first task is the same: observe and document before fixing.
yaml
# .github/workflows/insecure-pipeline.yaml — FIND THE ISSUES
name: Build and Deploy

on: [push]

jobs:
  build:
    runs-on: ubuntu-latest
    env:
      AWS_ACCESS_KEY_ID: AKIAIOSFODNN7EXAMPLE
      AWS_SECRET_ACCESS_KEY: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
      DB_PASSWORD: cricketpulse_prod_pass123

    steps:
      - uses: actions/checkout@main          # unpinned action

      - name: Install dependencies
        run: pip install -r requirements.txt  # no lock file, no audit

      - name: Build Docker image
        run: docker build -t cricketpulse:latest .
        # Dockerfile uses: FROM python:3.12-slim  (no digest pin)

      - name: Run tests
        run: pytest

      - name: Push to registry
        run: |
          docker login -u admin -p ${{ secrets.DOCKER_PASSWORD }}
          docker push myregistry.io/cricketpulse:latest  # mutable tag

      - name: Deploy to production
        run: |
          aws eks update-kubeconfig --name prod-cluster
          kubectl apply -f k8s/
          # No image signature verification
          # No approval gate

      - name: Notify team
        run: |
          curl -X POST https://hooks.slack.com/services/T123/B456/XXXXXXXXXXX             -d "{"text": "Deploy complete. DB_PASSWORD=$DB_PASSWORD"}"

Step 2 — Review the Audit Findings

Compare your findings against this reference audit. Every finding has a STRIDE classification, DREAD score, and recommended fix. The six highest-priority findings are the ones you will fix in Steps 3 and 4.

Analogy🏏Cricket
🏏 Think of it like cricket: reviewing your audit against the reference is like comparing your own scouting notes to the head coach's master dossier after watching the same batsman. Just as the coach's dossier tags each weakness by type (edge-prone, LBW-candidate, poor against spin) and rates how dangerous each is so the attack targets the worst first, the reference audit gives every finding a STRIDE classification and a DREAD score. Just as you don't try to exploit all ten weaknesses at once but pick the handful most likely to get him out, the six highest-priority findings are the ones you'll actually fix in the next steps. And just as checking your notes against the master list shows what you missed and what you over-rated, comparing findings calibrates your judgement. The payoff: you learn to spot and rank real pipeline weaknesses the way a seasoned analyst reads a batsman.
bash
# Reference audit report

| ID | Finding                              | STRIDE | DREAD | Fix |
|----|--------------------------------------|--------|-------|-----|
| F1 | AWS keys hardcoded in workflow env   | I, E   | 14    | OIDC ephemeral credentials |
| F2 | DB_PASSWORD leaked in Slack message  | I      | 13    | Remove from notify step |
| F3 | Unpinned action (checkout@main)      | T      | 12    | Pin to commit SHA |
| F4 | No dependency lock file or audit     | T      | 11    | pip freeze + pip-audit |
| F5 | Docker base image unpinned           | T      | 10    | Pin to SHA256 digest |
| F6 | Mutable image tag (:latest)          | T      | 10    | Tag with git SHA |
| F7 | No approval gate before production   | E      | 9     | Required reviewer environment |
| F8 | No image signature verification      | T      | 9     | cosign verify in deploy job |

Step 3 — Fix the Top Three Findings

Fix F1, F2, and F3 first — the highest DREAD scores. Replace hardcoded AWS credentials with OIDC, remove the credential leak from the Slack notification, and pin the checkout action to an immutable commit SHA.

Analogy🏏Cricket
🏏 Think of it like cricket: The batting coach addresses the most dangerous technical flaws first — footwork before grip, because bad footwork causes dismissals, bad grip just affects shot quality. F1 and F2 are active credential exposures; fixing them first closes the widest attack surface immediately.
yaml
# Fix F1: Replace hardcoded AWS credentials with OIDC
# Delete env block with hardcoded credentials
# Add OIDC permissions and aws-actions/configure-aws-credentials

permissions:
  id-token: write   # required for OIDC
  contents: read

jobs:
  build:
    runs-on: ubuntu-latest
    # No env block with credentials
    steps:
      - name: Configure AWS credentials via OIDC
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789:role/github-actions-deploy
          aws-region: ap-south-1
          # Credentials are ephemeral — expire after the job completes
          # No long-lived keys in any environment variable

# Fix F2: Remove credentials from Slack notification
      - name: Notify team
        run: |
          curl -X POST https://hooks.slack.com/services/T123/B456/XXXXXXXXXXX             -d '{"text": "Deploy complete to production cluster."}'
          # DB_PASSWORD is NOT included — never log credentials

# Fix F3: Pin checkout action to commit SHA (not tag)
# Find current SHA: https://github.com/actions/checkout/tags
      - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11  # v4.1.1

Step 4 — Add Supply Chain Security Gates

Now add the supply chain gates from Lesson 3: dependency lock file with pip-audit, Docker base image digest pinning, Trivy image scanning blocking on HIGH/CRITICAL, and SBOM generation. These gates transform the pipeline from a security liability into a security asset.

Analogy🏏Cricket
🏏 Think of it like cricket: After fixing the immediate safety hazards, you install permanent safety systems: boundary rope inspection before every match (dependency audit), ball certification process (image scanning), and match equipment manifest (SBOM). These run automatically before every match — no human needs to remember to check.
yaml
# Complete secure pipeline — add these steps to your workflow

      # Supply chain gate 1: Dependency audit
      - name: Lock and audit dependencies
        run: |
          pip install pip-audit
          pip-audit --requirement requirements.txt             --format json             --output pip-audit-report.json
          # Exit non-zero on HIGH or CRITICAL CVEs (pip-audit default)

      # Supply chain gate 2: Build with digest-pinned base image
      # Update your Dockerfile:
      # FROM python:3.12-slim@sha256:<digest>
      - name: Get base image digest
        run: |
          docker pull python:3.12-slim
          docker inspect python:3.12-slim             --format '{{index .RepoDigests 0}}'
          # Copy output to Dockerfile FROM line

      # Supply chain gate 3: Image scan
      - name: Trivy vulnerability scan
        uses: aquasecurity/trivy-action@master
        with:
          image-ref: cricketpulse:${{ github.sha }}
          exit-code: '1'
          severity: 'HIGH,CRITICAL'
          format: 'sarif'
          output: 'trivy-results.sarif'

      # Supply chain gate 4: Generate and upload SBOM
      - name: Generate SBOM
        uses: anchore/sbom-action@v0
        with:
          image: cricketpulse:${{ github.sha }}
          format: spdx-json
          output-file: sbom.spdx.json

      - name: Upload SBOM artifact
        uses: actions/upload-artifact@v4
        with:
          name: sbom-${{ github.sha }}
          path: sbom.spdx.json
          retention-days: 90

      # Tag image with git SHA (not :latest)
      - name: Push image with immutable tag
        run: |
          docker tag cricketpulse:${{ github.sha }}             myregistry.io/cricketpulse:${{ github.sha }}
          docker push myregistry.io/cricketpulse:${{ github.sha }}

Verify Your Work

Run through this verification checklist. A passing pipeline should show zero hardcoded credentials, a blocked build on a HIGH CVE, and a generated SBOM artifact.

Analogy🏏Cricket
🏏 Think of it like cricket: A practice session is worthless until you grade it against clear benchmarks. Just as a batting coach scores a net session on measurable targets — did the batter rotate strike, was the trigger movement quick enough, did they leave the balls outside off — you review the drill against five concrete criteria rather than a vague sense that 'it went okay'. Just as the coach times how fast the batter read the length (time to detect, target under ten minutes) and how quickly they adjusted their shot (time to contain, target under twenty), you measure detection and containment against fixed thresholds. Just as a session only passes if every benchmark is met, not most of them, a passing drill must satisfy all five: fast detection, fast containment, status page updated, a systemic root cause, and at least three owned action items. The payoff: honest scoring against a rubric shows exactly which reflex to sharpen before the real match, rather than leaving you falsely confident.
bash
# Verification tests

# 1. Confirm no credentials in workflow files
grep -r "AKIA\|password\|secret" .github/workflows/ && echo "FAIL: credentials found" || echo "PASS: no credentials"

# 2. Confirm pip-audit runs and finds no HIGH/CRITICAL
pip install pip-audit
pip-audit --requirement requirements.txt
# Expect: No known vulnerabilities found (or patch before proceeding)

# 3. Test Trivy blocks on HIGH CVE — inject a known vulnerable package
echo "cryptography==41.0.0" >> requirements.txt  # CVE-2023-49083 HIGH
pip install cryptography==41.0.0
trivy image --exit-code 1 --severity HIGH,CRITICAL cricketpulse:test
# Expect: exit code 1 (build blocked)
# Revert: remove the test line from requirements.txt

# 4. Confirm SBOM is generated
ls -la sbom.spdx.json
python3 -c "import json; d=json.load(open('sbom.spdx.json')); print(f'SBOM packages: {len(d["packages"])}')"
# Expect: SBOM packages: > 0

# 5. Confirm Docker image tagged with SHA not :latest
docker images | grep cricketpulse
# Expect: cricketpulse   <git-sha>   ...  (no 'latest' tag in registry push)

When running Trivy in CI, always set --exit-code 1 and --severity HIGH,CRITICAL together. Without --exit-code 1, Trivy reports vulnerabilities but does not fail the build — a common misconfiguration that gives a false sense of security while allowing vulnerable images to proceed to production.

GitHub Actions provides a Security tab (under repository Settings > Security > Code scanning) where Trivy SARIF output is automatically displayed as code scanning alerts. Uploading the trivy-results.sarif file using github/codeql-action/upload-sarif makes all vulnerabilities visible in the pull request interface, not just in CI logs.

  • Fix credential exposures first — hardcoded keys and credential leaks in logs are the highest DREAD-score findings.
  • Pin GitHub Actions to commit SHAs (not tags) to prevent supply chain attacks via action repo tampering.
  • pip-audit + requirements.txt lock file + Trivy image scan = three complementary vulnerability detection layers.
  • Tag images with git SHA instead of :latest — mutable tags make rollback and incident response harder.
  • SBOM artifacts should be retained alongside every image push for future CVE auditing.
Lesson 4 of 24
0% complete