diff options
| author | DanConwayDev <DanConwayDev@protonmail.com> | 2026-01-12 21:51:57 +0000 |
|---|---|---|
| committer | DanConwayDev <DanConwayDev@protonmail.com> | 2026-01-12 21:51:57 +0000 |
| commit | c8ab2c9c294ae9401ff542d0eecc6606b7908412 (patch) | |
| tree | 2ecf96e0265c855940df149781a0a24640408e1e /src/nostr/builder.rs | |
| parent | 70c577f10bbe150b6b13bec545dc8720ad005a64 (diff) | |
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: <npub>,<npub>,...
- 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
Diffstat (limited to 'src/nostr/builder.rs')
| -rw-r--r-- | src/nostr/builder.rs | 32 |
1 files changed, 31 insertions, 1 deletions
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 { | |||
| 56 | purgatory: std::sync::Arc<crate::purgatory::Purgatory>, | 56 | purgatory: std::sync::Arc<crate::purgatory::Purgatory>, |
| 57 | config: crate::config::Config, | 57 | config: crate::config::Config, |
| 58 | ) -> Self { | 58 | ) -> Self { |
| 59 | let ctx = PolicyContext::new(&config.domain, database, git_data_path, purgatory); | 59 | let ctx = PolicyContext::new( |
| 60 | &config.domain, | ||
| 61 | database, | ||
| 62 | git_data_path, | ||
| 63 | purgatory, | ||
| 64 | config.clone(), | ||
| 65 | ); | ||
| 60 | Self { | 66 | Self { |
| 61 | announcement_policy: AnnouncementPolicy::new(ctx.clone(), config.clone()), | 67 | announcement_policy: AnnouncementPolicy::new(ctx.clone(), config.clone()), |
| 62 | state_policy: StatePolicy::new(ctx.clone()), | 68 | state_policy: StatePolicy::new(ctx.clone()), |
| @@ -66,6 +72,19 @@ impl Nip34WritePolicy { | |||
| 66 | } | 72 | } |
| 67 | } | 73 | } |
| 68 | 74 | ||
| 75 | /// Check if an event author is blacklisted | ||
| 76 | /// | ||
| 77 | /// Returns Some(reason) if blacklisted, None if not blacklisted. | ||
| 78 | fn check_event_blacklist(&self, event: &Event) -> Option<String> { | ||
| 79 | let event_blacklist = self.ctx.config.event_blacklist_config(); | ||
| 80 | if !event_blacklist.enabled() { | ||
| 81 | return None; | ||
| 82 | } | ||
| 83 | |||
| 84 | let npub = event.pubkey.to_bech32().ok()?; | ||
| 85 | event_blacklist.check(&npub) | ||
| 86 | } | ||
| 87 | |||
| 69 | /// Get a reference to the purgatory for read-only access | 88 | /// Get a reference to the purgatory for read-only access |
| 70 | pub fn purgatory(&self) -> &std::sync::Arc<crate::purgatory::Purgatory> { | 89 | pub fn purgatory(&self) -> &std::sync::Arc<crate::purgatory::Purgatory> { |
| 71 | &self.ctx.purgatory | 90 | &self.ctx.purgatory |
| @@ -474,6 +493,17 @@ impl WritePolicy for Nip34WritePolicy { | |||
| 474 | addr: &'a SocketAddr, | 493 | addr: &'a SocketAddr, |
| 475 | ) -> BoxedFuture<'a, WritePolicyResult> { | 494 | ) -> BoxedFuture<'a, WritePolicyResult> { |
| 476 | Box::pin(async move { | 495 | Box::pin(async move { |
| 496 | // Check event blacklist FIRST - it overrides everything | ||
| 497 | if let Some(reason) = self.check_event_blacklist(event) { | ||
| 498 | tracing::debug!( | ||
| 499 | event_id = %event.id.to_bech32().unwrap_or_else(|_| event.id.to_hex()), | ||
| 500 | author = %event.pubkey.to_hex(), | ||
| 501 | reason = %reason, | ||
| 502 | "Rejected event from blacklisted author" | ||
| 503 | ); | ||
| 504 | return WritePolicyResult::reject(reason); | ||
| 505 | } | ||
| 506 | |||
| 477 | // Detect if this is a synced event (from proactive sync) vs user-submitted | 507 | // Detect if this is a synced event (from proactive sync) vs user-submitted |
| 478 | // Sync uses localhost:0 as a dummy address | 508 | // Sync uses localhost:0 as a dummy address |
| 479 | let is_synced = addr.ip().is_loopback() && addr.port() == 0; | 509 | let is_synced = addr.ip().is_loopback() && addr.port() == 0; |