{"record":{"id":"cabd08338a9078aa","repo":"block/buzz","slug":"moderator-access-required","errorCode":null,"errorMessage":"moderator access required","messagePattern":"moderator access required","errorType":"validation","errorClass":"IngestError::Rejected","httpStatus":null,"severity":"error","filePath":"crates/buzz-relay/src/handlers/moderation_authz.rs","lineNumber":178,"sourceCode":"        // 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::*;\n\n    /// Every community-wide action a community owner can take. Channel-local\n    /// actions (DeleteMessage/Kick) are included — the owner holds them too.\n    const ALL_ACTIONS: [ModerationAction; 8] = [\n        ModerationAction::DeleteMessage,\n        ModerationAction::Kick,\n        ModerationAction::Ban,\n        ModerationAction::Unban,\n        ModerationAction::Timeout,\n        ModerationAction::Untimeout,\n        ModerationAction::ResolveReport,","sourceCodeStart":160,"sourceCodeEnd":196,"githubUrl":"https://github.com/block/buzz/blob/f956e6fe06a76e50cbd8fba1a162482e752e7f1a/crates/buzz-relay/src/handlers/moderation_authz.rs#L160-L196","documentation":"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).","triggerScenarios":"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.","commonSituations":"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.","solutions":["Grant the actor community 'admin' or 'owner' in relay_members (buzz-admin / relay operator CLI) and re-sign with that exact pubkey","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())","Verify the client is signing with the pubkey that actually holds the role — dump relay_members for the community and compare hex pubkeys","Confirm the action is aimed at a channel/user in the same community; cross-community moderation is always denied"],"exampleFix":"// before: plain member's key signs a community-wide ban → \"moderator access required\"\nawait client.publish(banCommandEvent); // Err(moderator access required)\n\n// after: grant admin in relay_members, then sign with that key\n// buzz-admin members set-role --community <id> --pubkey <hex> --role admin\nawait client.publish(banCommandEvent); // Ok(ModerationAuthority::CommunityAdmin)","handlingStrategy":"validation","validationCode":"// Before issuing a moderation command, resolve the actor's authority\nlet role = client.get_relay_member(community, actor_hex).await?;\nlet is_channel_elevated = matches!(\n    client.get_channel_role(community, channel_id, actor).await?,\n    Some(r) if r == \"owner\" || r == \"admin\"\n);\nlet action_is_local = matches!(action, ModerationAction::DeleteMessage | ModerationAction::Kick);\nanyhow::ensure!(\n    matches!(role.as_deref(), Some(\"owner\") | Some(\"admin\")) || (action_is_local && is_channel_elevated),\n    \"actor lacks any moderation authority for {action:?}\"\n);","typeGuard":"fn can_moderate(actor_role: Option<&str>, channel_role: Option<&str>, action: &str) -> bool {\n    match actor_role {\n        Some(\"owner\") | Some(\"admin\") => true,\n        _ => matches!(action, \"delete_message\" | \"kick\")\n            && matches!(channel_role, Some(\"owner\") | Some(\"admin\")),\n    }\n}","tryCatchPattern":"match authorize_moderation_action(&tenant, &state, &actor, channel, target, action).await {\n    Ok(authority) => { /* record authority in audit row */ }\n    Err(e) if e.to_string().contains(\"moderator access required\") => {\n        // client-safe: surface as 403-style denial, do NOT retry with the same key\n        return deny(\"You need community admin or channel owner/admin rights for this action\");\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Gate moderation UI actions on a pre-fetched role check so unauthorized commands never leave the client","Keep one canonical admin key per community and verify it against relay_members after any key rotation","Remember v1 has no moderator tier — provisioning docs should say owner/admin only","Channel roles authorize only DeleteMessage/Kick; never assume they cover bans or timeouts"],"tags":["nostr","moderation","authorization","acl","roles"],"backgroundTag":"insufficient-permissions","analyzedSha":"f956e6fe06a76e50cbd8fba1a162482e752e7f1a","analyzedAt":"2026-08-16T22:11:40.750Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}