Enable NIP-17 inbox relay after next strfry release #661

Open
opened 2026-08-10 16:06:02 +00:00 by raucao · 1 comment
Owner

Goal

Use strfry as a NIP-17 inbox relay: accept giftwraps (kind 1059) addressed to our users, and only serve them to the authenticated recipient.

Current state — blocked on upstream

We're pinned to strfry 1.1.1 (site-cookbooks/strfry/attributes/default.rb:2). 1.1.1 only has a write-policy plugin; there is no read-side policy:

  • filterValidation (src/filters.h) validates the shape of REQ filters (allowedKinds, requireAuthorOrTag) but has no access to the authenticated pubkey, so it cannot enforce "only the recipient can read their giftwraps."
  • authed is only surfaced to the write plugin for NIP-70 protected events (src/apps/relay/RelayIngester.cpp).
  • Anyone can REQ {"kinds":[1059],"#p":["<victim>"]} and pull all stored giftwraps (ciphertext + metadata leak).

Confirmed by upstream issue #228 "Auth on DMs Req" (open).

Dependency — wait for an upstream strfry release tag

PR #250 "Restricted reads" was merged to master on Aug 5 2026 by the maintainer (Doug Hoyte). It adds exactly what we need:

  • relay.auth.restrictedReadKinds — kinds that require NIP-42 AUTH to read via REQ/COUNT/NEG-OPEN. A filter with no kinds field is treated as restricted.
  • relay.auth.restrictReadToInvolvedPubkey = true — for restricted kinds, the authed pubkey must be in the filter's authors/#p set, and per-event delivery is gated by ReadRestrictor::shouldSendToSubscriber (src/ReadRestrictor.h): an event is only delivered if the authed subscriber == the event's p-tag recipient (or the author).

Also already on master: PR #251 "Allow gift-wrap recipients to delete via NIP-09 (NIP-59)".

Block this issue until an upstream strfry release tag including #250 is cut. Then bump node["strfry"]["revision"] in site-cookbooks/strfry/attributes/default.rb:2 to that tag.

Decision

  • Source: wait for an upstream release tag. Do not build from a moving master commit.
  • Restricted kinds: 1059 only (NIP-59 giftwraps).

Tasks

  • site-cookbooks/strfry/attributes/default.rb: bump node["strfry"]["revision"] to the new release tag (once available).
  • site-cookbooks/strfry/templates/default/strfry.conf.erb: add the two new relay.auth keys — restrictedReadKinds and restrictReadToInvolvedPubkey (mirror upstream strfry.conf; defaults "" / true).
  • environments/production.json (strfry.config.relay): set
    • auth.enabled = true
    • auth.service_url = "wss://nostr.kosmos.org"
    • auth.restricted_read_kinds = "1059"
    • auth.restrict_read_to_involved_pubkey = true
    • advertise NIP-42 (and NIP-17/NIP-59) in info.nips.
  • akkounts repo extras/strfry/ldap-policy.ts (pulled by site-cookbooks/kosmos_strfry/recipes/policies.rb:70): special-case kind 1059 — accept if any p-tag matches a Kosmos user in LDAP (nostrKey=…), bypassing the author check (giftwraps are signed by a one-time random key). Keep the whitelist_pubkeys short-circuit.
  • Verify substr / monitoring / sync jobs don't issue broad {} REQs over kind 1059. With restrictedReadKinds="1059", a wildcard REQ {} is treated as restricted → unauthenticated clients get challenged/closed (fail-closed). Note: node["substr"]["relay_urls"] = ["ws://localhost:7777"] (environments/production.json).
  • Converge: knife zero converge name:strfry-1 (and draco/fornax, which run kosmos_strfry::nginx).

Acceptance

  • An unauthenticated client cannot REQ {"kinds":[1059],"#p":["<user>"]}.
  • An authenticated user gets only their own p-tagged giftwraps.
  • Giftwraps addressed to non-Kosmos users are rejected on write.
