block/buzz · error · anyhow::Error

an admin cannot ban or time out a community owner or fellow…

Error message

an admin cannot ban or time out a community owner or fellow admin

What it means

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.

Solutions

  1. Have the community owner issue the ban/timeout instead
  2. Or have the owner demote the target admin first, then any admin can act
  3. For lifting a restriction, use unban/untimeout — that path is intentionally allowed and audited

Example fix

# before (admin tries to ban a fellow admin)
buzz moderation ban --user <admin-pubkey>   # 403: an admin cannot ban or time out a community owner or fellow admin
# after (owner acts)
buzz moderation ban --user <admin-pubkey>   # run as the community owner's key
Defensive patterns

Strategy: validation

Validate before calling

-- Pre-flight for admin-driven ban/timeout: resolve the target's role first
SELECT role FROM relay_members WHERE pubkey = :target;
-- if role IN ('owner','admin'), route the action to the community owner instead

Type guard

fn admin_may_action(action: ModerationAction, target_role: Option<&str>) -> bool {
    match target_role {
        Some("owner") | Some("admin") => !matches!(
            action,
            ModerationAction::Ban | ModerationAction::Timeout
        ),
        _ => true,
    }
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Related errors


AI-assisted analysis of block/buzz@934f3325c3 (2026-08-20). Data as JSON: /api/errors/2cc747300acfd301. Report an issue: GitHub.

Appendix: source

Thrown at crates/buzz-relay/src/handlers/moderation_authz.rs:167

    channel_role: Option<&str>,
    action: ModerationAction,
) -> anyhow::Result<ModerationAuthority> {
    match actor_role {
        // Owner holds every capability, community-wide, with no guard rail.
        Some("owner") => Ok(ModerationAuthority::CommunityOwner),
        // Admin holds every capability, but cannot ban/timeout the owner or a
        // fellow admin — only the owner may action an admin. The guard trips only
        // on a target *role* of owner/admin: a target with no `relay_members` row
        // (a drive-by spammer who already left) is bannable. Unban/Untimeout lift
        // a restriction and are intentionally unguarded at this role seam. The
        // command handler separately rejects a banned actor on every transport,
        // so the reachable case is an unrestricted admin lifting another admin's
        // restriction; that remains benign, audited, and owner-reversible.
        Some("admin") => {
            if matches!(action, ModerationAction::Ban | ModerationAction::Timeout)
                && matches!(target_role, Some("owner") | Some("admin"))
            {
                anyhow::bail!("an admin cannot ban or time out a community owner or fellow admin");
            }
            Ok(ModerationAuthority::CommunityAdmin)
        }
        // Not a community owner/admin: channel owner/admin keep channel-local
        // authority for DeleteMessage/Kick only.
        _ => match (action, channel_role) {
            (
                ModerationAction::DeleteMessage | ModerationAction::Kick,
                Some("owner") | Some("admin"),
            ) => Ok(ModerationAuthority::ChannelRole),
            _ => anyhow::bail!("moderator access required"),
        },
    }
}

#[cfg(test)]
mod tests {
    use super::*;

View on GitHub (pinned to 934f3325c3)