-
Notifications
You must be signed in to change notification settings - Fork 1
Closed
Labels
Description
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-devimport 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=5000Monitoreo 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
Reactions are currently unavailable