diff --git a/.env.example b/.env.example index 707efd4..953ae93 100644 --- a/.env.example +++ b/.env.example @@ -277,4 +277,14 @@ # Examples: # NGIT_EVENT_BLACKLIST=npub1spam... # NGIT_EVENT_BLACKLIST=npub1spam...,npub1abuser... -# NGIT_EVENT_BLACKLIST= \ No newline at end of file +# NGIT_EVENT_BLACKLIST= + +# ============================================================================ +# RATE LIMITING & DOS PROTECTION +# ============================================================================ + +# Maximum total connections to the relay +# Prevents connection exhaustion DoS attacks +# CLI: --max-connections +# Default: 500 +# NGIT_MAX_CONNECTIONS=500 \ No newline at end of file diff --git a/README.md b/README.md index e0e39fd..189478c 100644 --- a/README.md +++ b/README.md @@ -237,6 +237,48 @@ NGIT_EVENT_BLACKLIST=npub1spam1...,npub1spam2... **See**: [Configuration Reference](docs/reference/configuration.md) for complete details +## Defensive Measures & Rate Limiting + +ngit-grasp implements multiple layers of defense against abuse, spam, and denial-of-service attacks: + +**Per-Connection Rate Limits:** +- Max 500 concurrent subscriptions per connection +- Max 60 events published per minute per connection +- Built-in to rust-nostr relay-builder + +**Per-IP Connection Monitoring:** +- Tracks connections per IP address (default threshold: 10) +- Flags potential abusers in logs and metrics +- **Does NOT enforce limits** (monitoring only) +- Privacy-preserving (IP addresses never exposed in Prometheus) + +**Content Filtering (Blacklists/Whitelists):** +- **Event blacklist** - Block ALL events from specific authors (npubs) +- **Repository blacklist** - Block specific repositories/developers/identifiers +- **Repository whitelist** - Curate which repositories are accepted (GRASP-01 mode) +- **Archive whitelist** - Mirror specific repositories (GRASP-05 mode) +- See [Curation & Moderation](#curation--moderation) section above for details + +**Relay Sync Protection (GRASP-02):** +- **Exponential backoff** - Failed connections: 5s → 10s → 20s → ... → 1 hour max +- **Naughty list** - Track relays with infrastructure issues separately (12h expiry) +- **Rate limit detection** - Auto 65s cooldown when remote relays rate limit us +- **Domain throttling** - Max 5 concurrent, 30/min per domain for git data fetching + +**Event Validation:** +- Strict GRASP-01 protocol validation via WritePolicy plugin system +- Extensible for custom validation logic (has access to client IP address) + +**Total Connection Limit:** +- Max 500 total connections (configurable via `NGIT_MAX_CONNECTIONS`) +- Prevents connection exhaustion DoS attacks + +**Not Implemented:** +- Per-IP connection limits (only monitored, not enforced) +- Per-IP event rate limits (tracked per connection, not per IP) + +**See**: [Defensive Measures](docs/explanation/defensive-measures.md) for complete details and future enhancements. + ## Roadmap ### GRASP-02 Enhancements diff --git a/docs/explanation/README.md b/docs/explanation/README.md index f477b73..58cc46f 100644 --- a/docs/explanation/README.md +++ b/docs/explanation/README.md @@ -151,6 +151,48 @@ Explanation documentation helps you **understand concepts** and design decisions --- +### [Defensive Measures & Rate Limiting](defensive-measures.md) +**Protection against abuse, spam, and denial-of-service attacks** + +**Topics:** +- Connection and subscription management +- Event publishing rate limits +- Content filtering (blacklists/whitelists) +- Event validation plugin system (WritePolicy/QueryPolicy) +- Relay health management (naughty list, exponential backoff) +- Privacy-preserving IP tracking +- Future enhancements (per-IP rate limiting) + +**Read when:** You want to understand how ngit-grasp protects against abuse and what defensive features are available + +--- + +### [GRASP-05 Archive Mode](grasp-05-archive.md) +**Read-only mirroring of repositories** + +**Topics:** +- Archive whitelist configuration +- Archive-all mode +- Read-only mode defaults +- Use cases for backup/mirror relays + +**Read when:** You want to understand how to run an archive/backup relay + +--- + +### [Deletion Requests](deletion-requests.md) +**Handling repository and event deletion** + +**Topics:** +- Deletion request architecture +- Delete disrespector concept +- Preventing left-pad scenarios +- Archival policies + +**Read when:** You want to understand how ngit-grasp handles deletion events (planned feature) + +--- + ## Planned Explanation Documentation ### GRASP Protocol Design diff --git a/docs/explanation/defensive-measures.md b/docs/explanation/defensive-measures.md new file mode 100644 index 0000000..51f7278 --- /dev/null +++ b/docs/explanation/defensive-measures.md @@ -0,0 +1,165 @@ +# Defensive Measures & Rate Limiting + +This document describes the defensive measures implemented in ngit-grasp to protect against abuse, spam, and denial-of-service attacks. + +**Note:** A point-in-time analysis of defensive measures in other Nostr relays (strfry, nostr-rs-relay, khatru) was conducted to inform these design decisions. The analysis examined connection limits, rate limiting approaches, and per-IP enforcement strategies across the ecosystem. + +## Overview + +ngit-grasp employs multiple layers of defense: + +1. **Connection & Subscription Limits** - Per-connection limits on subscriptions and event publishing +2. **Content Filtering** - Blacklist/whitelist system for repositories and event authors +3. **Event Validation** - Strict GRASP-01 protocol validation +4. **Relay Health Management** - Intelligent handling of problematic remote relays + +## What's Implemented + +### Per-Connection Rate Limits + +**Source:** Built-in to rust-nostr relay-builder + +- **Subscription limit:** Max 500 concurrent subscriptions per connection +- **Event publishing limit:** Max 60 events per minute per connection +- **Subscription ID length:** Max 250 characters +- **Filter limit:** Max 500 results per query (default) + +These limits prevent individual connections from overwhelming the relay. + +### Per-IP Connection Monitoring + +**Source:** Custom ngit-grasp implementation +**Location:** `src/metrics/connection.rs` + +- **Status:** Monitoring only (does NOT enforce limits) +- Tracks connections per IP address internally +- Flags IPs exceeding threshold (default: 10 connections) +- **Privacy:** IP addresses never exposed in Prometheus metrics, only aggregate counts +- Logs warnings when threshold exceeded + +**Note on enforcement:** Per-IP connection limits are not built into rust-nostr relay-builder (tracks per WebSocket connection, not per IP). If abuse is detected via metrics, enforcement should be implemented as a PR to rust-nostr/relay-builder to benefit the entire Nostr ecosystem, rather than custom code in ngit-grasp. + +### Content Filtering (Blacklists/Whitelists) + +**Source:** Custom ngit-grasp implementation +**Location:** `src/config.rs`, `src/nostr/builder.rs` + +**Event Blacklist:** +- Block ALL events from specific authors (npubs) +- Takes precedence over all other validation +- Events never reach storage or purgatory + +**Repository Blacklist:** +- Block specific repositories, developers, or identifiers +- Takes precedence over whitelists +- Three formats: `npub`, `npub/identifier`, `identifier` + +**Repository Whitelist:** +- Curate which repositories are accepted (GRASP-01 mode) +- Only accept announcements that both list your service AND match whitelist +- Same three formats as blacklist + +**Archive Whitelist (GRASP-05):** +- Mirror specific repositories even if they don't list your service +- Same three formats as blacklist +- Default: read-only mode when enabled + +**Privacy:** Blacklists not advertised in NIP-11 metadata. + +### Event Validation Plugin System + +**Source:** Built-in to rust-nostr relay-builder +**Implementation:** Custom GRASP-01 validation in `src/nostr/builder.rs` + +- **WritePolicy trait:** Controls which events are accepted +- **QueryPolicy trait:** Controls which queries are allowed (not currently used) +- Access to client IP address for future per-IP rate limiting +- Modular sub-policies for different event types (announcements, state events, PRs) + +### Relay Health Management (GRASP-02 Sync) + +**Source:** Custom ngit-grasp implementation +**Location:** `src/sync/health.rs` + +**Exponential Backoff:** +- Failed connections trigger increasing delays: 5s → 10s → 20s → ... → 1 hour max +- Prevents hammering dead or slow relays + +**Naughty List:** +- Tracks relays with persistent infrastructure issues (DNS, TLS, protocol errors) +- Separate from normal connection failures +- 12-hour expiration (configurable) +- Reduces retry frequency for broken relays + +**Rate Limit Detection:** +- Detects when remote relay rate limits us +- Automatic 65-second cooldown +- Prevents hammering relays that tell us to slow down + +**Domain Throttling (Git Data Fetching):** +- Max 5 concurrent requests per domain +- Max 30 requests per minute per domain +- Respectful rate limiting when fetching missing git data + +## What's NOT Implemented + +### Per-IP Rate Limiting + +- **Per-IP connection limits:** Not enforced (only monitored) +- **Per-IP subscription limits:** Not supported +- **Per-IP event publishing limits:** Not supported + +**Why:** rust-nostr relay-builder tracks limits per WebSocket connection, not per IP address. + +**To implement:** Would require custom middleware/WritePolicy to aggregate across connections from the same IP. + +### Query Filtering + +**Status:** QueryPolicy trait available but not currently used. + +**Potential uses:** Rate limit queries per IP, block expensive queries, restrict access to certain event kinds. + +## Future Enhancements + +### Per-IP Rate Limiting + +Per-IP connection and event rate limiting were considered but deferred until abuse is detected in production. The current protections (per-connection limits, total connection limit, content filtering) are sufficient for the git relay use case. + +**Decision rationale:** The primary DoS vector is connection exhaustion, which is addressed by the total connection limit (`NGIT_MAX_CONNECTIONS`). Per-IP enforcement would require custom middleware in rust-nostr relay-builder (which currently tracks limits per WebSocket connection, not per IP). If abuse is detected via the per-IP monitoring metrics, enforcement should be implemented as a PR to rust-nostr/relay-builder to benefit the entire Nostr ecosystem. + +**Related:** Git endpoint throttling (issue ff38) is a separate concern with different requirements. + +## Summary Table + +| Feature | Status | Enforced? | Configurable? | +|---------|--------|-----------|---------------| +| **Per-Connection Limits** | +| Max subscriptions (500) | ✅ Active | Yes | No (relay-builder default) | +| Event rate limit (60/min) | ✅ Active | Yes | No (relay-builder default) | +| **Total Connection Limit** | +| Max connections (500) | ✅ Active | Yes | Yes (`NGIT_MAX_CONNECTIONS`) | +| **Per-IP Monitoring** | +| Connection tracking | ✅ Active | No (monitor only) | Threshold only | +| **Content Filtering** | +| Event blacklist | ✅ Active | Yes | Yes | +| Repository blacklist | ✅ Active | Yes | Yes | +| Repository whitelist | ✅ Active | Yes (if set) | Yes | +| Archive whitelist | ✅ Active | Yes (if set) | Yes | +| **Event Validation** | +| GRASP-01 validation | ✅ Active | Yes | Via WritePolicy | +| **Relay Sync Protection** | +| Exponential backoff | ✅ Active | Yes | Yes | +| Naughty list | ✅ Active | Yes | Yes (12h default) | +| Rate limit detection | ✅ Active | Yes | Automatic | +| Domain throttling | ✅ Active | Yes | Hardcoded (5/30) | +| **Not Implemented** | +| Per-IP connection limit | ⚠️ Deferred | No | - | +| Per-IP rate limiting | ⚠️ Deferred | No | - | +| Query filtering | ⚠️ Available | No | Not implemented | + +## Related Documentation + +- [Configuration Reference](../reference/configuration.md) - All config options for defensive features +- [Monitoring Overview](monitoring.md) - Prometheus metrics for tracking abuse +- [GRASP-05 Archive](grasp-05-archive.md) - Archive whitelist details +- [Architecture](architecture.md) - Overall system design diff --git a/docs/reference/configuration.md b/docs/reference/configuration.md index 8b49297..c3001d3 100644 --- a/docs/reference/configuration.md +++ b/docs/reference/configuration.md @@ -925,6 +925,46 @@ Event blacklist does **not** affect NIP-11 metadata: --- +### Rate Limiting & DoS Protection + +#### `NGIT_MAX_CONNECTIONS` + +**Description:** Maximum total connections to the relay. Prevents connection exhaustion DoS attacks. +**Type:** Integer +**Default:** `500` +**Required:** No + +**Examples:** + +```bash +# Default: 500 connections +NGIT_MAX_CONNECTIONS=500 + +# Higher limit for large public relay +NGIT_MAX_CONNECTIONS=1000 + +# Lower limit for private relay +NGIT_MAX_CONNECTIONS=100 +``` + +**Notes:** + +- Limits total concurrent WebSocket connections to the relay +- Prevents connection exhaustion attacks +- Works in conjunction with per-connection limits (500 subscriptions, 60 events/min) +- When limit is reached, new connections are rejected +- Existing connections continue to work normally + +**Related Limits:** + +Per-connection limits (built-in to relay-builder, not configurable): +- Max subscriptions per connection: 500 +- Max events per minute per connection: 60 +- Max subscription ID length: 250 characters +- Max results per filter: 500 + +--- + ### Logging Configuration #### `RUST_LOG` diff --git a/nix/module.nix b/nix/module.nix index 09c56c1..4117b6d 100644 --- a/nix/module.nix +++ b/nix/module.nix @@ -250,6 +250,12 @@ let ''; }; + maxConnections = mkOption { + type = types.int; + default = 500; + description = "Maximum total connections to the relay"; + }; + user = mkOption { type = types.str; default = "ngit-grasp-${name}"; @@ -295,6 +301,7 @@ let NGIT_REPOSITORY_WHITELIST = concatStringsSep "," cfg.repositoryWhitelist; NGIT_REPOSITORY_BLACKLIST = concatStringsSep "," cfg.repositoryBlacklist; NGIT_EVENT_BLACKLIST = concatStringsSep "," cfg.eventBlacklist; + NGIT_MAX_CONNECTIONS = toString cfg.maxConnections; RUST_LOG = cfg.logLevel; } // optionalAttrs (cfg.relayName != null) { NGIT_RELAY_NAME = cfg.relayName; diff --git a/src/config.rs b/src/config.rs index 0f0d853..0014003 100644 --- a/src/config.rs +++ b/src/config.rs @@ -469,6 +469,11 @@ pub struct Config { /// All events from these authors are blocked from both relay storage and purgatory #[arg(long, env = "NGIT_EVENT_BLACKLIST", default_value = "")] pub event_blacklist: String, + + /// Maximum total connections to the relay (default: 500) + /// Prevents connection exhaustion DoS attacks + #[arg(long, env = "NGIT_MAX_CONNECTIONS", default_value_t = 500)] + pub max_connections: usize, } impl Config { @@ -703,6 +708,7 @@ impl Config { repository_whitelist: String::new(), repository_blacklist: String::new(), event_blacklist: String::new(), + max_connections: 500, } } } diff --git a/src/nostr/builder.rs b/src/nostr/builder.rs index c2de1df..ef1b700 100644 --- a/src/nostr/builder.rs +++ b/src/nostr/builder.rs @@ -624,6 +624,14 @@ pub async fn create_relay( let relay = LocalRelayBuilder::default() .database(database.clone()) .write_policy(write_policy.clone()) + // Explicitly set rate limits (make defaults visible in code) + // Per-connection limits: 500 max subscriptions, 60 events/min + .rate_limit(RateLimit { + max_reqs: 500, // Max concurrent subscriptions per connection + notes_per_minute: 60, // Max events per minute per connection + }) + // Total connection limit to prevent DoS attacks + .max_connections(config.max_connections) .build(); tracing::info!(