Back to Blog
high SEVERITY6 min read

How mutable GitHub Actions tags enable supply-chain attacks and how to fix them

A Node.js library's composite GitHub Action was using mutable version tags (`@v6`, `@v4.36.3`) for action dependencies, creating a supply-chain attack vector. The fix pins both `actions/setup-node` and `github/codeql-action/upload-sarif` to specific 40-character commit SHAs, eliminating the risk of silent repointing attacks.

O
By Orbis AppSec
Published September 7, 2026Reviewed September 7, 2026

Answer Summary

This is a **GitHub Actions mutable tag vulnerability** (CWE-1104: Use of Unmaintained Third Party Components) affecting Node.js CI/CD workflows. The vulnerability occurs when composite actions reference external actions by mutable tags or branch names instead of immutable commit SHAs. Attackers who compromise action repositories can silently repoint tags to malicious versions. The fix pins `actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38` and `github/codeql-action/upload-sarif@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a` to full 40-character commit SHAs with version comments preserved for readability.

Vulnerability at a Glance

cweCWE-1104 (Use of Unmaintained Third Party Components), CWE-829 (Inclusion of Functionality from Untrusted Control Sphere)
fixPin all action references to full 40-character commit SHAs
riskSupply-chain compromise allowing arbitrary code execution in CI/CD pipelines
languageYAML (GitHub Actions)
root causeUsing mutable version tags instead of immutable commit SHAs for action references
vulnerabilityMutable GitHub Actions tag reference

Introduction

The action.yml file in this Node.js library defines a composite GitHub Action used by downstream consumers—but two lines created a significant supply-chain vulnerability. At lines 58 and 123, the action referenced external dependencies using mutable version tags: actions/setup-node@v6 and github/codeql-action/upload-sarif@v4.36.3. These seemingly innocent references could have allowed attackers to silently inject malicious code into every CI/CD pipeline using this library.

This vulnerability isn't theoretical. In 2022, the trivy-action and kics-github-action were both compromised when attackers gained access and repointed version tags to malicious commits. Any workflow using @v1 or similar mutable references automatically pulled the compromised code. The fix requires understanding why Git tags provide no security guarantees and how commit SHAs restore immutability.

The Vulnerability Explained

The Problematic Code Pattern

Here's what the vulnerable action.yml contained:

# Line 55-60: Vulnerable setup-node reference
- name: Setup Node.js
  uses: actions/setup-node@v6
  with:
    node-version: '20'

# Line 118-125: Vulnerable codeql-action reference  
- name: Upload SARIF to GitHub Security
  if: inputs.sarif != '' && always()
  uses: github/codeql-action/upload-sarif@v4.36.3
  with:
    sarif_file: ${{ inputs.sarif }}
  continue-on-error: true

The critical issue: Git tags are mutable references. The @v6 and @v4.36.3 suffixes resolve to whatever commit SHA the tag currently points to. Repository owners—or attackers with compromised credentials—can delete and recreate these tags pointing to entirely different code.

How the Attack Works

Consider this specific attack scenario against this Node.js library:

  1. Reconnaissance: An attacker identifies popular actions using this library's composite action
  2. Compromise: The attacker gains access to either actions/setup-node or github/codeql-action (through credential theft, maintainer coercion, or supply-chain infiltration)
  3. Tag Repointing: The attacker pushes a malicious commit, deletes the v6 or v4.36.3 tag, and recreates it pointing to their malicious version
  4. Silent Execution: Every CI/CD run using this library's action automatically executes the malicious code with repository permissions
  5. Payload: The malicious setup-node action could exfiltrate NODE_AUTH_TOKEN, modify source code before build, or poison the SARIF upload to hide security findings

The continue-on-error: true on the SARIF upload step actually amplifies the risk—if the malicious action fails, the workflow continues silently, potentially masking detection.

Why Short SHAs Aren't Safe

Some developers attempt partial mitigation with short SHAs like @8ade135. This is insufficient:

Reference Type Example Mutable? Risk
Branch name @main ✅ Yes Direct compromise
Major version @v6 ✅ Yes Tag repointing
Semantic version @v4.36.3 ✅ Yes Tag repointing
Short SHA (7 chars) @8ade135 ⚠️ Collision possible SHA-1 collision attacks
Full SHA (40 chars) @249970729cb0ef3589644e2896645e5dc5ba9c38 No Secure

GitHub's infrastructure resolves short SHAs within the repository, but 7 characters provides only 28 bits of entropy—collision attacks, while expensive, become feasible for determined adversaries.

