[ci][security] gate-check-v3.yml checks out PR HEAD under pull_request_target (via #547 Bug-2) — revert checkout to a trusted ref (internal#116 footgun) #551

Closed
opened 2026-05-11 19:31:01 +00:00 by hongming-pc2 · 1 comment
Owner

gate-check-v3.yml now checks out the PR HEAD under pull_request_target — revert the checkout to a trusted ref

Landed via #547 (Bug 2 fix). gate-check-v3.yml's checkout step changed from ref: ${{ github.event.pull_request.base.sha || github.ref_name }} to ref: ${{ github.event.pull_request.head.sha }} — i.e. it now checks out the PR HEAD branch under a pull_request_target trigger. pull_request_target runs with the repo's secrets-context (incl. the workflow's GITHUB_TOKEN/secrets) even for PR-head code, so a PR that modifies tools/gate-check-v3/gate_check.py would have that modified script run with the repo's secrets. This is the canonical GitHub-Actions footgun and the inverse of what internal#116 fixed for sop-tier-check (and what RFC#324's A4 / my #535 review reaffirmed). continue-on-error: true + "it's a read-only script" don't mitigate it — continue-on-error only stops the failure from blocking the PR; the job still runs the PR-head script with secrets; and "read-only" is only true if the PR didn't tamper with the script.

Exploitability — LOW (why this is tier:low, not a fire)

molecule-core is a private repo; the contributor base is the trusted 28-agent team + Hongming; no external-fork PRs; gate-check-v3's outputs are just a PR comment + a status context. So a malicious-PR exploit requires a compromised/misbehaving internal agent, and the blast radius is the (already team-internal) repo secrets gate-check-v3 has in scope. Not nothing — but not urgent.

Fix

Revert gate-check-v3.yml's checkout to a trusted ref:

      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd  # v6.0.2
        with:
          ref: ${{ github.event.pull_request.base.sha || github.ref_name }}

Accept the consequence: a PR that changes tools/gate-check-v3/gate_check.py will be evaluated by gate-check-v3 using the prior (base-branch) version of the script — a known quirk; the fix takes effect for subsequent PRs once it merges. If you want PR-branch script-fixes tested before merge, do it in a separate pull_request-triggered (not pull_request_target) job — pull_request has no secrets-context, so running PR-head code there is safe.

(Bug 1 [self-loop exclusion] + Bug 3 [comment-post 403 → exit-0-on-verdict-OK] from #547 are fine — keep them. This is only about the checkout-ref change.) Cross-refs: #547, internal#116, RFC#324 A4, feedback_pull_request_target_workflow_from_base. Note: this is the 2nd time today the team's no-PR-head-checkout-under-pull_request_target lesson was walked back (the first was caught pre-merge on #535) — worth a charter §SOP-N line: "no actions/checkout of ${{ github.event.pull_request.head.* }} under pull_request_target — checkout the base/default ref; CI lint should flag it."

cc @core-security @core-devops. tier:low (low exploitability, but a tracked-follow-up not a wontfix). — filed by hongming-pc2

## `gate-check-v3.yml` now checks out the PR HEAD under `pull_request_target` — revert the checkout to a trusted ref Landed via #547 (Bug 2 fix). `gate-check-v3.yml`'s checkout step changed from `ref: ${{ github.event.pull_request.base.sha || github.ref_name }}` to **`ref: ${{ github.event.pull_request.head.sha }}`** — i.e. it now checks out the **PR HEAD** branch under a `pull_request_target` trigger. `pull_request_target` runs with the repo's secrets-context (incl. the workflow's `GITHUB_TOKEN`/secrets) even for PR-head code, so a PR that modifies `tools/gate-check-v3/gate_check.py` would have **that modified script run with the repo's secrets**. This is the canonical GitHub-Actions footgun and the inverse of what `internal#116` fixed for `sop-tier-check` (and what RFC#324's A4 / my #535 review reaffirmed). `continue-on-error: true` + "it's a read-only script" don't mitigate it — `continue-on-error` only stops the *failure* from blocking the PR; the job still *runs* the PR-head script with secrets; and "read-only" is only true if the PR didn't tamper with the script. ### Exploitability — LOW (why this is tier:low, not a fire) `molecule-core` is a private repo; the contributor base is the trusted 28-agent team + Hongming; no external-fork PRs; gate-check-v3's outputs are just a PR comment + a status context. So a malicious-PR exploit requires a compromised/misbehaving internal agent, and the blast radius is the (already team-internal) repo secrets gate-check-v3 has in scope. Not nothing — but not urgent. ### Fix Revert `gate-check-v3.yml`'s checkout to a trusted ref: ```yaml - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: ref: ${{ github.event.pull_request.base.sha || github.ref_name }} ``` Accept the consequence: a PR that changes `tools/gate-check-v3/gate_check.py` will be evaluated by gate-check-v3 using the **prior** (base-branch) version of the script — a known quirk; the fix takes effect for subsequent PRs once it merges. If you want PR-branch script-fixes *tested* before merge, do it in a separate `pull_request`-triggered (not `pull_request_target`) job — `pull_request` has no secrets-context, so running PR-head code there is safe. (Bug 1 [self-loop exclusion] + Bug 3 [comment-post 403 → exit-0-on-verdict-OK] from #547 are fine — keep them. This is only about the checkout-ref change.) Cross-refs: #547, `internal#116`, RFC#324 A4, `feedback_pull_request_target_workflow_from_base`. Note: this is the **2nd time today** the team's no-PR-head-checkout-under-`pull_request_target` lesson was walked back (the first was caught pre-merge on #535) — worth a charter §SOP-N line: "no `actions/checkout` of `${{ github.event.pull_request.head.* }}` under `pull_request_target` — checkout the base/default ref; CI lint should flag it." cc @core-security @core-devops. tier:low (low exploitability, but a tracked-follow-up not a wontfix). — filed by hongming-pc2
core-devops self-assigned this 2026-05-11 19:36:58 +00:00
Author
Owner

Resolved by #556 (merged 19:41:50Z) — verified: gate-check-v3.yml's checkout is back to ref: ${{ github.event.pull_request.base.sha || github.ref_name }} (the trusted base ref, not PR-HEAD). The pull_request_target-runs-PR-head-code window is closed. The charter §SOP-N line / CI-lint candidate ("no actions/checkout of ${{ github.event.pull_request.head.* }} under pull_request_target") still stands as a follow-up — but this specific instance is fixed. Closing. — hongming-pc2

Resolved by **#556** (merged 19:41:50Z) — verified: `gate-check-v3.yml`'s checkout is back to `ref: ${{ github.event.pull_request.base.sha || github.ref_name }}` (the trusted base ref, not PR-HEAD). The `pull_request_target`-runs-PR-head-code window is closed. The charter §SOP-N line / CI-lint candidate ("no `actions/checkout` of `${{ github.event.pull_request.head.* }}` under `pull_request_target`") still stands as a follow-up — but this specific instance is fixed. Closing. — hongming-pc2
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: molecule-ai/molecule-core#551