Join our Newsletter — 33% off our NHI Course
Home› Guides› OWASP CI/CD Top 10 CICD-SEC-4 Poisoned Pipeline Execution…
Guide Software & AI Supply Chain

OWASP CI/CD Top 10 CICD-SEC-4 Poisoned Pipeline Execution Guide: Preventing Poisoned Pipeline Execution

PDF for newsletter subscribers · free to read online

← All guides
By NHI Mgmt Group, edited by Lalit Choda Updated 26 October 2026 16 min read
On this page

Anyone who can change code that a pipeline executes can make that pipeline run their commands with the pipeline's identity, secrets and network reach. OWASP calls this Poisoned Pipeline Execution (PPE) and lists it as CICD-SEC-4 in the Top 10 CI/CD Security Risks. This guide explains the three forms OWASP defines, the controls that stop untrusted code reaching privileged jobs on GitHub Actions, GitLab CI/CD and Jenkins, how to detect and measure exposure, and where PPE maps to NIST, CIS, ISO and MITRE ATT&CK. It is written for platform engineers, AppSec teams and the people who own CI/CD service identities. It goes deeper than the Top 10 CI/CD Security Risks overview and describes the attack at concept level only.

Key takeaways

  • PPE needs repository access, not CI access. OWASP defines it as an attacker with access to source control, but not to the build environment, manipulating the build by injecting commands into the pipeline configuration or the files it relies on.
  • There are three forms. Direct PPE edits the CI configuration file, Indirect PPE edits files the configuration calls (Makefiles, scripts, tests, linter configs), and Public PPE (3PE) comes from anonymous contributors to public repositories.
  • Unreviewed code is the trigger condition. Pipelines that run on pull requests or pushes to arbitrary branches execute code nobody has approved, so they must run on isolated nodes with no secrets.
  • Privileged triggers are the most dangerous pattern. On GitHub, pull_request_target and workflow_run run with write access and secrets; checking out pull request code inside them, or interpolating untrusted text into scripts, hands that privilege to the contributor.
  • The blast radius is an identity problem. What a poisoned job can do equals what its tokens, cloud roles and runner host can do, which is why PPE controls go together with OWASP CI/CD Top 10 CICD-SEC-5 Insufficient PBAC Guide controls.

What it is and who it applies to

OWASP's definition is specific: PPE is the ability of an attacker with access to source control systems, and without access to the build environment, to manipulate the build process by injecting malicious code or commands into the build pipeline configuration, so that the code runs as part of the build. The OWASP page then describes how, once running inside the pipeline, the attacker can carry out a wide range of operations "within the context of the pipeline's identity". That phrase is the core of the risk: the attacker borrows a trusted non-human identity without ever stealing its credential directly.

It applies to any organisation where a CI system executes code taken from a repository, which is almost every organisation that builds software. The exposure is highest for:

  • Open source maintainers: public repositories accept pull requests from anyone, so 3PE is reachable by anonymous internet users.
  • Organisations with shared CI instances: OWASP notes that when a vulnerable public repository's pipeline runs on the same CI instance as private projects, private secrets can be exposed too.
  • Large engineering organisations: many people hold write access to many repositories, so a single compromised developer account or token is enough for Direct or Indirect PPE.
  • Teams using self-hosted runners or agents: persistent hosts keep state between jobs and often sit on internal networks.

PPE is distinct from its neighbours. OWASP CI/CD Top 10 CICD-SEC-1 Insufficient Flow Control Mechanisms Guide covers pushing code to production without review; PPE is about code that executes before any review happens. OWASP CI/CD Top 10 CICD-SEC-3 Dependency Chain Abuse Guide covers malicious packages pulled from outside; PPE comes through the repository itself.

Structure and key requirements

The OWASP entry has three parts that matter to defenders: the three PPE variants, the impact, and six recommendations.

The three variants

VariantWhat the attacker changesPreconditionWhy common defences miss it
Direct PPE (D-PPE)The CI configuration file itself, through a push to an unprotected branch or a pull request from a branch or forkThe pipeline is triggered by push or PR events and reads its definition from the changed branchTeams protect main but let feature branches trigger pipelines with secrets
Indirect PPE (I-PPE)Files the configuration calls: a Makefile, a script, test code, or linter and scanner configuration that loads external codeThe configuration is protected (separate branch, separate repository or defined in the CI tool) but still executes repository contentProtecting the pipeline file feels complete; the build still runs contributor-controlled files
Public PPE (3PE)Either of the above, submitted as a pull request to a public repositoryThe public project's CI runs unreviewed contributions automaticallyContributors are anonymous, so access reviews and account security do not help

