Files
ngit-grasp/ff38-git-endpoint-ip-throttling.md

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.rs with GitThrottleManager
  • 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 module
  • src/http/mod.rs - Integrate throttle check before git handler dispatch
  • src/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: ThrottleGuard decrements in_flight on 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 section
  • README.md - Update defensive measures section
  • src/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