upleb.uk

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

summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDanConwayDev <DanConwayDev@protonmail.com>2026-01-14 13:40:33 +0000
committerDanConwayDev <DanConwayDev@protonmail.com>2026-01-14 13:43:40 +0000
commit5897e4bccd41f1a9ebb01a11280cea929c93d2c0 (patch)
treecbe4d2447312b7bc7653bef874b6fb23d60a0ede
parent4c8f1813fada9ce2bfd371095b0721bff68173e3 (diff)
parent2821578202d1313c23c30a5dbae39548822e3c55 (diff)
Add defensive relay features with rate limiting and connection limits
Implement defensive measures to protect against DoS attacks: - Add explicit rate limits (500 subscriptions, 60 events/min per connection) - Add total connection limit (default: 500, configurable via NGIT_MAX_CONNECTIONS) - Update configuration across all 4 locations (src, nix, docs, .env.example) Per-IP rate limiting deferred until abuse is detected in production or implemented in rust-nostr relay-builder to benefit the entire Nostr ecosystem. Documentation added explaining the defensive features and rationale. Detailed analysis of other relay implementations preserved in commit history.
-rw-r--r--.env.example12
-rw-r--r--README.md42
-rw-r--r--docs/explanation/README.md42
-rw-r--r--docs/explanation/defensive-measures.md165
-rw-r--r--docs/reference/configuration.md40
-rw-r--r--nix/module.nix7
-rw-r--r--src/config.rs6
-rw-r--r--src/nostr/builder.rs8
8 files changed, 321 insertions, 1 deletions
diff --git a/.env.example b/.env.example
index 707efd4..953ae93 100644
--- a/.env.example
+++ b/.env.example
@@ -277,4 +277,14 @@
277# Examples: 277# Examples:
278# NGIT_EVENT_BLACKLIST=npub1spam... 278# NGIT_EVENT_BLACKLIST=npub1spam...
279# NGIT_EVENT_BLACKLIST=npub1spam...,npub1abuser... 279# NGIT_EVENT_BLACKLIST=npub1spam...,npub1abuser...
280# NGIT_EVENT_BLACKLIST= \ No newline at end of file 280# NGIT_EVENT_BLACKLIST=
281
282# ============================================================================
283# RATE LIMITING & DOS PROTECTION
284# ============================================================================
285
286# Maximum total connections to the relay
287# Prevents connection exhaustion DoS attacks
288# CLI: --max-connections <count>
289# Default: 500
290# 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...
237 237
238**See**: [Configuration Reference](docs/reference/configuration.md) for complete details 238**See**: [Configuration Reference](docs/reference/configuration.md) for complete details
239 239
240## Defensive Measures & Rate Limiting
241
242ngit-grasp implements multiple layers of defense against abuse, spam, and denial-of-service attacks:
243
244**Per-Connection Rate Limits:**
245- Max 500 concurrent subscriptions per connection
246- Max 60 events published per minute per connection
247- Built-in to rust-nostr relay-builder
248
249**Per-IP Connection Monitoring:**
250- Tracks connections per IP address (default threshold: 10)
251- Flags potential abusers in logs and metrics
252- **Does NOT enforce limits** (monitoring only)
253- Privacy-preserving (IP addresses never exposed in Prometheus)
254
255**Content Filtering (Blacklists/Whitelists):**
256- **Event blacklist** - Block ALL events from specific authors (npubs)
257- **Repository blacklist** - Block specific repositories/developers/identifiers
258- **Repository whitelist** - Curate which repositories are accepted (GRASP-01 mode)
259- **Archive whitelist** - Mirror specific repositories (GRASP-05 mode)
260- See [Curation & Moderation](#curation--moderation) section above for details
261
262**Relay Sync Protection (GRASP-02):**
263- **Exponential backoff** - Failed connections: 5s → 10s → 20s → ... → 1 hour max
264- **Naughty list** - Track relays with infrastructure issues separately (12h expiry)
265- **Rate limit detection** - Auto 65s cooldown when remote relays rate limit us
266- **Domain throttling** - Max 5 concurrent, 30/min per domain for git data fetching
267
268**Event Validation:**
269- Strict GRASP-01 protocol validation via WritePolicy plugin system
270- Extensible for custom validation logic (has access to client IP address)
271
272**Total Connection Limit:**
273- Max 500 total connections (configurable via `NGIT_MAX_CONNECTIONS`)
274- Prevents connection exhaustion DoS attacks
275
276**Not Implemented:**
277- Per-IP connection limits (only monitored, not enforced)
278- Per-IP event rate limits (tracked per connection, not per IP)
279
280**See**: [Defensive Measures](docs/explanation/defensive-measures.md) for complete details and future enhancements.
281
240## Roadmap 282## Roadmap
241 283
242### GRASP-02 Enhancements 284### 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
151 151
152--- 152---
153 153
154### [Defensive Measures & Rate Limiting](defensive-measures.md)
155**Protection against abuse, spam, and denial-of-service attacks**
156
157**Topics:**
158- Connection and subscription management
159- Event publishing rate limits
160- Content filtering (blacklists/whitelists)
161- Event validation plugin system (WritePolicy/QueryPolicy)
162- Relay health management (naughty list, exponential backoff)
163- Privacy-preserving IP tracking
164- Future enhancements (per-IP rate limiting)
165
166**Read when:** You want to understand how ngit-grasp protects against abuse and what defensive features are available
167
168---
169
170### [GRASP-05 Archive Mode](grasp-05-archive.md)
171**Read-only mirroring of repositories**
172
173**Topics:**
174- Archive whitelist configuration
175- Archive-all mode
176- Read-only mode defaults
177- Use cases for backup/mirror relays
178
179**Read when:** You want to understand how to run an archive/backup relay
180
181---
182
183### [Deletion Requests](deletion-requests.md)
184**Handling repository and event deletion**
185
186**Topics:**
187- Deletion request architecture
188- Delete disrespector concept
189- Preventing left-pad scenarios
190- Archival policies
191
192**Read when:** You want to understand how ngit-grasp handles deletion events (planned feature)
193
194---
195
154## Planned Explanation Documentation 196## Planned Explanation Documentation
155 197
156### GRASP Protocol Design 198### 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 @@
1# Defensive Measures & Rate Limiting
2
3This document describes the defensive measures implemented in ngit-grasp to protect against abuse, spam, and denial-of-service attacks.
4
5**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.
6
7## Overview
8
9ngit-grasp employs multiple layers of defense:
10
111. **Connection & Subscription Limits** - Per-connection limits on subscriptions and event publishing
122. **Content Filtering** - Blacklist/whitelist system for repositories and event authors
133. **Event Validation** - Strict GRASP-01 protocol validation
144. **Relay Health Management** - Intelligent handling of problematic remote relays
15
16## What's Implemented
17
18### Per-Connection Rate Limits
19
20**Source:** Built-in to rust-nostr relay-builder
21
22- **Subscription limit:** Max 500 concurrent subscriptions per connection
23- **Event publishing limit:** Max 60 events per minute per connection
24- **Subscription ID length:** Max 250 characters
25- **Filter limit:** Max 500 results per query (default)
26
27These limits prevent individual connections from overwhelming the relay.
28
29### Per-IP Connection Monitoring
30
31**Source:** Custom ngit-grasp implementation
32**Location:** `src/metrics/connection.rs`
33
34- **Status:** Monitoring only (does NOT enforce limits)
35- Tracks connections per IP address internally
36- Flags IPs exceeding threshold (default: 10 connections)
37- **Privacy:** IP addresses never exposed in Prometheus metrics, only aggregate counts
38- Logs warnings when threshold exceeded
39
40**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.
41
42### Content Filtering (Blacklists/Whitelists)
43
44**Source:** Custom ngit-grasp implementation
45**Location:** `src/config.rs`, `src/nostr/builder.rs`
46
47**Event Blacklist:**
48- Block ALL events from specific authors (npubs)
49- Takes precedence over all other validation
50- Events never reach storage or purgatory
51
52**Repository Blacklist:**
53- Block specific repositories, developers, or identifiers
54- Takes precedence over whitelists
55- Three formats: `npub`, `npub/identifier`, `identifier`
56
57**Repository Whitelist:**
58- Curate which repositories are accepted (GRASP-01 mode)
59- Only accept announcements that both list your service AND match whitelist
60- Same three formats as blacklist
61
62**Archive Whitelist (GRASP-05):**
63- Mirror specific repositories even if they don't list your service
64- Same three formats as blacklist
65- Default: read-only mode when enabled
66
67**Privacy:** Blacklists not advertised in NIP-11 metadata.
68
69### Event Validation Plugin System
70
71**Source:** Built-in to rust-nostr relay-builder
72**Implementation:** Custom GRASP-01 validation in `src/nostr/builder.rs`
73
74- **WritePolicy trait:** Controls which events are accepted
75- **QueryPolicy trait:** Controls which queries are allowed (not currently used)
76- Access to client IP address for future per-IP rate limiting
77- Modular sub-policies for different event types (announcements, state events, PRs)
78
79### Relay Health Management (GRASP-02 Sync)
80
81**Source:** Custom ngit-grasp implementation
82**Location:** `src/sync/health.rs`
83
84**Exponential Backoff:**
85- Failed connections trigger increasing delays: 5s → 10s → 20s → ... → 1 hour max
86- Prevents hammering dead or slow relays
87
88**Naughty List:**
89- Tracks relays with persistent infrastructure issues (DNS, TLS, protocol errors)
90- Separate from normal connection failures
91- 12-hour expiration (configurable)
92- Reduces retry frequency for broken relays
93
94**Rate Limit Detection:**
95- Detects when remote relay rate limits us
96- Automatic 65-second cooldown
97- Prevents hammering relays that tell us to slow down
98
99**Domain Throttling (Git Data Fetching):**
100- Max 5 concurrent requests per domain
101- Max 30 requests per minute per domain
102- Respectful rate limiting when fetching missing git data
103
104## What's NOT Implemented
105
106### Per-IP Rate Limiting
107
108- **Per-IP connection limits:** Not enforced (only monitored)
109- **Per-IP subscription limits:** Not supported
110- **Per-IP event publishing limits:** Not supported
111
112**Why:** rust-nostr relay-builder tracks limits per WebSocket connection, not per IP address.
113
114**To implement:** Would require custom middleware/WritePolicy to aggregate across connections from the same IP.
115
116### Query Filtering
117
118**Status:** QueryPolicy trait available but not currently used.
119
120**Potential uses:** Rate limit queries per IP, block expensive queries, restrict access to certain event kinds.
121
122## Future Enhancements
123
124### Per-IP Rate Limiting
125
126Per-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.
127
128**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.
129
130**Related:** Git endpoint throttling (issue ff38) is a separate concern with different requirements.
131
132## Summary Table
133
134| Feature | Status | Enforced? | Configurable? |
135|---------|--------|-----------|---------------|
136| **Per-Connection Limits** |
137| Max subscriptions (500) | ✅ Active | Yes | No (relay-builder default) |
138| Event rate limit (60/min) | ✅ Active | Yes | No (relay-builder default) |
139| **Total Connection Limit** |
140| Max connections (500) | ✅ Active | Yes | Yes (`NGIT_MAX_CONNECTIONS`) |
141| **Per-IP Monitoring** |
142| Connection tracking | ✅ Active | No (monitor only) | Threshold only |
143| **Content Filtering** |
144| Event blacklist | ✅ Active | Yes | Yes |
145| Repository blacklist | ✅ Active | Yes | Yes |
146| Repository whitelist | ✅ Active | Yes (if set) | Yes |
147| Archive whitelist | ✅ Active | Yes (if set) | Yes |
148| **Event Validation** |
149| GRASP-01 validation | ✅ Active | Yes | Via WritePolicy |
150| **Relay Sync Protection** |
151| Exponential backoff | ✅ Active | Yes | Yes |
152| Naughty list | ✅ Active | Yes | Yes (12h default) |
153| Rate limit detection | ✅ Active | Yes | Automatic |
154| Domain throttling | ✅ Active | Yes | Hardcoded (5/30) |
155| **Not Implemented** |
156| Per-IP connection limit | ⚠️ Deferred | No | - |
157| Per-IP rate limiting | ⚠️ Deferred | No | - |
158| Query filtering | ⚠️ Available | No | Not implemented |
159
160## Related Documentation
161
162- [Configuration Reference](../reference/configuration.md) - All config options for defensive features
163- [Monitoring Overview](monitoring.md) - Prometheus metrics for tracking abuse
164- [GRASP-05 Archive](grasp-05-archive.md) - Archive whitelist details
165- [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:
925 925
926--- 926---
927 927
928### Rate Limiting & DoS Protection
929
930#### `NGIT_MAX_CONNECTIONS`
931
932**Description:** Maximum total connections to the relay. Prevents connection exhaustion DoS attacks.
933**Type:** Integer
934**Default:** `500`
935**Required:** No
936
937**Examples:**
938
939```bash
940# Default: 500 connections
941NGIT_MAX_CONNECTIONS=500
942
943# Higher limit for large public relay
944NGIT_MAX_CONNECTIONS=1000
945
946# Lower limit for private relay
947NGIT_MAX_CONNECTIONS=100
948```
949
950**Notes:**
951
952- Limits total concurrent WebSocket connections to the relay
953- Prevents connection exhaustion attacks
954- Works in conjunction with per-connection limits (500 subscriptions, 60 events/min)
955- When limit is reached, new connections are rejected
956- Existing connections continue to work normally
957
958**Related Limits:**
959
960Per-connection limits (built-in to relay-builder, not configurable):
961- Max subscriptions per connection: 500
962- Max events per minute per connection: 60
963- Max subscription ID length: 250 characters
964- Max results per filter: 500
965
966---
967
928### Logging Configuration 968### Logging Configuration
929 969
930#### `RUST_LOG` 970#### `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
250 ''; 250 '';
251 }; 251 };
252 252
253 maxConnections = mkOption {
254 type = types.int;
255 default = 500;
256 description = "Maximum total connections to the relay";
257 };
258
253 user = mkOption { 259 user = mkOption {
254 type = types.str; 260 type = types.str;
255 default = "ngit-grasp-${name}"; 261 default = "ngit-grasp-${name}";
@@ -295,6 +301,7 @@ let
295 NGIT_REPOSITORY_WHITELIST = concatStringsSep "," cfg.repositoryWhitelist; 301 NGIT_REPOSITORY_WHITELIST = concatStringsSep "," cfg.repositoryWhitelist;
296 NGIT_REPOSITORY_BLACKLIST = concatStringsSep "," cfg.repositoryBlacklist; 302 NGIT_REPOSITORY_BLACKLIST = concatStringsSep "," cfg.repositoryBlacklist;
297 NGIT_EVENT_BLACKLIST = concatStringsSep "," cfg.eventBlacklist; 303 NGIT_EVENT_BLACKLIST = concatStringsSep "," cfg.eventBlacklist;
304 NGIT_MAX_CONNECTIONS = toString cfg.maxConnections;
298 RUST_LOG = cfg.logLevel; 305 RUST_LOG = cfg.logLevel;
299 } // optionalAttrs (cfg.relayName != null) { 306 } // optionalAttrs (cfg.relayName != null) {
300 NGIT_RELAY_NAME = cfg.relayName; 307 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 {
469 /// All events from these authors are blocked from both relay storage and purgatory 469 /// All events from these authors are blocked from both relay storage and purgatory
470 #[arg(long, env = "NGIT_EVENT_BLACKLIST", default_value = "")] 470 #[arg(long, env = "NGIT_EVENT_BLACKLIST", default_value = "")]
471 pub event_blacklist: String, 471 pub event_blacklist: String,
472
473 /// Maximum total connections to the relay (default: 500)
474 /// Prevents connection exhaustion DoS attacks
475 #[arg(long, env = "NGIT_MAX_CONNECTIONS", default_value_t = 500)]
476 pub max_connections: usize,
472} 477}
473 478
474impl Config { 479impl Config {
@@ -703,6 +708,7 @@ impl Config {
703 repository_whitelist: String::new(), 708 repository_whitelist: String::new(),
704 repository_blacklist: String::new(), 709 repository_blacklist: String::new(),
705 event_blacklist: String::new(), 710 event_blacklist: String::new(),
711 max_connections: 500,
706 } 712 }
707 } 713 }
708} 714}
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(
624 let relay = LocalRelayBuilder::default() 624 let relay = LocalRelayBuilder::default()
625 .database(database.clone()) 625 .database(database.clone())
626 .write_policy(write_policy.clone()) 626 .write_policy(write_policy.clone())
627 // Explicitly set rate limits (make defaults visible in code)
628 // Per-connection limits: 500 max subscriptions, 60 events/min
629 .rate_limit(RateLimit {
630 max_reqs: 500, // Max concurrent subscriptions per connection
631 notes_per_minute: 60, // Max events per minute per connection
632 })
633 // Total connection limit to prevent DoS attacks
634 .max_connections(config.max_connections)
627 .build(); 635 .build();
628 636
629 tracing::info!( 637 tracing::info!(