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.

Prevention & Best Practices

1. Always Pin to Commit SHAs

Every uses: directive in your GitHub Actions workflows should reference a full commit SHA:

# ❌ Vulnerable - mutable tag
uses: actions/checkout@v4

# ❌ Still vulnerable - minor version tags are also mutable
uses: actions/checkout@v4.2.2

# ✅ Secure - immutable commit SHA with version comment
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

2. Automate SHA Updates

Use Dependabot or Renovate to automatically propose PR updates when pinned actions release new versions:

# .github/dependabot.yml
version: 2
updates:
  - package-ecosystem: "github-actions"
    directory: "/"
    schedule:
      interval: "weekly"

3. Use GitHub's Verification Features

  • Enable Artifact Attestations for actions you publish
  • Review the Security tab of any action before using it
  • Prefer actions from verified creators (blue checkmark)

4. Implement Least Privilege

  • Set permissions: explicitly in your workflow to limit GITHUB_TOKEN scope
  • Use read permissions where possible instead of default write

5. Scan Workflows with Static Analysis

Use Semgrep, StepSecurity's harden-runner, or actionlint to catch mutable references:

semgrep --config "p/github-actions" .github/workflows/

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.

References

Frequently Asked Questions

What is a mutable action tag vulnerability in GitHub Actions?

It occurs when a workflow references an action using a version tag (like `@v4`) or branch name that can be silently changed by the action owner or an attacker who compromises the action repository, potentially injecting malicious code into your CI/CD pipeline.

How do you prevent mutable action tag vulnerabilities in GitHub Actions?

Pin every third-party action to its full 40-character commit SHA (e.g., `actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683`) and add a version comment (e.g., `# v4.2.2`) for readability. Use tools like Dependabot or Renovate to automate SHA updates.

What CWE is the mutable action tag vulnerability?

CWE-829: Inclusion of Functionality from Untrusted Control Sphere. This covers cases where software imports or includes functionality from a source that is not sufficiently trusted or verified.

Is using major version tags (like @v4) enough to prevent supply-chain attacks in GitHub Actions?

No. Major version tags are mutable — they can be force-pushed to point at any commit. Only full commit SHAs are immutable and provide cryptographic guarantees that the code you run hasn't changed.

Can static analysis detect mutable action tag vulnerabilities?

Yes. Tools like Semgrep have specific rules (e.g., `yaml.github-actions.security.github-actions-mutable-action-tag`) that flag any `uses:` directive referencing a tag or branch instead of a pinned commit SHA.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #1

Related Articles

high

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

A high-severity shell injection vulnerability was discovered in `setup-js/action.yml` where direct interpolation of `inputs.package-manager` in a `run:` step could allow attackers to execute arbitrary code on the GitHub Actions runner. The fix introduces intermediate environment variables to safely pass user-controlled inputs, preventing command injection while maintaining the same functionality.

high

How pnpm Trust Policy Misconfiguration happens in Node.js and how to fix it

A missing `trustPolicy` setting in `pnpm-workspace.yaml` left a Node.js workspace vulnerable to malicious packages silently downgrading security configurations. The fix adds `trustPolicy: no-downgrade` alongside `blockExoticSubdeps: true` and a stricter `minimumReleaseAge`, closing a supply-chain attack primitive before it could be chained with other weaknesses.

high

How Denial of Service via unbounded intermediate arrays happens in Node.js dependencies and how to fix it

A high-severity Denial of Service vulnerability (CVE-2026-69152) was discovered in the `brace-expansion` npm package, where crafted input could generate unbounded intermediate arrays that exhaust system memory. This bypasses the earlier CVE-2026-14257 mitigation. The fix upgrades `brace-expansion` from version 1.1.12 (and 2.0.2) to patched versions 1.1.18 across the dependency tree in the `exia-invasion` project.

critical

How Supply Chain Timing Attacks happen in pnpm Workspaces and how to fix it

The apple-mail-mcp repository was vulnerable to supply chain timing attacks because its pnpm workspace configuration only enforced a 1-day (1440 minute) minimum release age for newly published packages. This allowed a 5-day-old transitive dependency (ip-address@10.5.0) to be installed despite Dependabot's 7-day cooldown, creating a window where malicious or unstable packages could enter the dependency tree. The fix raises minimumReleaseAge to 10080 minutes (7 days) to ensure all packages—includi

high

How Supply Chain Risk from Missing Package Age Validation Happens in pnpm and How to Fix It

A pnpm workspace configuration was missing the `minimumReleaseAge` setting, creating a supply chain vulnerability where newly published (and potentially malicious) packages could be installed immediately. By adding a 10,080-minute (7-day) minimum release age to `pnpm-workspace.yaml`, the project now enforces a critical delay that allows the security community time to identify and report malicious or unstable packages before they reach production environments.

high

How Regular Expression Denial of Service happens in JavaScript and how to fix it

CVE-2026-33671 is a Regular Expression Denial of Service (ReDoS) vulnerability in the picomatch glob-matching library, triggered by specially crafted extglob patterns that cause catastrophic regex backtracking. The fix upgrades picomatch to version 4.0.4 (with overrides pinning all transitive copies) in the client's dependency tree, eliminating the vulnerable regex evaluation path. Left unpatched, any code path that passes user-influenced glob patterns to picomatch could be weaponized to stall a