The Fix

The remediation converts both action references to full 40-character commit SHAs with preserved version comments:

--- a/action.yml
+++ b/action.yml
@@ -55,7 +55,7 @@ runs:
   using: 'composite'
   steps:
     - name: Setup Node.js
-      uses: actions/setup-node@v6
+      uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38  # v6
       with:
         node-version: '20'

@@ -120,7 +120,7 @@ runs:

     - name: Upload SARIF to GitHub Security
       if: inputs.sarif != '' && always()
-      uses: github/codeql-action/upload-sarif@v4.36.3
+      uses: github/codeql-action/upload-sarif@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a  # v4.36.3
       with:
         sarif_file: ${{ inputs.sarif }}
       continue-on-error: true

Security Improvements

Aspect Before After
actions/setup-node reference Mutable tag @v6 Immutable SHA 249970729cb0ef3589644e2896645e5dc5ba9c38
codeql-action/upload-sarif reference Mutable tag @v4.36.3 Immutable SHA 54f647b7e1bb85c95cddabcd46b0c578ec92bc1a
Auditability Impossible to know exact version SHA uniquely identifies exact code
Supply-chain risk High Eliminated
Readability Clean Preserved with comments

The comment suffix # v6 and # v4.36.3 maintains human readability while the SHA guarantees immutability. This follows GitHub's recommended security hardening pattern.

Verification with Regression Tests

The included regression test validates that all action references match the ^[a-f0-9]{40}$ pattern:

const FULL_SHA_REGEX = /^[a-f0-9]{40}$/i;

function isPinnedToCommitSHA(ref) {
  return FULL_SHA_REGEX.test(ref);
}

// Rejects: v1, main, v1.2.3, 8ade135 (short SHA)
// Accepts: 249970729cb0ef3589644e2896645e5dc5ba9c38 (full SHA)

This test specifically catches the five adversarial patterns that developers commonly mistake for secure: major version tags, branch names, semantic version tags, short SHAs, and malformed references.

Prevention & Best Practices

1. Mandatory SHA Pinning Policy

Establish organization-wide requirements:
- All uses: statements must reference 40-character commit SHAs
- No exceptions for "trusted" actions—the actions/ namespace has been compromised before
- Use automated enforcement via CI checks

2. Dependency Update Automation with Verification

When updating action versions:

# Get current SHA for a tag
git ls-remote --tags https://github.com/actions/setup-node.git v6

# Output: 249970729cb0ef3589644e2896645e5dc5ba9c38  refs/tags/v6

Tools like Dependabot and Renovate can automate SHA updates while maintaining pinning.

3. Static Analysis Integration

Add Semgrep to CI pipelines with the specific rule that detected this issue:

# .github/workflows/security.yml
- uses: returntocorp/semgrep-action@v1
  with:
    config: >-
      p/github-actions

4. Composite Action Security

This vulnerability was in a composite action, which creates transitive trust:
- Consumers of this Node.js library inherit its action dependencies
- One vulnerable reference compromises all downstream users
- Document SHA pinning requirements for all composite action dependencies

Security Standards Alignment

Standard Mapping
CWE-1104 Use of Unmaintained Third Party Components
CWE-829 Inclusion of Functionality from Untrusted Control Sphere
OWASP CI/CD Top 10 CICD-SEC-5: Insufficient Pipeline-Based Access Controls
SLSA Level 3 Requires hermetic builds with pinned dependencies

Key Takeaways

  • Never use mutable references in action.yml composite actions—the uses: keyword in composite actions has identical security requirements to workflow files
  • The actions/ namespace is not special—GitHub's official actions have the same tag mutability as community actions; pin them equally
  • Version comments preserve usability—append # v6 to SHA-pinned references so humans understand intent without sacrificing security
  • Short SHAs fail silently—7-character SHAs pass casual review but provide insufficient collision resistance; enforce 40 characters
  • Composite actions amplify supply-chain risk—vulnerabilities in this library's action.yml affect all downstream consumers who use the package

How Orbis AppSec Detected This

Source: External action references in action.yml—specifically the uses: declarations for third-party actions

Sink: The composite action definition at action.yml:45 where actions/setup-node and github/codeql-action/upload-sarif were invoked with mutable tag references

Missing control: No commit SHA pinning; both references used mutable version tags (@v6 and @v4.36.3) that can be silently repointed by action maintainers or compromised accounts

CWE: CWE-1104 (Use of Unmaintained Third Party Components) and CWE-829 (Inclusion of Functionality from Untrusted Control Sphere)

