Threat Modeling for Pipelines
Threat Modeling for Pipelines
A delivery pipeline is not just an automation convenience — it is a high-privilege execution environment that has read/write access to source code, production secrets, artifact registries, and cloud infrastructure. Attackers who compromise a pipeline do not need to exploit your application: they become you. The SolarWinds, Codecov, and 3CX supply chain attacks all shared one pattern — the attacker found a seam in the delivery system and used it as an insertion point, not the application itself.
Threat modeling translates that abstract risk into concrete, actionable controls. This lesson applies a lightweight STRIDE variant — tuned for delivery systems — to systematically enumerate who can inject what, where, and how to close each gap.
Why STRIDE-Lite, Not Full STRIDE
Classic STRIDE (Spoofing, Tampering, Repudiation, Information Disclosure, Denial of Service, Elevation of Privilege) was designed for interactive software components. Pipelines have a narrower threat surface but a much higher consequence per successful attack. STRIDE-lite collapses the six categories into three dominant themes that map directly to pipeline anatomy:
- Injection — an attacker inserts malicious code or commands into the pipeline flow (covers Tampering + Spoofing)
- Exfiltration — secrets, source code, or internal network topology leak out (covers Information Disclosure)
- Privilege Escalation — a pipeline token, role, or runner is used to gain access beyond the intended scope (covers Elevation of Privilege)
Denial of Service and Repudiation are real but secondary; address them through SLO monitoring and audit logging covered in later lessons.
Threat Category 1 — Injection: Who Can Insert Code?
Injection is the most exploited class of pipeline attacks because every pipeline's core job is to run code from the repository. The question is: whose code, under what trust level?
Malicious Pull Requests
GitHub Actions and GitLab CI will execute pipeline definitions from any branch, including those opened by external contributors via fork PRs. An attacker can open a PR that modifies the workflow file to exfiltrate secrets.PROD_AWS_KEY into an outbound HTTP request. The pull_request_target trigger is especially dangerous — it runs in the context of the base branch (with secrets) but checks out the fork's code.
pull_request_target with a step that checks out PR code unless you explicitly scope it. The official GitHub Security Lab documented multiple real-world attacks on popular open-source projects (PyPI, npm packages) through this single misconfiguration.Dependency Confusion and Typosquatting
If your pipeline runs npm install or pip install against a public registry and your package name matches a private internal package name, an attacker can publish a malicious public package at a higher version number. The package manager resolves the public version first — this is dependency confusion. The Codecov breach followed a similar pattern against the CI runner itself.
Third-Party Action and Orb Poisoning
Every uses: some-vendor/action@main in a GitHub Actions workflow is a remote code execution point. If that action's repository is compromised, so is every pipeline that references it by a mutable tag. The @main or @v3 reference is not immutable.
Threat Category 2 — Exfiltration: What Leaks and Where?
Secrets injected into environment variables are the most common exfiltration vector. A pipeline that prints environment variables to stdout, or a test that logs request headers containing an Authorization header, can leak credentials into build logs that are readable by anyone with repository access.
The second vector is the runner filesystem. CI runners are ephemeral VMs, but if the runner is self-hosted and shared across jobs, a malicious job can read ~/.aws/credentials, /tmp/token-*, or cached credentials from a previous job's workspace without any elevated privileges.
env: MY_KEY: ${{ steps.get-token.outputs.token }}) is NOT redacted. Always use the secrets context for sensitive values, and call add-mask for dynamically generated tokens: echo "::add-mask::$DYNAMIC_TOKEN".Threat Category 3 — Privilege Escalation: What Can a Compromised Runner Do?
Pipeline tokens are the most over-privileged credentials in most engineering organizations. A GitHub Actions workflow token with permissions: write-all can push branches, approve PRs, modify repository settings, and create releases — all from a runner that any contributor's PR code can influence. On AWS, a CD pipeline IAM role with AdministratorAccess turns a pipeline compromise into a full cloud account takeover.
Systematic Enumeration — the Pipeline Trust Boundary Table
A practical threat model output is not a slide deck — it is a table that lists every trust boundary crossing in your pipeline, the actor that can trigger it, the worst-case impact, and the control that closes it. Here is a template populated for a typical GitHub Actions + AWS deployment:
Running a Pipeline Threat Model — the Four Questions
You do not need a threat modeling tool to start. Work through these four questions for every new pipeline component you add:
- What does this component receive as input? — Is that input validated? Can an external party influence it?
- What credentials does it hold or generate? — Are they scoped to the minimum required? Do they expire?
- What can it write or execute? — Can output propagate to production without a human gate?
- What is logged, and who can read it? — Does the log contain anything a reader should not see?
Connecting the Model to Controls
Each threat maps directly to a concrete tool or configuration introduced in the rest of this tutorial: Injection threats are addressed by SAST (lesson 3), SCA scanning (lesson 4), and signing/verification (lesson 8). Exfiltration threats are addressed by secrets scanning (lesson 9). Privilege escalation threats are addressed by container and IaC scanning (lesson 6) and SBOM provenance (lesson 7). Threat modeling is not a standalone exercise — it is the map that tells you why you are running every scanner.