fix(security): SOP tier gate authz bypass — drop org-member-as-all-teams fallback (fail-closed) #2326
Reference in New Issue
Block a user
Delete Branch "fix/sop-tier-authz-no-org-fallback"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
HIGH-SEV authz bypass: sop-tier-check.sh credited any org member as a member of EVERY queried team when team probes 403'd → a non-CEO satisfied tier:high. Removes the org-member fallback; cannot-verify team membership now fails LOUD (::error::+exit1), never grants the tier. Proven exploitable on main + fixed (10/10 tests). No overlap with #2323.
SOP Checklist (RFC#351)
sop-tier-check.sh probed team membership at /teams/{id}/members/{user}; if EVERY team probe failed (e.g. 403 — token lacks read:organization, or any visibility/token flakiness), it fell back to /orgs/{org}/members/{user} and credited that org member as a member of EVERY queried team. The evaluator treated those synthetic memberships as real, so a plain NON-CEO org member satisfied tier:high (ceo). An auth/visibility gap became a real highest-tier gate PASS — a privilege-escalation / authorization bypass. Fix (fail-closed authorization — the SOP tier gate is an authz gate): - REMOVE the "org-member ⇒ member of all queried teams" fallback. Org membership is NOT team membership and must never satisfy a team-gated tier. The /orgs/{org}/members/{user} probe is gone entirely. - Classify each team-membership probe explicitly: 200/204 → member (credit) 404 → verified non-member (no credit) 403/401/5xx/curl-failure/other → CANNOT VERIFY Any cannot-verify outcome on ANY probe is a hard infra failure: the gate publishes a loud cannot-verify status and exits non-zero. Inability to verify membership is a FAILURE, never a pass — and never an authz grant. (Same fail-closed principle as the new dev-sop section.) Tests: .gitea/scripts/tests/test_sop_tier_check_authz.sh runs the REAL script end-to-end against a fake-curl Gitea API: S1 team probe 403 + org member not in ceo → tier NOT granted (cannot-verify) S2 genuine ceo team member (204) → granted S3 org member, verified 404 non-member of ceo → never synthetic-credited All 3 pass on the fixed script; S1+S3 FAIL on origin/main (proves the bug). Coordination: no overlap with #2323 (fix/core-ci-fail-closed) — that PR touches sop-tier-refire.sh + the sop-tier-check.yml workflow env (removes SOP_FAIL_OPEN); this PR touches only the membership-resolution hunk of sop-tier-check.sh. Complementary: #2323 makes infra faults fail closed at the workflow level; this makes unverifiable team membership fail closed inside the script. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>SOP-ack (engineers, non-author core-security) for engineers-class items:
/sop-ack comprehensive-testing
/sop-ack local-postgres-e2e
/sop-ack staging-smoke
/sop-ack five-axis-review
/sop-ack memory-consulted
SOP-ack (ceo, non-author hongming-ceo-delegated) for high-risk items — CTO sign-off on root-cause + no-shim for this tier:high security change:
/sop-ack root-cause
/sop-ack no-backwards-compat
qa-review APPROVE (core-qa): checklist testing claims are consistent with the diff; CI / all-required green on head. SOP qa gate satisfied.
security-review APPROVE (core-security): fail-closed / no-silent-skip posture verified for the security surface in this change. SOP security gate satisfied.
ceo APPROVE (hongming-ceo-delegated): CTO sign-off for tier:high — security/fail-closed change, root-cause addressed, no shim. sop-tier-check ceo clause satisfied.
/qa-recheck /security-recheck /refire-tier-check
/security-recheck
/refire-tier-check
/security-recheck
/refire-tier-check
5-axis review at current head
9bb903c565.Correctness: APPROVED. The previous org-member-as-all-teams fallback is removed, and team membership now has explicit member / not-member / cannot-verify outcomes. Cannot-verify exits non-zero before tier expression evaluation, which is the right behavior for an authorization gate.
Security/robustness: this closes the privilege escalation where a token-scope or visibility gap could promote a plain org member into every queried team. Failing closed on 401/403/5xx/curl failure is appropriate for SOP tier authorization. Performance impact is bounded to existing team probes; readability is improved by documenting the state machine. Tests exercise the real script path with fake curl fixtures, including the non-CEO org-member bypass class.
Cross-PR overlap guard: overlaps conceptually with #2323's SOP fail-closed sweep, but this PR changes team-membership authorization in sop-tier-check.sh while #2323 changes status/refire/fail-open plumbing and branch-protection lints. The behaviors are additive fail-closed hardening, not contradictory.
APPROVED on current head.
Step-1 official-access confirmation plus 5-axis check: the SOP tier gate authz fix removes the org-member-as-all-teams fallback and fails closed; this is the intended security behavior. Existing team membership paths remain intact, no new secret/auth bypass or SSRF surface is introduced, performance impact is negligible, and the change is readable with current CI status checked before review.