Some checks failed
Block internal-flavored paths / Block forbidden paths (pull_request) Successful in 5s
Lint curl status-code capture / Scan workflows for curl status-capture pollution (pull_request) Successful in 10s
Secret scan / Scan diff for credential-shaped strings (pull_request) Successful in 13s
qa-review / approved (pull_request) Failing after 13s
security-review / approved (pull_request) Failing after 13s
sop-tier-check / tier-check (pull_request) Successful in 14s
gate-check-v3 / gate-check (pull_request) Failing after 22s
E2E Staging Canvas (Playwright) / detect-changes (pull_request) Successful in 25s
CI / Detect changes (pull_request) Successful in 25s
E2E API Smoke Test / detect-changes (pull_request) Successful in 26s
Handlers Postgres Integration / detect-changes (pull_request) Successful in 28s
Runtime PR-Built Compatibility / detect-changes (pull_request) Successful in 27s
E2E Staging Canvas (Playwright) / Canvas tabs E2E (pull_request) Successful in 4s
CI / Platform (Go) (pull_request) Successful in 3s
CI / Shellcheck (E2E scripts) (pull_request) Successful in 4s
CI / Canvas (Next.js) (pull_request) Successful in 5s
CI / Python Lint & Test (pull_request) Successful in 4s
E2E API Smoke Test / E2E API Smoke Test (pull_request) Successful in 5s
Handlers Postgres Integration / Handlers Postgres Integration (pull_request) Successful in 5s
Runtime PR-Built Compatibility / PR-built wheel + import smoke (pull_request) Successful in 5s
CI / Canvas Deploy Reminder (pull_request) Has been skipped
audit-force-merge / audit (pull_request) Successful in 13s
Token (especially long-lived RFC_324_TEAM_READ_TOKEN org-secret)
passed via -H "Authorization: token ${TOKEN}" is visible in
/proc/<pid>/cmdline and ps -ef on the runner host.
Fix: write token to a mode-600 temp file and pass it to curl via
-K (curl config file). The token never appears in the argv of any
process; curl reads it from the fd-backed file.
Affected:
- .gitea/scripts/review-check.sh: CURL_AUTH_FILE + -K on all 3 curl calls
- .gitea/workflows/qa-review.yml: privilege-check inline curl
- .gitea/workflows/security-review.yml: privilege-check inline curl
Fixes: #541
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
165 lines
7.7 KiB
YAML
165 lines
7.7 KiB
YAML
# qa-review — non-author APPROVE from the `qa` Gitea team required to merge.
|
||
#
|
||
# RFC#324 Step 1 of 5 (workflow-add). Pairs with `security-review.yml` and the
|
||
# branch-protection flip in Step 2.
|
||
#
|
||
# === DESIGN (RFC#324 v1.1 addendum) ===
|
||
#
|
||
# A1-α (refire mechanism):
|
||
# Triggers on:
|
||
# - `pull_request_target`: opened, synchronize, reopened
|
||
# → initial status posts when PR opens / re-pushes
|
||
# - `issue_comment`: /qa-recheck slash-command on the PR
|
||
# → manual re-fire after a QA reviewer clicks APPROVE
|
||
# (Gitea 1.22.6 doesn't re-fire on pull_request_review, per
|
||
# go-gitea/gitea#33700 + feedback_pull_request_review_no_refire)
|
||
# Workflow name = `qa-review` ; job name = `approved`.
|
||
# The job's own pass/fail conclusion publishes the status context
|
||
# `qa-review / approved (<event>)` — NO `POST /statuses` call → NO
|
||
# write:repository token scope needed. Sidesteps internal#321 defect #2.
|
||
#
|
||
# A1.1 (privilege check on slash-comment — INFORMATIONAL ONLY, NOT a gate):
|
||
# The `issue_comment` event fires for ANY commenter, including
|
||
# non-collaborators. The original (v1.2) design gated the eval step
|
||
# behind a collaborator probe → if a non-collaborator commented
|
||
# /qa-recheck, the eval was `if:`-skipped → the job exited 0 anyway →
|
||
# the status context published `success` with ZERO real APPROVE.
|
||
# That was a fail-open: any visitor could green the gate.
|
||
#
|
||
# RFC#324 v1.3 §A1.1 correction (option b per hongming-pc 1421):
|
||
# drop privilege-gating of the evaluation entirely. The eval is
|
||
# read-only and idempotent — it reads `pulls/{N}/reviews` and
|
||
# `teams/{id}/members/{u}` (both API-side state that a commenter can't
|
||
# change). Re-running it on a non-collaborator's comment is harmless
|
||
# AND correct: if a real team-member APPROVE exists, the eval flips
|
||
# green; if not, it stays red.
|
||
#
|
||
# We KEEP the privilege step as a `::notice::` log line only — useful
|
||
# for griefer-spotting (one operator spamming /recheck) without
|
||
# touching the gate. If rate-limiting is needed later, add it as a
|
||
# separate concern (time-window throttle, not a privilege gate).
|
||
#
|
||
# We MUST NOT use `github.event.comment.author_association` (the
|
||
# field doesn't exist on Gitea 1.22.6 webhook payload — this was
|
||
# sop-tier-refire's defect #1).
|
||
#
|
||
# A4 (no PR-head checkout under pull_request_target):
|
||
# We check out the BASE ref explicitly so the review-check.sh script is
|
||
# loaded from trusted source. We NEVER use `ref: ${{ github.event.pull_request.head.sha }}`.
|
||
# No PR-head code is executed in the runner. Trust boundary preserved.
|
||
#
|
||
# A5 (real Gitea team):
|
||
# `qa` team (id=20) verified by orchestrator preflight 2026-05-11; queried
|
||
# at run time via /api/v1/teams/20/members/{login}.
|
||
#
|
||
# === TOKEN ===
|
||
#
|
||
# The workflow reads PR state, PR reviews, and team membership.
|
||
# Gitea 1.22.6's /api/v1/teams/{id}/members/{u} returns 403 ('Must be a
|
||
# team member') for tokens whose owner is not in that team. The default
|
||
# `secrets.GITHUB_TOKEN` is owned by a workflow-scoped identity that is
|
||
# also not in qa/security teams → also 403.
|
||
#
|
||
# Resolution: a dedicated `RFC_324_TEAM_READ_TOKEN` secret, owned by an
|
||
# identity that IS in both `qa` and `security` teams (Owners-tier
|
||
# claude-ceo-assistant, or a new service-bot added to both teams).
|
||
# Provisioning of this secret is tracked as a follow-up issue (filed by
|
||
# core-devops at PR open).
|
||
#
|
||
# Until that secret is provisioned, the job will exit 1 with a clear
|
||
# 403-on-team-probe error and the `qa-review / approved` status will
|
||
# stay `failure`. This is the correct fail-closed behavior — the gate
|
||
# blocks merge until both (a) a QA team member APPROVEs and (b) the
|
||
# workflow has a token that can confirm their team membership.
|
||
#
|
||
# === SLASH-COMMAND CONTRACT ===
|
||
#
|
||
# /qa-recheck — re-evaluate the gate (e.g. after an APPROVE lands)
|
||
#
|
||
# Open to any PR commenter. The eval is read-only and idempotent, so
|
||
# unprivileged refires are harmless (RFC#324 v1.3 §A1.1). Collaborator
|
||
# status is logged for griefer-spotting but does NOT gate execution.
|
||
|
||
name: qa-review
|
||
|
||
on:
|
||
pull_request_target:
|
||
types: [opened, synchronize, reopened]
|
||
issue_comment:
|
||
types: [created]
|
||
|
||
permissions:
|
||
contents: read
|
||
pull-requests: read
|
||
|
||
jobs:
|
||
approved:
|
||
# Gate the job:
|
||
# - On pull_request_target events: always run.
|
||
# - On issue_comment events: only when it's a PR comment and the body
|
||
# contains the slash-command. NO privilege gate at the step level
|
||
# (RFC#324 v1.3 §A1.1): a non-collaborator's /qa-recheck is fine
|
||
# because the eval is read-only and idempotent — re-running it
|
||
# just re-confirms whether a real team-member APPROVE exists.
|
||
if: |
|
||
github.event_name == 'pull_request_target' ||
|
||
(github.event_name == 'issue_comment' &&
|
||
github.event.issue.pull_request != null &&
|
||
startsWith(github.event.comment.body, '/qa-recheck'))
|
||
runs-on: ubuntu-latest
|
||
steps:
|
||
- name: Privilege check (A1.1 — INFORMATIONAL log only, NOT a gate)
|
||
# RFC#324 v1.3 §A1.1: this step does NOT gate subsequent steps.
|
||
# It exists solely as a log line for griefer-spotting (one
|
||
# operator spamming /qa-recheck without merit). Re-running the
|
||
# read-only eval on a non-collaborator comment is harmless;
|
||
# gating it would be fail-open (skipped steps still publish
|
||
# `success` for the job's status context).
|
||
# Only runs on issue_comment events; pull_request_target has
|
||
# no comment.user.login so the step is a no-op skip there.
|
||
if: github.event_name == 'issue_comment'
|
||
env:
|
||
GITEA_TOKEN: ${{ secrets.RFC_324_TEAM_READ_TOKEN || secrets.GITHUB_TOKEN }}
|
||
run: |
|
||
set -euo pipefail
|
||
login="${{ github.event.comment.user.login }}"
|
||
# Write token to a mode-600 file so it never appears in curl's argv.
|
||
# (#541: -H "Authorization: token $TOKEN" puts the secret in /proc/<pid>/cmdline)
|
||
authfile=$(mktemp)
|
||
chmod 600 "$authfile"
|
||
printf 'header = "Authorization: token %s"\n' "$GITEA_TOKEN" > "$authfile"
|
||
code=$(curl -sS -o /dev/null -w '%{http_code}' -K "$authfile" \
|
||
"${{ github.server_url }}/api/v1/repos/${{ github.repository }}/collaborators/${login}")
|
||
rm -f "$authfile"
|
||
if [ "$code" = "204" ]; then
|
||
echo "::notice::Recheck from ${login} (collaborator=true)"
|
||
else
|
||
echo "::notice::Recheck from ${login} (collaborator=false, HTTP ${code}) — proceeding with read-only eval anyway"
|
||
fi
|
||
|
||
- name: Check out BASE ref (A4 — never PR-head)
|
||
# Loads the review-check.sh script from a trusted ref. For
|
||
# pull_request_target the default checkout is BASE already; we
|
||
# set ref explicitly for the issue_comment event too so the
|
||
# script source is always the default-branch version.
|
||
# NEVER use ref: ${{ github.event.pull_request.head.sha }} —
|
||
# that would execute PR-head code with secrets-context.
|
||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||
with:
|
||
ref: ${{ github.event.repository.default_branch }}
|
||
|
||
- name: Evaluate qa-review
|
||
env:
|
||
GITEA_TOKEN: ${{ secrets.RFC_324_TEAM_READ_TOKEN || secrets.GITHUB_TOKEN }}
|
||
GITEA_HOST: git.moleculesai.app
|
||
REPO: ${{ github.repository }}
|
||
# PR number lives in different places per event:
|
||
# pull_request_target → github.event.pull_request.number
|
||
# issue_comment → github.event.issue.number
|
||
PR_NUMBER: ${{ github.event.pull_request.number || github.event.issue.number }}
|
||
TEAM: qa
|
||
TEAM_ID: '20'
|
||
REVIEW_CHECK_DEBUG: '0'
|
||
REVIEW_CHECK_STRICT: '0'
|
||
run: bash .gitea/scripts/review-check.sh
|