Skip to content

🟡 ALTO: Falta de rate limiting - Vulnerable a ataques DoS #53

@JesusMaster

Description

@JesusMaster

Descripción del Problema

Severidad: ALTA

El servidor no implementa ningún mecanismo de rate limiting, lo que permite a atacantes realizar ataques de denegación de servicio (DoS), abusar de la API de GitHub, y consumir recursos excesivos del servidor.

Vulnerabilidad Identificada

// src/server.ts - Sin rate limiting en ningún endpoint
app.get('/sse', (req, res) => {
    // ❌ Sin límites de conexiones por cliente
});

app.post('/messages', (req, res) => {
    // ❌ Sin límites de mensajes por sesión
});

app.all('/mcp', (req, res) => {
    // ❌ Sin límites de requests por minuto
});

Problemas Identificados

1. Sin límites en conexiones SSE

Un atacante puede abrir múltiples conexiones SSE simultáneamente para agotar recursos del servidor.

2. Sin límites en requests MCP

Las herramientas de GitHub pueden ser llamadas sin restricciones, potencialmente agotando los rate limits de la API de GitHub.

3. Sin protección contra ataques de fuerza bruta

No hay protección contra intentos repetidos de conexión o autenticación.

4. Sin límites de payload

Aunque hay un límite de 300MB, no hay restricciones en la frecuencia de envío.

Impacto de Seguridad

  • DoS: Agotamiento de recursos del servidor
  • API Abuse: Consumo excesivo de rate limits de GitHub
  • Resource Exhaustion: Memoria y CPU saturadas
  • Service Degradation: Impacto en usuarios legítimos
  • Cost Increase: Mayor consumo de recursos en cloud

Solución Recomendada

1. Implementar rate limiting con express-rate-limit

npm install express-rate-limit
npm install @types/express-rate-limit --save-dev
import rateLimit from 'express-rate-limit';

// Rate limiting general
const generalLimiter = rateLimit({
    windowMs: 15 * 60 * 1000, // 15 minutos
    max: 100, // Máximo 100 requests por ventana
    message: {
        error: 'Too many requests from this IP',
        retryAfter: '15 minutes'
    },
    standardHeaders: true,
    legacyHeaders: false,
    handler: (req, res) => {
        logger.warn(`Rate limit exceeded for IP: ${req.ip}`);
        res.status(429).json({
            error: 'Rate limit exceeded',
            retryAfter: Math.ceil(req.rateLimit.resetTime / 1000)
        });
    }
});

// Rate limiting para conexiones SSE (más restrictivo)
const sseLimiter = rateLimit({
    windowMs: 60 * 1000, // 1 minuto
    max: 5, // Máximo 5 conexiones SSE por minuto
    message: 'Too many SSE connections from this IP'
});

// Rate limiting para mensajes
const messageLimiter = rateLimit({
    windowMs: 60 * 1000, // 1 minuto
    max: 30, // Máximo 30 mensajes por minuto
    message: 'Too many messages from this IP'
});

2. Rate limiting por usuario autenticado

import { Request } from 'express';

interface AuthenticatedRequest extends Request {
    user?: { id: string; rateLimits?: { requestsPerHour: number } };
}

const createUserLimiter = () => rateLimit({
    windowMs: 60 * 60 * 1000, // 1 hora
    max: (req: AuthenticatedRequest) => {
        return req.user?.rateLimits?.requestsPerHour || 100;
    },
    keyGenerator: (req: AuthenticatedRequest) => {
        return req.user?.id || req.ip;
    },
    message: 'User rate limit exceeded'
});

3. Rate limiting específico por herramienta

// Límites específicos para operaciones críticas
const criticalOperationsLimiter = rateLimit({
    windowMs: 60 * 60 * 1000, // 1 hora
    max: 10, // Solo 10 operaciones críticas por hora
    message: 'Critical operation rate limit exceeded'
});

// Operaciones que requieren límites más estrictos
const CRITICAL_TOOLS = [
    'create_repository',
    'merge_pull_request', 
    'push_files',
    'create_fork'
];

const isCriticalOperation = (toolName: string): boolean => {
    return CRITICAL_TOOLS.includes(toolName);
};

4. Implementación en endpoints

// Aplicar rate limiting
app.use(generalLimiter);

app.get('/sse', sseLimiter, authenticate, (req, res) => {
    // Endpoint SSE con rate limiting
});

app.post('/messages', messageLimiter, authenticate, (req, res) => {
    // Endpoint mensajes con rate limiting
});

app.all('/mcp', createUserLimiter(), authenticate, (req, res) => {
    // Endpoint MCP con rate limiting por usuario
});

5. Rate limiting en memoria vs Redis

import RedisStore from 'rate-limit-redis';
import Redis from 'ioredis';

// Para producción con múltiples instancias
const redisClient = new Redis(process.env.REDIS_URL);

const createRedisLimiter = (options: any) => rateLimit({
    ...options,
    store: new RedisStore({
        client: redisClient,
        prefix: 'rl:',
    }),
});

Configuración por Entorno

# Rate Limiting Configuration
RATE_LIMIT_WINDOW_MS=900000  # 15 minutos
RATE_LIMIT_MAX_REQUESTS=100
RATE_LIMIT_SSE_MAX=5
RATE_LIMIT_MESSAGES_MAX=30

# Redis para rate limiting (opcional)
REDIS_URL=redis://localhost:6379
USE_REDIS_RATE_LIMIT=false

# Rate limiting por usuario
DEFAULT_USER_RATE_LIMIT=1000  # requests por hora
PREMIUM_USER_RATE_LIMIT=5000

Monitoreo y Alertas

const rateLimitMonitor = (req: Request, res: Response, next: NextFunction) => {
    const remaining = req.rateLimit?.remaining || 0;
    const total = req.rateLimit?.limit || 0;
    
    // Alerta cuando se acerca al límite
    if (remaining < total * 0.1) {
        logger.warn(`Rate limit warning for ${req.ip}: ${remaining}/${total} remaining`);
    }
    
    // Métricas para monitoreo
    if (remaining === 0) {
        logger.error(`Rate limit exceeded for ${req.ip}`);
        // Enviar métrica a sistema de monitoreo
    }
    
    next();
};

Tareas

  • Instalar y configurar express-rate-limit
  • Implementar rate limiting general en todos los endpoints
  • Configurar límites específicos para SSE y mensajes
  • Implementar rate limiting por usuario autenticado
  • Configurar límites especiales para operaciones críticas
  • Agregar configuración por entorno (dev/prod)
  • Implementar monitoreo y alertas de rate limiting
  • Configurar Redis para entornos multi-instancia
  • Documentar límites en README
  • Agregar tests para rate limiting

Tests de Rate Limiting

describe('Rate Limiting', () => {
    it('should block requests after limit exceeded', async () => {
        // Hacer múltiples requests
        const promises = Array(101).fill(0).map(() => 
            request(app).get('/health')
        );
        
        const responses = await Promise.all(promises);
        const rateLimitedResponses = responses.filter(r => r.status === 429);
        
        expect(rateLimitedResponses.length).toBeGreaterThan(0);
    });
    
    it('should reset rate limit after window expires', async () => {
        // Test con mock de tiempo
        jest.useFakeTimers();
        
        // Exceeder límite
        for (let i = 0; i < 101; i++) {
            await request(app).get('/health');
        }
        
        // Avanzar tiempo
        jest.advanceTimersByTime(15 * 60 * 1000 + 1000);
        
        // Debería permitir requests nuevamente
        const response = await request(app).get('/health');
        expect(response.status).not.toBe(429);
        
        jest.useRealTimers();
    });
});

Referencias

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions