From 5fe335ffae9e346813f825cace7c0ab58297b53d Mon Sep 17 00:00:00 2001 From: claude-ceo-assistant Date: Fri, 8 May 2026 18:48:35 -0700 Subject: [PATCH] =?UTF-8?q?fix(sop-tier-check):=20use=20pull=5Frequest=5Ft?= =?UTF-8?q?arget=20=E2=80=94=20pull=5Frequest=20leaks=20token?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fans the security fix from internal#116 (cce89067) to molecule-core. Same rationale: pull_request loads workflow from PR HEAD, allowing any write-access contributor to rewrite the workflow file in their PR and exfiltrate SOP_TIER_CHECK_TOKEN. pull_request_target loads from base (main), neutralising the attack. Verified post-merge on internal: synthetic PR rewriting the workflow to print the token did NOT execute the modified version — main's pull_request_target version ran instead. ATTACK_PROBE never fired. Co-Authored-By: Claude Opus 4.7 (1M context) --- .gitea/workflows/sop-tier-check.yml | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/.gitea/workflows/sop-tier-check.yml b/.gitea/workflows/sop-tier-check.yml index db720f2f..2aaa13b7 100644 --- a/.gitea/workflows/sop-tier-check.yml +++ b/.gitea/workflows/sop-tier-check.yml @@ -31,8 +31,22 @@ name: sop-tier-check +# SECURITY: triggers MUST use `pull_request_target`, not `pull_request`. +# `pull_request_target` loads the workflow definition from the BASE +# branch (i.e. `main`), not the PR's HEAD. With `pull_request`, anyone +# with write access to a feature branch could rewrite this file in +# their PR to dump SOP_TIER_CHECK_TOKEN (org-read scope) to logs and +# exfiltrate it. Verified 2026-05-09 against Gitea 1.22.6 — +# `pull_request_target` (added in Gitea 1.21 via go-gitea/gitea#25229) +# is the documented mitigation. +# +# This workflow does NOT call `actions/checkout`, so no untrusted code +# is ever executed in the runner — we only HTTP-call the Gitea API. If +# a future change adds a checkout step, it MUST pin to +# `${{ github.event.pull_request.base.sha }}` (NOT `head.sha`) to keep +# the trust boundary. on: - pull_request: + pull_request_target: types: [opened, edited, synchronize, reopened, labeled, unlabeled] pull_request_review: types: [submitted, dismissed, edited]