From f24939564477c42f2e8ce6c370801abf4bb1db22 Mon Sep 17 00:00:00 2001 From: adlee-was-taken Date: Fri, 19 Jun 2026 20:00:09 -0400 Subject: [PATCH] =?UTF-8?q?fix(plan/C1):=20close=20Admin=E2=86=92Owner=20e?= =?UTF-8?q?scalation=20in=20enforce=5Fowner=5Fonly=5Felevation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Spot-check of the new H-C1 hook code found the owner-only-elevation gate was bypassable: it skipped any member ALREADY privileged in the parent, but since Admin is also "privileged", an Admin→Owner promotion was skipped and accepted — the exact escalation the gate exists to stop, and a failure of its own paired test. Gate now skips only UNCHANGED roles (parent role == new role), so every change into a privileged role (Member→Admin/Owner, Admin→Owner, new privileged member) requires an owner signer. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../plans/2026-06-06-enterprise-org-vault.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/docs/superpowers/plans/2026-06-06-enterprise-org-vault.md b/docs/superpowers/plans/2026-06-06-enterprise-org-vault.md index 549c097..d3b0b2e 100644 --- a/docs/superpowers/plans/2026-06-06-enterprise-org-vault.md +++ b/docs/superpowers/plans/2026-06-06-enterprise-org-vault.md @@ -4583,12 +4583,14 @@ fn enforce_owner_only_elevation( if !is_privileged(m.role) { continue; } - // Privileged now. Was it already privileged in the parent (no change)? - let already = parent_role(m.member_id.as_str()) - .map(is_privileged) - .unwrap_or(false); - if already { - continue; // not an introduction or elevation + // Privileged now. Skip ONLY if the role is unchanged from the parent + // (a no-op same-role entry). Any CHANGE into a privileged role — a new + // privileged member, Member→Admin/Owner, or Admin→Owner — must be + // owner-signed. (A naive "already privileged" skip is WRONG: Admin is + // also privileged, so it would let an Admin be elevated to Owner + // unchecked — the exact escalation this gate exists to stop.) + if parent_role(m.member_id.as_str()) == Some(m.role) { + continue; // unchanged role — not an introduction or elevation } // A new owner/admin, or a member elevated to owner/admin → owner-only. if !signer.role.can_manage_owners() {