import Ajv from "ajv"; import addFormats from "ajv-formats"; export class SecurityValidators { private ajv: Ajv; private secureAjv: Ajv; constructor() { this._initializeFastValidator(); this._initializeSecureValidator(); this._addSecurityKeywords(); } /** * Initialize fast validator with minimal security for performance */ private _initializeFastValidator(): void { this.ajv = new Ajv({ allErrors: true, verbose: true, strict: false, validateFormats: false, }); addFormats(this.ajv); } /** * Initialize secure validator with maximum security features */ private _initializeSecureValidator(): void { this.secureAjv = new Ajv({ allErrors: true, verbose: true, strict: true, validateFormats: true, removeAdditional: "all", useDefaults: false, coerceTypes: false, addUsedSchema: false, inlineRefs: false, loadSchema: false, code: { optimize: false, es5: true, }, }); addFormats(this.secureAjv, { mode: "full" }); } /** * Add custom security keywords to prevent common attacks */ private _addSecurityKeywords(): void { this._addPrototypePollutionKeyword(); this._addConstructorManipulationKeyword(); this._addDeepPropertyKeyword(); } /** * Add keyword to prevent prototype pollution attacks */ private _addPrototypePollutionKeyword(): void { this.secureAjv.addKeyword({ keyword: "noPrototypePollution", type: "object", compile: () => { return (data: any) => { if (!this._isObject(data)) return true; return this._validatePrototypePollution(data); }; }, }); } /** * Add keyword to prevent constructor manipulation */ private _addConstructorManipulationKeyword(): void { this.secureAjv.addKeyword({ keyword: "noConstructorManipulation", type: "object", compile: () => { return (data: any) => { if (!this._isObject(data)) return true; return this._validateConstructorManipulation(data); }; }, }); } /** * Add keyword for deep property validation */ private _addDeepPropertyKeyword(): void { this.secureAjv.addKeyword({ keyword: "deepSecurityCheck", type: "object", compile: () => { return (data: any) => { if (!this._isObject(data)) return true; return this._performDeepSecurityCheck(data); }; }, }); } /** * Validate against prototype pollution attacks */ private _validatePrototypePollution(data: any, path = ""): boolean { const dangerousProps = this._getDangerousProperties(); for (const key of Object.keys(data)) { if (dangerousProps.includes(key)) { this._setValidationError( "noPrototypePollution", path + "/" + key, `Dangerous property "${key}" detected - potential prototype pollution` ); return false; } if (this._isObject(data[key])) { if (!this._validatePrototypePollution(data[key], path + "/" + key)) { return false; } } } return true; } /** * Validate against constructor manipulation */ private _validateConstructorManipulation(data: any): boolean { if ( "constructor" in data && this._isConstructorManipulation(data.constructor) ) { this._setValidationError( "noConstructorManipulation", "/constructor", "Constructor property manipulation detected" ); return false; } return true; } /** * Perform deep security check on nested objects */ private _performDeepSecurityCheck(data: any, depth = 0): boolean { if (depth > this._getMaxDepth()) { this._setValidationError( "deepSecurityCheck", "", "Maximum object depth exceeded" ); return false; } if (!this._isObject(data)) return true; for (const [key, value] of Object.entries(data)) { if (!this._isPropertyNameSafe(key)) { this._setValidationError( "deepSecurityCheck", "/" + key, `Unsafe property name: ${key}` ); return false; } if (this._isObject(value)) { if (!this._performDeepSecurityCheck(value, depth + 1)) { return false; } } } return true; } /** * Get list of dangerous property names */ private _getDangerousProperties(): string[] { return [ "__proto__", "constructor", "prototype", "__defineGetter__", "__defineSetter__", ]; } /** * Check if value is a plain object */ private _isObject(value: any): boolean { return typeof value === "object" && value !== null && !Array.isArray(value); } /** * Check if constructor property indicates manipulation */ private _isConstructorManipulation(constructor: any): boolean { return ( this._isObject(constructor) && ("prototype" in constructor || "constructor" in constructor) ); } /** * Check if property name is safe */ private _isPropertyNameSafe(propName: string): boolean { const unsafePatterns = [ /^__/, // Double underscore prefix /prototype$/, // Ends with prototype /constructor$/, // Ends with constructor /^eval$/, // Eval function /^Function$/, // Function constructor ]; return !unsafePatterns.some((pattern) => pattern.test(propName)); } /** * Get maximum allowed object depth */ private _getMaxDepth(): number { return 50; // Configurable depth limit } /** * Set validation error for current validation context */ private _setValidationError( keyword: string, path: string, message: string ): void { // This would be set on the current validation function context // Implementation depends on AJV's internal error handling } /** * Create enhanced secure schema with security keywords */ private _createSecureSchema(baseSchema: object): object { return { ...baseSchema, noPrototypePollution: true, noConstructorManipulation: true, deepSecurityCheck: true, additionalProperties: false, }; } /** * Sanitize data before validation */ private _sanitizeData(data: any): any { if (!this._isObject(data)) return data; const sanitized = {}; const dangerousProps = this._getDangerousProperties(); for (const [key, value] of Object.entries(data)) { if (!dangerousProps.includes(key) && this._isPropertyNameSafe(key)) { sanitized[key] = this._isObject(value) ? this._sanitizeData(value) : value; } } return sanitized; } /** * Compile validator with caching */ private _compileValidator(ajvInstance: Ajv, schema: object): any { // In production, you might want to cache compiled validators return ajvInstance.compile(schema); } /** * Format validation errors for better debugging */ private _formatErrors(errors: any[]): any[] { return errors.map((error) => ({ ...error, severity: this._getErrorSeverity(error.keyword), suggestion: this._getErrorSuggestion(error.keyword), })); } /** * Get error severity based on keyword */ private _getErrorSeverity( keyword: string ): "low" | "medium" | "high" | "critical" { const severityMap = { noPrototypePollution: "critical", noConstructorManipulation: "critical", deepSecurityCheck: "high", additionalProperties: "medium", type: "medium", required: "low", }; return severityMap[keyword] || "low"; } /** * Get suggestion for fixing validation error */ private _getErrorSuggestion(keyword: string): string { const suggestions = { noPrototypePollution: "Remove dangerous properties like __proto__, constructor, prototype", noConstructorManipulation: "Avoid manipulating constructor properties", deepSecurityCheck: "Check for unsafe property names and excessive nesting", additionalProperties: "Remove unexpected properties from object", type: "Ensure property has correct data type", required: "Add missing required properties", }; return suggestions[keyword] || "Check schema requirements"; } // Public API - keeping your original method names validateFast(data: any, schema: object): { valid: boolean; errors?: any[] } { const validate = this._compileValidator(this.ajv, schema); const valid = validate(data); return { valid, errors: validate.errors ? this._formatErrors(validate.errors) : undefined, }; } validateSecure( data: any, schema: object ): { valid: boolean; errors?: any[] } { // Sanitize data first const sanitizedData = this._sanitizeData(data); // Create secure schema with security keywords const secureSchema = this._createSecureSchema(schema); const validate = this._compileValidator(this.secureAjv, secureSchema); const valid = validate(sanitizedData); return { valid, errors: validate.errors ? this._formatErrors(validate.errors) : undefined, }; } }