## Goal Use strfry as a **NIP-17 inbox relay**: accept giftwraps (kind 1059) addressed to our users, and only serve them to the authenticated recipient. ## Current state — blocked on upstream We're pinned to strfry **1.1.1** (`site-cookbooks/strfry/attributes/default.rb:2`). 1.1.1 only has a **write-policy** plugin; there is **no read-side policy**: - `filterValidation` (`src/filters.h`) validates the *shape* of REQ filters (`allowedKinds`, `requireAuthorOrTag`) but has no access to the authenticated pubkey, so it cannot enforce "only the recipient can read their giftwraps." - `authed` is only surfaced to the write plugin for NIP-70 protected events (`src/apps/relay/RelayIngester.cpp`). - Anyone can `REQ {"kinds":[1059],"#p":["<victim>"]}` and pull all stored giftwraps (ciphertext + metadata leak). Confirmed by upstream issue [#228 "Auth on DMs Req"](https://github.com/hoytech/strfry/issues/228) (open). ## Dependency — wait for an upstream strfry release tag PR [#250 "Restricted reads"](https://github.com/hoytech/strfry/pull/250) was merged to `master` on Aug 5 2026 by the maintainer (Doug Hoyte). It adds exactly what we need: - `relay.auth.restrictedReadKinds` — kinds that require NIP-42 AUTH to read via REQ/COUNT/NEG-OPEN. A filter with no `kinds` field is treated as restricted. - `relay.auth.restrictReadToInvolvedPubkey = true` — for restricted kinds, the authed pubkey must be in the filter's `authors`/`#p` set, **and** per-event delivery is gated by `ReadRestrictor::shouldSendToSubscriber` (`src/ReadRestrictor.h`): an event is only delivered if the authed subscriber == the event's `p`-tag recipient (or the author). Also already on master: PR [#251](https://github.com/hoytech/strfry/pull/251) "Allow gift-wrap recipients to delete via NIP-09 (NIP-59)". **Block this issue until an upstream strfry release tag including #250 is cut.** Then bump `node["strfry"]["revision"]` in `site-cookbooks/strfry/attributes/default.rb:2` to that tag. ## Decision - **Source**: wait for an upstream release tag. Do not build from a moving `master` commit. - **Restricted kinds**: `1059` only (NIP-59 giftwraps). ## Tasks - [ ] `site-cookbooks/strfry/attributes/default.rb`: bump `node["strfry"]["revision"]` to the new release tag (once available). - [ ] `site-cookbooks/strfry/templates/default/strfry.conf.erb`: add the two new `relay.auth` keys — `restrictedReadKinds` and `restrictReadToInvolvedPubkey` (mirror upstream `strfry.conf`; defaults `""` / `true`). - [ ] `environments/production.json` (`strfry.config.relay`): set - `auth.enabled = true` - `auth.service_url = "wss://nostr.kosmos.org"` - `auth.restricted_read_kinds = "1059"` - `auth.restrict_read_to_involved_pubkey = true` - advertise NIP-42 (and NIP-17/NIP-59) in `info.nips`. - [ ] `akkounts` repo `extras/strfry/ldap-policy.ts` (pulled by `site-cookbooks/kosmos_strfry/recipes/policies.rb:70`): special-case kind 1059 — accept if any `p`-tag matches a Kosmos user in LDAP (`nostrKey=…`), bypassing the author check (giftwraps are signed by a one-time random key). Keep the `whitelist_pubkeys` short-circuit. - [ ] Verify substr / monitoring / sync jobs don't issue broad `{}` REQs over kind 1059. With `restrictedReadKinds="1059"`, a wildcard `REQ {}` is treated as restricted → unauthenticated clients get challenged/closed (fail-closed). Note: `node["substr"]["relay_urls"] = ["ws://localhost:7777"]` (`environments/production.json`). - [ ] Converge: `knife zero converge name:strfry-1` (and `draco`/`fornax`, which run `kosmos_strfry::nginx`). ## Acceptance - An unauthenticated client cannot `REQ {"kinds":[1059],"#p":["<user>"]}`. - An authenticated user gets only their own `p`-tagged giftwraps. - Giftwraps addressed to non-Kosmos users are rejected on write.
raucao added the
service
nostr
feature
labels 2026-08-10 16:07:10 +00:00
raucao changed title from strfry: enable NIP-17 inbox relay (read-auth for kind 1059) once strfry release includes PR #250 to Enable NIP-17 inbox relay (read-auth for kind 1059) after next strfry release 2026-08-10 16:07:35 +00:00
raucao changed title from Enable NIP-17 inbox relay (read-auth for kind 1059) after next strfry release to Enable NIP-17 inbox relay after next strfry release 2026-08-10 16:07:45 +00:00
Author
Owner

Refined spec — write policy (ldap-policy.ts) for NIP-17 inbox relay

Capturing the finalized plan for the akkounts write-policy side, to execute as part of the coordinated deploy in this issue once the strfry read-auth release is live.

File

akkounts/extras/strfry/ldap-policy.ts (only file changing; no strfry-policy.ts, deno.json, or deno.lock changes — no new deps).

Change

Add a branch immediately after the existing 9735 (zap receipt) block, mirroring its "override pubkey to the relevant key, then fall through to the LDAP search" pattern. Covers both giftwrap kinds:

    // Gift wrap (NIP-59): signed by a random one-time key, so validate the
    // p-tag recipient is a Kosmos member instead of the author. Covers both
    // kind 1059 (stored) and kind 21059 (ephemeral).
    if (kind === 1059 || kind === 21059) {
      const pTag = tags.find(([t, v]) => t === 'p' && v);
      if (!pTag) {
        return ['OK', id, false, 'Gift wraps must include a p-tag recipient'];
      }
      pubkey = pTag[1];
    }