Two related patterns deserve the same treatment even though OWASP does not name them as separate variants. Script injection: GitHub's documentation warns that values in the github context ending in body, head_ref, label, message, ref or title, along with branch names and email addresses, are attacker-controllable and can be interpreted as code if written straight into a run script. Infrastructure-as-code plan steps: OWASP's references include research showing that commands such as terraform plan can execute code from a provider, so a "read-only" plan job on a pull request is not inherently safe.

The impact OWASP lists

  • Secrets: any secret available to the CI job, whether injected as environment variables or stored in the CI system, typically cloud, artefact registry and SCM credentials.
  • Node assets: files on the node's file system and credentials to a cloud environment reachable through the underlying host.
  • Artefact integrity: the ability to ship code and artefacts further down the pipeline disguised as legitimate build output.
  • Lateral movement: access to other hosts and assets on the job node's network.

The six OWASP recommendations, in practical terms

  1. Run pipelines that execute unreviewed code on isolated nodes with no secrets and no access to sensitive environments.
  2. Evaluate whether external contributors on public repositories need to trigger pipelines at all; avoid running pipelines from forks where possible and add manual approval.
  3. For sensitive pipelines, give every branch that can trigger the pipeline a matching branch protection rule in the SCM.
  4. Review every CI configuration change before the pipeline runs, or load the configuration from a separate protected branch.
  5. Remove SCM repository permissions that users do not need.
  6. Give each pipeline only the credentials it needs, with minimum privileges.

Identity and NHI requirements

PPE turns a contributor identity into a pipeline identity. Defenders should treat every credential a job can reach as part of the PPE attack surface:

Pipeline identityHow PPE reaches itControl
Built-in job token (GitHub GITHUB_TOKEN, GitLab CI_JOB_TOKEN)Available to every step; actions/checkout persists it to disk by defaultDefault permissions to read-only, grant write per job; set persist-credentials: false where later steps run untrusted code
Stored secrets and CI variablesLoaded into the environment of any job that references themEnvironment-scoped or protected variables that untrusted triggers cannot read; required reviewers on deployment environments
Cloud roles via OIDC federationA job that can request an OIDC token can assume any role whose trust policy matches its claimsPin trust to sub values such as repo:org/repo:environment:Production or a specific job_workflow_ref, never to a whole organisation or any branch
Package publishing tokensLeft in CI variables after migration to trusted publishingRevoke legacy tokens when moving to OIDC-based publishing
Runner host identityInstance metadata service, mounted credentials, Docker socket on a persistent self-hosted runnerEphemeral runners for untrusted jobs; block metadata access from job containers
Developer accounts and personal access tokensThe entry point for Direct and Indirect PPE in private repositoriesPhishing-resistant MFA, fine-grained tokens, least-privilege repository roles

GitHub documents that the GITHUB_TOKEN is a GitHub App installation token limited to the workflow's repository and expiring when the job finishes (at most 6 hours on GitHub-hosted runners). That short lifetime limits replay but does nothing to stop misuse during the run, which is exactly what PPE exploits. The OWASP CI/CD Top 10 CICD-SEC-2 Inadequate Identity and Access Management Guide covers the human and machine accounts that feed Direct PPE, and the CI/CD Pipeline Identity Security Guide covers pipeline credentials end to end. See also the Workload Identity Federation Guide.

Implementation steps

Roll out in this order. Each phase reduces exposure on its own, and the early phases can run in report-only mode before enforcement.

  1. Inventory triggers and secrets (week 1 to 2). List every pipeline, what triggers it (push, pull request, fork pull request, schedule, comment, pull_request_target, workflow_run), which runner it uses and which secrets, environments and cloud roles it can reach. OpenSSF Scorecard's Dangerous-Workflow check (rated Critical) and Token-Permissions check (rated High) can scan GitHub repositories for the most common patterns at scale.
  2. Remove secrets from untrusted triggers (week 2 to 4). Builds and tests for pull requests run with a read-only job token and no secrets. On GitHub, use the split the GitHub Security Lab recommends: an unprivileged pull_request workflow builds and uploads results as artefacts, and a separate privileged workflow_run workflow consumes the artefacts without checking out or executing pull request code, treating the artefacts as untrusted data.
  3. Gate fork and first-time contributor runs. Require maintainer approval for fork pull request workflows (GitHub repository, organisation or enterprise setting; runs awaiting approval for more than 30 days expire). On GitLab, fork merge request pipelines run in the fork with the fork's variables; a parent project member can run them in the parent project, which uses the parent's variables after a warning. Disable that with the ci_allow_fork_pipelines_to_run_in_parent_project project setting if you do not need it.
  4. Protect the pipeline definition. Add .github/workflows/, .gitlab-ci.yml, Jenkinsfile, and the scripts and Makefiles they call, to CODEOWNERS with required review. For Jenkins, load the pipeline definition from a protected branch or a separate repository.
  5. Align branch protection with triggers. Every branch that can trigger a pipeline with secrets needs a branch protection or ruleset entry. GitLab enforces this natively: merge request pipelines reach protected variables and runners only when both source and target branches are protected, the user has push or merge access to the target, and both branches are in the same project.
  6. Fix script injection. Pass untrusted context values through an intermediate environment variable rather than inline ${{ }} expressions in run blocks, and prefer actions that take inputs as arguments.
  7. Isolate execution. Use ephemeral runners for anything that runs unreviewed code. GitHub states self-hosted runners should almost never be used for public repositories, and offers just-in-time runners that perform at most one job. On Jenkins, set the built-in node's executors to 0 so builds run on agents, and keep Agent to Controller Access Control enabled (always on since Jenkins 2.326).
  8. Narrow identities. Set workflow default token permissions to read, pin OIDC trust policies to environment or workflow claims, and revoke static credentials that federation replaced.

