block/buzz · error · IngestError::Rejected

moderator access required

Error message

moderator access required

What it means

Thrown by decide_authority() in the relay's moderation authorization seam: the actor holds neither a community-wide 'owner'/'admin' role in relay_members nor a channel 'owner'/'admin' role for the target channel. Buzz v1 has no separate Moderator tier — every moderation capability (Ban, Unban, Timeout, Utimeout, ResolveReport, ViewQueue, plus channel-local DeleteMessage/Kick) routes through authorize_moderation_action, and channel roles only ever authorize DeleteMessage/Kick. Hitting this means no role source matched the actor's signing pubkey in this community (roles are tenant-fenced).

Source

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

        // 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::*;

    /// Every community-wide action a community owner can take. Channel-local
    /// actions (DeleteMessage/Kick) are included — the owner holds them too.
    const ALL_ACTIONS: [ModerationAction; 8] = [
        ModerationAction::DeleteMessage,
        ModerationAction::Kick,
        ModerationAction::Ban,
        ModerationAction::Unban,
        ModerationAction::Timeout,
        ModerationAction::Untimeout,
        ModerationAction::ResolveReport,

View on GitHub (pinned to f956e6fe06)

Solutions

  1. Grant the actor community 'admin' or 'owner' in relay_members (buzz-admin / relay operator CLI) and re-sign with that exact pubkey
  2. For DeleteMessage/Kick only: ensure the actor is an active 'owner'/'admin' member of that specific channel (kind 39002/9000 role row under tenant.community())
  3. Verify the client is signing with the pubkey that actually holds the role — dump relay_members for the community and compare hex pubkeys
  4. Confirm the action is aimed at a channel/user in the same community; cross-community moderation is always denied

Example fix

// before: plain member's key signs a community-wide ban → "moderator access required"
await client.publish(banCommandEvent); // Err(moderator access required)

// after: grant admin in relay_members, then sign with that key
// buzz-admin members set-role --community <id> --pubkey <hex> --role admin
await client.publish(banCommandEvent); // Ok(ModerationAuthority::CommunityAdmin)
Defensive patterns

Strategy: validation

Validate before calling

// Before issuing a moderation command, resolve the actor's authority
let role = client.get_relay_member(community, actor_hex).await?;
let is_channel_elevated = matches!(
    client.get_channel_role(community, channel_id, actor).await?,
    Some(r) if r == "owner" || r == "admin"
);
let action_is_local = matches!(action, ModerationAction::DeleteMessage | ModerationAction::Kick);
anyhow::ensure!(
    matches!(role.as_deref(), Some("owner") | Some("admin")) || (action_is_local && is_channel_elevated),
    "actor lacks any moderation authority for {action:?}"
);

Type guard

fn can_moderate(actor_role: Option<&str>, channel_role: Option<&str>, action: &str) -> bool {
    match actor_role {
        Some("owner") | Some("admin") => true,
        _ => matches!(action, "delete_message" | "kick")
            && matches!(channel_role, Some("owner") | Some("admin")),
    }
}

Try / catch

match authorize_moderation_action(&tenant, &state, &actor, channel, target, action).await {
    Ok(authority) => { /* record authority in audit row */ }
    Err(e) if e.to_string().contains("moderator access required") => {
        // client-safe: surface as 403-style denial, do NOT retry with the same key
        return deny("You need community admin or channel owner/admin rights for this action");
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: A plain community member issues a ban/timeout/unban/resolve-report/view-queue moderation command; a channel owner/admin attempts a community-wide action such as Ban (channel authority only covers DeleteMessage/Kick); a channel admin tries DeleteMessage/Kick against a channel_id where they hold no membership row; the signing pubkey differs from the pubkey that carries the role in relay_members.

Common situations: Teams expect a 'moderator' role that does not exist in v1; the moderator's client signs with a different key (or an agent key) than the one granted admin in relay_members; acting across community boundaries — authority never crosses the tenant fence; role was granted in a different community with the same relay.

Related errors


AI-assisted analysis of block/buzz@f956e6fe06 (2026-08-16). Data as JSON: /api/errors/cabd08338a9078aa. Report an issue: GitHub.