OS Command Injection in Cloud Infrastructure Automation Scripts

Unsanitized inputs in infrastructure automation can compromise production systems at scale.

· 9 min read
Cover illustration for “OS Command Injection in Cloud Infrastructure Automation Scripts”
Injection Flaws · October 17, 2026 · 9 min read · 1,944 words

OS command injection in cloud infrastructure automation scripts is both a coding mistake and an architectural risk. A single unsanitized input can reach a trusted execution path that has enough power to compromise production infrastructure. IaC and CI/CD code should be tested as privileged software, untrusted data should be kept out of shell instructions, and automation's reach should be limited when those boundaries fail.

Why OS command injection dominates the exploited-vulnerability landscape in 2026

OS command injection happens when an application builds an operating system command by joining instructions with text that a user controls. Without a boundary between data and shell syntax, characters including ;, |, &&, backticks, and $() can turn that text into instructions. Those instructions run with the application's privileges. Command separator injection appends commands to a fixed program, while full command control lets the attacker choose the program itself.

MITRE's 2025 KEV ranking placed CWE-78 first on the KEV Top 10, ahead of every memory-corruption class. That ranking concerns the weaknesses most consistently behind entries in CISA's Known Exploited Vulnerabilities catalogue. It is a signal of exploitation in production. Command injection is not a legacy edge case.

The shell still interprets instructions, but the position of that shell determines the damage. A command becomes far more dangerous when its execution context can control other systems.

How cloud automation scripts moved command injection from the application tier into the control plane

Terraform, Ansible, and CloudFormation provision and manage infrastructure. Their scripts therefore occupy a position of authority over the systems they create. Input validation may happen before encoding changes, argument expansion, shell parsing, and command assembly. By execution time, data has passed through layers that can reinterpret it. The gap between what was validated and what runs is where injection takes hold.

Terraform makes that authority concrete. Its local-exec and remote-exec provisioners execute arbitrary shell commands as a designed feature. A malicious module using local-exec, or an HTTP data source, can exfiltrate state data or run arbitrary commands when a developer runs apply. If a Terraform Cloud workspace is compromised, its credentials can let an attacker compromise production infrastructure by destroying databases, taking state files that contain secrets, and deploying infrastructure they control into production VPCs.

OWASP MCP Top 10 2025 identifies agents that construct executable operations from prompts, retrieved context, or third-party data without proper validation. Instructions hidden in documents can become malicious commands that look as though they have valid syntax. Autonomous execution with elevated privileges turns that failure into a path to data exfiltration, lateral movement, or complete system compromise.

Where injection enters IaC and CI/CD pipelines in practice

Terraform's provisioner command arguments are the first surface to examine. Any variable interpolated into a local-exec or remote-exec command can become an injection point. HTTP data sources give attackers another way in when a module fetches content from outside and passes it into a command string. The dangerous input may thus sit outside the repository even though the command constructor can be seen inside it.

CI/CD workflows expose another route through ordinary repository metadata. GitHub Actions context expressions such as ${{...}} can pass the text of PR titles, branch names, commit messages, and issue bodies straight into shell steps. Direct interpolation treats that content as part of the command being assembled. A Semgrep scan of a Cal.com fork found four GitHub Actions workflow files with shell injection findings from this pattern.

Scripts that do not specify absolute paths or scrub inherited variables can allow $PATH redirection to attacker-controlled binaries. Second-order injection stores a payload in a config file, database record, or PR description before a later step retrieves and executes it. MCP tools add similar boundaries when they pass agent output to exec(), system(), eval(), or subprocess.run(shell=True), or execute commands generated on another server without re-validation.

What real-world exploitation of these pipelines looks like

The tj-actions/changed-files compromise, CVE-2025-30066, showed how a trusted dependency can turn a workflow into an attack path. The action's version tags were changed to point to a malicious commit. Workflows using those tags executed a payload that dumped CI/CD secrets from runner memory into publicly available workflow logs. The attack could work without changes to the victim's workflow definition files and without external network exfiltration. A shared GitHub Action carried the payload across thousands of repositories.

The ChainDrop campaign in August 2026 demonstrated the reach of compromised package publishing. A stolen maintainer credential was used to seed a self-propagating worm that poisoned more than 400 npm packages across unrelated publishers. The supply chain above the pipeline was itself a route into trusted execution. These incidents demonstrate the consequences of compromised automation dependencies, rather than proving that every compromise starts with a shell separator.

A trusted execution path can bring code that an attacker controls into an environment that holds powerful credentials. Monitoring at the application layer may miss that path. The pipeline is an independent attack target.

Why traditional defenses and checkbox pen tests fail to catch this class of vulnerability

Perimeter defenses look in the wrong place for much of this activity. WAF rules and signature-based detection check for suspicious strings at the network edge, but pipeline injection can take place entirely inside trusted execution paths. Static filters also fail when they recognize only known bad characters. Even PHP's escapeshellcmd() can stop separator injection and still let argument injection through with flags such as --output-document=.

Testing scope creates a second blind spot. An engagement drawn around web endpoints and external IP ranges leaves IaC files, provisioner blocks, workflow definitions, and module dependencies outside the assessment. Treating those executable artifacts as configuration hides their execution role. A report may describe the application accurately while saying little about the automation that can replace it.

Annual testing leaves new workflow steps, cloud misconfigurations, and third-party integrations outside the assessment between engagements. Fixing a handful of findings in a PDF does not account for the next PR merge. A test's value depends on whether it follows data into privileged execution.

