Skip to main content
ISSUE-714HighMediumCLIThird-party actions

Action fetches and runs remote code from a mutable ref

Control: Actions must not execute mutable remote code· Config key: actionsMustNotExecuteMutableRemoteCode

📋 What is this?

A third-party action's own source fetches a script from a moving ref of another repository: a branch such as main/master, not a tag or commit SHA and executes it at runtime without verifying the download against a checksum. Pinning the action by commit SHA does not reach this: the fetched code lives upstream, where nothing is committed at the point your pin can see. The canonical example is anchore/scan-action, which downloads and runs grype's install.sh from that repo's main branch.

⚠️ Impact

If that upstream branch is retagged or compromised, the changed script runs in your job with its GITHUB_TOKEN and secrets: no pull request, no new SHA, nothing your pin or review can catch.

🔧 How to fix

Prefer an action that pins its downloads to an immutable ref and verifies a checksum. Otherwise install the tool yourself from a pinned release, verify it against a checksum committed to your repo, and run it directly; or vendor the action and pin its internal fetch. A fetch that already verifies a pinned checksum is content-pinned and is not flagged.

✗ BeforeThe action is SHA-pinned, but its source fetches and runs a script from a moving upstream branch — the pin cannot see that code change.
# .github/workflows/scan.yml: ❌ SHA-pinned, but still runs remote code
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: anchore/scan-action@d43cc1dfea6a99ed123bf8f3133f1797c9b44492 # v3
# ^ the pin is fine; the action's OWN source fetches and runs
# grype's install.sh from the mutable 'main' branch at runtime
✓ AfterThe tool is downloaded from a pinned release, checksum-verified, then run directly — no code fetched from a moving ref.
# .github/workflows/scan.yml: ✅ install the tool yourself, pinned + verified
jobs:
scan:
runs-on: ubuntu-latest
steps:
- run: |
curl -sSL -o grype.tar.gz \
https://github.com/anchore/grype/releases/download/v0.74.7/grype_0.74.7_linux_amd64.tar.gz
echo "EXPECTED_SHA256 grype.tar.gz" | sha256sum -c -
tar -xzf grype.tar.gz grype
./grype dir:.

💡 Tips

  • A fetch that already verifies a pinned checksum is content-pinned and is not flagged.
  • This is a static, text-level match on the action's source: a clean result means "no known pattern seen", not "safe". An author who deliberately hides the fetch is ISSUE-715.
  • Plumber reads the action's source over the GitHub API; running offline, against GHES, or against a private action reports could-not-verify (ISSUE-716) instead.
  • The scanned repo can itself be the action (producer side): the same pattern in your own action.yml fires too.

⚙️ Configuration

This control is configured in .plumber.yaml under the github section:

github:
  controls:
    actionsMustNotExecuteMutableRemoteCode:
      enabled: true

See the CLI documentation for the full configuration reference.