fix(relay): claim NIP-98 replay slot only after authorization, attribute cancels

Two defects from the kalvin-agent security review of the admin moderation API.

Replay-before-authorization: authorize_nip98 claimed the deployment-scoped
replay ID immediately after crypto verification, before resolve_admin_principal
ran the roster check. Any validly-signing but unrostered key (every
WARP-admitted laptop) could allocate replay slots at request rate. Split the
NIP-98 path into verify-only (authorize_nip98, returns pubkey + event id) and a
separate claim_nip98_replay called only after principal resolution succeeds, so
an unrostered signer never consumes a slot. Fail-closed Redis behavior and the
deployment-scoped key format are unchanged.

Cancel actor trail: cancel_report discarded the resolved principal and
cancel_action persisted nothing about who cancelled — the one mutation with no
actor attribution while BUZZ_AUDIT_ENABLED=false. Add a cancelled_by column to
relay_admin_actions (mirroring moderation_reports.resolved_by), stamped in the
cancel UPDATE and surfaced through AdminActionDto.cancelledBy. Migration 0033 is
branch-local and unshipped, so the column is added in place with matching
schema.sql; the pgschema parity test round-trips it through bin/pgschema.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
This commit is contained in:
Duncan
2026-08-14 16:01:19 -04:00
co-authored by Will Pfleger
parent 5d638af126
commit feaeb32f57
7 changed files with 157 additions and 42 deletions
+4
View File
@@ -1715,6 +1715,10 @@ CREATE TABLE relay_admin_actions (
-- Step marker: the last durably committed mutation step (NULL = none yet).
-- Values: 'mutation_committed' (core DB mutation done), 'artifacts_done' (tombstone/notice done).
step_marker TEXT CHECK (step_marker IN ('mutation_committed', 'artifacts_done')),
-- Principal who cancelled a pre-mutation failed action; NULL until cancelled.
-- Attributes the cancel transition on the action row itself, mirroring
-- moderation_reports.resolved_by for report resolution.
cancelled_by BYTEA CHECK (cancelled_by IS NULL OR length(cancelled_by) = 32),
-- Error from the last failure, if any.
error_message TEXT,
-- Per-action exclusive lease (migration 0034): fences concurrent same-request