{"record":{"id":"2cc747300acfd301","repo":"block/buzz","slug":"an-admin-cannot-ban-or-time-out-a-community-owner","errorCode":null,"errorMessage":"an admin cannot ban or time out a community owner or fellow admin","messagePattern":"an admin cannot ban or time out a community owner or fellow admin","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/buzz-relay/src/handlers/moderation_authz.rs","lineNumber":167,"sourceCode":"    channel_role: Option<&str>,\n    action: ModerationAction,\n) -> anyhow::Result<ModerationAuthority> {\n    match actor_role {\n        // Owner holds every capability, community-wide, with no guard rail.\n        Some(\"owner\") => Ok(ModerationAuthority::CommunityOwner),\n        // Admin holds every capability, but cannot ban/timeout the owner or a\n        // fellow admin — only the owner may action an admin. The guard trips only\n        // on a target *role* of owner/admin: a target with no `relay_members` row\n        // (a drive-by spammer who already left) is bannable. Unban/Untimeout lift\n        // a restriction and are intentionally unguarded at this role seam. The\n        // command handler separately rejects a banned actor on every transport,\n        // so the reachable case is an unrestricted admin lifting another admin's\n        // restriction; that remains benign, audited, and owner-reversible.\n        Some(\"admin\") => {\n            if matches!(action, ModerationAction::Ban | ModerationAction::Timeout)\n                && matches!(target_role, Some(\"owner\") | Some(\"admin\"))\n            {\n                anyhow::bail!(\"an admin cannot ban or time out a community owner or fellow admin\");\n            }\n            Ok(ModerationAuthority::CommunityAdmin)\n        }\n        // Not a community owner/admin: channel owner/admin keep channel-local\n        // authority for DeleteMessage/Kick only.\n        _ => match (action, channel_role) {\n            (\n                ModerationAction::DeleteMessage | ModerationAction::Kick,\n                Some(\"owner\") | Some(\"admin\"),\n            ) => Ok(ModerationAuthority::ChannelRole),\n            _ => anyhow::bail!(\"moderator access required\"),\n        },\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;","sourceCodeStart":149,"sourceCodeEnd":185,"githubUrl":"https://github.com/block/buzz/blob/934f3325c3fdaa3a6f23134b74518139aac8ca3f/crates/buzz-relay/src/handlers/moderation_authz.rs#L149-L185","documentation":"Relay-side moderation authorization: an admin holds every moderation capability except ban and timeout against the community owner or a fellow admin — only the owner may action an admin. The target's role is read from relay_members; a target with no membership row (e.g. a drive-by spammer who left) is still bannable. Unban/Untimeout lift restrictions and are intentionally not blocked at this seam.","triggerScenarios":"A kind moderation command (ban or timeout) issued by an actor whose relay_members role is admin, where the target's relay_members role is owner or admin; authorize_moderation_action returns this bail before any state change.","commonSituations":"Two admins in a community and one tries to moderate the other; an admin bot auto-banning spam tries to ban a promoted admin account; confusion over who is allowed to moderate whom after role changes.","solutions":["Have the community owner issue the ban/timeout instead","Or have the owner demote the target admin first, then any admin can act","For lifting a restriction, use unban/untimeout — that path is intentionally allowed and audited"],"exampleFix":"# before (admin tries to ban a fellow admin)\nbuzz moderation ban --user <admin-pubkey>   # 403: an admin cannot ban or time out a community owner or fellow admin\n# after (owner acts)\nbuzz moderation ban --user <admin-pubkey>   # run as the community owner's key","handlingStrategy":"validation","validationCode":"-- Pre-flight for admin-driven ban/timeout: resolve the target's role first\nSELECT role FROM relay_members WHERE pubkey = :target;\n-- if role IN ('owner','admin'), route the action to the community owner instead","typeGuard":"fn admin_may_action(action: ModerationAction, target_role: Option<&str>) -> bool {\n    match target_role {\n        Some(\"owner\") | Some(\"admin\") => !matches!(\n            action,\n            ModerationAction::Ban | ModerationAction::Timeout\n        ),\n        _ => true,\n    }\n}","tryCatchPattern":null,"preventionTips":["Check the target's role with a users/members lookup before offering ban/timeout in admin tooling","Automated moderation bots should run as plain moderators, not admins, to avoid hitting the owner/admin seam entirely"],"tags":["moderation","authorization","roles","relay","nostr"],"backgroundTag":"role-based-access-denied","analyzedSha":"934f3325c3fdaa3a6f23134b74518139aac8ca3f","analyzedAt":"2026-08-20T04:38:24.874Z","contentChangedAt":"2026-08-20T04:38:24.874Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}