Falls through to the existing (nostrKey=${pubkey}) search → memberKey === pubkey check, so the giftwrap is accepted iff the recipient (first p-tag) is a Kosmos user. nostrKey is effectively single-valued per user (LdapManager::UpdateNostrKey uses replace_attribute with one value), so the single-entry match works for the recipient unchanged.

Verification

cd extras/strfry
deno check ldap-policy.ts
deno lint .
deno fmt --check .

No runtime test (needs live LDAP); the 1059/21059 branch is pure logic over tags, type-checked against @nostrify/types.

PR & deploy (coordinated with strfry read-auth — DO NOT merge first)

Privacy-regression window: today 1059 is rejected (random author ∉ LDAP → nothing stored). Merging this write-policy change alone would store giftwraps on strfry 1.1.1, which has no read-side policy → anyone could REQ {"kinds":[1059],"#p":["…"]} and read them (ciphertext + recipient metadata). Worse than today.

Deploy order — strict:

  1. strfry read-auth release bumped & converged on strfry-1 / draco / fornax (chef side of this issue).
  2. Then: branch strfry/inbox-giftwrap-policy off master → PR target master → merge → masterlive.
  3. Converge strfry nodes so site-cookbooks/kosmos_strfry/recipes/policies.rb re-fetches the new script.

Consistency note

The write gate (recipient == first p-tag) matches the read gate from upstream PR #250 (ReadRestrictor::shouldSendToSubscriber checks the first p-tag == authed subscriber), so write and read agree on who "the recipient" is.

Scope decisions

  • 1059 and 21059 accepted (ephemeral 21059 not persisted by strfry, but its live delivery is gated by read-auth once enabled).
  • No legacy NIP-04 kind 4 — out of scope (would need its own author/recipient semantics; separate decision if ever wanted).
## Refined spec — write policy (ldap-policy.ts) for NIP-17 inbox relay Capturing the finalized plan for the `akkounts` write-policy side, to execute as part of the coordinated deploy in this issue once the strfry read-auth release is live. ### File `akkounts/extras/strfry/ldap-policy.ts` (only file changing; no `strfry-policy.ts`, `deno.json`, or `deno.lock` changes — no new deps). ### Change Add a branch immediately after the existing `9735` (zap receipt) block, mirroring its "override `pubkey` to the relevant key, then fall through to the LDAP search" pattern. Covers **both** giftwrap kinds: ```ts // Gift wrap (NIP-59): signed by a random one-time key, so validate the // p-tag recipient is a Kosmos member instead of the author. Covers both // kind 1059 (stored) and kind 21059 (ephemeral). if (kind === 1059 || kind === 21059) { const pTag = tags.find(([t, v]) => t === 'p' && v); if (!pTag) { return ['OK', id, false, 'Gift wraps must include a p-tag recipient']; } pubkey = pTag[1]; } ``` Falls through to the existing `(nostrKey=${pubkey})` search → `memberKey === pubkey` check, so the giftwrap is accepted iff the **recipient** (first `p`-tag) is a Kosmos user. `nostrKey` is effectively single-valued per user (`LdapManager::UpdateNostrKey` uses `replace_attribute` with one value), so the single-entry match works for the recipient unchanged. ### Verification ``` cd extras/strfry deno check ldap-policy.ts deno lint . deno fmt --check . ``` No runtime test (needs live LDAP); the 1059/21059 branch is pure logic over `tags`, type-checked against `@nostrify/types`. ### PR & deploy (coordinated with strfry read-auth — DO NOT merge first) **Privacy-regression window**: today 1059 is rejected (random author ∉ LDAP → nothing stored). Merging this write-policy change **alone** would store giftwraps on strfry 1.1.1, which has no read-side policy → anyone could `REQ {"kinds":[1059],"#p":["…"]}` and read them (ciphertext + recipient metadata). Worse than today. Deploy order — strict: 1. strfry read-auth release bumped & converged on `strfry-1` / `draco` / `fornax` (chef side of this issue). 2. Then: branch `strfry/inbox-giftwrap-policy` off `master` → PR target `master` → merge → `master`→`live`. 3. Converge strfry nodes so `site-cookbooks/kosmos_strfry/recipes/policies.rb` re-fetches the new script. ### Consistency note The write gate (recipient == first `p`-tag) matches the read gate from upstream PR #250 (`ReadRestrictor::shouldSendToSubscriber` checks the first `p`-tag == authed subscriber), so write and read agree on who "the recipient" is. ### Scope decisions - `1059` and `21059` accepted (ephemeral 21059 not persisted by strfry, but its live delivery is gated by read-auth once enabled). - No legacy NIP-04 kind 4 — out of scope (would need its own author/recipient semantics; separate decision if ever wanted).
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: kosmos/chef#661