File: crates/state/src/materialize.rs:222
Severity: security
Obvious? no
apply_proposed_action::RevokeAdmin only refuses to apply when state.admins.len() == 1 && state.admins.contains(peer_id) (line 224). There is no exemption for state.genesis_author.
Bypass: once two admins exist, a malicious admin emits Propose { action: RevokeAdmin { peer_id: genesis_author } }; if a second admin votes yes (or threshold = Count(1) was set earlier), check_and_apply_proposal fires state.admins.remove(&genesis_author). After that point the genesis owner can no longer satisfy is_admin() in check_permission (materialize.rs:107), so every subsequent Propose/Vote they sign is rejected — including the owner_override path on line 197-200 (gated on prop.proposer being admin via the upstream check). Impact: collusion of any admin majority permanently strips the server owner of the "unilateral push" they are documented to retain in docs/specs/2026-04-12-state-authority-and-mutations.md and CLAUDE.md ("Owner is root of trust").
Fix: in apply_proposed_action::RevokeAdmin, add if state.genesis_author == Some(*peer_id) { return; } ahead of state.admins.remove(peer_id) (and the same guard for KickMember, since kick also strips admin and removes membership). The owner role must be structurally non-revocable, exactly the way Permission::__UnknownLegacy is structurally inert.
Filed by /general-audit @ 88498a5 (2026-05-04). master: #600.
File:
crates/state/src/materialize.rs:222Severity: security
Obvious? no
apply_proposed_action::RevokeAdminonly refuses to apply whenstate.admins.len() == 1 && state.admins.contains(peer_id)(line 224). There is no exemption forstate.genesis_author.Bypass: once two admins exist, a malicious admin emits
Propose { action: RevokeAdmin { peer_id: genesis_author } }; if a second admin votes yes (or threshold =Count(1)was set earlier),check_and_apply_proposalfiresstate.admins.remove(&genesis_author). After that point the genesis owner can no longer satisfyis_admin()incheck_permission(materialize.rs:107), so every subsequentPropose/Votethey sign is rejected — including theowner_overridepath on line 197-200 (gated onprop.proposerbeing admin via the upstream check). Impact: collusion of any admin majority permanently strips the server owner of the "unilateral push" they are documented to retain indocs/specs/2026-04-12-state-authority-and-mutations.mdand CLAUDE.md ("Owner is root of trust").Fix: in
apply_proposed_action::RevokeAdmin, addif state.genesis_author == Some(*peer_id) { return; }ahead ofstate.admins.remove(peer_id)(and the same guard forKickMember, since kick also strips admin and removes membership). The owner role must be structurally non-revocable, exactly the wayPermission::__UnknownLegacyis structurally inert.Filed by
/general-audit@88498a5(2026-05-04). master: #600.