Skip to content

fix(ci): run e2e-local on labeled fork PRs via pull_request_target - #987

Merged
DorianZheng merged 1 commit into
mainfrom
worktree-lazy-orbiting-spark
Jul 14, 2026
Merged

DorianZheng merged 1 commit into
mainfrom
worktree-lazy-orbiting-spark

Conversation

@DorianZheng

@DorianZheng DorianZheng commented Jul 14, 2026 •

Copy link
Copy Markdown
Member

Summary

Run 29324915498 failed on fork PR #982: GitHub withholds secrets and repository variables from fork pull_request runs (Secret source: None), so the maintainer-applied e2e-local label sent the run into start-runner, where create-github-app-token failed with "Input required and not supplied: app-id". The label contract ("label ⇒ e2e runs") was implemented on an event type that structurally cannot honor it for forks.

Switch the PR trigger to label-gated pull_request_target (base context: credentials available, workflow definition always from main), hardened per the standard pattern (protobuf test_runner.yml, GitHub security-hardening guidance):

  • Per-push fork approval — a fork PR runs only on the e2e-local labeling event, pinned to the labeled head SHA; pushes after approval are skipped with a notice until a maintainer re-labels. Same-repo PRs keep today's behavior (re-run on every push while labeled).
  • Checkout pins head.sha — pull_request_target resolves to the base branch by default, which would silently test main; persist-credentials: false so no token remains on the persistent runner when fork code runs.
  • Read-only default token — top-level permissions: contents: read (the pull_request_target default is write-capable); should-run gate inputs move from inline ${{ }} interpolation into env:.

Blast radius of an approved-then-malicious fork run stays contained: secrets exist only in start-runner/stop-runner (GitHub-hosted, never execute PR code), and the runner's instance profile allows only self-termination (scripts/ci/setup-ci-runner.sh).

Verification

  • actionlint: identical 2 pre-existing findings as HEAD's version — zero new findings
  • Gate behavior: the should-run script extracted from the YAML and executed across 9 event scenarios passes 9/9 (push/dispatch run; same-repo labeled runs on label+synchronize; fork runs only on the e2e-local labeling event; stale/unapproved fork events skip)
  • Regression side: HEAD's gate script, given the failing run's exact inputs (pull_request, fork, e2e-local present), returns run=true — the path into the credential-less crash

After merge: remove and re-add e2e-local on #982 to run e2e against its current head.

https://claude.ai/code/session_01HkVMvjNbCKPrbwxps95jTp

Summary by CodeRabbit

  • CI Improvements

    • Updated end-to-end test workflows to run securely for forked pull requests.
    • Tests now run only when the e2e-local label is applied, using the labeled commit.
    • After new changes are pushed to a forked pull request, remove and re-add the label to run tests again.
  • Documentation

    • Clarified labeling behavior, permissions, and troubleshooting guidance for local end-to-end tests.

Fork-PR `pull_request` runs get no secrets and no repository variables,
so a maintainer-labeled fork PR passed the label gate and crashed in
start-runner ("Input required and not supplied: app-id", run
29324915498). Switch the PR trigger to `pull_request_target` so labeled
PRs run with base-repo credentials, hardened for untrusted code:

- fork approval is per-push: only the `e2e-local` labeling event runs a
  fork PR, pinned to the labeled head SHA; new pushes need a re-label
  (same-repo PRs keep re-running on every push while labeled)
- checkout pins github.event.pull_request.head.sha (pull_request_target
  resolves to the base branch by default) with persist-credentials off
- top-level `permissions: contents: read` keeps the default token
  read-only in e2e-tests; gate inputs move from inline ${{ }} to env

Claude-Session: https://claude.ai/code/session_01HkVMvjNbCKPrbwxps95jTp
@coderabbitai

coderabbitai Bot commented Jul 14, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 66492a9c-c630-4f00-a7c0-abd9aa516f0b

📥 Commits

Reviewing files that changed from the base of the PR and between 48c2435 and f1c40a1.

📒 Files selected for processing (3)
  • .github/workflows/README.md
  • .github/workflows/e2e-local.yml
  • docs/ci/e2e-local.md

📝 Walkthrough

Walkthrough

The e2e-local workflow now uses pull_request_target, read-only default permissions, label-based fork gating, and pull request head-SHA checkout. Documentation describes labeled fork commit behavior and relabeling requirements after new pushes.

Changes

e2e-local fork gating

