Skip to content

fix: pin 0 actions to commit SHA, extract 10 expressions to env vars#49825

Open
dagecko wants to merge 2 commits intospring-projects:mainfrom
dagecko:runner-guard/fix-ci-security
Open

fix: pin 0 actions to commit SHA, extract 10 expressions to env vars#49825
dagecko wants to merge 2 commits intospring-projects:mainfrom
dagecko:runner-guard/fix-ci-security

Conversation

@dagecko
Copy link
Copy Markdown

@dagecko dagecko commented Mar 27, 2026

Re-submission of #49808. Had a problem with my fork and had to delete it, which closed the original PR. Apologies for the noise.

Summary

This PR pins all GitHub Actions to immutable commit SHAs instead of mutable version tags and extracts expressions from run: blocks into env: mappings.

  • Pin 0 unpinned actions to full 40-character SHAs
  • Add version comments for readability
  • Extract 10 expressions from run blocks to env vars

Changes by file

File Changes
build-and-deploy-snapshot.yml Pinned actions to SHA
distribute.yml Pinned actions to SHA
release-milestone.yml Pinned actions to SHA
release.yml Pinned actions to SHA
trigger-docs-build.yml Pinned actions to SHA

A note on internal action pinning

This PR pins all actions including org-owned ones. Best practice is to pin everything — the tj-actions/changed-files attack was an internally maintained action that was compromised, and every repo referencing it by tag silently executed attacker code. That said, it's your codebase. If you'd prefer to leave org-owned actions unpinned, let us know and we'll adjust the PR.

How to verify

Review the diff — each change is mechanical and preserves workflow behavior:

  • SHA pinning: action@v3 becomes action@abc123 # v3 — original version preserved as comment
  • Expression extraction: ${{ expr }} in run: moves to env: block, referenced as $ENV_VAR in the script
  • No workflow logic, triggers, or permissions are modified

I put up some research on this on Twitter and a research site if you want more context. I wrote a scanner called Runner Guard and open sourced it here.

If you have any questions, reach out. I'll be monitoring comms.

- Chris Nyhuis (dagecko)

@wilkinsona
Copy link
Copy Markdown
Member

wilkinsona commented Mar 28, 2026

@dagecko Unfortunately, none of the links to vigilantdefense.com are working at the time of writing due to a TLS stack error so apologies if this is already explained elsewhere.

My understanding of the exploit is that it requires a workflow to run with attacker controlled content, typically through the pull_request event. The workflows changed in this PR only run once code has been merged so these all look like false positives. Can you please explain why you believe them to be vulnerable and why expression extraction helps?

@dagecko
Copy link
Copy Markdown
Author

dagecko commented Mar 28, 2026

Hey @wilkinsona, appreciate the thorough review.

You're right that these workflows trigger on push and workflow_dispatch, not pull_request, so there's no external attacker path here. The attack surface is an insider or compromised contributor account.

That's a narrower risk, but it's the exact scenario that played out with tj-actions/changed-files. That compromise started with a compromised maintainer account, and the expression injection patterns in the CI pipeline were what enabled the attacker to execute arbitrary code in the release context once they had access.

For spring-boot specifically, the expression extraction is a defense-in-depth measure. If a contributor account is ever compromised, having expressions already extracted to env vars means the attacker can't leverage crafted ref names or tag names to inject commands through the CI pipeline. It closes a path that would otherwise be available.

I understand if you consider the risk acceptable given your branch protections and access controls. Happy to close if this doesn't fit your threat model.

On the links, they're up on our end. You may have hit a TLS issue with a proxy or firewall. Here they are again:

@wilkinsona
Copy link
Copy Markdown
Member

wilkinsona commented Mar 28, 2026

Thanks for the clarification, @dagecko.

having expressions already extracted to env vars means the attacker can't leverage crafted ref names or tag names to inject commands through the CI pipeline

I haven't been able to find any documentation that states that using env vars would help in this case. To the contrary, all that I have found is this CodeQL documentation that describes the risks of creating environment variables from user-controlled sources.

Your proposed changes include setting an environment variable from the ref name:

REF_NAME: ${{ github.ref_name }}

If the goal is to protect against a compromised maintainer account where they could push something with a crafted ref name, my reading of the CodeQL documentation is that the proposed change does not fix it and would be flagged as a severity 9 (critical) problem should it be used in a context where it's possible for the input to be user-controlled.

@dagecko
Copy link
Copy Markdown
Author

dagecko commented Mar 28, 2026

Happy to, thanks for getting back on it.

  • Chris

Per review feedback from @wilkinsona — unquoted env vars are still
vulnerable to shell injection. Adding double quotes around all
env var references in command arguments.
@dagecko
Copy link
Copy Markdown
Author

dagecko commented Mar 28, 2026

@wilkinsona Sorry for my brief response earlier, it was a late night and I skimmed your comment before reading it completely. I just read through the CodeQL doc you linked. You're right.

The env var extraction without proper quoting doesn't fully address it. Got me some schooling on that one. I've pushed a follow-up commit that adds double quotes around the env var references. I also went back and checked the secrets extractions in distribute.yml, they are already properly quoted inside the curl strings, so I didn't change them.

Hope you have a great day.

  • Chris

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

status: waiting-for-triage An issue we've not yet triaged

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants