8.8 KiB
Git Endpoint IP-Based Throttling
ID: ff38
Problem
Git HTTP endpoints currently have no IP-based rate limiting, making the relay vulnerable to resource exhaustion attacks:
- Bandwidth exhaustion: Repeated large clone operations can saturate network and incur high egress costs
- CPU exhaustion: Pack generation for complex histories can saturate server CPU
- Connection exhaustion: Many concurrent git operations can exhaust file descriptors and memory
- Slowloris-style attacks: Slow pack downloads holding connections open
Current State
Git HTTP endpoints (implemented in src/git/handlers.rs, routed via src/http/mod.rs):
/<npub>/<id>.git/info/refs?service=git-upload-pack(clone/fetch ref advertisement)/<npub>/<id>.git/info/refs?service=git-receive-pack(push ref advertisement)/<npub>/<id>.git/git-upload-pack(clone/fetch pack transfer)/<npub>/<id>.git/git-receive-pack(push pack transfer)
Current protection level: NONE
- No rate limiting middleware
- No concurrent connection limits per IP
- No bandwidth throttling
- Only GRASP authorization for push operations
Threat Model
Git operations are fundamentally different from Nostr relay operations:
| Aspect | Git Endpoints | Nostr Relay |
|---|---|---|
| Resource intensity | Very high (pack generation, large transfers) | Low (small JSON events) |
| Connection duration | Long (minutes for large repos) | Variable (seconds to hours) |
| Bandwidth | Potentially GB per operation | KB per event |
| CPU | High (delta compression, pack generation) | Low (signature verification) |
This is why git endpoint throttling must be separate from relay rate limiting (issue d6ee).
Plan
Phase 1: Implement GitThrottleManager (9-12 hours)
Scope:
- Create
src/git/throttle.rswithGitThrottleManager - Per-IP concurrent operation limit: 5 concurrent git operations
- Per-IP rate limit: 30 requests per minute
- Hardcoded sensible defaults (no configuration initially)
Files to create:
src/git/throttle.rs- NEW: GitThrottleManager implementation
Files to modify:
src/git/mod.rs- Export throttle modulesrc/http/mod.rs- Integrate throttle check before git handler dispatchsrc/main.rs- Initialize GitThrottleManager, spawn cleanup task
Architecture:
// src/git/throttle.rs
use dashmap::DashMap;
use std::collections::VecDeque;
use std::net::IpAddr;
use std::time::{Duration, Instant};
/// Per-IP throttle state for git operations
struct IpThrottleState {
/// Current in-flight git operations
in_flight: u32,
/// Request timestamps (sliding window for rate limiting)
request_times: VecDeque<Instant>,
}
/// Manages IP-based rate limiting for git HTTP endpoints
pub struct GitThrottleManager {
states: DashMap<IpAddr, IpThrottleState>,
max_concurrent_per_ip: u32, // Hardcoded: 5
max_per_minute_per_ip: u32, // Hardcoded: 30
}
impl GitThrottleManager {
pub fn with_defaults() -> Self {
Self::new(5, 30)
}
/// Check if IP can proceed with git operation
/// Returns Ok(guard) if allowed, Err(reason) if throttled
pub fn try_acquire(&self, ip: IpAddr) -> Result<ThrottleGuard, ThrottleError>;
/// Cleanup expired entries (called periodically)
pub fn cleanup(&self);
}
/// RAII guard that releases throttle on drop
pub struct ThrottleGuard { /* ... */ }
pub enum ThrottleError {
TooManyConcurrent { current: u32, max: u32 },
RateLimitExceeded { requests_in_window: u32, max: u32 },
}
Integration in src/http/mod.rs:
// In HttpService::call(), before git handler dispatch
if let Some((npub, identifier, subpath)) = git::parse_git_url(&path) {
let ip = self.remote.ip();
let throttle_guard = match self.git_throttle.try_acquire(ip) {
Ok(guard) => guard,
Err(e) => {
tracing::warn!(ip = %ip, error = %e, "Git request throttled");
return Box::pin(async move {
Ok(add_cors_headers(Response::builder())
.status(hyper::StatusCode::TOO_MANY_REQUESTS)
.header("Retry-After", "60")
.body(Full::new(Bytes::from(format!("Rate limited: {}", e))))
.unwrap())
});
}
};
// ... existing git handling ...
// throttle_guard dropped automatically when response completes
}
Cleanup strategy:
- RAII:
ThrottleGuarddecrementsin_flighton drop - Background task: Every 60 seconds, remove IPs with no activity in last 5 minutes
- Sliding window: Prune request timestamps older than 60 seconds on each access
Acceptance Criteria:
- GitThrottleManager implements concurrent limit (5 per IP)
- GitThrottleManager implements rate limit (30/min per IP)
- 6th concurrent git operation from same IP returns 429
- 31st request in 60 seconds from same IP returns 429
- Different IPs can each have 5 concurrent operations
- ThrottleGuard properly releases on drop (RAII)
- Cleanup task removes stale IP entries (no memory leak)
- Unit tests: Concurrent limit enforcement
- Unit tests: Rate limit sliding window
- Unit tests: Cleanup logic
- Integration test: Multiple concurrent git clones from same IP
- Integration test: Rapid git requests hitting rate limit
- All tests passing
- Changes committed with message: "Add IP-based throttling for git HTTP endpoints"
Phase 2: Documentation & Metrics (2-3 hours)
Scope:
- Document git throttling in defensive-measures.md
- Add Prometheus metrics for git throttling
- Update README with git endpoint protection
Files to modify:
docs/explanation/defensive-measures.md- Add git throttling sectionREADME.md- Update defensive measures sectionsrc/git/throttle.rs- Add Prometheus metrics (optional)
Metrics to add (optional):
ngit_git_requests_throttled_total{reason="concurrent|rate_limit"}ngit_git_concurrent_operations{ip_count}(gauge of active IPs)
Acceptance Criteria:
- defensive-measures.md documents git throttling
- README updated with git endpoint protection
- Metrics added (if implemented)
- Changes committed with message: "docs: Document git endpoint throttling"
Phase 3: Configuration Support (Future - If Needed)
Status: Deferred until operators request tuning
Scope:
- Add environment variables for git throttle limits
- Update all 4 config locations (src/config.rs, docs, nix/module.nix, .env.example)
Proposed config:
NGIT_GIT_MAX_CONCURRENT_PER_IP=5
NGIT_GIT_MAX_REQUESTS_PER_MINUTE_PER_IP=30
Progress
2026-01-14 [Session 14:45]
- Created: Issue ff38 for git endpoint IP-based throttling
- Separated from d6ee (relay rate limiting) due to different threat model
- Completed: Comprehensive threat analysis via architect subagent
- Completed: Architecture design with GitThrottleManager
- Decision: Hardcoded defaults (5 concurrent, 30/min) initially
- Decision: No interaction with relay code (HTTP layer only)
- Next: Implement Phase 1 after d6ee Phase 1 completes
Key Design Decisions:
- Reuse patterns from ThrottleManager (outbound git) and ConnectionTracker (relay)
- RAII guard pattern for automatic cleanup
- DashMap for concurrent access
- Sliding window for rate limiting
- Privacy-preserving (IPs not exposed in metrics)
Notes
Relationship to Issue d6ee
d6ee (Defensive Relay Features):
- Focuses on Nostr relay WebSocket connections
- Per-connection limits (500 subs, 60 events/min)
- Total connection limit (500)
- Deferred: Per-IP relay enforcement (Phase 2/3)
ff38 (Git Endpoint Throttling):
- Focuses on git HTTP endpoints
- Per-IP concurrent operations (5)
- Per-IP request rate (30/min)
- Independent implementation (no relay code interaction)
Legitimate Use Cases to Preserve
- CI/CD pipelines: Multiple concurrent clones from same IP (5 allows this)
- Corporate proxies/NAT: Many developers behind single IP (30/min allows burst activity)
- Shallow clones: Partial fetches for large repos
- Incremental fetches: Small delta updates
- Mirror operations: Periodic full syncs
Comparison with Existing Throttling
ThrottleManager (src/purgatory/sync/throttle.rs):
- Purpose: Rate limit outbound git fetches to remote servers
- Key: Domain (remote server)
- Limits: 5 concurrent, 30/min per domain
- Queue: Yes (identifier queue for processing)
GitThrottleManager (this issue):
- Purpose: Rate limit inbound git requests from clients
- Key: IP address (client)
- Limits: 5 concurrent, 30/min per IP
- Queue: No (immediate reject with 429)
References
- Threat analysis: Session 2026-01-14 architect subagent analysis
- Related issue: d6ee (relay rate limiting)
- Existing patterns:
src/purgatory/sync/throttle.rs,src/metrics/connection.rs - Git HTTP protocol: https://git-scm.com/docs/http-protocol