Free sample lesson · Penetration Testing for Azure DevOps

Pipeline Attacks

How easily can an attacker hijack your automated build steps?

Lesson 1 of the Wondering course Penetration Testing for Azure DevOps

The Pipeline Is Code

A pipeline definition is executable code that runs with production credentials.

In Azure DevOps, the YAML file lives in the same repository the build serves. Whoever can influence that file can influence the build agent.

This is why pipeline attacks shift your target from the application to the delivery mechanism itself.

Trust flows from the repository into the agent, then into the artifact, then into production.

Nested trust scope
  • Repository: Holds the YAML file that defines every build step.
  • Build agent: Executes the definition using production credentials.
  • Artifact: Carries whatever the agent produced.
  • Production: Consumes the artifact and inherits its trust.

Each layer inherits the trust of the layer outside it

Where Attacker Input Enters

Pipeline definitions accept untrusted input more often than teams expect.

Pull request triggers run attacker-authored YAML. Variables and parameters carry attacker-supplied strings. Third party tasks and container images pull attacker-controllable code.

Each entry point maps to a different audit question.

Injection surfaces in a pipeline

Treat every one of these as an injection surface, exactly as you would treat a query string in a web application test.

Hijacking a Build Step

The classic attack is straightforward. An attacker opens a pull request that edits the pipeline file, adding a script step.

The trigger fires. The agent checks out the fork, reads the modified definition, and runs the new step with the pipeline service connection still attached.

Nothing is exploited in the usual sense. The pipeline simply does what its definition says.

Anatomy of a hijacked step
  • Open PR: The attacker opens a pull request that edits the pipeline file.
  • Add step: A new script step is inserted into the definition.
  • Trigger fires: The agent checks out the fork and reads the modified definition.
  • Step runs: The script executes with the pipeline service connection attached.

Injection Without Editing YAML

Editing the definition is loud and often blocked. Expression injection is quieter.

When a pipeline interpolates a branch name, PR title, or commit message directly into a shell step, the attacker controls part of the command line.

The definition never changes. The attacker changes the data the definition reads.

This is the pipeline equivalent of unparameterized input reaching an interpreter.

Two routes into the agent
  • YAML edit
  • Expression injection

Auditing the Definition

Your audit follows a decision path rather than a checklist.

Start by asking whether the trigger accepts forks. If it does, ask whether secrets are still exposed to that run. If they are, you have a finding worth escalating.

If secrets are scoped away, move on to interpolation. Any untrusted value reaching a shell step is the next finding.

Audit decision path
  • Read definition
  • Trigger accepts forks?
  • Secrets exposed?
  • Untrusted interpolation?
  • Escalate finding
  • No finding here

A Question Worth Sitting With

Pipeline attacks rarely break a control. They use a control exactly as designed.

The agent was built to run whatever the definition says, with the credentials the definition requests.

So the vulnerability is not a bug in the runner.

If nothing is exploited, what is the flaw?

Hardening What You Found

Securing pipeline definitions stops malicious code injection at the source.

Require approval before fork-triggered runs execute. Scope secrets so they never reach an untrusted run.

Pin third party tasks and container images to immutable versions.

Pass untrusted values as environment variables rather than interpolating them into shell steps.

Controls and what they close
  • Fork approval
  • Secret scoping
  • Version pinning
  • Env var passing

Check yourself

In Azure DevOps, a pipeline definition is executable code that can influence the build agent.

  • True
  • False

Answer: True

This is true because the agent runs the instructions in the pipeline definition. The definition therefore deserves code-level security review, especially because runs may have production credentials.

Pipeline input surfaces include _____ for attacker-authored YAML, _____ for supplied strings, and _____ for attacker-controllable code.

Answer: Pipeline input surfaces include pull request triggers for attacker-authored YAML, variables for supplied strings, and container images for attacker-controllable code.

Each entry point introduces a different kind of untrusted input. Pull request triggers can run modified definitions, variables carry attacker-controlled data, and container images can supply executable code.

A fork pull request adds a script step to an Azure DevOps YAML pipeline. The trigger runs the modified definition with its service connection attached. What is the main security issue?

  • The agent executes attacker-controlled instructions with pipeline credentials
  • The application has a vulnerable input-validation routine
  • The artifact is automatically encrypted with the wrong key
  • The repository loses its branch history

Answer: The agent executes attacker-controlled instructions with pipeline credentials

The agent is doing exactly what the modified definition requests, but the attacker has influenced code that runs with the pipeline’s attached credentials. This is a pipeline attack even without exploiting a runner bug.

Keep going

This is lesson 1 of 8 in Penetration Testing for Azure DevOps. Next up:

  1. Secret Exposure
  2. Access Audits
  3. Infrastructure Flaws
  4. Static Analysis
  5. Dynamic Scanning

Start this course free

Wondering turns any topic into short lessons you can read, watch, or listen to, with practice questions and an AI tutor.