mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
fix(relay): gate KIND_DREAM_DUE on matching #p to prevent info leak
KIND_DREAM_DUE (24300) was absent from P_GATED_KINDS, so p_gated_filters_authorized() never enforced #p-must-match-self for it. Any authenticated relay user could subscribe with kinds:[24300], #p:[victim_pubkey] and receive another agent's dream-due signals, leaking "agent X is over memory budget and idle" to anyone who knows an agent pubkey. The kind's own doc already states "single-delivery to the authenticated agent"; P_GATED_KINDS is the registry that makes the relay enforce it. Fix: add KIND_DREAM_DUE to P_GATED_KINDS. Ephemeral kinds are included in this list for filter-layer enforcement only and are never stored, so no schema/migration/tsvector change is needed. The ACP harness's own subscription (DREAM_SIGNAL_SUB_ID, #p=[self]) continues to be accepted — the self case is exactly what P_GATED_KINDS allows through. Test: dream_due_subscription_requires_matching_p_tag verifies: - no #p → rejected - #p:[other] → rejected - #p:[self] → accepted Confirmed: test fails without the registry add and passes with it. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
This commit is contained in:
co-authored by
Will Pfleger
parent
ef963a3353
commit
30e25b4a41
@@ -131,6 +131,7 @@ pub const P_GATED_KINDS: &[u32] = &[
|
||||
KIND_MEMBER_REMOVED_NOTIFICATION,
|
||||
KIND_GIFT_WRAP,
|
||||
KIND_DM_VISIBILITY,
|
||||
KIND_DREAM_DUE,
|
||||
];
|
||||
|
||||
/// NIP-AP: Agent Persona (parameterized replaceable, owner-authored).
|
||||
|
||||
@@ -1296,6 +1296,33 @@ mod tests {
|
||||
assert!(p_gated_filters_authorized(&[matching_p], authed));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dream_due_subscription_requires_matching_p_tag() {
|
||||
// KIND_DREAM_DUE (24300) is a #p-gated ephemeral kind: "single-delivery
|
||||
// to the authenticated agent". A filter targeting another agent's pubkey
|
||||
// must be rejected; a filter targeting self must be accepted.
|
||||
let p_tag = SingleLetterTag::lowercase(Alphabet::P);
|
||||
let authed = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
|
||||
let other = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";
|
||||
|
||||
// No #p at all → rejected (could subscribe to all agents' dream signals).
|
||||
let no_p = Filter::new()
|
||||
.kind(nostr::Kind::Custom(buzz_core::kind::KIND_DREAM_DUE as u16));
|
||||
assert!(!p_gated_filters_authorized(&[no_p], authed));
|
||||
|
||||
// #p targeting another agent → rejected (info leak: "agent X is over budget + idle").
|
||||
let wrong_p = Filter::new()
|
||||
.kind(nostr::Kind::Custom(buzz_core::kind::KIND_DREAM_DUE as u16))
|
||||
.custom_tags(p_tag, [other]);
|
||||
assert!(!p_gated_filters_authorized(&[wrong_p], authed));
|
||||
|
||||
// #p targeting self → accepted (this is the ACP harness's own subscription).
|
||||
let self_p = Filter::new()
|
||||
.kind(nostr::Kind::Custom(buzz_core::kind::KIND_DREAM_DUE as u16))
|
||||
.custom_tags(p_tag, [authed]);
|
||||
assert!(p_gated_filters_authorized(&[self_p], authed));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn d_tag_pushdown_only_for_nip33_kinds() {
|
||||
let d_tag = SingleLetterTag::lowercase(Alphabet::D);
|
||||
|
||||
Reference in New Issue
Block a user