@@ -282,6 +282,12 @@ pub async fn query_events(
282282 "restricted: agent-engram reads require authors=[self] or #p=[self]" ,
283283 ) ) ;
284284 }
285+ if !crate :: handlers:: req:: author_only_filters_authorized ( & filters, & authed_pubkey_hex) {
286+ return Err ( api_error (
287+ StatusCode :: FORBIDDEN ,
288+ "restricted: author-only kinds require authors=[self]" ,
289+ ) ) ;
290+ }
285291
286292 // Get channels this user can access — same enforcement as WS REQ handler.
287293 let accessible_channels = state
@@ -291,8 +297,14 @@ pub async fn query_events(
291297
292298 // ── NIP-50 search: route to Typesense if any filter has a `search` field ──
293299 if filters. iter ( ) . any ( |f| f. search . is_some ( ) ) {
294- return handle_bridge_search ( & state, & filters, & accessible_channels, & authed_pubkey_hex)
295- . await ;
300+ return handle_bridge_search (
301+ & state,
302+ & filters,
303+ & accessible_channels,
304+ & authed_pubkey_hex,
305+ & pubkey_bytes,
306+ )
307+ . await ;
296308 }
297309
298310 // ── Presence: synthesize kind:20001 from Redis (ephemeral, never in DB) ──
@@ -472,6 +484,9 @@ pub async fn query_events(
472484 ) {
473485 continue ;
474486 }
487+ if crate :: handlers:: req:: is_author_only_event ( & se. event , & pubkey_bytes) {
488+ continue ;
489+ }
475490 if let Ok ( v) = serde_json:: to_value ( & se. event ) {
476491 events. push ( v) ;
477492 }
@@ -528,6 +543,12 @@ pub async fn count_events(
528543 "restricted: agent-engram reads require authors=[self] or #p=[self]" ,
529544 ) ) ;
530545 }
546+ if !crate :: handlers:: req:: author_only_filters_authorized ( & filters, & authed_pubkey_hex) {
547+ return Err ( api_error (
548+ StatusCode :: FORBIDDEN ,
549+ "restricted: author-only kinds require authors=[self]" ,
550+ ) ) ;
551+ }
531552
532553 // Get channels this user can access.
533554 let accessible_channels = state
@@ -537,6 +558,9 @@ pub async fn count_events(
537558
538559 let mut total: u64 = 0 ;
539560 for filter in & filters {
561+ let needs_author_only_filtering =
562+ crate :: handlers:: req:: filter_can_match_author_only_kinds ( filter) ;
563+
540564 // If filter targets a specific channel, verify access.
541565 if let Some ( ch_id) = extract_channel_from_filter ( filter) {
542566 if !accessible_channels. contains ( & ch_id) {
@@ -546,7 +570,15 @@ pub async fn count_events(
546570 let query =
547571 crate :: handlers:: req:: build_event_query_from_filter ( filter, & pubkey_bytes, & state)
548572 . await ;
549- if crate :: handlers:: req:: filter_fully_pushable ( filter) {
573+ let author_is_self = filter. authors . as_ref ( ) . is_some_and ( |authors| {
574+ !authors. is_empty ( )
575+ && authors
576+ . iter ( )
577+ . all ( |a| a. to_hex ( ) . eq_ignore_ascii_case ( & authed_pubkey_hex) )
578+ } ) ;
579+ if crate :: handlers:: req:: filter_fully_pushable ( filter)
580+ && ( !needs_author_only_filtering || author_is_self)
581+ {
550582 match state. db . count_events ( & query) . await {
551583 Ok ( n) => total += n as u64 ,
552584 Err ( e) => {
@@ -561,9 +593,15 @@ pub async fn count_events(
561593 match state. db . query_events ( & q) . await {
562594 Ok ( stored_events) => {
563595 for se in stored_events {
564- if buzz_core:: filter:: filters_match ( std:: slice:: from_ref ( filter) , & se) {
565- total += 1 ;
596+ if !buzz_core:: filter:: filters_match ( std:: slice:: from_ref ( filter) , & se)
597+ {
598+ continue ;
566599 }
600+ if crate :: handlers:: req:: is_author_only_event ( & se. event , & pubkey_bytes)
601+ {
602+ continue ;
603+ }
604+ total += 1 ;
567605 }
568606 }
569607 Err ( e) => {
@@ -579,7 +617,15 @@ pub async fn count_events(
579617 . await ;
580618 query. channel_ids = Some ( accessible_channels. to_vec ( ) ) ;
581619
582- if crate :: handlers:: req:: filter_fully_pushable ( filter) {
620+ let author_is_self = filter. authors . as_ref ( ) . is_some_and ( |authors| {
621+ !authors. is_empty ( )
622+ && authors
623+ . iter ( )
624+ . all ( |a| a. to_hex ( ) . eq_ignore_ascii_case ( & authed_pubkey_hex) )
625+ } ) ;
626+ if crate :: handlers:: req:: filter_fully_pushable ( filter)
627+ && ( !needs_author_only_filtering || author_is_self)
628+ {
583629 query. limit = None ;
584630 match state. db . count_events ( & query) . await {
585631 Ok ( n) => total += n as u64 ,
@@ -594,9 +640,15 @@ pub async fn count_events(
594640 match state. db . query_events ( & query) . await {
595641 Ok ( stored_events) => {
596642 for se in stored_events {
597- if buzz_core:: filter:: filters_match ( std:: slice:: from_ref ( filter) , & se) {
598- total += 1 ;
643+ if !buzz_core:: filter:: filters_match ( std:: slice:: from_ref ( filter) , & se)
644+ {
645+ continue ;
646+ }
647+ if crate :: handlers:: req:: is_author_only_event ( & se. event , & pubkey_bytes)
648+ {
649+ continue ;
599650 }
651+ total += 1 ;
600652 }
601653 }
602654 Err ( e) => {
@@ -651,6 +703,7 @@ async fn handle_bridge_search(
651703 filters : & [ nostr:: Filter ] ,
652704 accessible_channels : & [ uuid:: Uuid ] ,
653705 reader_pubkey_hex : & str ,
706+ pubkey_bytes : & [ u8 ] ,
654707) -> Result < Json < Value > , ( StatusCode , Json < Value > ) > {
655708 // Bridge always includes global (non-channel) events — same as WS with full scopes.
656709 let channel_scope = match crate :: handlers:: req:: build_search_channel_scope_filter (
@@ -766,6 +819,9 @@ async fn handle_bridge_search(
766819 if !search_hit_accepted ( filter, stored, accessible_channels, reader_pubkey_hex) {
767820 continue ;
768821 }
822+ if crate :: handlers:: req:: is_author_only_event ( & stored. event , pubkey_bytes) {
823+ continue ;
824+ }
769825 // Dedup across filters.
770826 if !seen_ids. insert ( id_array) {
771827 continue ;
0 commit comments