feat(security): Implement input validation and fix logging conflicts

- Added comprehensive input validation with size/rate limits
- Fixed logging conflicts by renaming _sanitizeString → _sanitizeInputString
- Implemented XSS/injection protection via pattern detection
- Added safe logging recovery mechanism
- Maintained all existing security protections
This commit is contained in:
lockbitchat
2025-08-31 18:35:31 -04:00
parent 6c4d0eeaee
commit 35d83d46f2

View File

@@ -828,6 +828,245 @@ this._secureLog('info', '🔒 Enhanced Mutex system fully initialized and valida
}
}
/**
* SECURE: Comprehensive input validation to prevent DoS and injection attacks
* @param {any} data - Data to validate
* @param {string} context - Context for validation (e.g., 'sendMessage', 'sendSecureMessage')
* @returns {Object} Validation result with isValid and sanitizedData
*/
_validateInputData(data, context = 'unknown') {
const validationResult = {
isValid: false,
sanitizedData: null,
errors: [],
warnings: []
};
try {
// 1. Basic type validation
if (data === null || data === undefined) {
validationResult.errors.push('Data cannot be null or undefined');
return validationResult;
}
// 2. Size validation for strings
if (typeof data === 'string') {
if (data.length > this._inputValidationLimits.maxStringLength) {
validationResult.errors.push(`String too long: ${data.length} > ${this._inputValidationLimits.maxStringLength}`);
return validationResult;
}
// 3. Malicious pattern detection for strings
for (const pattern of this._maliciousPatterns) {
if (pattern.test(data)) {
validationResult.errors.push(`Malicious pattern detected: ${pattern.source}`);
this._secureLog('warn', '🚨 Malicious pattern detected in input', {
context: context,
pattern: pattern.source,
dataLength: data.length
});
return validationResult;
}
}
// 4. Sanitize string data
validationResult.sanitizedData = this._sanitizeInputString(data);
validationResult.isValid = true;
return validationResult;
}
// 5. Object validation
if (typeof data === 'object') {
// Check for circular references
const seen = new WeakSet();
const checkCircular = (obj, path = '') => {
if (obj === null || typeof obj !== 'object') return;
if (seen.has(obj)) {
validationResult.errors.push(`Circular reference detected at path: ${path}`);
return;
}
seen.add(obj);
// Check object depth
if (path.split('.').length > this._inputValidationLimits.maxObjectDepth) {
validationResult.errors.push(`Object too deep: ${path.split('.').length} > ${this._inputValidationLimits.maxObjectDepth}`);
return;
}
// Check array length
if (Array.isArray(obj) && obj.length > this._inputValidationLimits.maxArrayLength) {
validationResult.errors.push(`Array too long: ${obj.length} > ${this._inputValidationLimits.maxArrayLength}`);
return;
}
// Recursively check all properties
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
checkCircular(obj[key], path ? `${path}.${key}` : key);
}
}
};
checkCircular(data);
if (validationResult.errors.length > 0) {
return validationResult;
}
// 6. Check total object size
const objectSize = this._calculateObjectSize(data);
if (objectSize > this._inputValidationLimits.maxMessageSize) {
validationResult.errors.push(`Object too large: ${objectSize} bytes > ${this._inputValidationLimits.maxMessageSize} bytes`);
return validationResult;
}
// 7. Sanitize object data
validationResult.sanitizedData = this._sanitizeInputObject(data);
validationResult.isValid = true;
return validationResult;
}
// 8. ArrayBuffer validation
if (data instanceof ArrayBuffer) {
if (data.byteLength > this._inputValidationLimits.maxMessageSize) {
validationResult.errors.push(`ArrayBuffer too large: ${data.byteLength} bytes > ${this._inputValidationLimits.maxMessageSize} bytes`);
return validationResult;
}
validationResult.sanitizedData = data;
validationResult.isValid = true;
return validationResult;
}
// 9. Other types are not allowed
validationResult.errors.push(`Unsupported data type: ${typeof data}`);
return validationResult;
} catch (error) {
validationResult.errors.push(`Validation error: ${error.message}`);
this._secureLog('error', '❌ Input validation failed', {
context: context,
errorType: error?.constructor?.name || 'Unknown',
message: error?.message || 'Unknown error'
});
return validationResult;
}
}
/**
* SECURE: Calculate approximate object size in bytes
* @param {any} obj - Object to calculate size for
* @returns {number} Size in bytes
*/
_calculateObjectSize(obj) {
try {
const jsonString = JSON.stringify(obj);
return new TextEncoder().encode(jsonString).length;
} catch (error) {
// If JSON.stringify fails, estimate size
return 1024 * 1024; // Assume 1MB to be safe
}
}
/**
* SECURE: Sanitize string data for input validation
* @param {string} str - String to sanitize
* @returns {string} Sanitized string
*/
_sanitizeInputString(str) {
if (typeof str !== 'string') return str;
// Remove null bytes
str = str.replace(/\0/g, '');
// Normalize whitespace
str = str.replace(/\s+/g, ' ');
// Trim
str = str.trim();
return str;
}
/**
* SECURE: Sanitize object data for input validation
* @param {any} obj - Object to sanitize
* @returns {any} Sanitized object
*/
_sanitizeInputObject(obj) {
if (obj === null || typeof obj !== 'object') return obj;
if (Array.isArray(obj)) {
return obj.map(item => this._sanitizeInputObject(item));
}
const sanitized = {};
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
const value = obj[key];
if (typeof value === 'string') {
sanitized[key] = this._sanitizeInputString(value);
} else if (typeof value === 'object') {
sanitized[key] = this._sanitizeInputObject(value);
} else {
sanitized[key] = value;
}
}
}
return sanitized;
}
/**
* SECURE: Rate limiting for message sending
* @param {string} context - Context for rate limiting
* @returns {boolean} true if rate limit allows
*/
_checkRateLimit(context = 'message') {
const now = Date.now();
// Initialize rate limiter if not exists
if (!this._rateLimiter) {
this._rateLimiter = {
messageCount: 0,
lastReset: now,
burstCount: 0,
lastBurstReset: now
};
}
// Reset counters if needed
if (now - this._rateLimiter.lastReset > 60000) { // 1 minute
this._rateLimiter.messageCount = 0;
this._rateLimiter.lastReset = now;
}
if (now - this._rateLimiter.lastBurstReset > 1000) { // 1 second
this._rateLimiter.burstCount = 0;
this._rateLimiter.lastBurstReset = now;
}
// Check burst limit
if (this._rateLimiter.burstCount >= this._inputValidationLimits.rateLimitBurstSize) {
this._secureLog('warn', '⚠️ Rate limit burst exceeded', { context });
return false;
}
// Check overall rate limit
if (this._rateLimiter.messageCount >= this._inputValidationLimits.rateLimitMessagesPerMinute) {
this._secureLog('warn', '⚠️ Rate limit exceeded', { context });
return false;
}
// Increment counters
this._rateLimiter.messageCount++;
this._rateLimiter.burstCount++;
return true;
}
// ============================================
// SECURE KEY STORAGE MANAGEMENT
// ============================================
@@ -1025,6 +1264,41 @@ this._secureLog('info', '🔒 Enhanced Mutex system fully initialized and valida
ivHistory: this._resourceLimits.maxIVHistory * 0.8,
processedMessageIds: this._resourceLimits.maxProcessedMessageIds * 0.8
};
// SECURE: Input validation limits to prevent DoS attacks
this._inputValidationLimits = {
maxStringLength: 100000, // 100KB for strings
maxObjectDepth: 10, // Maximum object nesting depth
maxArrayLength: 1000, // Maximum array length
maxMessageSize: 1024 * 1024, // 1MB total message size
maxConcurrentMessages: 10, // Maximum concurrent message processing
rateLimitMessagesPerMinute: 60, // Rate limiting
rateLimitBurstSize: 10 // Burst size for rate limiting
};
// SECURE: Malicious pattern detection
this._maliciousPatterns = [
/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, // Script tags
/javascript:/gi, // JavaScript protocol
/data:text\/html/gi, // Data URLs with HTML
/on\w+\s*=/gi, // Event handlers
/eval\s*\(/gi, // eval() calls
/document\./gi, // Document object access
/window\./gi, // Window object access
/localStorage/gi, // LocalStorage access
/sessionStorage/gi, // SessionStorage access
/fetch\s*\(/gi, // Fetch API calls
/XMLHttpRequest/gi, // XHR calls
/import\s*\(/gi, // Dynamic imports
/require\s*\(/gi, // Require calls
/process\./gi, // Process object access
/global/gi, // Global object access
/__proto__/gi, // Prototype pollution
/constructor/gi, // Constructor access
/prototype/gi, // Prototype access
/toString\s*\(/gi, // toString calls
/valueOf\s*\(/gi // valueOf calls
];
// CRITICAL FIX: Comprehensive blacklist with all sensitive patterns
this._absoluteBlacklist = new Set([
@@ -1745,6 +2019,10 @@ this._secureLog('info', '🔒 Enhanced Mutex system fully initialized and valida
safeGlobalAPI.emergency.disableLogging = this._emergencyDisableLogging.bind(this);
}
if (typeof this._resetLoggingSystem === 'function') {
safeGlobalAPI.emergency.resetLogging = this._resetLoggingSystem.bind(this);
}
// SECURE: Add file transfer system status
safeGlobalAPI.getFileTransferSystemStatus = () => ({
initialized: !!this.fileTransferSystem,
@@ -3104,6 +3382,12 @@ this._secureLog('info', '🔒 Enhanced Mutex system fully initialized and valida
}
};
// CRITICAL FIX: Store original functions before overriding
this._originalSanitizeString = this._sanitizeString;
this._originalSanitizeLogData = this._sanitizeLogData;
this._originalAuditLogMessage = this._auditLogMessage;
this._originalContainsSensitiveContent = this._containsSensitiveContent;
// CRITICAL FIX: Override all logging methods to prevent bypass
this._sanitizeString = () => '[LOGGING_DISABLED]';
this._sanitizeLogData = () => ({ error: 'LOGGING_DISABLED' });
@@ -3122,6 +3406,25 @@ this._secureLog('info', '🔒 Enhanced Mutex system fully initialized and valida
// CRITICAL FIX: Notify about the emergency shutdown
this._originalConsole?.error?.('🚨 CRITICAL: Secure logging system disabled due to potential data exposure');
}
/**
* SECURE: Reset logging system after emergency shutdown
* Use this function to restore normal logging functionality
*/
_resetLoggingSystem() {
this._secureLog('info', '🔧 Resetting logging system after emergency shutdown');
// Restore original sanitize functions
this._sanitizeString = this._originalSanitizeString || ((str) => str);
this._sanitizeLogData = this._originalSanitizeLogData || ((data) => data);
this._auditLogMessage = this._originalAuditLogMessage || (() => true);
this._containsSensitiveContent = this._originalContainsSensitiveContent || (() => false);
// Reset security violation counters
this._logSecurityViolations = 0;
this._secureLog('info', '✅ Logging system reset successfully');
}
/**
* CRITICAL FIX: Enhanced audit function for log message security
*/
@@ -4817,61 +5120,83 @@ async processOrderedPackets() {
}
async sendMessage(data) {
if (!this.dataChannel || this.dataChannel.readyState !== 'open') {
throw new Error('Data channel not ready');
}
// SECURE: Comprehensive input validation
const validation = this._validateInputData(data, 'sendMessage');
if (!validation.isValid) {
const errorMessage = `Input validation failed: ${validation.errors.join(', ')}`;
this._secureLog('error', '❌ Input validation failed in sendMessage', {
errors: validation.errors,
dataType: typeof data,
dataLength: data?.length || data?.byteLength || 0
});
throw new Error(errorMessage);
}
try {
this._secureLog('debug', 'sendMessage called', {
hasDataChannel: !!this.dataChannel,
dataChannelReady: this.dataChannel?.readyState === 'open',
isInitiator: this.isInitiator,
isVerified: this.isVerified,
connectionReady: this.peerConnection?.connectionState === 'connected'
});
// SECURE: Rate limiting check
if (!this._checkRateLimit('sendMessage')) {
throw new Error('Rate limit exceeded for message sending');
}
this._secureLog('debug', '🔍 sendMessage DEBUG', {
dataType: typeof data,
isString: typeof data === 'string',
isArrayBuffer: data instanceof ArrayBuffer,
dataLength: data?.length || data?.byteLength || 0,
});
// SECURE: Connection validation
if (!this.dataChannel || this.dataChannel.readyState !== 'open') {
throw new Error('Data channel not ready');
}
// FIX: Check whether this is a file-transfer message
if (typeof data === 'string') {
try {
const parsed = JSON.parse(data);
// Send file messages directly without additional encryption
if (parsed.type && parsed.type.startsWith('file_')) {
this._secureLog('debug', '📁 Sending file message directly', { type: parsed.type });
this.dataChannel.send(data);
return true;
try {
this._secureLog('debug', 'sendMessage called', {
hasDataChannel: !!this.dataChannel,
dataChannelReady: this.dataChannel?.readyState === 'open',
isInitiator: this.isInitiator,
isVerified: this.isVerified,
connectionReady: this.peerConnection?.connectionState === 'connected'
});
this._secureLog('debug', '🔍 sendMessage DEBUG', {
dataType: typeof validation.sanitizedData,
isString: typeof validation.sanitizedData === 'string',
isArrayBuffer: validation.sanitizedData instanceof ArrayBuffer,
dataLength: validation.sanitizedData?.length || validation.sanitizedData?.byteLength || 0,
});
// SECURE: Check whether this is a file-transfer message
if (typeof validation.sanitizedData === 'string') {
try {
const parsed = JSON.parse(validation.sanitizedData);
// Send file messages directly without additional encryption
if (parsed.type && parsed.type.startsWith('file_')) {
this._secureLog('debug', '📁 Sending file message directly', { type: parsed.type });
this.dataChannel.send(validation.sanitizedData);
return true;
}
} catch (jsonError) {
// Not JSON — continue normal handling
}
} catch (jsonError) {
// Not JSON — continue normal handling
}
}
// For regular text messages, send via secure path
if (typeof data === 'string') {
return await this.sendSecureMessage({ type: 'message', data, timestamp: Date.now() });
}
// SECURE: For regular text messages, send via secure path
if (typeof validation.sanitizedData === 'string') {
return await this.sendSecureMessage({
type: 'message',
data: validation.sanitizedData,
timestamp: Date.now()
});
}
// For binary data, apply security layers with a limited mutex
this._secureLog('debug', '🔐 Applying security layers to non-string data');
const securedData = await this._applySecurityLayersWithLimitedMutex(data, false);
this.dataChannel.send(securedData);
return true;
} catch (error) {
this._secureLog('error', '❌ Failed to send message', {
error: error.message,
errorType: error.constructor.name
});
throw error;
// SECURE: For binary data, apply security layers with a limited mutex
this._secureLog('debug', '🔐 Applying security layers to non-string data');
const securedData = await this._applySecurityLayersWithLimitedMutex(validation.sanitizedData, false);
this.dataChannel.send(securedData);
return true;
} catch (error) {
this._secureLog('error', '❌ Failed to send message', {
error: error.message,
errorType: error.constructor.name
});
throw error;
}
}
}
// FIX: New method applying security layers with limited mutex use
async _applySecurityLayersWithLimitedMutex(data, isFakeMessage = false) {
@@ -9115,16 +9440,32 @@ async processMessage(data) {
}
async sendSecureMessage(message) {
// Quick readiness check WITHOUT mutex
// SECURE: Comprehensive input validation
const validation = this._validateInputData(message, 'sendSecureMessage');
if (!validation.isValid) {
const errorMessage = `Input validation failed: ${validation.errors.join(', ')}`;
this._secureLog('error', '❌ Input validation failed in sendSecureMessage', {
errors: validation.errors,
messageType: typeof message
});
throw new Error(errorMessage);
}
// SECURE: Rate limiting check
if (!this._checkRateLimit('sendSecureMessage')) {
throw new Error('Rate limit exceeded for secure message sending');
}
// SECURE: Quick readiness check WITHOUT mutex
if (!this.isConnected() || !this.isVerified) {
if (message && typeof message === 'object' && message.type && message.type.startsWith('file_')) {
if (validation.sanitizedData && typeof validation.sanitizedData === 'object' && validation.sanitizedData.type && validation.sanitizedData.type.startsWith('file_')) {
throw new Error('Connection not ready for file transfer. Please ensure the connection is established and verified.');
}
this.messageQueue.push(message);
this.messageQueue.push(validation.sanitizedData);
throw new Error('Connection not ready. Message queued for sending.');
}
// FIX: Use mutex ONLY for cryptographic operations
// SECURE: Use mutex ONLY for cryptographic operations
return this._withMutex('cryptoOperation', async (operationId) => {
// Re-check inside critical section
if (!this.isConnected() || !this.isVerified) {
@@ -9136,18 +9477,18 @@ async processMessage(data) {
throw new Error('Encryption keys not initialized');
}
// Rate limiting check
// SECURE: Additional rate limiting check
if (!window.EnhancedSecureCryptoUtils.rateLimiter.checkMessageRate(this.rateLimiterId)) {
throw new Error('Message rate limit exceeded (60 messages per minute)');
}
try {
// Accept strings and objects; stringify objects
const textToSend = typeof message === 'string' ? message : JSON.stringify(message);
// SECURE: Accept strings and objects; stringify objects
const textToSend = typeof validation.sanitizedData === 'string' ? validation.sanitizedData : JSON.stringify(validation.sanitizedData);
const sanitizedMessage = window.EnhancedSecureCryptoUtils.sanitizeMessage(textToSend);
const messageId = `msg_${Date.now()}_${this.messageCounter++}`;
// Use enhanced encryption with metadata protection
// SECURE: Use enhanced encryption with metadata protection
const encryptedData = await window.EnhancedSecureCryptoUtils.encryptMessage(
sanitizedMessage,
this.encryptionKey,
@@ -9165,9 +9506,9 @@ async processMessage(data) {
};
this.dataChannel.send(JSON.stringify(payload));
// Locally display only plain strings to avoid UI duplication
if (typeof message === 'string') {
this.deliverMessageToUI(message, 'sent');
// SECURE: Locally display only plain strings to avoid UI duplication
if (typeof validation.sanitizedData === 'string') {
this.deliverMessageToUI(validation.sanitizedData, 'sent');
}
this._secureLog('debug', '📤 Secure message sent successfully', {