block / block/buzz

NIP-OA owner never materialized for agents that are direct relay members (closed relay)

Open
#6,072 0 comments 0 reactions 0 assignees View on GitHub
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.