Caloogy Article
中文

CI/CD Pipeline Security

Date
Sep 13, 2025
Series
Infra
Reading
4 min
Topics
InfraCi cdGithub actionsSupply chain

The one-sentence version

A CI/CD pipeline is a machine holding all the keys: it can read source code, access deployment credentials, push to production environments, and publish software packages. If an attacker can run their own code within the pipeline, it is equivalent to obtaining all those keys. Common entry points include three areas: External contributor PRs running in a privileged context, Third-party Actions being tampered with, and Long-lived secrets being read from the pipeline. The countermeasures are: do not trust external code, pin third-party components to specific commits, and use OIDC to exchange for short-lived credentials.

This article has three layers:

  1. Layer 1: The Intuition: The key cabinet of an automated factory
  2. Layer 2: Real-World Use: Codecov, tj-actions, and various pull_request_target incidents
  3. Layer 3: Principles and Engineering details: Trigger permission differences, pinning Actions, OIDC, and least privilege

Layer 1: The Intuition

1.1 The key cabinet of an automated factory

Imagine a fully automated factory:

If the factory's key cabinet is left open while processing raw materials sent by a stranger, the stranger only needs to hide an instruction in the raw material that says "make a copy of the key and mail it to me."

1.2 What the pipeline holds

Asset Consequence of Compromise
Cloud deployment credentials Control over production environments
Package publishing tokens Publish malicious versions, affecting all downstream users (see "Dependency Poisoning")
Code repository write access Modify code, implant backdoors
Signing keys Apply "official" signatures to malicious software

Layer 2: Real-World Use

Event Mechanism
Codecov (2021) Attackers tampered with the upload script provided by Codecov. When many customers ran this script in their CI, environment variables (containing various credentials) were sent to the attackers.
tj-actions/changed-files (2025, CVE-2025-30066) A GitHub Action used by over twenty thousand repositories was tampered with. Attackers pointed all of its version tags to a malicious commit. The malicious code prints pipeline secrets to the build logs, and anyone can view the build logs of public repositories.
pull_request_target Misuse (Multiple instances) To allow external PRs to comment or add labels, many projects use this high-privilege trigger, yet they also checkout and run the code from the PR within it, allowing external contributors to read repository secrets.
Persistence on self-hosted runners When public repositories use self-hosted runners, code from external PRs can leave backdoors on the runner, affecting all subsequent builds.

Layer 3: Principles and Engineering

3.1 Trigger permission differences (using GitHub Actions as an example)

Trigger Which code runs Can it access secrets? Risk
pull_request (from fork) The PR's code No, token is read-only Low
pull_request_target The target branch's workflow definition Yes If the workflow checks out and runs the PR's code, it is equivalent to handing secrets to an external contributor.
workflow_run Triggered after a previous workflow completes Yes Equally dangerous if it processes artifacts from a previous workflow (which may come from a PR).

Principle: Never execute untrusted code in a privileged context. If you must process the results of an external PR, split "running untrusted code" and "using secrets" into two independent workflows, passing only verified data between them.

3.2 Pinning third-party Actions

# Dangerous: tags can be repointed to other commits
- uses: some-org/some-action@v4

# Safe: pinned to the full commit hash
- uses: some-org/some-action@8f4b7f84864484a7bf31766abe9204da3cbe65b3  # v4.1.0 (example hash)

In the tj-actions incident, the attacker changed the version tag to point to a malicious commit. By pinning to a commit hash, this type of attack becomes ineffective. Use dependency update tools (such as Dependabot, Renovate) to update hashes, and review the changes during the update process.

3.3 OIDC: Using short-lived credentials instead of long-lived keys

The traditional approach is to store long-lived access keys from cloud platforms as CI secrets. If leaked once, an attacker can use them indefinitely.

The OIDC approach:

  1. The CI platform issues an identity token for each run, specifying "this is a run for repository X, branch Y, workflow Z";
  2. The cloud platform verifies this token and issues a temporary credential that expires in a few dozen minutes, based on pre-configured rules (e.g., "only trust deployment workflows from the main branch");
  3. No long-lived secrets exist within the CI.

Even if a temporary credential is leaked, it expires quickly and can only be obtained by workflows that meet the required conditions.

3.4 Least privilege

Measure Description
Set default token permissions to read-only In repository or organization settings, set the default permissions of the workflow token to read-only; workflows requiring write access must declare them explicitly.
Isolate secrets by environment Production deployment secrets are only available in the "production environment" and require manual approval.
Branch protection Deployment can only be triggered from protected branches; modifying workflow files requires code review.
Do not use self-hosted runners for public repositories Or destroy the runner after every run.
Log masking The platform automatically masks registered secrets, but secrets that have been encoded (e.g., Base64) may not be masked.

3.5 Provenance for artifacts

Even if the pipeline itself is secure, users need to be able to verify that "this package was indeed built from this specific commit in this repository." Build provenance (such as SLSA, Sigstore, or trusted publishing in package repositories) turns this into a verifiable signature, preventing "clean source code, poisoned package" scenarios (see the xz incident in "Dependency Poisoning").


Summary

Layer Key points
Risk The pipeline holds all the keys
Entry points External PRs running in privileged contexts, tampered third-party Actions, long-lived secrets
Key practices Do not run untrusted code in privileged contexts; pin Action commit hashes; use OIDC short-lived credentials
Additional Default read-only tokens, environment isolation, branch protection, provenance