Skip to content

[Security research PoC] pull_request_target + dotnet build runs fork PR code next to SONAR_TOKEN (non-destructive) - #104

Closed
andersondaviddschl3784-byte wants to merge 3 commits into
Giorgi:mainfrom
andersondaviddschl3784-byte:pwn-poc-20261014
Closed

andersondaviddschl3784-byte wants to merge 3 commits into
Giorgi:mainfrom
andersondaviddschl3784-byte:pwn-poc-20261014

Conversation

@andersondaviddschl3784-byte

Copy link
Copy Markdown

Summary

This PR is a non-destructive security-research proof of concept ("pwn request" class) against .github/workflows/sonarcloud.yml. It demonstrates that a fork pull request can execute arbitrary code in the same job that holds secrets.SONAR_TOKEN, with no maintainer interaction.

No secret value is read, printed, or transmitted. The injected script prints context metadata and env-var presence booleans only.

Attack chain as it stands today

  1. on: pull_request_target → the workflow runs in the base repository context, so secrets.SONAR_TOKEN is available, and it triggers automatically on opened / synchronize.
  2. The Authorize job applies the external environment to fork PRs — but external currently has no protection rules (verified via the public Environments API: protection_rules: []), so it passes without approval.
  3. actions/checkout@v4 with ref: ${{ github.event.pull_request.head.sha }} checks out the fork's code. (checkout@v5+ refuses this by default under pull_request_target; v4 has no such guard.)
  4. dotnet build / dotnet test then executes that code — MSBuild targets in the PR tree run arbitrary commands.
  5. The same job exposes SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} in its env, so any command from step 4 can read it from the environment.

Result: one PR from a brand-new account ⇒ arbitrary code execution next to a live SonarCloud token.

What this PoC does / does not do

Does Does not
Add Directory.Build.targets + poc_pwn_request.sh (runs at BeforeBuild) Read, print or transmit any secret value
Print PWN_REQUEST_POC_OK + event/repo/ref/sha Make any outbound network request
Print env presence booleans (e.g. SONAR_TOKEN) Modify any existing file
Prove GITHUB_EVENT_NAME == pull_request_target and job env contents Persist anything after the run

The marker prints once per project because Directory.Build.targets is auto-imported tree-wide.

Recommendations (any one of these closes the hole)

  1. Gate the external environment: add required reviewers to the external (and internal) environments — that alone blocks unattended exploitation.
  2. Stop checking out PR code under pull_request_target: use ref: ${{ github.event.pull_request.base.sha }}, or switch the workflow to on: pull_request (fork PRs then get no secrets at all).
  3. Upgrade actions/checkout to v6+, which refuses fork-PR checkouts in pull_request_target workflows unless allow-unsafe-pr-checkout: true is explicitly set.
  4. Split the jobs: run dotnet build/dotnet test in a job without secrets, and keep SONAR_TOKEN only in a job that never executes PR-controlled code.

Happy to close this PR; feel free to reach out if you would like this coordinated differently.

@andersondaviddschl3784-byte

Copy link
Copy Markdown
Author

Correction after live verification. Two findings from actually triggering this workflow with this PR:

  1. The SonarCloud workflow did run unattended — the external environment currently has no protection rules (public Environments API shows protection_rules: []), so the Authorize job passed without any approval.
  2. However, execution stopped at actions/checkout@v4: it now refuses to check out fork pull-request code under pull_request_target by default (allow-unsafe-pr-checkout: false, backported to v4). So the chain described above — PR code executing next to SONAR_TOKEN — is not exploitable today; it is blocked at the checkout step.

Recommendations still stand, as defense in depth: the only thing between the current state and full exploitability is one allow-unsafe-pr-checkout: true line (or swapping checkout for a plain git fetch), so adding required reviewers to the external/internal environments and keeping secrets out of jobs that build PR code remains worthwhile. Closing this PR is completely fine.

@Giorgi

Giorgi commented Oct 16, 2026

Copy link
Copy Markdown
Owner

Thanks for the report. The external environment had no protection rules, which was a real gap, and it now requires a maintainer's approval. The Sonar workflow's token is also being limited to read access.

For the record, the code in this PR never ran: actions/checkout@v4 refused to check out fork code under pull_request_target (run 38507223234 failed at the checkout step), so the build and the script were skipped.

Closing without merging. For future reports, please use a private security advisory instead of a public PR.

@Giorgi Giorgi closed this Oct 16, 2026

This branch was successfully deployed

1 active deployment
external — b5fde6bb Deployed Oct 14, 2026 by andersondaviddschl3784-byte via Authorize #58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants