import {ErrorResponseConfig} from '../types'; /** * Custom security error class for handling security violations */ export class SecurityError extends Error { public readonly type: string; public readonly statusCode: 400 | 403 | 404 | 500; public readonly severity: 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL'; public readonly publicMessage: string; public readonly metadata?: Record; constructor( message: string, type = 'SECURITY_ERROR', statusCode: 400 | 403 | 404 | 500 = 403, severity: 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL' = 'MEDIUM', publicMessage?: string, metadata?: Record ) { super(message); this.name = 'SecurityError'; this.type = type; this.statusCode = statusCode; this.severity = severity; this.publicMessage = publicMessage ?? 'Security violation detected'; this.metadata = metadata; // Maintains proper stack trace for where our error was thrown (only available on V8) if (Error.captureStackTrace) { Error.captureStackTrace(this, SecurityError); } } /** * Create SecurityError from unknown error */ static fromError(error: unknown): SecurityError { if (error instanceof SecurityError) { return error; } if (error instanceof Error) { return new SecurityError( error.message, 'UNKNOWN_ERROR', 500, 'MEDIUM', 'Internal security error occurred', { originalErrorName: error.name, originalStack: error.stack } ); } return new SecurityError( String(error), 'UNKNOWN_ERROR', 500, 'MEDIUM', 'Unknown security error occurred' ); } /** /** * Create SQL injection error */ static sqlInjectionDetected( location: string, pattern?: string, metadata?: Record ): SecurityError { return new SecurityError( `Potential SQL injection detected in ${location}${pattern ? ` (pattern: ${pattern})` : ''}`, 'SQL_INJECTION', 400, 'HIGH', 'Request blocked by server', { location, pattern, ...metadata } ); } /** * Create CSV injection error */ static csvInjectionDetected( location: string, pattern?: string, metadata?: Record ): SecurityError { return new SecurityError( `Potential CSV injection detected in ${location}${pattern ? ` (pattern: ${pattern})` : ''}`, 'CSV_INJECTION', 400, 'HIGH', 'Request blocked by server', { location, pattern, ...metadata } ); } /** * Create XSS attack error */ static xssAttackDetected( location: string, pattern?: string, metadata?: Record ): SecurityError { return new SecurityError( `Potential XSS attack detected in ${location}${pattern ? ` (pattern: ${pattern})` : ''}`, 'XSS_ATTACK', 400, 'HIGH', 'Request blocked by server', { location, pattern, ...metadata } ); } /** * Create input validation error */ static validationFailed( field: string, reason: string, metadata?: Record ): SecurityError { return new SecurityError( `Validation failed for ${field}: ${reason}`, 'VALIDATION_FAILED', 400, 'LOW', 'Request blocked by server', { field, reason, ...metadata } ); } /** * Create file upload error */ static fileUploadError( reason: string, filename?: string, metadata?: Record ): SecurityError { return new SecurityError( `File upload error${filename ? ` for ${filename}` : ''}: ${reason}`, 'FILE_UPLOAD_ERROR', 400, 'MEDIUM', 'Request blocked by server', { filename, reason, ...metadata } ); } /** * Create malware detection error */ static malwareDetected( filename: string, threatType?: string, metadata?: Record ): SecurityError { return new SecurityError( `Malware detected in file ${filename}${threatType ? ` (${threatType})` : ''}`, 'MALWARE_DETECTED', 400, 'CRITICAL', 'Request blocked by server', { filename, threatType, ...metadata } ); } /** * Create CORS error */ static corsViolation( origin: string, reason = 'Origin not allowed', metadata?: Record ): SecurityError { return new SecurityError( `CORS violation: ${reason} for origin ${origin}`, 'CORS_VIOLATION', 400, 'MEDIUM', 'Request blocked by server', { origin, reason, ...metadata } ); } /** * Create security header error */ static securityHeaderError( header: string, reason: string, metadata?: Record ): SecurityError { return new SecurityError( `Security header error for ${header}: ${reason}`, 'SECURITY_HEADER_ERROR', 400, 'LOW', 'Request blocked by server', { header, reason, ...metadata } ); } /** * Create file upload violation error */ static fileUploadViolation( message: string, metadata?: Record ): SecurityError { return new SecurityError( `File upload violation: ${message}`, 'FILE_UPLOAD_VIOLATION', 400, 'MEDIUM', 'Request blocked by server', metadata ); } /** * Create a generic security violation error with detailed internal logging * but simple public message */ static genericViolation( internalMessage: string, violationType: string, severity: 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL' = 'MEDIUM', metadata?: Record ): SecurityError { return new SecurityError( internalMessage, violationType.toUpperCase().replace(/\s+/g, '_'), 400, severity, 'Request blocked by server', metadata ); } /** * Create a security violation with custom public message */ static customViolation( internalMessage: string, violationType: string, publicMessage: string, severity: 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL' = 'MEDIUM', statusCode: 400 | 403 | 404 | 500 = 400, metadata?: Record ): SecurityError { return new SecurityError( internalMessage, violationType.toUpperCase().replace(/\s+/g, '_'), statusCode, severity, publicMessage, metadata ); } /** * Get error details for logging */ getDetails(): Record { return { name: this.name, type: this.type, message: this.message, statusCode: this.statusCode, severity: this.severity, publicMessage: this.publicMessage, metadata: this.metadata, stack: this.stack }; } /** * Get public error response (safe for client) */ getPublicResponse(): Record { return { error: this.type, message: this.publicMessage, statusCode: this.statusCode, timestamp: new Date().toISOString() }; } /** * Get public error response with configuration support * @param errorResponseConfig - Error response configuration to determine error exposure level */ getPublicResponseWithConfig(errorResponseConfig?: ErrorResponseConfig): Record { const shouldExposeDetailedErrors = errorResponseConfig?.exposeDetailedErrors ?? false; const customErrorMessage = errorResponseConfig?.customErrorMessage ?? 'Request blocked by server'; return { error: this.type, message: shouldExposeDetailedErrors ? this.message : customErrorMessage, statusCode: this.statusCode, timestamp: new Date().toISOString(), // Include additional details only if detailed errors are enabled ...(shouldExposeDetailedErrors && this.metadata && { details: this.metadata }) }; } /** * Convert to JSON for serialization */ toJSON(): Record { return this.getDetails(); } }