Skip to content

Fork pull requests never run e2e — build image push is denied, and the skipped required check still lets the PR merge #3257

Description

Fork pull requests never run e2e — build image push is denied, and the skipped required check still lets the PR merge

Summary

Pull requests opened from a fork cannot run the e2e pipeline, and — more importantly — an approved fork PR can still merge with e2e having never executed. External contributions therefore land without any e2e coverage.

Mechanism

.github/workflows/pull-requests.yaml guards every registry login with if: ${{ !github.event.pull_request.head.repo.fork }}, so on a fork PR the Login to ... registry step is skipped (correct — pull_request runs from a fork receive no secrets). But the build itself pushes unconditionally: hack/common-envs.mk sets PUSH := 1 and BUILDX_ARGS := ... --push=$(PUSH), so make image still runs docker buildx build --push. With no login it pushes anonymously and dies in ~20s:

denied: Anonymous users are only allowed read access on public repos

That fails every Build <pkg> and Build Talos job. finalize requires build to have succeeded or been skipped (not failed), so it is skipped; e2e requires finalize to have succeeded, so it is skipped too. The whole build → finalize → e2e chain collapses on any fork PR.

Impact

Two distinct problems:

  1. Every fork PR shows a wall of red Build <pkg> ❌. These are not required checks, so they do not block merge, but they read as "this PR is broken" to contributors and reviewers.
  2. E2E Tests is a required check, and on a fork PR it ends in skipping. Branch protection treats a skipped required check as satisfied, so an approved fork PR reaches MERGEABLE and can be merged with e2e never having run. For a project where e2e is the merge gate, external contributions bypass it entirely.

Recently observed on a batch of fork PRs; the mechanism is documented with a job link in a comment on #2196.

Options

I am filing this to track the gap and settle direction before implementing — not proposing a specific fix yet.

  • A. Privileged workflow_run split. The unprivileged pull_request run builds images without pushing (e.g. --output type=oci) and uploads them as artifacts; a privileged workflow_run workflow from the default branch (which has secrets) pushes those pre-built images and runs finalize + e2e, reporting status back to the PR head SHA. Restores e2e for forks without handing registry credentials to the untrusted build. Most work.
  • B. ok-to-test label + pull_request_target. A maintainer applies a label that triggers a privileged run over the fork's code with secrets (the Prow pattern). Less work, but it runs untrusted code with registry credentials, which is a weaker security posture and needs careful per-commit SHA pinning.
  • C. Minimal — stop failing on forks. Skip the build/push cleanly on fork PRs so the red ❌ disappears. Does not restore e2e, so the coverage hole in (2) remains; it only fixes the cosmetics of (1).

Notes

  • This affects all external contributors, not any one fork.
  • Required checks on main today are pre-commit and E2E Tests; the Build <pkg> jobs are not required.

Suggested labels: kind/bug, area/ci, priority/important-soon.

Activity

  1. added
    kind/bugCategorizes issue or PR as related to a bug
    priority/important-soonMust be staffed and worked on either currently, or very soon, ideally in time for the next release
    area/ciIssues or PRs related to CI workflows, GitHub Actions, automation
    on Jul 9, 2026
  2. myasnikovdaniil commented on Jul 10, 2026

    @myasnikovdaniil
    Contributor

    Implementing this as Option A (privileged workflow_run split) in #3262.

    It restores e2e for fork PRs while never exposing the registry push credentials to untrusted fork code: the unprivileged pull_request build exports images as OCI archives, and a privileged workflow_run from the default branch pushes them by digest, runs e2e, and reports the required E2E Tests check on the head SHA.

    Option B (pull_request_target + ok-to-test) was rejected — it runs untrusted code with secrets in scope; Option C (build-without-push) restores neither e2e nor the merge gate. The skipped-required-check bypass is closed by making E2E Tests an explicitly-concluded check-run instead of a skippable job (a skipped required job counts as passing).

  3. myasnikovdaniil commented on Jul 15, 2026

    @myasnikovdaniil
    Contributor

    Recording the direction here, since this issue asked to settle it before implementing.

    Decision: Option A — a privileged workflow_run split. The fork's unprivileged pull_request run builds every image to an OCI archive (no push); a privileged workflow_run (running from the default branch) then pushes those archives to the existing CI registry by digest and runs the e2e suite in an ephemeral, credential-less sandbox. Fork build and test code never receive registry credentials — a stronger posture than pull_request_target, which would hand fork code the secrets directly.

    Alternatives considered and why not:

    • pull_request_target — exposes repo secrets to the fork's checked-out code. Rejected on security posture.
    • Skip e2e for forks / maintainer-run only — leaves the merge-gate hole this issue is about (a skipped required check counts as satisfied by branch protection) and shifts the burden onto maintainers every time.

    Implemented in #3262. Flagging so the direction is on the record; happy to revisit if there's a preference before it leaves draft.

  4. 21 remaining items

  5. added a commit that references this issue on Aug 8, 2026
    7dca642
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area/ciIssues or PRs related to CI workflows, GitHub Actions, automationkind/bugCategorizes issue or PR as related to a bugpriority/important-soonMust be staffed and worked on either currently, or very soon, ideally in time for the next release

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions