Back to Blog
high SEVERITY6 min read

How github-actions-mutable-action-tag happens in GitHub Actions YAML and how to fix it

A GitHub Actions workflow in `templates/devto/devto-readme.yml` referenced `actions/checkout@v4` and `actions/setup-node@v4` using mutable version tags instead of pinned commit SHAs. This pattern enables supply-chain attacks where a compromised action owner silently repoints a tag to malicious code. The fix pins both actions to their full 40-character commit SHAs while preserving version comments for maintainability.

O
By Orbis AppSec
Published August 19, 2026Reviewed August 19, 2026

Answer Summary

A mutable action tag vulnerability (CWE-829) occurs in GitHub Actions YAML workflows when actions are referenced by version tags (e.g., `@v4`) instead of immutable commit SHAs. This enables supply-chain attacks if the tag is repointed to malicious code. The fix is to pin each action to its full 40-character commit SHA, such as changing `actions/checkout@v4` to `actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683`.

Vulnerability at a Glance

cweCWE-829 (Inclusion of Functionality from Untrusted Control Sphere)
fixPin `actions/checkout` and `actions/setup-node` to full 40-character commit SHAs
riskSupply-chain compromise via silently repointed action tags
languageYAML (GitHub Actions)
root causeUsing mutable tag references (`@v4`) instead of immutable commit SHA pins
vulnerabilityGitHub Actions Mutable Action Tag (Supply-Chain)

Introduction

In the templates/devto/devto-readme.yml workflow file, two GitHub Actions steps at lines 18 and 21 referenced third-party actions using mutable version tags — actions/checkout@v4 and actions/setup-node@v4. While these tags point to legitimate releases today, they represent a ticking time bomb: any compromise of the upstream action repository could silently redirect these tags to malicious code, executing it in your CI/CD environment with full repository access.

This isn't theoretical. In 2024, both the trivy-action and kics-github-action were compromised through exactly this mechanism — attackers repointed existing tags to inject credential-stealing code into thousands of downstream workflows.

The workflow in question sets up a Node.js 20 environment for what appears to be a Dev.to README generation pipeline. The checkout step grants access to repository contents, and the setup-node step configures the runtime. Both are high-privilege operations that, if compromised, could exfiltrate secrets, modify source code, or pivot into production environments.

The Vulnerability Explained

What Are Mutable Tags?

In Git, tags are simply pointers to commits. Unlike commit SHAs (which are cryptographic hashes of the content), tags can be deleted and recreated pointing to a different commit. GitHub Actions uses Git references to resolve which version of an action to run.

When your workflow contains:

steps:
  - name: Checkout repository
    uses: actions/checkout@v4

  - name: Setup Node.js
    uses: actions/setup-node@v4
    with:
      node-version: "20"

GitHub resolves v4 to whatever commit that tag currently points to at runtime. If an attacker gains write access to the actions/checkout repository (or any third-party action you use), they can:

  1. Force-push the v4 tag to a new commit containing malicious code
  2. Every workflow using @v4 immediately starts executing the attacker's code
  3. No notification is sent to downstream consumers

The Attack Scenario for This Workflow

Consider this specific attack against templates/devto/devto-readme.yml:

  1. An attacker compromises the actions/setup-node repository (or a less-maintained action in your workflow)
  2. They repoint the v4 tag to a commit that, in addition to setting up Node.js, also:
    - Reads all repository secrets (${{ secrets.* }})
    - Exfiltrates them to an attacker-controlled server
    - Injects a backdoor into the generated README content
  3. Since this is a private Node.js application, the attacker now has access to any deployment credentials, API keys, or tokens stored as GitHub secrets

The actions/checkout step is particularly dangerous because it runs first and has access to GITHUB_TOKEN, which by default can push code back to the repository.

Why Version Tags Provide Zero Security Guarantees

A version tag like @v4 provides:
- ❌ No integrity verification
- ❌ No immutability guarantee
- ❌ No audit trail of changes
- ❌ No protection against upstream compromise

A commit SHA like @11bd71901bbe5b1630ceea73d27597364c9af683 provides:
- ✅ Cryptographic integrity (SHA-1 hash of exact content)
- ✅ Immutability (cannot be changed without changing the hash)
- ✅ Deterministic builds (same code runs every time)
- ✅ Auditability (you can verify exactly what code will execute)

The Fix

The fix pins both action references to their exact commit SHAs while preserving version comments for human readability:

Before (Vulnerable)

steps:
  - name: Checkout repository
    uses: actions/checkout@v4

  - name: Setup Node.js
    uses: actions/setup-node@v4
    with:
      node-version: "20"

After (Hardened)

steps:
  - name: Checkout repository
    uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

  - name: Setup Node.js
    uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
    with:
      node-version: "20"

What Changed and Why

Action Before After (SHA) Version
actions/checkout @v4 @11bd71901bbe5b1630ceea73d27597364c9af683 v4.2.2
actions/setup-node @v4 @49933ea5288caeca8642d1e84afbd3f7d6820020 v4.4.0

