CI/CD Pipeline Security: Your Code Passed Every Scan. What About the Pipeline?
Most AppSec programs put a lot of effort into the code: static analysis on every pull request, dependency scanning, secrets detection, infrastructure-as-code checks. Then all of that code goes through a build system that holds cloud keys, signing certificates and deploy rights to production, and that system often gets far less attention than the code it builds.
That is a problem, because an attacker who controls your pipeline does not need a vulnerability in your application. The pipeline already has the access they want. This guide covers how CI/CD pipelines get attacked, what the OWASP Top 10 CI/CD Security Risks says to look at, and a short list of controls that make the biggest difference.
Why the pipeline is such a good target
A CI/CD system is, by design, a machine that runs code from many sources with powerful credentials. In a typical build it pulls your source, downloads third-party dependencies and plugins, runs scripts that anyone with write access can change, and then uses secrets to publish packages, push images or deploy to the cloud.
Every one of those steps is a place where someone else's code runs next to your credentials. That is why pipeline compromises tend to be quiet and wide: one poisoned step can reach every repository that uses it.
A real example: one action, about 23,000 repositories
In March 2025, the popular GitHub Action tj-actions/changed-files was compromised. According to BleepingComputer, the action was used by about 23,000 repositories. Attackers used a compromised personal access token from a bot account with privileged access, pushed a malicious commit on March 14, and retroactively changed multiple version tags so they pointed to it. The malicious code dumped secrets from the runner's memory into the workflow logs, and on public repositories anyone could read those logs.
The incident was tracked as CVE-2025-30066. CISA published an alert, added it to its Known Exploited Vulnerabilities catalog, and listed cloud access keys, GitHub personal access tokens, npm tokens and private RSA keys among the secrets at risk.
The lesson is not about one action. Teams that referenced the action by a version tag got the malicious code automatically, without any change in their own repositories. Teams that had pinned it to a full commit SHA did not.
The OWASP Top 10 CI/CD Security Risks
OWASP's Top 10 CI/CD Security Risks, first released in 2022, is the most useful map of where pipelines fail. The ten risks are:
- CICD-SEC-1: Insufficient Flow Control Mechanisms. Code can reach production without an independent review or approval.
- CICD-SEC-2: Inadequate Identity and Access Management. Too many accounts, stale accounts and broad permissions across source control and CI systems.
- CICD-SEC-3: Dependency Chain Abuse. Attacks through the packages your build pulls in. We covered one form in malicious open source packages.
- CICD-SEC-4: Poisoned Pipeline Execution (PPE). Getting the pipeline to run attacker-controlled commands.
- CICD-SEC-5: Insufficient PBAC (Pipeline-Based Access Controls). Pipeline steps can reach more secrets and systems than they need.
- CICD-SEC-6: Insufficient Credential Hygiene. Secrets that are too powerful, too long-lived or too widely shared.
- CICD-SEC-7: Insecure System Configuration. Weak defaults and unpatched CI servers, runners and plugins.
- CICD-SEC-8: Ungoverned Usage of 3rd Party Services. Integrations and apps connected to your source control with permissions nobody reviews.
- CICD-SEC-9: Improper Artifact Integrity Validation. Nothing proves that what you deploy is what you built from reviewed code.
- CICD-SEC-10: Insufficient Logging and Visibility. Not enough logs to detect or investigate an attack on the pipeline.
Poisoned Pipeline Execution, in plain terms
PPE deserves a closer look because it surprises many teams. OWASP describes it as an attacker with access to source control manipulating the build process without access to the build environment. The malicious commands then run with the pipeline's identity and its secrets.
OWASP describes three variants:
- Direct PPE: the attacker edits the CI configuration file itself, for example in an unprotected branch or a pull request.
- Indirect PPE: the configuration is protected, so the attacker changes files the pipeline calls, such as Makefiles, scripts, test code or linter configuration.
- Public PPE: a public project runs unreviewed code from outside contributors in its pipeline, so an anonymous user can trigger it.
Controls that matter most
You do not need to solve all ten risks at once. These controls address the failure patterns above and are supported by the GitHub Actions security hardening guide and the OWASP project.
Pin third-party actions and plugins
GitHub recommends pinning actions to a full-length commit SHA and checking that the SHA comes from the action's own repository, not a fork. A tag can be moved; a commit SHA cannot. Pair pinning with an allow list of approved actions and a process to update pins deliberately.
Give tokens and steps the least privilege
Set the default GITHUB_TOKEN permissions to read-only and raise them per job only where needed. OWASP's credential hygiene guidance adds that each pipeline and step should only have access to the secrets it requires, and that the same credentials should not be reused across contexts.
Prefer short-lived credentials
Instead of storing long-lived cloud keys as pipeline secrets, use OpenID Connect so workflows authenticate directly to the cloud provider with short-lived tokens. OWASP puts it simply: prefer temporary credentials over static ones, and rotate the static ones you cannot remove.
Treat untrusted code as untrusted
Avoid triggers that run with secrets on code from forks unless you need them; GitHub specifically warns about pull_request_target. OWASP recommends running pipelines that execute unreviewed code on isolated nodes, away from secrets, and requiring approval for pipelines started by outside contributors. GitHub also says self-hosted runners should almost never be used for public repositories.
Protect the pipeline definition
Add the workflow directory to CODEOWNERS so changes to pipeline files need approval from a designated reviewer, and make sure every branch that can trigger a pipeline with secrets has branch protection. This addresses direct and indirect PPE.
Keep secrets out of logs and artifacts
Mask secrets in console output and check existing logs. OWASP also recommends verifying that secrets are not left in artifacts such as container image layers, binaries or Helm charts. If a secret does leak, follow the order in our leaked secret playbook: revoke first, clean up later.
Log the pipeline like production
Keep audit logs from source control, CI servers and cloud accounts, and make sure someone can answer basic questions after an incident: which workflow ran, with which version of each action, and which secrets it could read.
Where to start this week
- List the third-party actions and plugins your pipelines use, and pin them to commit SHAs.
- Set default token permissions to read-only.
- Find pipelines that hold long-lived cloud keys and plan their move to OIDC.
- Check which workflows run on code from forks, and what secrets they can reach.
- Put workflow files under CODEOWNERS.
Frequently asked questions
Is this only a GitHub problem? No. The OWASP list is vendor-neutral and applies to GitLab CI, Jenkins, Azure DevOps and others. GitHub Actions is used here because its guidance is public and specific.
Do code scanners cover the pipeline? Partly. Secrets scanning and SCA catch some issues, such as a leaked key or a malicious dependency, but permissions, runner configuration and third-party integrations need their own review. See SAST, SCA, secrets and IaC explained for what each scanner covers.
Does pinning to a SHA mean we never get updates? No. It means updates happen when you choose to review and change the pin, not when someone moves a tag.
How priorIQ.ai helps
CodeIQ scans your repositories and pipelines: SAST, SCA with malicious-package detection, container and infrastructure-as-code scanning, and secrets across the full git history with live-secret validation, plus a pipeline gate that stops new issues before they merge. Its findings join the same risk model as endpoints and cloud in PriorIQ Core, so a live secret in a build log or a risky dependency competes for attention on real risk, in a single prioritized queue. Request a demo.