NIP-OA owner never materialized for agents that are direct relay members (closed relay)
- Dominant language
- Rust
- Stars
- 32.7k
- Forks
- 4.3k
- Avg merge
- 1d 13h
- Merged PRs (30d)
- 253
Description
## Summary
On a closed relay (`BUZZ_REQUIRE_RELAY_MEMBERSHIP=true`), an agent that is
enrolled directly in `relay_members` never gets `users.agent_owner_pubkey`
populated, no matter what it sends. A valid, cryptographically self-proving
NIP-OA attestation on its NIP-42 AUTH event is accepted and then discarded.
## Why
Two pieces of correct-in-isolation logic combine badly.
`handlers/auth.rs` only runs the backfill on open relays:
```rust
let nip_oa_owner = nip_oa_owner.or_else(|| {
if !state.config.require_relay_membership && auth_tag_json.is_some() {
crate::api::relay_members::extract_nip_oa_owner(...)
```
The comment says closed relays are covered because "enforce_relay_membership
already handles NIP-OA delegation". But that helper reports an owner only when
it *used* the delegation to admit the agent:
```rust
Ok(MembershipDecision::OpenRelay) | Ok(MembershipDecision::Member) => Ok(None),
Ok(MembershipDecision::ViaOwner(owner)) => Ok(Some(owner)),
```
An agent that is *also* a direct member takes the `::Member` branch and returns
`Ok(None)`. So on a closed relay, a directly-enrolled agent hits neither path.
## Impact
`agent_owner_pubkey` is not cosmetic — at least three behaviours derive from it:
1. **Rate limiting.** `connection.rs` computes
`is_agent = ctx.agent_owner_pubkey.is_some()`, so the agent is limited at
`human_messages_per_min` (60) instead of `agent_standard_messages_per_min`
(120).
2. **Agent lists.** With no owner relationship, the agent does not appear in
owner-managed agent lists in clients, while remaining DM-able — so it looks
half-registered.
3. **Channel membership.** `side_effects.rs` refuses `add-member` under the
agent's own `channel_add_policy = "owner_only"` with
`policy:owner_only — agent has no owner set`, for *every* actor including the
real owner. The agent cannot be added to any channel by anyone.
## Reproduction
Relay with `BUZZ_REQUIRE_RELAY_MEMBERSHIP=true`; agent present in
`relay_members`; agent sends a valid NIP-OA `auth` tag on its NIP-42 AUTH event.
Observed: `"NIP-42 auth successful"` is logged, no materialization is attempted,
no warning is emitted, and:
```
SELECT channel_add_policy, agent_owner_pubkey FROM users WHERE pubkey = ;
owner_only | NULL
```
## Suggested fix
Run the extraction whenever an `auth` tag is present, independent of how
membership was satisfied. The existing doc comment on `extract_nip_oa_owner`
already argues this is safe:
> The NIP-OA signature is cryptographically self-proving, so no feature flag is
> needed — if the tag verifies, the owner relationship is authentic.
Membership and the owner relationship are separate questions; the bug is
treating the second as a by-product of the first.
Happy to open a PR — I have the change and regression tests ready. Wanted to
follow the issue-first guidance given it touches the auth path.
Contributor guide
Assessment
This issue has not been assessed yet.