What testing command injection in IaC and CI/CD contexts actually requires

Testing these execution paths needs whitebox access because the paths are in source artifacts. Testers need to examine IaC files, provisioner blocks, workflow YAML, build scripts, and module dependencies. They must follow externally controlled values through storage, transformation, and command assembly. A second-order payload in a PR description cannot be understood by examining only its submission point. The later step that consumes it is part of the same test.

Static review must therefore connect each input to the operation that executes it. That includes variable-bearing Terraform provisioners, module dependencies that may be tampered with or typosquatted, and GitHub Actions context expressions reaching shell steps. Dynamic testing must then establish whether the suspected path is exploitable with a working proof of concept. An unverified possibility is not the same evidence as a demonstrated exploit. Retesting must establish that remediation has not merely replaced separator injection with argument injection.

Human review matters because interpreter chains do not behave like isolated pattern matches. Multi-layer injection, CAPEC-267, can survive one interpreter and trigger in another. Encoding ambiguity, argument expansion, and shell parsing require the tester to model the complete execution context. Automated tools can support that work, but checking each layer separately can miss the connection.

Continuous pull-request scanning complements this deeper assessment. It can flag new provisioners, direct context-expression interpolation, and new shell=True calls when they enter the codebase.

Defensive controls that reduce injection risk in automation pipelines

The primary control is to remove shell invocation where possible. Structured argument lists such as subprocess.run(['ls', 'logs']) pass arguments without invoking a shell, so shell metacharacters are not interpreted. Language-native APIs can replace shell wrappers where the task supports them. This changes the execution boundary rather than trying to recognize every malicious string. Argument validation still matters because even when the program is fixed, it can take dangerous options.

At tool boundaries, allowlists should set out which commands, arguments, and file path patterns are allowed. Normalize and validate paths before they reach command construction. OWASP MCP05 recommends validating agent output against a schema and rejecting compound commands, redirection operators, wildcards, and command substitution patterns. It also recommends human approval for destructive, privileged, or system-modifying operations. Approval is one defense that must work alongside safe execution.

GitHub Actions workflows should pass context expressions through an intermediate environment variable rather than interpolate them directly into shell instructions. Actions should be pinned to a full commit SHA because tags are mutable. Terraform controls include auditing all local-exec and remote-exec blocks, avoiding variable-derived command content, and preferring resource-based management where the provider supports it. Module sources should be pinned to verified registry versions or internal mirrors, with checksums validated and unrecognized modules treated as untrusted.

Least privilege puts a limit on how much damage can still be done. Runners and IaC environments should receive only the IAM permissions and secrets needed for their work. Sandboxed execution through containers, micro-VMs, or jailed users provides an isolation boundary. These controls need testing, because a configured boundary is not proof that the complete execution chain respects it.

How compliance frameworks treat command injection testing in CI/CD and IaC environments

PCI DSS v4.0.1 expressly requires penetration testing under Requirement 11.4 every year and after significant changes to infrastructure. It also requires segmentation testing every six months for service providers. A major IaC refactor or pipeline architecture change is a significant change that triggers retesting.

SOC 2 Type II has no explicit rule that requires penetration testing, but most auditors expect it as strong evidence for the Trust Services Criteria, especially CC4.1 monitoring activities. A web-only scope that omits IaC and CI/CD may be challenged as incomplete evidence. ISO 27001:2022 Annex A 8.29 addresses security testing in development and acceptance. Whitebox review of infrastructure and pipeline code is a direct way to carry out that security testing.

HHS proposed a HIPAA Security Rule update in January 2025 that would make annual penetration testing explicit. As of September 2026, it remains proposed. Covered entities and business associates should treat that proposal as a signal of auditor expectation.

These frameworks have different rules for testing. Their practical connection is the need for evidence that fits the systems under assessment. Auditors may reject scanner output repackaged as a penetration test and require retesting.

What to ask a penetration testing vendor before letting them near your IaC and pipelines

Start by asking which source artifacts the testers will examine. The answer should address Terraform modules, provisioner blocks, module registry dependencies, and CI workflow definitions alongside endpoints. Ask how testers follow context expressions into shell steps and track second-order payloads from where they are stored to where they run. A scope limited to IP ranges or application URLs does not establish infrastructure automation coverage.

Then ask what proves a finding and what proves a fix. The vendor should explain its use of working proof-of-concept exploits and its retesting of argument injection after separator injection is removed. Reports dominated by scanner CVSS scores or unverified “potential” issues are warning signs. So are absent retests or retesting priced separately at full engagement cost.

Finally, ask who performs the review, what certifications those testers hold, and who signs off on findings. Ask whether continuous PR scanning catches new paths as they are added and whether there is a dedicated channel to talk with the testers during and after the engagement. Rigorous coverage includes whitebox access to source repositories, cloud configurations, and IaC files, with human review by certified penetration testers, exploit evidence, on-demand retests, and an audit-ready attestation letter. The standard is not a polished report but a demonstrated understanding of what trusted automation can be made to do.

Sources

  1. What Is Command Injection? How Attackers Exploit It
  2. What Is OS Command Injection? Exploitation, Impact & Defense
  3. MCP05:2025
  4. The Comprehensive Guide to Prompt Injection Attacks in 2026
  5. A Defect Taxonomy for Infrastructure as Code: A Replication Study
Filed underInjection Flaws

More in Injection Flaws