Testing: before enforcing, run Scorecard or an equivalent linter in report mode, raise test pull requests from a fork in a sandbox repository, and confirm the fork run receives no secrets and only a read-only token. Rollback: keep workflow and ruleset changes in version control so a broken release pipeline can be restored from the previous commit; approval gates and fork settings can be relaxed per repository without undoing the organisation default.

Choosing the right pattern

SituationPreferred approachTrade-off
Public repository, external contributorsUnprivileged PR builds on hosted ephemeral runners, approval for first-time contributors, privileged work only after mergeSlower feedback for contributors; maintainers spend time approving runs
PR needs to post a comment or labelSplit workflow (pull_request then workflow_run) that never executes PR codeMore workflow files to maintain
Private repository, trusted staff onlyNo secrets on PR builds, protected branches for deployment, CODEOWNERS on pipeline filesIntegration tests needing real credentials move to post-merge or to approved environments
Label-gated pull_request_targetUse only as a temporary measureGitHub Security Lab notes a race: new commits can be pushed after the label is applied
Large organisationOrganisation-level rulesets, central reusable workflows, runner groups by sensitivity, policy scanning in CINeeds a platform team and exception process

Evidence and assessment

Assessors and internal auditors should ask for evidence, not policy statements. Useful evidence for CICD-SEC-4:

  • Trigger inventory: an export listing every workflow or job, its triggers and the secrets and environments it references.
  • Platform settings: screenshots or API output for fork workflow approval, default workflow permissions, GitLab fork pipeline settings, Jenkins built-in node executor count.
  • Scanner output: Scorecard Dangerous-Workflow results, or equivalent, with remediation tickets for findings.
  • Ownership rules: CODEOWNERS entries covering pipeline files and the scripts they invoke, and branch rulesets that require that review.
  • Test records: results of a fork pull request test showing no secrets were exposed.

Detection and metrics

Monitor for: changes to pipeline definition files and called scripts outside normal review; workflow runs from forks or first-time contributors; new pull_request_target or workflow_run usage; outbound connections from runners to unexpected destinations; OIDC token exchanges or cloud API calls from pipeline roles outside expected repositories and branches; and secret use (registry publishes, cloud logins) that does not correlate with a protected-branch run. GitHub's audit log and the cloud provider's own logs (for example federated role assumption records) are the primary sources.

MetricTarget
Workflows that run untrusted code with access to secrets or a write tokenZero
Repositories with default job token permissions set to read100 percent
Pipeline definition files covered by CODEOWNERS and required review100 percent of repositories with deployment secrets
Branches that can trigger secret-bearing pipelines without a protection ruleZero
Self-hosted runners serving public repositories or mixed-trust workloadsZero
Median time to fix a Dangerous-Workflow findingWithin the critical vulnerability SLA

Mapping to other frameworks