Layer / File(s) Summary
Workflow trigger and label gate
.github/workflows/e2e-local.yml, .github/workflows/README.md, docs/ci/e2e-local.md
PR events use pull_request_target; should-run permits same-repository labeled runs and fork labeling events, while documentation explains relabeling after fork pushes.
PR head commit checkout
.github/workflows/e2e-local.yml
The test job checks out github.event.pull_request.head.sha for pull request target events and disables credential persistence.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant GitHub
  participant should-run
  participant e2e-tests
  GitHub->>should-run: labeled or synchronize pull_request_target event
  should-run->>should-run: inspect e2e-local label and event action
  should-run->>e2e-tests: enable run for permitted labeled commit
  e2e-tests->>e2e-tests: checkout pull request head SHA
Loading

Possibly related PRs

Suggested labels: e2e-local

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch worktree-lazy-orbiting-spark

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@cla-assistant

cla-assistant Bot commented Jul 14, 2026

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.


tester seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account.
You have signed the CLA already but the status is still pending? Let us recheck it.

@DorianZheng
DorianZheng marked this pull request as ready for review July 14, 2026 15:12
@DorianZheng
DorianZheng requested a review from a team July 14, 2026 15:12
@boxlite-agent

boxlite-agent Bot commented Jul 14, 2026 •

Copy link
Copy Markdown

📦 BoxLite review — looks good · f1c40a1

Review evidence

  • ✅ git diff --numstat origin/main...HEAD && git diff origin/main...HEAD — 3 files changed, CI workflow + docs only
  • ✅ python3 -c "import yaml; yaml.safe_load(open('.github/workflows/e2e-local.yml'))" — YAML parses, 4 jobs present
  • ✅ bash repro of should-run gate logic (fork+synchronize scenario) — correctly returns run=false, matches doc'd re-label requirement
  • ✅ grep secrets./permissions: across e2e-local.yml — secrets/GH App token confined to start-runner (ephemeral), not e2e-tests
  • ✅ grep IAM policy in scripts/ci/setup-ci-runner.sh — instance profile role scoped to ec2:TerminateInstances only

Risk notes

  • pull_request_target + fork code exec — switch from pull_request to pull_request_target intentionally grants secrets/vars to start-runner for labeled fork PRs; e2e-tests (which runs untrusted fork code as root on a persistent self-hosted EC2 instance) itself gets only the default read-only token per top-level permissions:contents:read, and no secrets — verified via grep. Instance-profile IAM confirmed limited to self-terminate.
  • gate correctness — should-run job uses env: indirection (not inline ${{ }} interpolation) for untrusted-ish fields like ADDED_LABEL/HEAD_REPO, avoiding script-injection; verified fork/same-repo/labeled/synchronize branches by hand-running the shell logic — behaves as documented (fork needs fresh 'labeled' event matching e2e-local; synchronize alone does not re-run).
  • checkout ref pinning — ref: pull_request.head.sha with persist-credentials:false prevents pull_request_target's default base-branch checkout trap and avoids leaving a token on the persistent runner; only docs/workflow/README changed, not verified against a live GitHub Actions run (no network trigger available in this sandbox).
  • residual/inherent risk — e2e-tests runs on a persistent (not ephemeral) self-hosted runner as root, so a malicious labeled fork PR could still attempt runner-level persistence (cache/toolchain tampering) beyond the single job; this is an accepted, pre-existing architecture tradeoff the maintainer-only label gate is meant to bound, not a regression introduced by this diff.
.github/workflows/e2e-local.yml
  on.pull_request_target  +46/-10  pull_request -> pull_request_target trigger
  jobs.should-run.steps.Check trigger conditions  included above  fork-vs-same-repo per-push label gate
  permissions (top-level)  +6/-0  pins default token to contents:read
  jobs.e2e-tests.steps.Checkout code  +6/-0  pins ref to approved head SHA
docs/ci/e2e-local.md
  +5/-1  documents fork re-label requirement
.github/workflows/README.md
  +2/-1  one-line summary of fork behavior

reviewed f1c40a1 in a BoxLite microVM · @boxlite-agent review to re-run · powered by BoxLite

@DorianZheng
DorianZheng merged commit 1cdc8f1 into main Jul 14, 2026
28 of 31 checks passed
@DorianZheng
DorianZheng deleted the worktree-lazy-orbiting-spark branch July 14, 2026 15:13
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.

1 participant