Introduction
The .github/workflows/bench-probe.yml file drives the automated benchmarking pipeline for this repository — the same pipeline responsible for building and running performance probes like bench/wordcount/wordcount.c. Like most CI configuration, it invokes third-party GitHub Actions using the standard uses: syntax. The problem: at least one step referenced its action by a mutable tag or branch name rather than an immutable commit SHA — a pattern flagged as high severity by Semgrep's github-actions-mutable-action-tag rule.
This might look harmless in a review — after all, uses: some-action@v4 is everywhere in GitHub Actions examples online. But that convenience is exactly what makes it dangerous. Tags and branches are just pointers; the action's maintainer (or anyone who compromises their account) can silently move that pointer to a new, malicious commit at any time. The next time your CI runs, it pulls and executes that new code — no code review, no PR, no warning.
The Vulnerability Explained
Semgrep's github-actions-mutable-action-tag rule looks for uses: steps in workflow YAML that resolve to anything other than a full 40-character commit SHA. A typical vulnerable step looks like this:
steps:
- name: Run benchmark probe
uses: some-org/some-action@v4
Here, @v4 is a tag, not a commit. Tags in Git are mutable by design — the repository owner can git tag -f v4 <new-commit> and push, and every workflow referencing @v4 will pick up the new code on its next run. The same applies to branch references like @main or @master.
This is not a theoretical risk. It's the exact mechanism behind two real supply-chain incidents:
- trivy-action compromise: a widely used security-scanning action was repointed, causing downstream CI pipelines to execute unexpected code.
- kics-github-action compromise: a similar tag-repointing attack affected another popular scanning action used across thousands of repositories.
Attack scenario for bench-probe.yml
Imagine the mutable-tagged action in bench-probe.yml is used to check out code, set up a build environment, or upload benchmark artifacts. An attacker who compromises the action maintainer's account (via phished credentials, a leaked publish token, or a malicious contributor merge) could push a new commit to the tag that:
- Exfiltrates repository secrets (
GITHUB_TOKEN, API keys, cloud credentials) available to the workflow. - Injects a backdoor into the benchmark build artifacts.
- Modifies the checked-out source before the
wordcount.cbenchmark (or any other bench target) is compiled and run.
Because CI runners often have elevated permissions — access to secrets, push rights, or deployment credentials — a single repointed tag can turn a benchmarking workflow into a foothold for a much larger compromise.
The Fix
The fix is conceptually simple but security-critical: replace the mutable tag/branch reference with the full 40-character commit SHA that tag currently points to.
Before (vulnerable pattern):
- uses: some-org/some-action@v4
After (hardened):
- uses: some-org/some-action@8ade135a41bc03ea155e62e844d188df1ea18608 # v4
Note the trailing comment — it's good practice to annotate the SHA with the human-readable version it corresponds to, so future maintainers know what they're looking at without losing the immutability guarantee.
Why does this work? A commit SHA is a cryptographic hash of the commit's content and history. It cannot be silently repointed the way a tag can — if the underlying code changes, the SHA changes with it. Pinning to a SHA means the exact bytes of code that run in your CI today are the exact bytes that will run tomorrow, until you explicitly choose to bump the pin (ideally via a reviewed pull request, which is how Dependabot and Renovate handle SHA updates safely).
This change requires no behavioral modification to the workflow — the action still does exactly what it did before. It simply removes the attacker's ability to change that behavior out from under you.
Prevention & Best Practices
- Pin every third-party action to a commit SHA, not a tag or branch. This applies to
actions/checkout,actions/setup-node, and any community or third-party action. - Use Dependabot or Renovate with GitHub Actions support enabled — both tools understand SHA pinning and will open PRs to bump pins safely, including the version comment.
- Enable branch protection and required reviews on workflow file changes so a modified
uses:line doesn't slip through unnoticed. - Run
actionlintor Semgrep'sgithub-actions-mutable-action-tagrule in CI itself, so any newly added mutable reference is caught before merge. - Audit existing workflows periodically — this class of risk accumulates silently as new steps are added by different contributors over time.
- Treat CI as production infrastructure. Workflow files have access to secrets and can execute arbitrary code; they deserve the same scrutiny as application source.
This maps directly to CWE-829: Inclusion of Functionality from Untrusted Control Sphere, and is closely related to general software supply-chain integrity guidance from OWASP's Software Component Verification Standard and the SLSA framework.
Key Takeaways
bench-probe.ymlreferenced an action by a mutable tag/branch instead of a commit SHA — an easy-to-miss but high-severity gap.- Mutable tags gave an attacker who compromised the action's repository a path to silently alter CI behavior, mirroring the real trivy-action and kics-github-action incidents.
- The fix — pinning to a full 40-character SHA (e.g.,
actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608) — is a drop-in change with no functional impact. - CI/CD configuration is part of your attack surface; it should be linted and reviewed with the same rigor as application code.
- Automated tooling (Dependabot/Renovate + Semgrep) can keep SHA pins current without reintroducing the mutable-reference risk.
How Orbis AppSec Detected This
- Source: The
uses:directive in.github/workflows/bench-probe.yml, resolved at workflow-run time from GitHub's action registry. - Sink: The GitHub Actions runner, which fetches and executes whatever commit the tag/branch currently points to.
- Missing control: No pinning to an immutable commit SHA — the workflow trusted a mutable reference to determine what code executes in CI.
- CWE: CWE-829 (Inclusion of Functionality from Untrusted Control Sphere).
- Fix: The action reference in
bench-probe.ymlwas pinned to its full 40-character commit SHA, eliminating the ability for the tag/branch to be silently repointed.
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
A single unpinned uses: line in bench-probe.yml was enough to expose the benchmarking pipeline to a supply-chain attack pattern that has already been used against real projects. Pinning GitHub Actions to full commit SHAs is a small, low-friction change that removes an entire class of risk — one that's easy to overlook because tag-based references are the default example in almost every piece of GitHub Actions documentation. Treat every uses: line in your workflows as a dependency that deserves the same scrutiny as a package in your package.json or requirements.txt.