upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.env.example25
-rw-r--r--README.md100
-rw-r--r--docs/reference/configuration.md92
-rw-r--r--nix/module.nix14
-rw-r--r--src/config.rs110
-rw-r--r--src/nostr/builder.rs32
-rw-r--r--src/nostr/policy/mod.rs4
7 files changed, 374 insertions, 3 deletions
diff --git a/.env.example b/.env.example
index 993399a..fb954c0 100644
--- a/.env.example
+++ b/.env.example
@@ -254,4 +254,27 @@
254# NGIT_REPOSITORY_BLACKLIST=npub1spam... 254# NGIT_REPOSITORY_BLACKLIST=npub1spam...
255# NGIT_REPOSITORY_BLACKLIST=npub1alice.../bad-repo 255# NGIT_REPOSITORY_BLACKLIST=npub1alice.../bad-repo
256# NGIT_REPOSITORY_BLACKLIST=malware-repo,spam-repo 256# NGIT_REPOSITORY_BLACKLIST=malware-repo,spam-repo
257# NGIT_REPOSITORY_BLACKLIST= \ No newline at end of file 257# NGIT_REPOSITORY_BLACKLIST=
258
259# ============================================================================
260# EVENT BLACKLIST
261# ============================================================================
262
263# Blacklist events from specific authors (npubs)
264# Comma-separated list of npubs whose events are rejected
265# ALL events from these authors are blocked from both relay storage and purgatory
266#
267# Event blacklist takes precedence over ALL other validation:
268# - Blacklisted events are rejected before any other policy checks
269# - Applies to announcements, state events, PRs, and all other event types
270# - Events never reach purgatory (rejected immediately)
271#
272# Rejection reason:
273# - "Event author <npub> is blacklisted"
274#
275# CLI: --event-blacklist <list>
276# Default: (empty - no events are blacklisted by author)
277# Examples:
278# NGIT_EVENT_BLACKLIST=npub1spam...
279# NGIT_EVENT_BLACKLIST=npub1spam...,npub1abuser...
280# NGIT_EVENT_BLACKLIST= \ No newline at end of file
diff --git a/README.md b/README.md
index 50bee24..b4a430f 100644
--- a/README.md
+++ b/README.md
@@ -36,7 +36,7 @@ Unlike the reference implementation ([ngit-relay](https://gitworkshop.dev/npub15
36- **Pure Rust Implementation**: Single binary, no external dependencies beyond Git itself 36- **Pure Rust Implementation**: Single binary, no external dependencies beyond Git itself
37- **Integrated Authorization**: Push validation happens inline during the Git receive-pack operation 37- **Integrated Authorization**: Push validation happens inline during the Git receive-pack operation
38- **GRASP-01 Compliant**: Core service requirements for Git hosting with Nostr authorization 38- **GRASP-01 Compliant**: Core service requirements for Git hosting with Nostr authorization
39 - **Repository Whitelist/Blacklist**: Optional curation via pubkey/identifier whitelist (GRASP-01 mode) and blacklist (overrides all whitelists) 39 - **Flexible Curation & Moderation**: Repository whitelists (GRASP-01 mode), repository blacklists (moderation), and event blacklists (author blocking)
40- **GRASP-02 Proactive Sync**: Sophisticated relay-to-relay event and git data synchronization 40- **GRASP-02 Proactive Sync**: Sophisticated relay-to-relay event and git data synchronization
41 - **NIP-77 Negentropy**: Efficient set reconciliation with automatic fallback to REQ+EOSE 41 - **NIP-77 Negentropy**: Efficient set reconciliation with automatic fallback to REQ+EOSE
42 - **Live & Historic Sync**: Real-time event streaming plus catch-up for past events 42 - **Live & Historic Sync**: Real-time event streaming plus catch-up for past events
@@ -150,6 +150,93 @@ See [GRASP-02 Proactive Sync](docs/explanation/grasp-02-proactive-sync.md) for f
150 150
151**See**: [GRASP-05 Archive Mode](docs/explanation/grasp-05-archive.md) 151**See**: [GRASP-05 Archive Mode](docs/explanation/grasp-05-archive.md)
152 152
153## Curation & Moderation
154
155ngit-grasp provides flexible tools for both curation (repository selection) and moderation (blocking spam/abuse):
156
157### Repository Whitelists (Curation)
158
159Control which repositories your relay accepts via two independent whitelist modes:
160
161**Repository Whitelist (GRASP-01 Mode):**
162- Only accept announcements that **both** list your service AND match the whitelist
163- Three formats: `<npub>`, `<npub>/<identifier>`, `<identifier>`
164- Environment: `NGIT_REPOSITORY_WHITELIST=npub1alice...,bitcoin-core`
165- Use case: Curated relay accepting specific projects/developers
166
167**Archive Whitelist (GRASP-05 Mode):**
168- Accept announcements matching the whitelist **even if they don't list your service**
169- Same three formats as repository whitelist
170- Environment: `NGIT_ARCHIVE_WHITELIST=npub1satoshi...,linux`
171- Use case: Backup/mirror relay for critical projects
172- Default: Read-only mode (`NGIT_ARCHIVE_READ_ONLY=true`)
173
174Both whitelists support flexible matching:
175```bash
176# Accept all repos from specific developer
177NGIT_REPOSITORY_WHITELIST=npub1alice...
178
179# Accept specific repository
180NGIT_REPOSITORY_WHITELIST=npub1alice.../my-project
181
182# Accept repos with specific identifier (any author)
183NGIT_REPOSITORY_WHITELIST=bitcoin-core
184```
185
186### Blacklists (Moderation)
187
188Block unwanted content without affecting your curation policy:
189
190**Repository Blacklist:**
191- Block specific repositories/developers/identifiers
192- **Takes precedence over ALL whitelists** (checked first)
193- Three formats: `<npub>`, `<npub>/<identifier>`, `<identifier>`
194- Environment: `NGIT_REPOSITORY_BLACKLIST=npub1spam...,malware-repo`
195- Use case: Block spam/malware repos while maintaining whitelist curation
196
197**Event Blacklist:**
198- Block **ALL events** from specific authors (npubs)
199- **Takes precedence over ALL other validation** (checked first)
200- Applies to all event types: announcements, state events, PRs, comments, etc.
201- Events never reach relay storage or purgatory
202- Environment: `NGIT_EVENT_BLACKLIST=npub1spammer...,npub1abuser...`
203- Use case: Block abusive users completely
204
205### Precedence & Interaction
206
207Validation order (from first to last):
208
2091. **Event Blacklist** → Reject if author is blacklisted (ALL event types)
2102. **Repository Blacklist** → Reject if repository/npub/identifier is blacklisted (announcements only)
2113. **Repository Whitelist** → Accept if announcement lists service AND matches whitelist
2124. **Archive Whitelist** → Accept if announcement matches whitelist (even without listing service)
2135. **Default GRASP-01** → Accept if announcement lists service (no whitelist configured)
214
215Examples:
216```bash
217# Curated relay blocking spam
218NGIT_REPOSITORY_WHITELIST=npub1alice...,npub1bob...
219NGIT_REPOSITORY_BLACKLIST=npub1alice.../spam-repo
220NGIT_EVENT_BLACKLIST=npub1spammer...
221# Result: Accept Alice & Bob's repos EXCEPT Alice's spam-repo, block all events from spammer
222
223# Archive relay with moderation
224NGIT_ARCHIVE_WHITELIST=bitcoin-core,linux
225NGIT_EVENT_BLACKLIST=npub1abuser...
226# Result: Mirror bitcoin-core and linux projects, block all events from abuser
227
228# Public relay with spam protection
229NGIT_EVENT_BLACKLIST=npub1spam1...,npub1spam2...
230# Result: Accept all GRASP-01 repos, block all events from spammers
231```
232
233**Privacy & Transparency:**
234- Blacklists are **not advertised** in NIP-11 metadata (operational, not curation policy)
235- Rejected events receive specific error messages for operator debugging
236- No client-visible indication that blacklists are in use
237
238**See**: [Configuration Reference](docs/reference/configuration.md) for complete details
239
153## Roadmap 240## Roadmap
154 241
155### GRASP-02 Enhancements 242### GRASP-02 Enhancements
@@ -326,6 +413,17 @@ NGIT_RELAY_OWNER_NSEC=nsec1... ngit-grasp --domain relay.example.com
326| Disable negentropy | `--sync-disable-negentropy` | `NGIT_SYNC_DISABLE_NEGENTROPY` | `false` | 413| Disable negentropy | `--sync-disable-negentropy` | `NGIT_SYNC_DISABLE_NEGENTROPY` | `false` |
327| Batch window | N/A | `NGIT_SYNC_BATCH_WINDOW_MS` | `5000` ms | 414| Batch window | N/A | `NGIT_SYNC_BATCH_WINDOW_MS` | `5000` ms |
328 415
416#### Curation & Moderation Settings
417
418| Option | CLI Flag | Environment Variable | Default |
419| -------------------- | --------------------------- | ------------------------------ | --------- |
420| Repository whitelist | `--repository-whitelist` | `NGIT_REPOSITORY_WHITELIST` | (empty) |
421| Archive whitelist | `--archive-whitelist` | `NGIT_ARCHIVE_WHITELIST` | (empty) |
422| Archive all | `--archive-all` | `NGIT_ARCHIVE_ALL` | `false` |
423| Archive read-only | `--archive-read-only` | `NGIT_ARCHIVE_READ_ONLY` | (auto) |
424| Repository blacklist | `--repository-blacklist` | `NGIT_REPOSITORY_BLACKLIST` | (empty) |
425| Event blacklist | `--event-blacklist` | `NGIT_EVENT_BLACKLIST` | (empty) |
426
329**Sync Notes:** 427**Sync Notes:**
330 428
331- **Bootstrap relay**: Optional starting point for relay discovery. System automatically discovers additional relays from repository announcements. URL scheme is optional - if not provided, `wss://` is assumed (e.g., `git.shakespeare.diy` → `wss://git.shakespeare.diy`). 429- **Bootstrap relay**: Optional starting point for relay discovery. System automatically discovers additional relays from repository announcements. URL scheme is optional - if not provided, `wss://` is assumed (e.g., `git.shakespeare.diy` → `wss://git.shakespeare.diy`).
diff --git a/docs/reference/configuration.md b/docs/reference/configuration.md
index b90686e..66f39f1 100644
--- a/docs/reference/configuration.md
+++ b/docs/reference/configuration.md
@@ -833,6 +833,98 @@ Blacklist does **not** affect NIP-11 metadata:
833 833
834--- 834---
835 835
836### Event Blacklist
837
838#### `NGIT_EVENT_BLACKLIST`
839
840**Description:** Blacklist events from specific authors (npubs)
841**Type:** Comma-separated list of npubs
842**Default:** Empty (no events are blacklisted by author)
843**Required:** No
844
845**Format:**
846- `npub1...` - Block all events from this author
847
848**Precedence:** Event blacklist takes precedence over **ALL** other validation:
849- Blacklisted events are rejected **before** any other policy checks
850- Applies to all event types (announcements, state events, PRs, etc.)
851- Events never reach purgatory (rejected immediately)
852- Overrides repository blacklist, whitelists, and all other policies
853
854**Examples:**
855
856```bash
857# Block all events from specific author
858NGIT_EVENT_BLACKLIST=npub1spam...
859
860# Block events from multiple authors
861NGIT_EVENT_BLACKLIST=npub1spam...,npub1abuser...,npub1troll...
862```
863
864**Rejection Reason:**
865
866The event blacklist provides a specific rejection reason:
867- **Format:** `"Event author <npub> is blacklisted"`
868
869This reason helps operators understand why an event was rejected without needing to flag it in metadata.
870
871**Behavior:**
872
873Event blacklist is checked **first** before all other validation:
8741. Check event blacklist → Reject if author is blacklisted
8752. Check repository blacklist (for announcements) → Reject if matched
8763. Check event-type specific policies → Accept/Reject based on policy
8774. Process event normally
878
879**Use Cases:**
880
881```bash
882# Block spam/abusive users
883NGIT_EVENT_BLACKLIST=npub1spammer...,npub1abuser...
884
885# Block malicious actors
886NGIT_EVENT_BLACKLIST=npub1malware...,npub1phisher...
887
888# Temporary block for investigation
889NGIT_EVENT_BLACKLIST=npub1suspicious...
890```
891
892**Comparison with Repository Blacklist:**
893
894| Configuration | Scope | Checked When | Applies To |
895|---------------|-------|--------------|------------|
896| Event Blacklist | Author-based | **First** (before all policies) | **All events** from author |
897| Repository Blacklist | Repo-based | Second (announcements only) | Specific repositories |
898
899**Event Blacklist vs Repository Blacklist:**
900
901```bash
902# Scenario: npub1alice is event-blacklisted
903NGIT_EVENT_BLACKLIST=npub1alice...
904
905# Result:
906# - ALL events from npub1alice are rejected (announcements, PRs, etc.)
907# - Events never reach relay or purgatory
908# - Rejection: "Event author npub1alice... is blacklisted"
909
910# Scenario: npub1alice/repo is repository-blacklisted
911NGIT_REPOSITORY_BLACKLIST=npub1alice.../malware
912
913# Result:
914# - Only announcements for npub1alice.../malware are rejected
915# - Other events from npub1alice are still processed normally
916# - PRs/state events for different repos from npub1alice are accepted
917```
918
919**NIP-11 Impact:**
920
921Event blacklist does **not** affect NIP-11 metadata:
922- No `curation` field changes (blacklist is operational, not policy)
923- Blacklist is transparent to clients (rejected with specific reason)
924- Operators can use blacklist without advertising moderation
925
926---
927
836### Logging Configuration 928### Logging Configuration
837 929
838#### `RUST_LOG` 930#### `RUST_LOG`
diff --git a/nix/module.nix b/nix/module.nix
index cfac0fc..799ae2d 100644
--- a/nix/module.nix
+++ b/nix/module.nix
@@ -237,6 +237,19 @@ let
237 ''; 237 '';
238 }; 238 };
239 239
240 eventBlacklist = mkOption {
241 type = types.listOf types.str;
242 default = [ ];
243 example = [ "npub1spam..." "npub1abuser..." ];
244 description = ''
245 Event blacklist for blocking all events from specific authors (npubs).
246 Takes precedence over ALL other validation (checked first).
247 ALL events from these authors are rejected from relay storage and purgatory.
248 Applies to announcements, state events, PRs, and all other event types.
249 Does not affect NIP-11 metadata (operational, not curation policy).
250 '';
251 };
252
240 user = mkOption { 253 user = mkOption {
241 type = types.str; 254 type = types.str;
242 default = "ngit-grasp-${name}"; 255 default = "ngit-grasp-${name}";
@@ -281,6 +294,7 @@ let
281 NGIT_ARCHIVE_WHITELIST = concatStringsSep "," cfg.archiveWhitelist; 294 NGIT_ARCHIVE_WHITELIST = concatStringsSep "," cfg.archiveWhitelist;
282 NGIT_REPOSITORY_WHITELIST = concatStringsSep "," cfg.repositoryWhitelist; 295 NGIT_REPOSITORY_WHITELIST = concatStringsSep "," cfg.repositoryWhitelist;
283 NGIT_REPOSITORY_BLACKLIST = concatStringsSep "," cfg.repositoryBlacklist; 296 NGIT_REPOSITORY_BLACKLIST = concatStringsSep "," cfg.repositoryBlacklist;
297 NGIT_EVENT_BLACKLIST = concatStringsSep "," cfg.eventBlacklist;
284 RUST_LOG = cfg.logLevel; 298 RUST_LOG = cfg.logLevel;
285 } // optionalAttrs (cfg.relayName != null) { 299 } // optionalAttrs (cfg.relayName != null) {
286 NGIT_RELAY_NAME = cfg.relayName; 300 NGIT_RELAY_NAME = cfg.relayName;
diff --git a/src/config.rs b/src/config.rs
index 5f8cbca..a5e4344 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -244,6 +244,42 @@ impl Default for BlacklistConfig {
244 } 244 }
245} 245}
246 246
247/// Event blacklist configuration for blocking events by author npub
248#[derive(Debug, Clone, Serialize, Deserialize)]
249pub struct EventBlacklistConfig {
250 /// Blacklisted npubs - events from these authors are rejected
251 ///
252 /// If empty, no events are blacklisted by author.
253 /// Applies to ALL event types, preventing events from reaching both the relay and purgatory.
254 pub blacklisted_npubs: Vec<String>,
255}
256
257impl EventBlacklistConfig {
258 /// Check if event blacklist is enabled (non-empty blacklist)
259 pub fn enabled(&self) -> bool {
260 !self.blacklisted_npubs.is_empty()
261 }
262
263 /// Check if an event author is blacklisted
264 ///
265 /// Returns Some(reason) if blacklisted, None if not blacklisted.
266 pub fn check(&self, npub: &str) -> Option<String> {
267 if self.blacklisted_npubs.contains(&npub.to_string()) {
268 Some(format!("Event author {} is blacklisted", npub))
269 } else {
270 None
271 }
272 }
273}
274
275impl Default for EventBlacklistConfig {
276 fn default() -> Self {
277 Self {
278 blacklisted_npubs: Vec::new(),
279 }
280 }
281}
282
247/// Database backend type for the relay 283/// Database backend type for the relay
248#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default, ValueEnum)] 284#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default, ValueEnum)]
249#[serde(rename_all = "lowercase")] 285#[serde(rename_all = "lowercase")]
@@ -428,6 +464,11 @@ pub struct Config {
428 /// Blacklist takes precedence over all whitelists (archive and repository) 464 /// Blacklist takes precedence over all whitelists (archive and repository)
429 #[arg(long, env = "NGIT_REPOSITORY_BLACKLIST", default_value = "")] 465 #[arg(long, env = "NGIT_REPOSITORY_BLACKLIST", default_value = "")]
430 pub repository_blacklist: String, 466 pub repository_blacklist: String,
467
468 /// Event blacklist: comma-separated list of npubs whose events are rejected
469 /// All events from these authors are blocked from both relay storage and purgatory
470 #[arg(long, env = "NGIT_EVENT_BLACKLIST", default_value = "")]
471 pub event_blacklist: String,
431} 472}
432 473
433impl Config { 474impl Config {
@@ -612,6 +653,20 @@ impl Config {
612 BlacklistConfig { blacklist } 653 BlacklistConfig { blacklist }
613 } 654 }
614 655
656 /// Get parsed event blacklist configuration
657 ///
658 /// This method assumes config has been validated - call Config::validate() first!
659 pub fn event_blacklist_config(&self) -> EventBlacklistConfig {
660 let blacklisted_npubs: Vec<String> = self
661 .event_blacklist
662 .split(',')
663 .map(|s| s.trim())
664 .filter(|s| !s.is_empty())
665 .map(|s| s.to_string())
666 .collect();
667 EventBlacklistConfig { blacklisted_npubs }
668 }
669
615 /// Create config for testing 670 /// Create config for testing
616 #[cfg(test)] 671 #[cfg(test)]
617 pub fn for_testing() -> Self { 672 pub fn for_testing() -> Self {
@@ -647,6 +702,7 @@ impl Config {
647 archive_read_only: None, 702 archive_read_only: None,
648 repository_whitelist: String::new(), 703 repository_whitelist: String::new(),
649 repository_blacklist: String::new(), 704 repository_blacklist: String::new(),
705 event_blacklist: String::new(),
650 } 706 }
651 } 707 }
652} 708}
@@ -1248,4 +1304,58 @@ mod tests {
1248 let result = config.check(&test_npub, "allowed-repo"); 1304 let result = config.check(&test_npub, "allowed-repo");
1249 assert!(result.is_none()); 1305 assert!(result.is_none());
1250 } 1306 }
1307
1308 #[test]
1309 fn test_event_blacklist_config_parsing() {
1310 let keys1 = Keys::generate();
1311 let keys2 = Keys::generate();
1312 let npub1 = keys1.public_key().to_bech32().unwrap();
1313 let npub2 = keys2.public_key().to_bech32().unwrap();
1314 let config = Config {
1315 event_blacklist: format!("{},{}", npub1, npub2),
1316 ..Config::for_testing()
1317 };
1318 let event_blacklist_config = config.event_blacklist_config();
1319 assert_eq!(event_blacklist_config.blacklisted_npubs.len(), 2);
1320 assert!(event_blacklist_config.enabled());
1321 assert!(event_blacklist_config.blacklisted_npubs.contains(&npub1));
1322 assert!(event_blacklist_config.blacklisted_npubs.contains(&npub2));
1323 }
1324
1325 #[test]
1326 fn test_event_blacklist_config_empty() {
1327 let config = Config::for_testing();
1328 let event_blacklist_config = config.event_blacklist_config();
1329 assert!(event_blacklist_config.blacklisted_npubs.is_empty());
1330 assert!(!event_blacklist_config.enabled());
1331 }
1332
1333 #[test]
1334 fn test_event_blacklist_check_blacklisted() {
1335 let keys = Keys::generate();
1336 let test_npub = keys.public_key().to_bech32().unwrap();
1337 let config = EventBlacklistConfig {
1338 blacklisted_npubs: vec![test_npub.clone()],
1339 };
1340
1341 let result = config.check(&test_npub);
1342 assert!(result.is_some());
1343 let reason = result.unwrap();
1344 assert!(reason.contains("author"));
1345 assert!(reason.contains(&test_npub));
1346 }
1347
1348 #[test]
1349 fn test_event_blacklist_check_not_blacklisted() {
1350 let keys1 = Keys::generate();
1351 let keys2 = Keys::generate();
1352 let banned_npub = keys1.public_key().to_bech32().unwrap();
1353 let allowed_npub = keys2.public_key().to_bech32().unwrap();
1354 let config = EventBlacklistConfig {
1355 blacklisted_npubs: vec![banned_npub],
1356 };
1357
1358 let result = config.check(&allowed_npub);
1359 assert!(result.is_none());
1360 }
1251} 1361}
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;
diff --git a/src/nostr/policy/mod.rs b/src/nostr/policy/mod.rs
index dc023a9..1566b6c 100644
--- a/src/nostr/policy/mod.rs
+++ b/src/nostr/policy/mod.rs
@@ -32,6 +32,8 @@ pub struct PolicyContext {
32 pub purgatory: Arc<Purgatory>, 32 pub purgatory: Arc<Purgatory>,
33 /// Local relay for notifying WebSocket subscribers (set after relay creation) 33 /// Local relay for notifying WebSocket subscribers (set after relay creation)
34 pub local_relay: Arc<std::sync::RwLock<Option<LocalRelay>>>, 34 pub local_relay: Arc<std::sync::RwLock<Option<LocalRelay>>>,
35 /// Configuration reference for policy settings (includes blacklists)
36 pub config: crate::config::Config,
35} 37}
36 38
37impl PolicyContext { 39impl PolicyContext {
@@ -40,6 +42,7 @@ impl PolicyContext {
40 database: SharedDatabase, 42 database: SharedDatabase,
41 git_data_path: impl Into<std::path::PathBuf>, 43 git_data_path: impl Into<std::path::PathBuf>,
42 purgatory: Arc<Purgatory>, 44 purgatory: Arc<Purgatory>,
45 config: crate::config::Config,
43 ) -> Self { 46 ) -> Self {
44 Self { 47 Self {
45 domain: domain.into(), 48 domain: domain.into(),
@@ -47,6 +50,7 @@ impl PolicyContext {
47 git_data_path: git_data_path.into(), 50 git_data_path: git_data_path.into(),
48 purgatory, 51 purgatory,
49 local_relay: Arc::new(std::sync::RwLock::new(None)), 52 local_relay: Arc::new(std::sync::RwLock::new(None)),
53 config,
50 } 54 }
51 } 55 }
52 56