A CI/CD pipeline runs whatever code reaches it with whatever credentials it holds, so an attacker who can influence that code gets the pipeline's identity for free. MITRE ATT&CK T1677 Poisoned Pipeline Execution sits under the Execution tactic on the SaaS platform and describes adversaries injecting malicious code into the build process to steal credentials, move laterally or push malicious components further down the pipeline. This guide takes the ATT&CK technique view: the procedure examples recorded in ATT&CK v20, the detection strategy and data components MITRE publishes, the two mitigations, and how to test that your controls hold. It is written for platform engineering, AppSec and SOC teams. For the OWASP risk framing, requirements and evidence, see the OWASP CI/CD Top 10 CICD-SEC-4 Poisoned Pipeline Execution Guide; this page does not repeat it.
Key takeaways
- The target is the pipeline's credentials. ATT&CK describes poisoned builds exfiltrating secrets to a remote server or exporting them as workflow artifacts, and every recent case on this page turned a stolen token into the next compromise.
- Three routes, one root cause. Direct (edit the CI file), indirect (edit files the CI file calls) and public (a fork pull request) all succeed because unreviewed code runs with privileged access.
- Some triggers and inputs are the attack surface. In GitHub Actions,
pull_request_targetwith a checkout of the pull request head, and branch names or titles interpolated intorunsteps, recur across the cases. - Detection lives in SaaS audit logs. ATT&CK DET0533 correlates fork-triggered workflows, secret access, encoded artifacts and changes to build configuration files.
- Two mitigations carry the weight. M1054 Software Configuration (isolate unreviewed code from secrets) and M1018 User Account Management (minimum pipeline permissions, fewer writers).
What the technique covers
ATT&CK v20 lists T1677 with no sub-techniques. Its description follows the OWASP CICD-SEC-4 taxonomy and adds GitHub-specific detail. The table maps each route to the access the adversary needs and the first control that breaks it.
| Route | What the adversary changes | Access needed | Control that breaks it |
|---|---|---|---|
| Direct pipeline execution | The CI configuration itself, for example .gitlab-ci.yml or a file under .github/workflows/ | Push or pull request rights on a branch that triggers the pipeline | Branch protection and code owner review on CI files |
| Indirect pipeline execution | Files the CI configuration references: makefiles, scripts, unit tests, linters, package.json scripts | Write access to referenced files, or a dependency they install | Review of build scripts as code, no secrets in jobs that run untrusted code |
| Public pipeline execution | A pull request from a fork, or crafted inputs such as a branch name | None beyond a public account | Fork workflows without secrets, approval before running, no self-hosted runners on public repositories |
The outcome is often a MITRE ATT&CK T1195 Supply Chain Compromise: the poisoned pipeline signs, publishes or deploys something your customers trust. Keep the two separate in your threat model: T1677 is the execution inside your pipeline; T1195 is what happens to everyone downstream.
How adversaries use it: cases from ATT&CK and incident reporting
- TeamPCP (G1056): ATT&CK records the group compromising trusted CI/CD pipelines by injecting credential-stealing payloads into legitimate workflows and packages, including the Trivy and KICS security tools and the LiteLLM AI gateway. In the Trivy case, a
pull_request_targetworkflow ran code from an outside pull request with the repository's privileges, a personal access token was stolen, and after an incomplete rotation the attacker poisoned a release and force-pushed trivy-action tags in March 2026. The stealer read secrets from runner memory. See the Trivy supply chain attack analysis and the downstream LiteLLM PyPI package breach. - Shai-Hulud (S9008): ATT&CK records the worm using GitHub Actions from stolen accounts to create a malicious workflow at
.github/workflows/discussion.yaml. The first wave in September 2025 pushed a workflow that dumped repository secrets; the November wave registered infected hosts as self-hosted runners for later command execution. One entry point in that wave was apull_request_targetworkflow that checked out pull request code and leaked a bot token. See the Shai-Hulud npm worm analysis. - Mini Shai-Hulud (S9043) and CanisterWorm (S9042): ATT&CK records Mini Shai-Hulud propagating through triggered workflows, and CanisterWorm using tokens stolen from Trivy users to publish itself across more than 46 npm packages. See the Mini Shai-Hulud 2026 analysis.
- APT38 (G0082): ATT&CK records poisoned npm packages with fake runtime dependencies that execute during install or update on developer endpoints or CI/CD systems. This is the indirect route: the CI file is untouched, but
npm installruns attacker code. - Branch name injection (Ultralytics, December 2024): Wiz reported that a workflow treated
github.head_refas a trusted string, so a crafted branch name in a pull request executed as shell code. Versions 8.3.41 and 8.3.42 published to PyPI carried the XMRig cryptominer. - Self-hosted runners (PyTorch research, 2023): researchers reported to Meta's bug bounty programme that fork pull requests from previous contributors ran on self-hosted runners without approval, which gave them root on runners,
GITHUB_TOKENs with write permission, personal access tokens and AWS keys able to write to release buckets.
Where identity fits
Every case above is a non-human identity problem. The pipeline holds a workflow token, publishing tokens, cloud keys and often a bot account's personal access token, and the poisoned code inherits all of them. The questions that decide the blast radius are identity questions: which secrets are visible to the job that runs untrusted code, how long they live, whether a bot token can push to protected branches, and whether rotation after an incident reaches every credential. The Trivy case turned on an incomplete rotation that left a service account valid. See the CI/CD Pipeline Identity Security Guide and How to Audit CI/CD Secrets Scope.
What to detect and where
ATT&CK publishes one detection strategy, DET0533, with analytic AN1473 for SaaS. It looks for workflows triggered from forks with suspicious pull request metadata, insecure triggers such as pull_request_target, unusual secret use, and artifacts or network destinations that suggest exfiltration. The data components and log sources it names:
| ATT&CK data component | What AN1473 looks for | Practical source |
|---|---|---|
| DC0069 Cloud Service Modification | Workflow triggered via pull_request_target from a forked repository | GitHub organisation audit log, workflow run events |
| DC0070 Cloud Service Metadata | CI/CD secret accessed or exported | Audit log secret events, cloud provider logs for the identities the secrets map to |
| DC0025 Cloud Storage Access | Artifact containing base64 or encoded payloads or URLs | Artifact uploads, artifact contents scanned after run |
| DC0059 File Metadata | New or modified build configuration in a pull request (.github/workflows/*.yml, .gitlab-ci.yml, build.gradle, pom.xml) | Repository push and pull request events |
| DC0064 Command Execution | Commit message or branch name containing encoded strings or payload indicators | Pull request metadata, runner step logs |
The analytic exposes tunable elements: a time window, user context, a trigger type allowlist, an artifact entropy threshold and a secret access rate threshold. Set the allowlist from an inventory of the triggers you actually use, and baseline secret access per workflow before alerting on rate.
NHI Mgmt Group recommends adding three signals that sit outside the CI system and catch the cases above after the pipeline has already run:
- Token use from the wrong place: a publishing or cloud credential that normally appears only from runner IP ranges or an OIDC federation used from elsewhere.
- Unexpected releases and tag moves: releases, package versions or force-pushed tags outside the release workflow, the pattern in the Trivy case.
- New runners and new workflows: self-hosted runner registrations and new workflow files not made through reviewed pull requests, the patterns Shai-Hulud used.
For the general method of turning ATT&CK analytics into rules, see the ATT&CK Detection Strategies and Analytics Guide.
Mitigations
ATT&CK v20 maps two mitigations to T1677. Under each, the specific settings come from GitHub's secure use reference and GitHub Security Lab guidance; equivalent controls exist in other CI systems.
M1054 Software Configuration
- Do not run unreviewed code with secrets: where unreviewed code must run, use isolated nodes without access to secrets, as ATT&CK states.
- Avoid
pull_request_target: usepull_requestfor building and testing fork code with a read-only token. Where a privileged step is needed, GitHub Security Lab recommends splitting it into aworkflow_runworkflow and treating anything the first workflow produced as untrusted. Label gating is racy and only a temporary measure. - Treat inputs as data: never interpolate attacker-controllable fields such as
github.head_ref, pull request titles, issue bodies or commit messages into arunscript. Pass them through an intermediate environment variable and quote it. - Self-hosted runners: GitHub's guidance is that self-hosted runners should almost never be used for public repositories. For private repositories, use ephemeral runners in separate runner groups.
- Pin third-party actions to a full-length commit SHA, or a lockfile where adopted. See How to Pin GitHub Actions to Commit SHAs.
- Block install scripts in jobs that hold secrets, for example
npm ci --ignore-scripts, to break the APT38 pattern.
M1018 User Account Management
- Minimum workflow token: set the default
GITHUB_TOKENpermission to read and grant per job with thepermissionskey. - Fewer writers: limit who can write to repositories and who can change workflow files; add
.github/workflowsto CODEOWNERS. - Short-lived credentials: replace stored cloud keys with OIDC federation (see the GitHub Actions OIDC Federation Hardening Guide) and publishing tokens with trusted publishing where the registry supports it.
- Gate secrets by environment: put release secrets in an environment with required reviewers so a poisoned job on another branch cannot read them.
How to put it in place
- Inventory triggers and runners. List every workflow using
pull_request_target,workflow_runorissue_comment, every self-hosted runner group and which repositories can use it. - Inventory secrets per job. Map each secret to the jobs that can read it and the identity it represents. Flag secrets reachable by any job that runs fork code or installs third-party packages.
- Fix the high-risk patterns first. Remove checkouts of pull request heads under privileged triggers, move inline
${{ }}expressions into environment variables, and take self-hosted runners off public repositories. Change one repository first and confirm builds still pass before rolling the pattern out with a shared reusable workflow. - Reduce token scope. Set organisation default token permissions to read, then raise per job. Roll back by restoring a job's explicit permissions if a build fails, not by reverting the default.
- Move to federation. Replace long-lived cloud keys with OIDC; keep the old key disabled, not deleted, for one release cycle so you can roll back.
- Turn on detection. Stream the SCM audit log to your SIEM and build the DET0533 correlations plus the three external signals above.
- Prepare the response. Write down which tokens to revoke together and how to verify old credentials fail. See the CI/CD Pipeline Compromise Playbook.
How to test
- Static review: scan workflow files for privileged triggers combined with pull request checkouts, for
${{ github.event.* }}insiderunblocks and for unpinned actions. - Harmless fork test: in an authorised test repository, open a fork pull request whose build step only writes a marker value and attempts to read a canary secret. Confirm the secret is absent and an alert fires on the fork-triggered run.
- Canary credential: place a honeytoken in a secret that no legitimate job uses and alert on any use of it.
- Rotation drill: rotate all pipeline credentials for one repository and verify every old one fails.
Decision guidance
- Public open source projects: assume every pull request is hostile. GitHub-hosted runners only, no secrets on fork builds, approval for outside collaborators, and publishing from a separate protected workflow.
- Private repositories in large organisations: the threat is a compromised developer or bot account. Prioritise code owner review on CI files, environment protection on release secrets and token use monitoring.
- Small teams: start with read-only default tokens, SHA pinning and OIDC. These are configuration changes with little operational cost.
- Self-hosted versus hosted runners: self-hosted runners reach your network, which ATT&CK names as a route to code execution inside the organisation. Use them only with ephemeral instances, private repositories and dedicated runner groups.
Measurement
- Number of workflows combining a privileged trigger with a pull request checkout (target: zero).
- Percentage of third-party actions pinned to a commit SHA.
- Percentage of cloud access from pipelines using OIDC rather than stored keys.
- Number of secrets readable by jobs that run untrusted code or install packages.
- Mean time from a credential exposure alert to verified revocation of every related token.
Common failure patterns
- Fixing the trigger, keeping the token: a workflow moved off
pull_request_targetstill runs with a write token and broad secrets because default permissions were never lowered. - Partial rotation: rotating the token you know about and missing a bot or service account, which the Trivy timeline shows reopened the door.
- Trusting artifacts: a privileged
workflow_runjob executes a binary or script built from the pull request. - Movable tags: referencing actions by version tag, which an attacker with publishing rights can repoint, as in the tj-actions/changed-files compromise.
- Persistent self-hosted runners: long-lived runners that keep state between jobs, so one poisoned job leaves something behind for the next.
Framework mapping
| Framework | Items | Relevance |
|---|---|---|
| MITRE ATT&CK v20 | T1677; M1054, M1018; DET0533; related T1195.002 Compromise Software Supply Chain | Technique, mitigations and detection strategy |
| NIST SP 800-53 Rev 5 | CM-3, CM-5, SA-10, AC-6, IA-5, SI-7, AU-6 | Change control on CI files, developer configuration management, least privilege, credential management, integrity, log review |
| NIST CSF 2.0 | PR.PS-01, PR.PS-06, PR.AA-05, DE.CM-09 | Configuration management, secure development, least privilege, monitoring of runtime environments |
| CIS Controls v8.1 | Control 16 Application Software Security | Secure build and release processes |
| ISO/IEC 27001:2022 Annex A | 8.25 Secure development life cycle, 8.31 Separation of development, test and production environments, 8.32 Change management | Governance of pipelines and build environments |
| OWASP Top 10 CI/CD Security Risks | CICD-SEC-4 | The same risk in OWASP terms |
Practitioner checklist
- No workflow combines
pull_request_targetorworkflow_runwith a checkout or execution of pull request code. - No attacker-controllable context field is interpolated into a
runscript. - Default
GITHUB_TOKENpermission is read; jobs request more explicitly. - Third-party actions are pinned to full commit SHAs.
- Self-hosted runners are ephemeral, grouped and never used by public repositories.
- Release and publishing secrets sit behind protected environments; cloud access uses OIDC.
- CI files are under code owner review and branch protection.
- SCM audit logs feed the SIEM with DET0533 correlations, token use monitoring and runner registration alerts.
- A tested runbook revokes every pipeline credential together and verifies the old ones fail.
Standards and references
- MITRE ATT&CK: T1677 Poisoned Pipeline Execution
- MITRE ATT&CK: M1054 Software Configuration
- OWASP: Top 10 CI/CD Security Risks, CICD-SEC-4 Poisoned Pipeline Execution
- GitHub Docs: Secure use reference for GitHub Actions
- GitHub Security Lab: Keeping your GitHub Actions and workflows secure, preventing pwn requests
- GitHub Security Lab: Keeping your GitHub Actions and workflows secure, untrusted input
- Wiz: Ultralytics AI library hacked via GitHub for cryptomining
- John Stawinski: Playing with fire, how we executed a critical supply chain attack on PyTorch
Related NHI Mgmt Group resources: GitHub and GitHub Actions Security Guide, Self-hosted CI/CD Runners and Build Agents, ATT&CK Execution Tactic Guide, CI/CD Security Guide.