Files
buzz/crates
Will Pfleger b3795236cc fix(relay): reject unreadable write targets as missing (#8249)
🤖 A sender who cannot use or read a referenced event, artifact or
workflow now gets exactly the response it would get for an ID that does
not exist. #8197 (merged) set this rule for deletions; this applies it
to the other writes that look up a target, plus the workflow read and
webhook endpoints. Artifact `create` and workflow definition saves are
the exceptions, described below. Access is checked before any message
that depends on the target, readability goes through
`event_visible_to_reader` (including #8197's private-deletion rule), and
soft-deleted targets keep matching missing ones.

| Path | Checks, in order | Response for any unusable target |
|---|---|---|
| Reaction (kind 7) | target lookup → `event_visible_to_reader` → every
channel gate for the derived channel, including archived | `invalid:
reaction target event not found` |
| Edit (kind 40003) | target in the edit's `h` channel and readable →
author / agent-owner check, including an author since removed from a
private channel | `invalid: edit target not found or not editable by
you` |
| Forum vote (kind 45002) | target in the vote's `h` channel and
readable → kind check | `invalid: vote target event not found` |
| Report by event (kind 1984 `e`) | `event_visible_to_reader` → token
channel scope → channel membership or open | `invalid: report target
event not found` |
| Reply (NIP-10 and workflow `send_message` replies) | parent in the
reply's channel and readable | `reply parent not found` |
| Artifact update / delete / restore / move | head exists and is homed
in `h` (move: in the source the sender can write) → revision, type,
deleted and root checks | `conflict: artifact head unavailable` |
| Workflow trigger (kind 46020) | workflow exists and sender is its
owner | `invalid: workflow not found or not triggerable by you` |
| `GET /workflows/{id}/runs` and `/runs/{run_id}/approvals` | workflow
exists, has a channel, and the caller can read it | `404
{"error":"workflow not found"}` |
| `POST /hooks/{id}` | workflow exists and the provided secret matches
its stored secret → definition and trigger-type checks | `404
{"error":"workflow not found"}` |

Two of these reject writes that used to be accepted: a reaction to an
event the sender cannot read (such as someone else's author-only event),
and a report on an event the reporter cannot read. Reports by pubkey and
by blob are unchanged. In an archived channel, writes whose channel
comes from their target (reactions and deletions) get that target's
not-found response, and an artifact move out of an archived source
channel gets `conflict: artifact head unavailable` like any other source
the sender cannot write; every other write keeps `invalid: channel is
archived`. A webhook call with an absent or wrong secret, or to a
workflow without a webhook secret, now gets `404` instead of
`401`/`400`. Artifact `create` and workflow definition saves still
report a taken ID, as NIP-AR allows for artifacts.

Removed messages, none of which any client depends on from these
endpoints: `edit target event not found`, `must be event author to
edit`, `target event belongs to a different channel`, `target event has
no channel`, `parent event belongs to a different channel`, `parent
event has no channel association`, `artifact home changed`, `workflow is
not accessible`, `workflow is not channel-scoped`, the webhook's
`authentication failed` and `webhook secret required but not
configured`, `forbidden: not authorized to trigger this workflow` for
non-owners (it remains for an owner whose channel authority lapsed), and
`restricted: not a channel member` for an edit by an author removed from
a private channel.

`e2e_existence_oracle` checks each event write over HTTP and WS, and the
workflow read and webhook endpoints over HTTP, against missing,
soft-deleted, other-channel, private-channel, archived, author-only and
wrong-kind targets as applicable, with a positive control per path. It
runs in the Backend Integration job alongside
`e2e_author_only_deletion`. `removed_author_edit_does_not_reveal_target`
checks that a removed author's edits of their remaining message, their
deleted message and a missing ID get identical answers. In the
PostgreSQL suite, `head_outside_authorized_channel_matches_missing_head`
covers the artifact transaction,
`workflow_reply_to_unusable_parent_matches_missing` covers workflow
replies through `RelayActionSink::send_message`,
`scoped_token_report_does_not_reveal_target` and
`scoped_token_reaction_does_not_reveal_target` cover channel-scoped
tokens, and `channelless_workflow_runs_read_as_missing` covers the runs
read for a workflow with no channel.

---------

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: Hayt <211b96e6a2b7f45fd4047988976c7bbbeeda0c15f3ae7b32eec20834b5a55118@buzz.block.builderlab.xyz>
2026-10-14 17:18:38 -04:00
..