import { readFileSync, writeFileSync } from 'fs'; import { join } from 'path'; import { logger, securityLogger } from '../commons/logger.js'; // Configuration const BAN_THRESHOLD = parseInt(process.env.AUTO_BAN_THRESHOLD || '5', 10); // Number of violations before ban const VIOLATION_WINDOW_MS = parseInt(process.env.AUTO_BAN_WINDOW_MS || '10000', 10); // 1 minute default const VIOLATION_CLEANUP_INTERVAL = 10000; // Clean up old violations every minute console.log('Auto-ban configured with:', { threshold: BAN_THRESHOLD, window: VIOLATION_WINDOW_MS / 60000, cleanupInterval: VIOLATION_CLEANUP_INTERVAL / 60000 }); // In-memory violation tracking const violations = new Map(); let banList = { bannedIPs: [], bannedUserIds: [], bannedTokens: [], }; /** * Load ban list from JSON file */ export function loadBanList() { try { const banListPath = join(process.cwd(), 'config', 'ban.json'); const data = readFileSync(banListPath, 'utf-8'); banList = JSON.parse(data); return banList; } catch (error) { logger.error({ error }, 'Failed to load ban list'); return banList; } } /** * Save ban list to JSON file */ function saveBanList() { try { const banListPath = join(process.cwd(), 'config', 'ban.json'); writeFileSync(banListPath, JSON.stringify(banList, null, 4), 'utf-8'); logger.info('Ban list saved'); } catch (error) { logger.error({ error }, 'Failed to save ban list'); } } /** * Get current ban list */ export function getBanList() { return banList; } /** * Check if an IP is banned */ export function isIPBanned(ip) { return banList.bannedIPs.includes(ip); } /** * Check if a user ID is banned */ export function isUserBanned(userId) { return banList.bannedUserIds.includes(userId); } /** * Check if an auth token is banned */ export function isTokenBanned(token) { return banList.bannedTokens.includes(token); } /** * Extract IP address from request */ export function getClientIP(c) { // Check forwarded headers first (for proxies) const forwarded = c.req.header('x-forwarded-for'); if (forwarded) { return forwarded.split(',')[0].trim(); } const realIp = c.req.header('x-real-ip'); if (realIp) { return realIp; } // Fallback to connection IP (works for localhost) // In Node.js/Hono, we can try to get the remote address try { // @ts-ignore - accessing internal request object const remoteAddress = c.req.raw?.socket?.remoteAddress || c.env?.ip; if (remoteAddress) { return remoteAddress; } } catch (e) { // Ignore errors } // Last resort: use localhost identifier return '127.0.0.1'; } /** * Extract user ID from authorization header */ function getUserId(c) { const authHeader = c.req.header('authorization'); if (!authHeader) return null; return authHeader; } /** * Record a rate limit violation */ export function recordViolation(key) { const now = Date.now(); const existing = violations.get(key); if (existing) { // Check if violation is within the window if (now - existing.firstViolation <= VIOLATION_WINDOW_MS) { existing.count++; existing.lastViolation = now; violations.set(key, existing); // Check if threshold exceeded if (existing.count >= BAN_THRESHOLD) { banEntity(key); } } else { // Reset violation count if outside window violations.set(key, { count: 1, firstViolation: now, lastViolation: now, }); } } else { // First violation violations.set(key, { count: 1, firstViolation: now, lastViolation: now, }); } logger.debug({ key, violations: violations.get(key) }, 'Violation recorded'); } /** * Ban an entity (IP, user, or token) */ function banEntity(key) { const [type, value] = key.split(':', 2); const violationRecord = violations.get(key); let added = false; if (type === 'ip' && !banList.bannedIPs.includes(value)) { banList.bannedIPs.push(value); added = true; // Log to security.json securityLogger.warn({ event: 'auto_ban', type: 'ip', ip: value, violations: violationRecord?.count, firstViolation: violationRecord?.firstViolation, lastViolation: violationRecord?.lastViolation }, 'IP auto-banned for excessive requests'); // Also log to console logger.info({ ip: value, violations: violationRecord?.count }, '🚫 IP auto-banned for excessive requests'); } else if (type === 'user' && !banList.bannedUserIds.includes(value)) { banList.bannedUserIds.push(value); added = true; // Log to security.json securityLogger.warn({ event: 'auto_ban', type: 'user', userId: value, violations: violationRecord?.count, firstViolation: violationRecord?.firstViolation, lastViolation: violationRecord?.lastViolation }, 'User auto-banned for excessive requests'); // Also log to console logger.info({ userId: value, violations: violationRecord?.count }, '🚫 User auto-banned for excessive requests'); } else if (type === 'token' && !banList.bannedTokens.includes(value)) { banList.bannedTokens.push(value); added = true; // Log to security.json securityLogger.warn({ event: 'auto_ban', type: 'token', token: value.substring(0, 20) + '...', violations: violationRecord?.count, firstViolation: violationRecord?.firstViolation, lastViolation: violationRecord?.lastViolation }, 'Token auto-banned for excessive requests'); // Also log to console logger.info({ token: value.substring(0, 20) + '...', violations: violationRecord?.count }, '🚫 Token auto-banned for excessive requests'); } if (added) { saveBanList(); // Clear violation record after ban violations.delete(key); } } /** * Clean up old violation records */ function cleanupViolations() { const now = Date.now(); let cleaned = 0; for (const [key, record] of violations.entries()) { if (now - record.lastViolation > VIOLATION_WINDOW_MS) { violations.delete(key); cleaned++; } } if (cleaned > 0) { logger.debug({ cleaned }, 'Cleaned up old violation records'); } } /** * Auto-ban middleware * Checks if request is from a banned entity */ // Simple in-memory rate limiting const requestCounts = new Map(); const RATE_LIMIT_MAX = parseInt(process.env.RATE_LIMIT_MAX || '20', 10); const RATE_LIMIT_WINDOW_MS = parseInt(process.env.RATE_LIMIT_WINDOW_MS || '1000', 10); export async function autoBanMiddleware(c, next) { const ip = getClientIP(c); const path = c.req.path; const method = c.req.method; // Skip ban/rate-limit checks for local requests (dev & e2e tests) if (ip === '127.0.0.1' || ip === 'localhost' || ip === '::1' || ip === '::ffff:127.0.0.1') { return next(); } const authHeader = c.req.header('authorization'); const userId = getUserId(c); // Generate key for rate limiting let key; if (authHeader) { key = `user:${authHeader}`; } else { key = `ip:${ip}`; } // Check if IP is banned if (isIPBanned(ip)) { /* securityLogger.info({ event: 'blocked_request', type: 'ip', ip, path, method }, 'Blocked request from banned IP') */ // logger.info({ ip, path }, '🚫 Blocked request from banned IP') return c.json({ error: 'Forbidden', message: 'Your IP address has been banned for excessive requests', }, 403); } // Check if auth token is banned if (authHeader && isTokenBanned(authHeader)) { securityLogger.info({ event: 'blocked_request', type: 'token', token: authHeader.substring(0, 20) + '...', path, method }, 'Blocked request from banned token'); logger.info({ token: authHeader.substring(0, 20) + '...', path }, '🚫 Blocked request from banned token'); return c.json({ error: 'Forbidden', message: 'Your access token has been banned for excessive requests', }, 403); } // Check if user ID is banned if (userId && isUserBanned(userId)) { securityLogger.info({ event: 'blocked_request', type: 'user', userId, path, method }, 'Blocked request from banned user'); logger.info({ userId, path }, '🚫 Blocked request from banned user'); return c.json({ error: 'Forbidden', message: 'Your account has been banned for excessive requests', }, 403); } // Built-in rate limiting (since hono-rate-limiter isn't working) const now = Date.now(); const record = requestCounts.get(key); if (record) { if (now < record.resetTime) { // Within the window record.count++; if (record.count > RATE_LIMIT_MAX) { // Rate limit exceeded! console.log(`⚠️ Rate limit exceeded for ${key} (${record.count}/${RATE_LIMIT_MAX})`); recordViolation(key); return c.json({ error: 'Too many requests', message: `Rate limit exceeded. Maximum ${RATE_LIMIT_MAX} requests per ${RATE_LIMIT_WINDOW_MS}ms`, }, 429); } } else { // Window expired, reset record.count = 1; record.resetTime = now + RATE_LIMIT_WINDOW_MS; } } else { // First request requestCounts.set(key, { count: 1, resetTime: now + RATE_LIMIT_WINDOW_MS }); } await next(); } /** * Manually unban an IP */ export function unbanIP(ip) { const index = banList.bannedIPs.indexOf(ip); if (index > -1) { banList.bannedIPs.splice(index, 1); saveBanList(); securityLogger.info({ event: 'unban', type: 'ip', ip }, 'IP unbanned'); logger.info({ ip }, 'IP unbanned'); return true; } return false; } /** * Manually unban a user */ export function unbanUser(userId) { const index = banList.bannedUserIds.indexOf(userId); if (index > -1) { banList.bannedUserIds.splice(index, 1); saveBanList(); securityLogger.info({ event: 'unban', type: 'user', userId }, 'User unbanned'); logger.info({ userId }, 'User unbanned'); return true; } return false; } /** * Get current violation stats */ export function getViolationStats() { return { totalViolations: violations.size, violations: Array.from(violations.entries()).map(([key, record]) => ({ key, ...record, })), }; } // Load ban list on module initialization loadBanList(); // Start cleanup interval setInterval(cleanupViolations, VIOLATION_CLEANUP_INTERVAL); //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXV0b0Jhbi5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9taWRkbGV3YXJlL2F1dG9CYW4udHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQ0EsT0FBTyxFQUFFLFlBQVksRUFBRSxhQUFhLEVBQUUsTUFBTSxJQUFJLENBQUE7QUFDaEQsT0FBTyxFQUFFLElBQUksRUFBRSxNQUFNLE1BQU0sQ0FBQTtBQUMzQixPQUFPLEVBQUUsTUFBTSxFQUFFLGNBQWMsRUFBRSxNQUFNLHNCQUFzQixDQUFBO0FBYzdELGdCQUFnQjtBQUNoQixNQUFNLGFBQWEsR0FBRyxRQUFRLENBQUMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxrQkFBa0IsSUFBSSxHQUFHLEVBQUUsRUFBRSxDQUFDLENBQUEsQ0FBQyxrQ0FBa0M7QUFDNUcsTUFBTSxtQkFBbUIsR0FBRyxRQUFRLENBQUMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxrQkFBa0IsSUFBSSxPQUFPLEVBQUUsRUFBRSxDQUFDLENBQUEsQ0FBQyxtQkFBbUI7QUFDdkcsTUFBTSwwQkFBMEIsR0FBRyxLQUFLLENBQUEsQ0FBQyx1Q0FBdUM7QUFFaEYsT0FBTyxDQUFDLEdBQUcsQ0FBQywyQkFBMkIsRUFBRTtJQUNyQyxTQUFTLEVBQUUsYUFBYTtJQUN4QixNQUFNLEVBQUUsbUJBQW1CLEdBQUcsS0FBSztJQUNuQyxlQUFlLEVBQUUsMEJBQTBCLEdBQUcsS0FBSztDQUN0RCxDQUFDLENBQUE7QUFFRiwrQkFBK0I7QUFDL0IsTUFBTSxVQUFVLEdBQUcsSUFBSSxHQUFHLEVBQTJCLENBQUE7QUFFckQsSUFBSSxPQUFPLEdBQVk7SUFDbkIsU0FBUyxFQUFFLEVBQUU7SUFDYixhQUFhLEVBQUUsRUFBRTtJQUNqQixZQUFZLEVBQUUsRUFBRTtDQUNuQixDQUFBO0FBRUQ7O0dBRUc7QUFDSCxNQUFNLFVBQVUsV0FBVztJQUN2QixJQUFJLENBQUM7UUFDRCxNQUFNLFdBQVcsR0FBRyxJQUFJLENBQUMsT0FBTyxDQUFDLEdB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