From c8ab2c9c294ae9401ff542d0eecc6606b7908412 Mon Sep 17 00:00:00 2001 From: DanConwayDev Date: Mon, 12 Jan 2026 21:51:57 +0000 Subject: feat(config): add event blacklist to block all events from specific authors Adds NGIT_EVENT_BLACKLIST option for blocking all events from specific npubs, taking precedence over all other validation to enable comprehensive moderation without affecting curation policy. Key features: - Simple npub-only format: ,,... - Checked FIRST before any other validation (including repository blacklist) - Blocks ALL event types (announcements, state events, PRs, comments, etc.) - Events never reach relay storage or purgatory - Specific rejection reason for operator debugging Implementation: - Add EventBlacklistConfig struct with check() method - Add NGIT_EVENT_BLACKLIST config option and event_blacklist_config() method - Add config field to PolicyContext for policy access - Add check_event_blacklist() to Nip34WritePolicy - Check event blacklist first in admit_event() method (before any other validation) - 4 new unit tests covering all blacklist behavior Configuration synced across all four sources: - src/config.rs: Core implementation with EventBlacklistConfig - .env.example: Comprehensive documentation with examples - docs/reference/configuration.md: Complete reference documentation - nix/module.nix: NixOS module option with environment mapping README updates: - Add comprehensive "Curation & Moderation" section - Document repository whitelists (GRASP-01 and GRASP-05 modes) - Document repository and event blacklists with precedence order - Add configuration table for all curation/moderation settings - Provide real-world examples for different relay configurations Testing: - 4 new tests for event blacklist functionality - All 336 library tests passing - All 64 integration tests passing - All 38 filter support tests passing Verification: - Repository blacklist confirmed to apply to sync (uses same admit_event flow) - Sync events validated through process_event_static -> write_policy.admit_event Use cases: - Block spam/abusive users completely - Prevent malicious actors from submitting any events - Temporary blocks for investigation - Moderation without affecting whitelist curation policy --- src/nostr/builder.rs | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) (limited to 'src/nostr/builder.rs') diff --git a/src/nostr/builder.rs b/src/nostr/builder.rs index 9819e37..c2de1df 100644 --- a/src/nostr/builder.rs +++ b/src/nostr/builder.rs @@ -56,7 +56,13 @@ impl Nip34WritePolicy { purgatory: std::sync::Arc, config: crate::config::Config, ) -> Self { - let ctx = PolicyContext::new(&config.domain, database, git_data_path, purgatory); + let ctx = PolicyContext::new( + &config.domain, + database, + git_data_path, + purgatory, + config.clone(), + ); Self { announcement_policy: AnnouncementPolicy::new(ctx.clone(), config.clone()), state_policy: StatePolicy::new(ctx.clone()), @@ -66,6 +72,19 @@ impl Nip34WritePolicy { } } + /// Check if an event author is blacklisted + /// + /// Returns Some(reason) if blacklisted, None if not blacklisted. + fn check_event_blacklist(&self, event: &Event) -> Option { + let event_blacklist = self.ctx.config.event_blacklist_config(); + if !event_blacklist.enabled() { + return None; + } + + let npub = event.pubkey.to_bech32().ok()?; + event_blacklist.check(&npub) + } + /// Get a reference to the purgatory for read-only access pub fn purgatory(&self) -> &std::sync::Arc { &self.ctx.purgatory @@ -474,6 +493,17 @@ impl WritePolicy for Nip34WritePolicy { addr: &'a SocketAddr, ) -> BoxedFuture<'a, WritePolicyResult> { Box::pin(async move { + // Check event blacklist FIRST - it overrides everything + if let Some(reason) = self.check_event_blacklist(event) { + tracing::debug!( + event_id = %event.id.to_bech32().unwrap_or_else(|_| event.id.to_hex()), + author = %event.pubkey.to_hex(), + reason = %reason, + "Rejected event from blacklisted author" + ); + return WritePolicyResult::reject(reason); + } + // Detect if this is a synced event (from proactive sync) vs user-submitted // Sync uses localhost:0 as a dummy address let is_synced = addr.ip().is_loopback() && addr.port() == 0; -- cgit v1.2.3