Fix: Replaced mutable tag references with immutable 40-character commit SHAs (249970729cb0ef3589644e2896645e5dc5ba9c38 for setup-node, 54f647b7e1bb85c95cddabcd46b0c578ec92bc1a for codeql-action) with preserved version comments for traceability

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

Mutable action references in GitHub Actions represent one of the most dangerous supply-chain vulnerabilities because they appear benign while enabling silent, widespread compromise. The fix in this Node.js library—pinning actions/setup-node and github/codeql-action/upload-sarif to specific commit SHAs—eliminates an entire class of attacks without functional impact.

For developers maintaining composite actions or workflow definitions, treat every uses: statement as a potential supply-chain entry point. The 40-character SHA requirement may seem verbose, but it provides the only cryptographic guarantee available in the GitHub Actions ecosystem. Combined with automated detection and regression testing, SHA pinning transforms supply-chain security from reactive incident response to proactive defense-in-depth.

References

Frequently Asked Questions

What is a mutable GitHub Actions tag vulnerability?

It's when GitHub Actions workflows use version tags, branch names, or short SHAs that can be silently repointed by the action owner, enabling supply-chain attacks where malicious code runs in your CI/CD pipeline.

How do you prevent mutable GitHub Actions tags in YAML?

Replace all mutable references with full 40-character commit SHAs. Keep version comments for readability: `uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0`

What CWE is mutable GitHub Actions tag?

CWE-1104 (Use of Unmaintained Third Party Components) and CWE-829 (Inclusion of Functionality from Untrusted Control Sphere)

Is semantic versioning with tags enough to prevent this?

No. Semantic version tags like `v1.2.3` are mutable and can be repointed. Only full commit SHAs provide immutability guarantees.

Can static analysis detect mutable GitHub Actions tags?

Yes. Semgrep's `yaml.github-actions.security.github-actions-mutable-action-tag` rule specifically flags non-SHA-pinned action references.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #687

Related Articles

high

How missing dependency update cooldowns happen in GitHub Dependabot configurations and how to fix it

A semgrep scan flagged `.github/dependabot.yml` for lacking a cooldown period, meaning Dependabot would immediately propose updates to brand-new package versions across npm, Bundler, and Docker ecosystems. The fix adds a `cooldown: default-days: 7` block to every `package-ecosystem` entry, forcing a one-week waiting period before newly published releases are considered — reducing exposure to malicious or unstable package drops.

high

How dependabot-missing-cooldown happens in GitHub Actions/Node.js and how to fix it

The repository's `.github/dependabot.yml` had no cooldown period configured, meaning Dependabot could immediately propose updates to newly published package versions with zero time for the community to flag malware or instability. The fix adds a `cooldown` block with `default-days: 7` to both the `npm` and `github-actions` ecosystems, forcing a 7-day waiting period before new releases are surfaced as update PRs.

high

How Path Traversal Happens in TensorFlow's Data Service and How to Fix It

TensorFlow's data service dispatcher validated dataset IDs against forward-slash traversal attacks but overlooked backslash characters on non-Windows platforms, allowing attackers to escape the root directory. A targeted fix adds explicit backslash validation across all platforms, closing a high-severity path traversal vulnerability in the snapshot management system.

critical

How Unbounded WebSocket Message Handling Causes Resource Exhaustion in Node.js and How to Fix It

The WebSocketCrossServerAdapter class in a popular Node.js WebSocket library lacked any rate limiting on inbound messages, allowing attackers to flood Redis nodes and WebSocket servers with high-volume traffic. The fix introduces a configurable `rateLimit` option that caps messages per connection per second, preventing resource exhaustion while preserving legitimate functionality.

critical

How Remote Code Execution Happens in Handlebars Template Compilation and How to Fix It

CVE-2026-33937 is a critical remote code execution vulnerability in Handlebars.js that allows attackers to execute arbitrary code by passing maliciously crafted Abstract Syntax Tree (AST) objects to the compile() function. The vulnerability was patched in version 4.7.9, and we've upgraded to protect against this threat vector.

critical

How Denial of Service via Gzip Bomb happens in Node.js and how to fix it

A critical Denial of Service vulnerability (CVE-2026-59873) in the `tar` npm package allowed attackers to craft malicious gzip archives that could exhaust memory or CPU during decompression. The fix upgrades `tar` from 7.5.11 to 7.5.21 across `package.json` and `package-lock.json`, closing the resource-exhaustion path without changing any application code.