Skip to content

Commit 30e25b4

Browse files
npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7wpfleger96
andcommitted
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>
1 parent ef963a3 commit 30e25b4

2 files changed

Lines changed: 28 additions & 0 deletions

File tree

crates/buzz-core/src/kind.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ pub const P_GATED_KINDS: &[u32] = &[
131131
KIND_MEMBER_REMOVED_NOTIFICATION,
132132
KIND_GIFT_WRAP,
133133
KIND_DM_VISIBILITY,
134+
KIND_DREAM_DUE,
134135
];
135136

136137
/// NIP-AP: Agent Persona (parameterized replaceable, owner-authored).

crates/buzz-relay/src/handlers/req.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1296,6 +1296,33 @@ mod tests {
12961296
assert!(p_gated_filters_authorized(&[matching_p], authed));
12971297
}
12981298

1299+
#[test]
1300+
fn dream_due_subscription_requires_matching_p_tag() {
1301+
// KIND_DREAM_DUE (24300) is a #p-gated ephemeral kind: "single-delivery
1302+
// to the authenticated agent". A filter targeting another agent's pubkey
1303+
// must be rejected; a filter targeting self must be accepted.
1304+
let p_tag = SingleLetterTag::lowercase(Alphabet::P);
1305+
let authed = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
1306+
let other = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";
1307+
1308+
// No #p at all → rejected (could subscribe to all agents' dream signals).
1309+
let no_p = Filter::new()
1310+
.kind(nostr::Kind::Custom(buzz_core::kind::KIND_DREAM_DUE as u16));
1311+
assert!(!p_gated_filters_authorized(&[no_p], authed));
1312+
1313+
// #p targeting another agent → rejected (info leak: "agent X is over budget + idle").
1314+
let wrong_p = Filter::new()
1315+
.kind(nostr::Kind::Custom(buzz_core::kind::KIND_DREAM_DUE as u16))
1316+
.custom_tags(p_tag, [other]);
1317+
assert!(!p_gated_filters_authorized(&[wrong_p], authed));
1318+
1319+
// #p targeting self → accepted (this is the ACP harness's own subscription).
1320+
let self_p = Filter::new()
1321+
.kind(nostr::Kind::Custom(buzz_core::kind::KIND_DREAM_DUE as u16))
1322+
.custom_tags(p_tag, [authed]);
1323+
assert!(p_gated_filters_authorized(&[self_p], authed));
1324+
}
1325+
12991326
#[test]
13001327
fn d_tag_pushdown_only_for_nip33_kinds() {
13011328
let d_tag = SingleLetterTag::lowercase(Alphabet::D);

0 commit comments

Comments
 (0)