FrameworkItemRelevance to PPE
NIST SP 800-53 Rev. 5CM-3 Configuration Change Control; CM-5 Access Restrictions for ChangeReview and restrict changes to pipeline definitions and called scripts
NIST SP 800-53 Rev. 5AC-6 Least Privilege; SC-39 Process IsolationMinimal job credentials; isolated nodes for unreviewed code
NIST SP 800-53 Rev. 5SA-15 Development Process, Standards, and Tools; SI-7 Software, Firmware, and Information IntegritySecure build process; prevent tampered artefacts shipping
NIST CSF 2.0PR.PS-06; PR.AA-05; DE.CM-09Secure development practices; least-privilege permissions; monitoring runtime environments
CIS Controls v8.1Control 16 Application Software Security; Control 6 Access Control ManagementSecure development pipeline and least-privilege access
ISO/IEC 27001:2022 Annex AA.8.25 Secure development life cycle; A.8.32 Change management; A.8.31 Separation of development, test and production environmentsControlled change to build logic; separation of untrusted builds from production credentials
MITRE ATT&CK v20T1195.002 Compromise Software Supply Chain; T1059.004 Unix Shell; T1528 Steal Application Access Token; T1552.005 Cloud Instance Metadata APITampered builds, commands executed in jobs, theft of job tokens and host credentials
MITRE D3FENDD3-EI Execution Isolation; D3-OTF Outbound Traffic FilteringIsolated runners; limiting where a poisoned job can send data

The SLSA and Build Provenance Guide covers how build isolation and provenance help consumers detect output from a poisoned build.

Common pitfalls

  • Protecting main but not the triggers. Feature branches or pull requests still run a pipeline with secrets. The CI/CD pipeline exploitation case showed a researcher with leaked repository credentials editing a Bitbucket pipeline and pushing it, so the next run deployed their SSH key to the server.
  • Privileged triggers plus untrusted input. In the Ultralytics PyPI compromise, a pull_request_target workflow inserted a pull request branch name into a shell script; the resulting execution is believed to have poisoned the shared Actions cache later restored by the release build. The flaw had been fixed once and then reintroduced, which is why scanning must be continuous.
  • Trusting the platform to withhold secrets. Travis CI CVE-2021-41077 passed public projects' secrets to fork pull request builds for about a week in September 2021. Defence in depth means fork builds should not need secrets at all, so a platform bug exposes nothing.
  • Stopping at the configuration file. Moving the Jenkinsfile to a protected branch while the build still runs make, test suites or linters from the contributor's branch leaves Indirect PPE open.
  • Label or comment gates treated as permanent. Approval by label has a time-of-check to time-of-use gap; old pull requests also keep the workflow version they were opened with, so close or rebase them after a fix.
  • Persistent self-hosted runners for untrusted jobs. State, caches and host credentials survive between jobs, turning one poisoned run into a foothold for later privileged ones. The CI/CD Pipeline Compromise Playbook covers response when this happens.

Practitioner checklist

  • Inventory every pipeline trigger and the secrets, environments and cloud roles each one reaches.
  • Ensure pull request and fork builds run with no secrets and a read-only job token on ephemeral runners.
  • Require approval for fork and first-time contributor workflow runs; disable GitLab fork pipelines in the parent project unless needed.
  • Replace pull_request_target with pull_request plus workflow_run where privileged follow-up is needed, and never check out PR code in privileged jobs.
  • Put pipeline files and the scripts, Makefiles, test and linter configuration they call under CODEOWNERS with required review.
  • Give every branch that can trigger a secret-bearing pipeline a protection rule.
  • Pass untrusted context values through environment variables, never inline into scripts.
  • Set Jenkins built-in node executors to 0 and keep agents separate by trust level.
  • Pin OIDC trust policies to environment or workflow claims and revoke static keys they replaced.
  • Run Scorecard Dangerous-Workflow (or equivalent) on every repository continuously and track findings to closure.

Standards and references

Related NHI Mgmt Group resources: Top 10 CI/CD Security Risks · GitHub and GitHub Actions Security Guide · CI/CD Security Guide · OWASP CI/CD Top 10 CICD-SEC-5 Insufficient PBAC Guide · CI/CD Pipeline Identity Security Guide

PDF for subscribers

Take this guide with you

Every section, table and checklist in one PDF, with links to the sources and related guides. Subscribe to our weekly NHI & AI Identity Journal and we’ll email you a download link.

Free weekly newsletter

Subscribe to the NHI & AI Identity Journal

The latest on NHI and Agentic AI security – articles, research, breaches, news and events every week.

Bonus 33% off our NHI Course when you subscribe.

NHIMG Editorial Note
Researched and written by the NHI Mgmt Group editorial team using AI tools, under the editorial direction of Lalit Choda. Facts are drawn from the sources listed on this page and checked by our automated quality process. Last updated 26 October 2026. Spotted an error? Use our contact page.
NHI Mgmt Group, the independent authority on Non-Human Identity, IAM and Agentic AI security. nhimg.org

PDF Downloads for Newsletter Subscribers

Get the guide

Enter your details and we’ll email you a download link.

The full guide is free to read online. The PDF is a thank you for subscribing.

Fill in all fields marked * and tick the PDF box to continue.