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:
- Reconnaissance: An attacker identifies popular actions using this library's composite action
- Compromise: The attacker gains access to either
actions/setup-nodeorgithub/codeql-action(through credential theft, maintainer coercion, or supply-chain infiltration) - Tag Repointing: The attacker pushes a malicious commit, deletes the
v6orv4.36.3tag, and recreates it pointing to their malicious version - Silent Execution: Every CI/CD run using this library's action automatically executes the malicious code with repository permissions
- Payload: The malicious
setup-nodeaction could exfiltrateNODE_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.ymlcomposite actions—theuses: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
# v6to 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.ymlaffect 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
- CWE-1104: Use of Unmaintained Third Party Components
- CWE-829: Inclusion of Functionality from Untrusted Control Sphere
- OWASP CI/CD Security Cheat Sheet
- GitHub Security Hardening for GitHub Actions
- Semgrep rule: github-actions-mutable-action-tag
- harden: github actions step uses a mutable tag or branc... in...