agent-smith/dist-in/middleware/autoBan.js
2026-02-26 19:41:09 +01:00

376 lines
25 KiB
JavaScript

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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