{"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":"validation","errorClass":"IngestError::Rejected","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/f956e6fe06a76e50cbd8fba1a162482e752e7f1a/crates/buzz-relay/src/handlers/moderation_authz.rs#L149-L185","documentation":"Moderation authorization in the relay: an actor whose relay_members role is 'admin' holds community-wide capabilities, but the Ban and Timeout actions are explicitly capped — they may never target a member whose role is 'owner' or 'admin'. Only the community owner may action an admin (and nobody but the process owner path actions the owner). The guard keys on the TARGET's role row only: a target with no relay_members row (e.g. a drive-by spammer who left) is still bannable. Unban/Untimeout are intentionally NOT guarded at this seam — lifting another admin's restriction is benign, audited, and owner-reversible — and the command handler separately rejects banned actors on every transport.","triggerScenarios":"A community admin sends a kind:900x ban/timeout moderation event (via WebSocket NIP-29 or the equivalent HTTP path) whose p-tag targets the community owner's pubkey or another admin's pubkey. The authz resolution sees actor role 'admin' + target role 'owner'/'admin' + action Ban|Timeout and bails before any state change.","commonSituations":"Two admins in a heated channel, one tries to timeout the other; automation/bot accounts with admin role applying anti-spam bans that sweep an admin's pubkey; attempts to ban the owner during a dispute. Legitimate path: have the OWNER issue the ban/timeout against the admin.","solutions":["Route the action through the community owner: the owner's key signs the ban/timeout against the admin target — that path is permitted.","If the target admin must be stopped urgently and the owner is unavailable, use channel-scope tools an admin still holds (e.g. delete-message) or demote via owner-approved governance; do not attempt direct bans.","For anti-spam bots, exclude owner/admin pubkeys from automatic ban lists (fetch the kind:39001 admin list first) so the guard is never hit.","If you intended UNban or un-timeout of a fellow admin, that is intentionally allowed — re-issue as Unban/Untimeout, which is unguarded at this seam."],"exampleFix":"// before (admin key signs ban targeting another admin)\nconst ev = await createModerationEvent(9000, { p: adminB_pubkey, reason: 'spam' }); // -> 403 guard\n\n// after (community owner's key signs the same action)\nawait impersonateOwner(); // or have the owner run the moderation\nconst ev = await createModerationEvent(9000, { p: adminB_pubkey, reason: 'spam' });","handlingStrategy":"type-guard","validationCode":"// before issuing ban/timeout, fetch roles and veto elevated targets\nconst admins = await fetchAdminList(relayUrl, channelId); // kind:39001 p-tags + owner\nfunction canModerate(actorRole: string, targetPubkey: string, action: 'ban'|'timeout'): boolean {\n  if (actorRole !== 'admin' && actorRole !== 'owner') return false;\n  if (actorRole === 'admin' && admins.has(targetPubkey)) return false; // guard mirrors relay\n  return true;\n}","typeGuard":"function isProtectedTarget(actorRole: string, targetRole: string | null, action: string): boolean {\n  const elevated = targetRole === 'owner' || targetRole === 'admin';\n  return actorRole === 'admin' && elevated && (action === 'ban' || action === 'timeout');\n}","tryCatchPattern":"// relay returns an authz error for kind:900x — surface it as a role issue, not a transport failure\ntry { await publishModeration(action, targetPubkey); }\ncatch (e) {\n  if (String(e).includes('cannot ban or time out'))\n    throw new Error(`Refused: ${targetPubkey} is owner/admin; ask the community owner to act`);\n  throw e;\n}","preventionTips":["Mod bots must exclude owner/admin pubkeys (kind:39001 list) from automated bans/timeouts.","Design admin UIs to disable ban/timeout actions on members shown with owner/admin badges.","Remember the asymmetry: admins CAN unban/untimeout peers — route remediation through those actions or the owner."],"tags":["buzz-relay","moderation","authorization","role-hierarchy","nip-29"],"backgroundTag":"insufficient-permissions","analyzedSha":"f956e6fe06a76e50cbd8fba1a162482e752e7f1a","analyzedAt":"2026-08-16T22:11:40.750Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}