openclaw/src/security/middleware.ts
Ulrich Diedrichsen 79597b7a98 feat(security): add security shield coordinator and middleware
Add main security shield that coordinates all security checks:
- IP blocklist checking
- Rate limiting (auth, connections, requests, webhooks, pairing)
- Intrusion detection integration
- Security event logging

Add HTTP middleware for Express/HTTP integration:
- Request rate limiting middleware
- Connection rate limit checks
- Auth rate limit checks
- Webhook rate limit checks
- Pairing rate limit checks

Features:
- Extract IP from X-Forwarded-For/X-Real-IP headers
- Security context creation from requests
- Unified API for all security checks

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-30 11:11:48 +01:00

181 lines
3.7 KiB
TypeScript

/**
* Security shield HTTP middleware
* Integrates security checks into Express/HTTP request pipeline
*/
import type { IncomingMessage, ServerResponse } from "node:http";
import { getSecurityShield, SecurityShield, type SecurityContext } from "./shield.js";
/**
* Create security context from HTTP request
*/
export function createSecurityContext(req: IncomingMessage): SecurityContext {
return {
ip: SecurityShield.extractIp(req),
userAgent: req.headers["user-agent"],
requestId: (req as any).requestId, // May be set by other middleware
};
}
/**
* Security middleware for HTTP requests
* Checks IP blocklist and rate limits
*/
export function securityMiddleware(
req: IncomingMessage,
res: ServerResponse,
next: () => void
): void {
const shield = getSecurityShield();
if (!shield.isEnabled()) {
next();
return;
}
const ctx = createSecurityContext(req);
// Check if IP is blocked
if (shield.isIpBlocked(ctx.ip)) {
res.statusCode = 403;
res.setHeader("Content-Type", "text/plain");
res.end("Forbidden: IP blocked");
return;
}
// Check request rate limit
const requestCheck = shield.checkRequest(ctx);
if (!requestCheck.allowed) {
res.statusCode = 429;
res.setHeader("Content-Type", "text/plain");
res.setHeader("Retry-After", String(Math.ceil((requestCheck.rateLimitInfo?.retryAfterMs ?? 60000) / 1000)));
res.end("Too Many Requests");
return;
}
next();
}
/**
* Connection rate limit check
* Call this when accepting new connections
*/
export function checkConnectionRateLimit(req: IncomingMessage): {
allowed: boolean;
reason?: string;
} {
const shield = getSecurityShield();
if (!shield.isEnabled()) {
return { allowed: true };
}
const ctx = createSecurityContext(req);
const result = shield.checkConnection(ctx);
return {
allowed: result.allowed,
reason: result.reason,
};
}
/**
* Authentication rate limit check
* Call this before processing authentication
*/
export function checkAuthRateLimit(req: IncomingMessage, deviceId?: string): {
allowed: boolean;
reason?: string;
retryAfterMs?: number;
} {
const shield = getSecurityShield();
if (!shield.isEnabled()) {
return { allowed: true };
}
const ctx = createSecurityContext(req);
if (deviceId) {
ctx.deviceId = deviceId;
}
const result = shield.checkAuthAttempt(ctx);
return {
allowed: result.allowed,
reason: result.reason,
retryAfterMs: result.rateLimitInfo?.retryAfterMs,
};
}
/**
* Log failed authentication
* Call this after authentication fails
*/
export function logAuthFailure(req: IncomingMessage, reason: string, deviceId?: string): void {
const shield = getSecurityShield();
if (!shield.isEnabled()) {
return;
}
const ctx = createSecurityContext(req);
if (deviceId) {
ctx.deviceId = deviceId;
}
shield.logAuthFailure(ctx, reason);
}
/**
* Pairing rate limit check
*/
export function checkPairingRateLimit(params: {
channel: string;
sender: string;
ip: string;
}): {
allowed: boolean;
reason?: string;
} {
const shield = getSecurityShield();
if (!shield.isEnabled()) {
return { allowed: true };
}
const result = shield.checkPairingRequest(params);
return {
allowed: result.allowed,
reason: result.reason,
};
}
/**
* Webhook rate limit check
*/
export function checkWebhookRateLimit(params: {
token: string;
path: string;
ip: string;
}): {
allowed: boolean;
reason?: string;
retryAfterMs?: number;
} {
const shield = getSecurityShield();
if (!shield.isEnabled()) {
return { allowed: true };
}
const result = shield.checkWebhook(params);
return {
allowed: result.allowed,
reason: result.reason,
retryAfterMs: result.rateLimitInfo?.retryAfterMs,
};
}