Poisoned Pipeline Execution (T1677) is an Execution technique aimed at continuous integration and continuous delivery (CI/CD). Adversaries inject malicious code into the build process so that the pipeline runs it. MITRE lists SaaS as the platform.
Pipelines are attractive because they sit in a privileged spot. They hold credentials for registries and clouds, they run code automatically, and what they build is trusted downstream. MITRE says that by poisoning a pipeline, adversaries may reach credentials, move laterally to more hosts, or insert malicious components that are shipped further down the pipeline, which connects to Supply Chain Compromise.
How attackers use it
MITRE describes three scenarios.
Direct Pipeline Execution. The adversary can edit the CI configuration file itself, for example gitlab-ci.yml in GitLab. They might add a command that sends credentials used in the build to a remote server, or exports them as a workflow artifact.
Indirect Pipeline Execution. The adversary cannot change the CI file, but can change files that it references. These include makefiles, scripts, unit tests and linters. The pipeline runs them as part of the normal build.
Public Pipeline Execution. The adversary has no access to the repository. Instead, they open a malicious pull request from a fork that triggers part of the pipeline. MITRE uses GitHub Actions as the example: the pull_request_target trigger lets workflows from forked repositories access secrets. If that trigger is combined with an explicit checkout of the pull request and somewhere for the adversary to insert code, such as an npm build command, pipeline credentials can leak. Pull requests may also carry malicious input such as a crafted branch name, if the pipeline treats those inputs as trusted. And if a pipeline uses a self-hosted runner, an adversary may be able to run arbitrary code on a host inside the organization's network.
The technique page also records real-world patterns where npm packages with fake runtime dependencies triggered malicious payloads on developer machines or CI/CD systems during install or update, and where stolen tokens or GitHub Actions workflows were used to spread across packages.
How to detect it
MITRE's detection strategy for SaaS CI/CD workflows looks for a cluster of signals that, together, are convincing:
- CI/CD workflow runs that originate from forked repositories.
- Pull request metadata or commit messages with suspicious patterns, such as encoded payloads.
- Use of risky triggers such as
pull_request_target, or excessive API use of CI/CD secrets. - Unusual artifact generation, or secret exfiltration through encoded data or external destination URLs.
In practice this means collecting your CI platform audit logs and workflow run logs, and asking: which workflows touched secrets, who triggered them, and from which branch or fork? Alert when a workflow file changes in a pull request, and when a job makes a network call to a destination it has never used before.
How to reduce the risk
MITRE lists two mitigations.
- User Account Management. Give pipelines only the permissions they need. Limit the number of users with write access to internal repositories to those who need it.
- Software Configuration. Avoid letting pipelines run unreviewed code where possible. Where it is necessary, run those pipelines on isolated nodes with no access to secrets. In GitHub specifically, avoid the
pull_request_targettrigger where you can, do not treat user-controlled inputs such as branch names as trusted, and do not use self-hosted runners on public repositories.
Add to that a few habits that follow from the same logic: pin third-party actions and dependencies, require review on changes to workflow files, and keep secrets scoped to the jobs that need them.
Check your own environment
A review you can do in an afternoon:
- Search your workflow files for
pull_request_targetand check each use. - Find every self-hosted runner and note whether it serves a public repository.
- List which secrets each pipeline can read. Remove any that a job does not use.
- Check who has write access to repositories that build and deploy.
- Look for any step that interpolates a branch name, title or other user input straight into a shell command.
- Confirm that changes to CI configuration need a reviewer who is not the author.
On a Linux or macOS shell, this read-only search finds the trigger in a checked-out repository:
grep -rn "pull_request_target" .github/workflows/
Dependencies are the other half of the story. A pipeline that installs a poisoned package runs the attacker's code just as surely as one with a poisoned config file. Safeguard offers a free in-browser dependency scan at https://gold.safeguard.sh/scan. It needs no login, the file never leaves your machine, and it covers up to 100 dependencies. It is a quick way to inspect a lockfile before you trust a build with it.
Click through it at your own pace, or open it full screen.
Quick check
Why is pull_request_target risky?
It lets workflows triggered from forked repositories access secrets. Combined with an explicit checkout of the pull request and a place to insert code, it can leak pipeline credentials to someone with no repository access.
What is the difference between direct and indirect pipeline execution?
In direct execution the adversary edits the CI configuration file itself. In indirect execution they change files that the configuration references, such as makefiles, scripts, tests or linters.
Where should pipelines that must run unreviewed code execute?
On isolated nodes without access to secrets, according to MITRE.
Sources
- MITRE ATT&CK, Poisoned Pipeline Execution (T1677): https://attack.mitre.org/techniques/T1677