upleb.uk

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

summaryrefslogtreecommitdiff
path: root/src/nostr
diff options
context:
space:
mode:
authorDanConwayDev <DanConwayDev@protonmail.com>2026-01-12 21:32:38 +0000
committerDanConwayDev <DanConwayDev@protonmail.com>2026-01-12 21:33:15 +0000
commit70c577f10bbe150b6b13bec545dc8720ad005a64 (patch)
tree4f390cd523248db007ecb4335a61598b930ccad9 /src/nostr
parent1948312d40f34fca868d1ef6d6d94e165c09738c (diff)
feat(config): add repository blacklist to block specific repos/npubs/identifiers
Adds NGIT_REPOSITORY_BLACKLIST option for blocking repositories, taking precedence over all whitelists (archive and repository) to enable moderation without affecting curation policy. Key features: - Three blacklist formats: <npub>, <npub>/<identifier>, <identifier> - Blacklist checked first before any other validation - Overrides archive whitelist and repository whitelist - Specific rejection reasons based on match type (npub/identifier/both) - Not flagged in NIP-11 curation (operational, not policy) Implementation: - Add BlacklistConfig struct with check() method returning detailed reasons - Add NGIT_REPOSITORY_BLACKLIST config option and blacklist_config() method - Update validate_announcement() to check blacklist first with specific reasons - 12 new unit tests covering all blacklist behavior and precedence Configuration synced across all four sources: - src/config.rs: Core implementation with BlacklistConfig - .env.example: Comprehensive documentation with examples - docs/reference/configuration.md: Complete reference documentation - nix/module.nix: NixOS module option with environment mapping Testing: - 12 new tests for blacklist functionality (config + validation) - All 332 library tests passing - All 38 integration tests passing Use cases: - Block spam/malware repos by identifier - Block abusive users by npub - Block specific problematic repos by npub/identifier - Temporary blocks for investigation
Diffstat (limited to 'src/nostr')
-rw-r--r--src/nostr/events.rs187
1 files changed, 187 insertions, 0 deletions
diff --git a/src/nostr/events.rs b/src/nostr/events.rs
index 3b4ef25..39014da 100644
--- a/src/nostr/events.rs
+++ b/src/nostr/events.rs
@@ -366,6 +366,9 @@ impl RepositoryState {
366/// - AcceptArchive: Announcement matches archive config (GRASP-05) 366/// - AcceptArchive: Announcement matches archive config (GRASP-05)
367/// - Reject: Validation failed 367/// - Reject: Validation failed
368/// 368///
369/// Blacklist takes precedence over all whitelists:
370/// - If blacklisted, always reject with specific reason (npub/identifier/npub+identifier)
371///
369/// When archive_read_only is true: 372/// When archive_read_only is true:
370/// - ONLY accept announcements matching archive whitelist/all 373/// - ONLY accept announcements matching archive whitelist/all
371/// - REJECT announcements listing our service but not in whitelist (read-only sync mode) 374/// - REJECT announcements listing our service but not in whitelist (read-only sync mode)
@@ -403,10 +406,16 @@ pub fn validate_announcement(
403 // Get validated configs (config.validate() must be called at startup) 406 // Get validated configs (config.validate() must be called at startup)
404 let archive_config = config.archive_config(); 407 let archive_config = config.archive_config();
405 let repository_config = config.repository_config(); 408 let repository_config = config.repository_config();
409 let blacklist_config = config.blacklist_config();
406 410
407 let npub = announcement.owner_npub(); 411 let npub = announcement.owner_npub();
408 let lists_service = announcement.lists_service(&config.domain); 412 let lists_service = announcement.lists_service(&config.domain);
409 413
414 // Check blacklist FIRST - it overrides everything
415 if let Some(reason) = blacklist_config.check(&npub, &announcement.identifier) {
416 return AnnouncementResult::Reject(reason);
417 }
418
410 // GRASP-01: Normal mode - accept if announcement lists our service AND matches repository whitelist (if enabled) 419 // GRASP-01: Normal mode - accept if announcement lists our service AND matches repository whitelist (if enabled)
411 if lists_service && !archive_config.read_only { 420 if lists_service && !archive_config.read_only {
412 // Check repository whitelist if enabled 421 // Check repository whitelist if enabled
@@ -1309,4 +1318,182 @@ mod tests {
1309 let result = validate_announcement(&event, &config); 1318 let result = validate_announcement(&event, &config);
1310 assert!(matches!(result, AnnouncementResult::Reject(_))); 1319 assert!(matches!(result, AnnouncementResult::Reject(_)));
1311 } 1320 }
1321
1322 #[test]
1323 fn test_blacklist_rejects_npub() {
1324 use crate::config::Config;
1325 use crate::nostr::policy::AnnouncementResult;
1326
1327 let keys = create_test_keys();
1328 let npub = keys.public_key().to_bech32().unwrap();
1329
1330 // Create announcement that lists our service
1331 let event = create_announcement_event(
1332 &keys,
1333 "test-repo",
1334 vec!["https://gitnostr.com/alice/test-repo.git"],
1335 vec!["wss://gitnostr.com"],
1336 );
1337
1338 // Config with blacklist for this npub
1339 let config = Config {
1340 domain: "gitnostr.com".to_string(),
1341 repository_blacklist: npub.clone(),
1342 ..Config::for_testing()
1343 };
1344
1345 let result = validate_announcement(&event, &config);
1346 if let AnnouncementResult::Reject(reason) = result {
1347 assert!(reason.contains("owner"));
1348 assert!(reason.contains(&npub));
1349 } else {
1350 panic!("Expected Reject, got {:?}", result);
1351 }
1352 }
1353
1354 #[test]
1355 fn test_blacklist_rejects_identifier() {
1356 use crate::config::Config;
1357 use crate::nostr::policy::AnnouncementResult;
1358
1359 let keys = create_test_keys();
1360
1361 // Create announcement that lists our service
1362 let event = create_announcement_event(
1363 &keys,
1364 "banned-repo",
1365 vec!["https://gitnostr.com/alice/banned-repo.git"],
1366 vec!["wss://gitnostr.com"],
1367 );
1368
1369 // Config with blacklist for this identifier
1370 let config = Config {
1371 domain: "gitnostr.com".to_string(),
1372 repository_blacklist: "banned-repo".to_string(),
1373 ..Config::for_testing()
1374 };
1375
1376 let result = validate_announcement(&event, &config);
1377 if let AnnouncementResult::Reject(reason) = result {
1378 assert!(reason.contains("identifier"));
1379 assert!(reason.contains("banned-repo"));
1380 } else {
1381 panic!("Expected Reject, got {:?}", result);
1382 }
1383 }
1384
1385 #[test]
1386 fn test_blacklist_rejects_specific_repository() {
1387 use crate::config::Config;
1388 use crate::nostr::policy::AnnouncementResult;
1389
1390 let keys = create_test_keys();
1391 let npub = keys.public_key().to_bech32().unwrap();
1392
1393 // Create announcement that lists our service
1394 let event = create_announcement_event(
1395 &keys,
1396 "specific-repo",
1397 vec!["https://gitnostr.com/alice/specific-repo.git"],
1398 vec!["wss://gitnostr.com"],
1399 );
1400
1401 // Config with blacklist for this specific repo
1402 let config = Config {
1403 domain: "gitnostr.com".to_string(),
1404 repository_blacklist: format!("{}/specific-repo", npub),
1405 ..Config::for_testing()
1406 };
1407
1408 let result = validate_announcement(&event, &config);
1409 if let AnnouncementResult::Reject(reason) = result {
1410 assert!(reason.contains(&npub));
1411 assert!(reason.contains("specific-repo"));
1412 } else {
1413 panic!("Expected Reject, got {:?}", result);
1414 }
1415 }
1416
1417 #[test]
1418 fn test_blacklist_overrides_repository_whitelist() {
1419 use crate::config::Config;
1420 use crate::nostr::policy::AnnouncementResult;
1421
1422 let keys = create_test_keys();
1423 let npub = keys.public_key().to_bech32().unwrap();
1424
1425 // Create announcement that lists our service
1426 let event = create_announcement_event(
1427 &keys,
1428 "test-repo",
1429 vec!["https://gitnostr.com/alice/test-repo.git"],
1430 vec!["wss://gitnostr.com"],
1431 );
1432
1433 // Config with both whitelist and blacklist - blacklist should win
1434 let config = Config {
1435 domain: "gitnostr.com".to_string(),
1436 repository_whitelist: npub.clone(),
1437 repository_blacklist: npub.clone(),
1438 ..Config::for_testing()
1439 };
1440
1441 let result = validate_announcement(&event, &config);
1442 assert!(matches!(result, AnnouncementResult::Reject(_)));
1443 }
1444
1445 #[test]
1446 fn test_blacklist_overrides_archive_whitelist() {
1447 use crate::config::Config;
1448 use crate::nostr::policy::AnnouncementResult;
1449
1450 let keys = create_test_keys();
1451 let npub = keys.public_key().to_bech32().unwrap();
1452
1453 // Create announcement that does NOT list our service
1454 let event = create_announcement_event(
1455 &keys,
1456 "test-repo",
1457 vec!["https://other-service.com/alice/test-repo.git"],
1458 vec!["wss://other-service.com"],
1459 );
1460
1461 // Config with archive whitelist and blacklist - blacklist should win
1462 let config = Config {
1463 domain: "gitnostr.com".to_string(),
1464 archive_whitelist: npub.clone(),
1465 archive_read_only: Some(false),
1466 repository_blacklist: npub.clone(),
1467 ..Config::for_testing()
1468 };
1469
1470 let result = validate_announcement(&event, &config);
1471 assert!(matches!(result, AnnouncementResult::Reject(_)));
1472 }
1473
1474 #[test]
1475 fn test_blacklist_allows_non_blacklisted() {
1476 use crate::config::Config;
1477 use crate::nostr::policy::AnnouncementResult;
1478
1479 let keys = create_test_keys();
1480
1481 // Create announcement that lists our service
1482 let event = create_announcement_event(
1483 &keys,
1484 "allowed-repo",
1485 vec!["https://gitnostr.com/alice/allowed-repo.git"],
1486 vec!["wss://gitnostr.com"],
1487 );
1488
1489 // Config with blacklist for different identifier
1490 let config = Config {
1491 domain: "gitnostr.com".to_string(),
1492 repository_blacklist: "banned-repo".to_string(),
1493 ..Config::for_testing()
1494 };
1495
1496 let result = validate_announcement(&event, &config);
1497 assert!(matches!(result, AnnouncementResult::Accept));
1498 }
1312} 1499}