Key aspects of this fix:

  1. Full 40-character SHA: The commit hash is immutable — even if the v4 tag is repointed, this workflow will continue running the verified code at the pinned commit.

  2. Version comments (# v4.2.2, # v4.4.0): These trailing comments serve as documentation, making it easy for maintainers to know which version the SHA corresponds to when it's time to update.

  3. Specific patch versions: Rather than pinning to the vague v4 major version, the fix documents the exact minor/patch version (v4.2.2 and v4.4.0), providing better visibility into what's actually running.

  4. Behavioral preservation: The workflow still checks out the repository and sets up Node.js 20 — the only change is how GitHub resolves which code to run for each action.

Key Takeaways

  • The actions/checkout@v4 reference in templates/devto/devto-readme.yml:18 was vulnerable to tag-repointing attacks — an attacker compromising the upstream action could silently execute arbitrary code in this workflow.
  • Pinning to @11bd71901bbe5b1630ceea73d27597364c9af683 makes the reference cryptographically immutable — no upstream change can alter what code runs without a deliberate PR to update the SHA.
  • Both actions/checkout and actions/setup-node needed pinning — a supply chain is only as strong as its weakest link, and either action could serve as an entry point.
  • Version comments (# v4.2.2) are essential for maintainability — without them, developers can't easily determine if their pinned SHA is outdated or what version it corresponds to.
  • Real-world compromises (trivy-action, kics-github-action) prove this isn't theoretical — automated attackers actively target the mutable-tag pattern in CI/CD pipelines.

How Orbis AppSec Detected This

  • Source: The uses: directives in templates/devto/devto-readme.yml at lines 18 and 21, which resolve external action code at runtime based on mutable Git references.
  • Sink: GitHub Actions runner execution engine, which downloads and runs whatever code the tag reference resolves to at the time of workflow execution.
  • Missing control: No commit SHA pinning was in place — the workflow relied entirely on mutable tag names (v4) that can be silently repointed by upstream repository owners or attackers.
  • CWE: CWE-829 (Inclusion of Functionality from Untrusted Control Sphere)
  • Fix: Replaced mutable tag references @v4 with full 40-character commit SHAs (@11bd71901bbe5b1630ceea73d27597364c9af683 for checkout, @49933ea5288caeca8642d1e84afbd3f7d6820020 for setup-node) while adding version comments for maintainability.

Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.

Conclusion

Supply-chain attacks against CI/CD pipelines are among the fastest-growing threat vectors in software security. The fix applied to templates/devto/devto-readme.yml is simple — replacing two mutable tag references with pinned commit SHAs — but it eliminates an entire class of attack. Every GitHub Actions workflow in your repository should follow this pattern: pin to SHAs, comment the version, and use automated tools to keep those pins current.

The cost of this hardening is minimal (slightly less readable uses: lines), but the security benefit is substantial: cryptographic assurance that the code running in your CI/CD pipeline is exactly what you reviewed and approved.

Prevention and further reading

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #1

Related Articles

critical

How Unvalidated Dynamic Component Loading happens in TypeScript/Viewi and how to fix it

A critical vulnerability in Viewi's component loader allowed attackers to inject malicious JavaScript through compromised or MITM-attacked external component servers. The fix adds proper HTTP response validation before parsing dynamically fetched JSON components.

high

How remote memory exhaustion happens in Rust QUIC (Quinn) and how to fix it

A high-severity vulnerability (GHSA-4w2j-m93h-cj5j) in `quinn-proto`, the QUIC protocol implementation underlying the Quinn library, allowed remote attackers to exhaust server memory by sending unbounded out-of-order stream data. The `crosshash` project's `Cargo.lock` pinned the vulnerable `quinn-proto` 0.11.14; upgrading to 0.11.15 closes the gap by bounding how much out-of-order stream data the reassembly buffer will retain.

high

How Trust-Prefix Bypass via Path Traversal Happens in Python Copier and How to Fix It

CVE-2026-53951 is a high-severity path traversal vulnerability in Copier 9.15.0 that allowed attackers to bypass trust-prefix checks and execute tasks without user confirmation. Upgrading to Copier 9.15.2 eliminates this attack vector by properly validating file paths before task execution.

critical

How Type Confusion Vulnerabilities Happen in JavaScript Dependencies and How to Fix Them

A critical type confusion vulnerability (CVE-2021-23436) was discovered in immer 9.0.7, a popular immutable state management library used in the client application. By upgrading to immer 9.0.6, the vulnerability was patched, eliminating a flaw that could have allowed attackers to bypass previous security fixes (CVE-2020-28477). This fix demonstrates why keeping dependencies current is essential for maintaining application security.

high

How shell injection happens in GitHub Actions workflows and how to fix it

A composite GitHub Action in `.github/actions/design-health/action.yml` interpolated `inputs.path`, `inputs.verbose`, and other values directly into `run:` shell scripts using `${{ ... }}` syntax. Because these values are substituted as raw text before the shell ever runs, an attacker-influenced input could inject arbitrary shell commands into the CI runner. The fix moves every interpolated value into `env:` blocks so the shell treats them as data, not code.

critical

How SSRF via Vulnerable Dependency Versions Happens in Node.js and How to Fix It

A permissive semver range in `package.json` allowed npm to install axios versions vulnerable to SSRF (CVE-2024-39338). By bumping the minimum version from `^1.6.0` to `^1.7.4`, all downstream consumers of this SDK are now protected from server-side request forgery attacks. This critical fix required changing just one line in the dependency manifest.