Severity: HIGH — security (mafintosh + systems lens)
delivery-event (notify-service.js:457-470) returns redactDeliveryEvent(...) for any {eventId}/{intentId} with zero auth — no signature, no ownership check (unlike sibling unwatch). The redacted event still exposes metering.{billable,attempts,payloadBytes}, provider class, receiver device key, app, and timing. Since intentId is a caller-chosen field, anyone who sent/received/observed an intent can read that tenant's delivery outcome — the exact "notification timing leaks activity" surface the threat model (spec:1035) claims to mitigate. Separately, status() (:423-455) verifies a signature only when present and otherwise returns relay-global deliveryEvents.size/revocations.size.
Fix: require+verify a signed request on delivery-event (same verifySignedAny pattern), then filter events to the authenticated caller's app/device/sender. For status, don't return relay-global counts on the unsigned path — apply the existing matches() closure (:431-435). Spec:826 already requires "filtered by caller authorization" — this is a spec deviation. Test: unauthenticated call returns empty / no global counts.
Severity: HIGH — security (mafintosh + systems lens)
delivery-event(notify-service.js:457-470) returnsredactDeliveryEvent(...)for any{eventId}/{intentId}with zero auth — no signature, no ownership check (unlike siblingunwatch). The redacted event still exposesmetering.{billable,attempts,payloadBytes}, provider class, receiver device key, app, and timing. SinceintentIdis a caller-chosen field, anyone who sent/received/observed an intent can read that tenant's delivery outcome — the exact "notification timing leaks activity" surface the threat model (spec:1035) claims to mitigate. Separately,status()(:423-455) verifies a signature only when present and otherwise returns relay-globaldeliveryEvents.size/revocations.size.Fix: require+verify a signed request on
delivery-event(sameverifySignedAnypattern), then filter events to the authenticated caller'sapp/device/sender. Forstatus, don't return relay-global counts on the unsigned path — apply the existingmatches()closure (:431-435). Spec:826 already requires "filtered by caller authorization" — this is a spec deviation. Test: unauthenticated call returns empty / no global counts.