{"record":{"id":"a10865f9377a1b18","repo":"block/buzz","slug":"policy-owner-only-only-the-agent-owner-can-add-t","errorCode":null,"errorMessage":"policy:owner_only — only the agent owner can add this agent","messagePattern":"policy:owner_only — only the agent owner can add this agent","errorType":"validation","errorClass":"IngestError::Rejected","httpStatus":null,"severity":"error","filePath":"crates/buzz-relay/src/handlers/side_effects.rs","lineNumber":429,"sourceCode":"\n            // Self-add: always allowed regardless of policy.\n            if target_pubkey == actor_bytes {\n                return Ok(());\n            }\n\n            // Third-party add: check channel_add_policy on the target.\n            if let Some((policy, owner)) = state\n                .db\n                .get_agent_channel_policy(tenant.community(), &target_pubkey)\n                .await?\n            {\n                match policy.as_str() {\n                    \"owner_only\" => {\n                        let owner_bytes = owner.ok_or_else(|| {\n                            anyhow::anyhow!(\"policy:owner_only — agent has no owner set\")\n                        })?;\n                        if actor_bytes != owner_bytes {\n                            return Err(anyhow::anyhow!(\n                                \"policy:owner_only — only the agent owner can add this agent\"\n                            ));\n                        }\n                    }\n                    \"nobody\" => {\n                        return Err(anyhow::anyhow!(\n                            \"policy:nobody — this agent has disabled external channel additions\"\n                        ));\n                    }\n                    // \"anyone\" or any unknown value → allow.\n                    // NOTE: DB ENUM constraint prevents unknown values from being stored.\n                    // If a new policy value is added to the ENUM, update this match.\n                    _ => {}\n                }\n            }\n\n            Ok(())\n        }","sourceCodeStart":411,"sourceCodeEnd":447,"githubUrl":"https://github.com/block/buzz/blob/f956e6fe06a76e50cbd8fba1a162482e752e7f1a/crates/buzz-relay/src/handlers/side_effects.rs#L411-L447","documentation":"The target agent's channel_add_policy is 'owner_only', its owner is known, and the actor publishing the 9000 add is not that owner. Only the registered NIP-OA owner of the agent may add it to channels; this is the agent opting out of third-party adds except by its controlling human. The agent itself can still self-add.","triggerScenarios":"A channel owner or community admin tries to add someone else's agent bot to their channel; a well-meaning teammate adds an agent that was configured owner_only; automation that bulk-adds all known agents to a new channel.","commonSituations":"Users assume channel-owner authority covers adding any participant — agent add policies cut across channel roles; onboarding scripts that add every agent in the directory; different teams sharing an agent key with conflicting policy expectations.","solutions":["Ask the agent's registered owner to perform the add (or publish it from the owner's key)","Let the agent join the channel itself (self-add is always allowed)","If the add is legitimate and routine, have the owner change the policy to 'anyone'","Confirm you are signing with the owner key registered for that agent — role or membership elsewhere does not help"],"exampleFix":"// before: channel owner adds someone else's owner_only agent\nput_user(ch, agent_pk).publish_as(channel_owner_keys)\n// → policy:owner_only — only the agent owner can add this agent\n\n// after: the agent's registered owner signs the add\nput_user(ch, agent_pk).publish_as(agent_owner_keys);","handlingStrategy":"validation","validationCode":"// Route the add through the right signer based on the agent's policy\nlet (policy, owner) = client.get_agent_channel_policy(community, target_pubkey)\n    .await?.unwrap_or((\"anyone\".into(), None));\nmatch policy.as_str() {\n    \"owner_only\" => {\n        let owner = owner.context(\"owner_only agent missing owner\")?;\n        anyhow::ensure!(actor_bytes == owner, \"only the agent's owner may add it — switch signers\");\n    }\n    \"nobody\" => anyhow::bail!(\"agent has disabled external adds\"),\n    _ => {}\n}","typeGuard":"const canAddAgent = (\n  actor: string,\n  policy: string | null,\n  owner: string | null,\n): boolean => {\n  if (policy === \"nobody\") return false;\n  if (policy === \"owner_only\") return owner !== null && actor === owner;\n  return true;\n};","tryCatchPattern":"match validate_admin_event(&tenant, 9000, &event, &state).await {\n    Err(e) if e.to_string().contains(\"only the agent owner can add this agent\") => {\n        // deterministic denial for this signer: forward the request to the agent's owner instead of retrying\n        delegate_to_agent_owner(target_pubkey, channel_id).await\n    }\n    other => other,\n}","preventionTips":["Bulk-add scripts must read each agent's add policy first and skip owner_only/nobody agents","Channel-owner authority does not override agent policies — never assume admin rights suffice","Keep a directory of agents with their add policy so UIs can disable invalid add actions up front"],"tags":["nostr","nip-29","agents","policy","authorization"],"backgroundTag":"agent-add-policy-denied","analyzedSha":"f956e6fe06a76e50cbd8fba1a162482e752e7f1a","analyzedAt":"2026-08-16T22:11:40.750Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}