UNPKG

aimless-security

Version:

Enhanced Runtime Application Self-Protection (RASP) and API Fuzzing Engine with advanced threat detection, behavioral analysis, and intelligent response scoring for Node.js applications

250 lines 8.49 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.CSRFDetector = void 0; const types_1 = require("../types"); const crypto = __importStar(require("crypto")); class CSRFDetector { constructor(trustedOrigins = []) { this.defaultExpiry = 3600000; // 1 hour this.cleanupInterval = 300000; // 5 minutes this.trustedOrigins = new Set(trustedOrigins); this.tokenStore = new Map(); this.startCleanup(); } /** * Auto-cleanup expired tokens */ startCleanup() { this.cleanupTimer = setInterval(() => { this.cleanupExpiredTokens(); }, this.cleanupInterval); } /** * Clean up expired tokens to prevent memory leaks */ cleanupExpiredTokens() { const now = Date.now(); for (const [sessionId, data] of this.tokenStore.entries()) { if (data.expires < now) { this.tokenStore.delete(sessionId); } } } /** * Stop cleanup timer (call when shutting down) */ destroy() { if (this.cleanupTimer) { clearInterval(this.cleanupTimer); } } /** * Generate a cryptographically secure CSRF token */ generateToken(sessionId, expiryMs) { const token = crypto.randomBytes(32).toString('hex'); const expires = Date.now() + (expiryMs || this.defaultExpiry); const createdAt = Date.now(); this.tokenStore.set(sessionId, { token, expires, createdAt, used: false }); return token; } /** * Validate CSRF token with one-time use option */ validateToken(sessionId, token, oneTimeUse = false) { const stored = this.tokenStore.get(sessionId); if (!stored) return false; // Check expiry if (stored.expires < Date.now()) { this.tokenStore.delete(sessionId); return false; } // Check if already used (for one-time tokens) if (oneTimeUse && stored.used) { return false; } // Validate token using timing-safe comparison const isValid = this.timingSafeEqual(stored.token, token); if (isValid && oneTimeUse) { stored.used = true; } return isValid; } /** * Timing-safe comparison to prevent timing attacks */ timingSafeEqual(a, b) { if (a.length !== b.length) return false; const bufA = Buffer.from(a); const bufB = Buffer.from(b); return crypto.timingSafeEqual(bufA, bufB); } /** * Enhanced CSRF detection with better origin validation */ detect(method, origin, referer, csrfToken, sessionId, cookies) { // Only check state-changing methods if (!['POST', 'PUT', 'DELETE', 'PATCH'].includes(method.toUpperCase())) { return null; } // Check Origin header if (origin) { try { const originUrl = new URL(origin); if (!this.isTrustedOrigin(originUrl.origin)) { return { type: types_1.ThreatType.CSRF, severity: 'critical', description: 'CSRF attack detected: Untrusted origin', payload: origin, timestamp: new Date(), blocked: true, metadata: { method, origin, check: 'origin-header' } }; } } catch (e) { // Invalid origin URL return { type: types_1.ThreatType.CSRF, severity: 'high', description: 'CSRF attack detected: Invalid origin header', payload: origin, timestamp: new Date(), blocked: true, metadata: { method, origin, check: 'origin-invalid' } }; } } // Check Referer header as fallback if (!origin && referer) { try { const refererUrl = new URL(referer); if (!this.isTrustedOrigin(refererUrl.origin)) { return { type: types_1.ThreatType.CSRF, severity: 'high', description: 'CSRF attack detected: Untrusted referer', payload: referer, timestamp: new Date(), blocked: true, metadata: { method, referer, check: 'referer-header' } }; } } catch (e) { // Invalid referer - might be stripped, don't block } } // Double-submit cookie check if (cookies && cookies['csrf-token']) { if (csrfToken !== cookies['csrf-token']) { return { type: types_1.ThreatType.CSRF, severity: 'high', description: 'CSRF attack detected: Cookie mismatch', timestamp: new Date(), blocked: true, metadata: { method, check: 'double-submit-cookie' } }; } } // Check CSRF token (synchronizer token pattern) if (sessionId && !this.validateToken(sessionId, csrfToken || '')) { return { type: types_1.ThreatType.CSRF, severity: 'high', description: 'CSRF attack detected: Invalid or missing token', timestamp: new Date(), blocked: true, metadata: { method, hasToken: !!csrfToken, check: 'synchronizer-token' } }; } return null; } /** * Check if origin is trusted */ isTrustedOrigin(origin) { if (this.trustedOrigins.size === 0) return true; return this.trustedOrigins.has(origin); } /** * Add a trusted origin */ addTrustedOrigin(origin) { this.trustedOrigins.add(origin); } /** * Remove a trusted origin */ removeTrustedOrigin(origin) { this.trustedOrigins.delete(origin); } /** * Get all trusted origins */ getTrustedOrigins() { return Array.from(this.trustedOrigins); } /** * Revoke a specific token */ revokeToken(sessionId) { return this.tokenStore.delete(sessionId); } /** * Get token info (for debugging/monitoring) */ getTokenInfo(sessionId) { const stored = this.tokenStore.get(sessionId); if (!stored) { return { valid: false }; } const now = Date.now(); const valid = stored.expires > now; return { valid, expiresIn: valid ? stored.expires - now : 0, used: stored.used }; } } exports.CSRFDetector = CSRFDetector; //# sourceMappingURL=csrf-detector.js.map