Security teams should treat every repository-controlled field as untrusted and avoid interpolating it directly into shell commands. Put user input into environment variables, quote it carefully, and prefer prebuilt actions over custom run steps when possible. The safest pattern is to minimize script logic in the pipeline and assume attacker-controlled text can become executable code if it reaches a shell.
Why repository inputs become a command-injection problem in CI/CD
Issue titles, pull request text, branch names, labels, commit messages, and similar repository-controlled fields are not metadata you can safely trust in a pipeline. The danger starts when a workflow turns that text into shell syntax, so the real question is not whether the input looks harmless, but whether it can still alter command structure once it reaches a runner.
That risk is easiest to miss in “small” helper steps, such as echoing text into a script, building filenames, or assembling flags from variables. A pipeline becomes vulnerable when untrusted text is parsed by a shell, templating layer, or script interpreter instead of being treated as data. At that point, a normal-looking field can change the command being executed.
This is why security teams should think in terms of trust boundaries, not just source systems. A repository field supplied by a contributor, fork, bot, or automation can be attacker-controlled even when it originates inside the development workflow. When that text is interpolated into a shell command, the pipeline effectively grants execution authority to content that should only have been read.
How to structure pipeline steps so text stays data
The safest pattern is to keep script logic as small and explicit as possible. Put repository text into environment variables or other data-only channels, then consume it as a value rather than embedding it inside a command line. When a shell must be used, prefer fixed commands with fixed arguments and handle untrusted values with strict quoting and validation rules that match the runtime, not the human reader’s intent.
Prebuilt actions and maintained pipeline components are usually safer than custom run steps because they reduce the amount of shell code you expose to untrusted input. That does not make them magically safe, but it does lower the number of places where interpolation mistakes can happen. For high-risk jobs, isolate the step that handles repository text from the step that performs privileged operations.
It also helps to distinguish control flow from content. If the workflow only needs to compare a title, route a ticket, or name an artifact, keep that logic in a parser or condition block rather than in a shell string. If the job must transform text, use APIs or scripting interfaces that pass arguments structurally instead of constructing a command string from fragments.
What usually goes wrong in real pipelines
Most command-injection failures in CI/CD come from convenience coding: concatenating variables into bash, assuming repository text will not contain metacharacters, or using a “temporary” debug command that later becomes permanent. Another common failure is treating pull request text differently from code, even though both can influence workflow execution when the pipeline reads them.
Attackers look for exactly these weak spots because CI/CD systems often have broad access to source, build artifacts, package registries, signing material, or deployment targets. Shai Hulud npm malware campaign and Reviewdog GitHub Action supply chain attack both illustrate how trust in pipeline components can turn routine automation into secret exposure and wider compromise. The lesson is not only that dependencies can be malicious, but that a build environment with too much authority magnifies any input-handling mistake.
Another recurring failure mode is assuming “internal” inputs are safe while external inputs are dangerous. In practice, repository-controlled fields often become attacker-controlled through forks, malicious pull requests, compromised maintainer accounts, or injected content from issue automation. If a workflow interprets text, it needs the same defensive handling regardless of whether the text came from a human comment, a bot, or a repository event payload.
Standards & Framework Alignment
This section maps relevant standards and security frameworks to the operational risks and controls described in this guidance.
OWASP Non-Human Identity Top 10 addresses the attack and risk surface, while OWASP ASVS, NIST SP 800-53 Rev 5 and CIS Controls v8 set the governance and control requirements practitioners need to meet.
| Framework | Control / Reference | Relevance |
|---|---|---|
| OWASP ASVS | V8 — Authorization | Pipeline command injection is an unsafe authorization boundary for executed actions. |
| Recommendation — Keep untrusted repository input out of executable command paths and enforce fixed-argument execution. | ||
| NIST SP 800-53 Rev 5 | SI-10 — Information Input Validation | Repository-controlled fields must be validated before any execution or command construction. |
| AC-6 — Least Privilege | CI/CD command injection is far more damaging when runners have excessive access. | |
| Recommendation — Validate and constrain pipeline inputs before they reach scripts or command interpreters. Reduce runner and job privileges so injected commands have minimal blast radius. | ||
| CIS Controls v8 | CIS-5 — Account Management | Pipeline accounts and automation identities must be tightly controlled to limit abuse after injection. |
| Recommendation — Restrict automation accounts and remove unnecessary access from build and release identities. | ||
| OWASP Non-Human Identity Top 10 | NHI-04 — Insecure Authentication | CI/CD command abuse often pairs with weak machine authentication and token handling. |
| Recommendation — Use strong, short-lived machine authentication so compromised workflow input cannot pivot into persistent access. | ||
Practitioner Guidance
What to verify: Review every workflow step that consumes repository text and confirm whether the value is ever embedded directly into a shell, command substitution, eval-like construct, or generated script fragment. If yes, treat that step as high risk until the input path is rewritten to keep the value data-only.
What to prioritise: Fix the steps that can reach privileged actions first, especially anything that can read secrets, publish artifacts, trigger deployments, or modify repository state. A harmless-looking injection in a low-privilege lint step is still worth fixing, but a build or release step has a much larger blast radius.
Common mistake: Teams often over-focus on escaping a single variable while leaving the overall pattern intact. The better judgement is to reduce shell exposure, use fixed arguments, and remove the need to compose commands from repository text wherever possible.
What good looks like: The workflow can accept untrusted issue, pull request, and repository text without changing command structure, and privileged steps remain separated from text-processing steps. The strongest indicator is not “we escaped it once,” but “untrusted content never becomes executable syntax in the first place.”
Practitioner takeaway: Command injection prevention in CI/CD is mostly an architecture problem, not a quoting trick, so teams should design pipelines so untrusted repository text is consumed as data and never as shell syntax.
Related resources from NHI Mgmt Group
- How should security teams prevent command injection in CI/CD pipelines that execute debugging commands with untrusted input?
- How should security teams prevent SQL injection in CI/CD pipelines without slowing delivery?
- How should security teams prevent XML injection in CI/CD pipelines and application stacks?
- How should security teams prevent 403 errors in CI/CD pipelines?
Deepen Your Knowledge
Reviewed and updated by the NHIMG editorial team on September 29, 2026.
NHI Mgmt Group — the #1 independent authority on Non-Human Identity, IAM, and Agentic AI security. nhimg.org