Skip to content

ci: validate workflow_run origin before consuming the E2E artifact (fixes fork artifact poisoning)#27753

Open
adilburaksen wants to merge 1 commit into
google-gemini:mainfrom
adilburaksen:fix/e2e-workflow-run-artifact-poisoning
Open

ci: validate workflow_run origin before consuming the E2E artifact (fixes fork artifact poisoning)#27753
adilburaksen wants to merge 1 commit into
google-gemini:mainfrom
adilburaksen:fix/e2e-workflow-run-artifact-poisoning

Conversation

@adilburaksen

Copy link
Copy Markdown

Summary

The chained E2E pipeline is vulnerable to workflow_run artifact poisoning, allowing a fork PR to run attacker-controlled code with repository secrets.

  • trigger_e2e.yml runs on pull_request (including forks) and writes the PR head repo.full_name and sha into the repo_name artifact.
  • chained_e2e.yml runs on workflow_run (base-repo context, full secret access). download_repo_name downloads that artifact and the downstream jobs (e2e_linux, e2e_mac, e2e_windows, evals) actions/checkout the artifact's repository/sha and run npm ci (executing postinstall), npm run build, and tests with GEMINI_API_KEY, GEMINI_CLI_ROBOT_GITHUB_PAT, and permissions: write-all in scope.

A fork can therefore put its own repo + SHA into the artifact and have the privileged workflow execute its code with those secrets. The existing github.repository == 'google-gemini/gemini-cli' guard is always true under workflow_run (it runs in the base context) and does not validate the artifact's origin.

This is the workflow_run artifact-poisoning pattern documented by GitHub Security Lab.

Fix

Gate artifact consumption (download_repo_name) on the triggering run's head_repository being this repository:

if: >-
  github.repository == 'google-gemini/gemini-cli' &&
  (github.event_name == 'workflow_dispatch' ||
  (github.event_name == 'workflow_run' &&
  github.event.workflow_run.head_repository.full_name == github.repository))

For a fork-triggered run this skips the artifact download, so parse_run_context falls back to github.repository/base sha and the E2E jobs no longer check out or execute fork code with secrets. Same-repo PRs and workflow_dispatch are unaffected.

Note on fork E2E

This change intentionally stops the secret-bearing E2E jobs from running fork code. If you want to keep E2E coverage for fork PRs, the safe pattern is to gate it behind a human-applied label (or a deployment environment with required reviewers) before checking out fork code with secrets — similar to how other Google repos (e.g. protocolbuffers/protobuf) require a "safe for tests" label on fork PRs. Happy to follow up with that if preferred.

The chained E2E workflow runs on `workflow_run` in the base-repo context
with GEMINI_API_KEY, GEMINI_CLI_ROBOT_GITHUB_PAT, and a write-all
GITHUB_TOKEN in scope. `download_repo_name` downloads the repo_name/head_sha
artifact produced by the `Trigger E2E` run (which runs on `pull_request`,
including forks) and the downstream jobs check out and build that
repository/sha. A fork PR can write its own repo and SHA into the artifact,
causing the privileged workflow to execute attacker-controlled code
(`npm ci`/`postinstall`, build, tests) with those secrets available.

Gate artifact consumption on the triggering run's `head_repository` being
this repository, so a fork-triggered run falls back to the base repo/sha
and does not run fork code with secrets. The existing `github.repository`
check is always true under `workflow_run` and does not validate the
artifact's origin.
@adilburaksen adilburaksen requested a review from a team as a code owner June 9, 2026 13:00
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Note

Gemini is unable to generate a summary for this pull request due to the file types involved not being currently supported.

@github-actions github-actions Bot added the size/s A small PR label Jun 9, 2026
@github-actions

github-actions Bot commented Jun 9, 2026

Copy link
Copy Markdown

📊 PR Size: size/S

  • Lines changed: 16
  • Additions: +15
  • Deletions: -1
  • Files changed: 1

@sin325 sin325 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

``

@gemini-cli gemini-cli Bot added the status/need-issue Pull requests that need to have an associated issue. label Jun 9, 2026
@adilburaksen

Copy link
Copy Markdown
Author

Thanks for taking a look @sin325. The status/need-issue label is the blocker here — this PR is a security hardening that came out of an active Google OSS-VRP report (workflow_run artifact-origin validation), so I'd rather not spell out the exploit in a public issue before it's merged.

A couple of ways to unblock, whichever your team prefers:

  • I open a minimal tracking issue ("harden chained_e2e workflow_run artifact origin check") with no exploit detail and link it, or
  • I share the specifics with you privately.

On the change itself: the diff gates download_repo_name on github.event.workflow_run.head_repository.full_name == github.repository, so a fork-triggered run falls back to the base repo/sha and the secret-bearing E2E jobs no longer check out or execute fork code. If you'd rather keep fork E2E working, I can switch to the label-gate approach instead. Happy to make whatever change you'd like — just let me know.

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

Labels

size/s A small PR status/need-issue Pull requests that need to have an associated issue.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants