fix(security): restore outgoing message integrity, add HSTS/Permissions-Policy
CodeQL Analysis / Analyze CodeQL (push) Has been cancelled
Deploy Application / deploy (push) Has been cancelled
Mirror to Codeberg / mirror (push) Has been cancelled
Mirror to PrivacyGuides / mirror (push) Has been cancelled

- Remove send-path keyword blocklist that silently rejected legitimate
  messages (e.g. "constructor", "global", "document.", literal "javascript:")
  without adding protection. XSS is enforced at the rendering boundary by the
  receive-side DOMPurify pass and by sanitizeMessage() before encryption.
- Preserve newlines/tabs/indentation in _sanitizeInputString; stop collapsing
  all whitespace which destroyed multi-line messages and code snippets.
- Stop logging raw AAD (sessionId + keyFingerprint) on validation failure;
  log length only, in both message and file-message AAD validators.
- Add Strict-Transport-Security (2y + preload) and Permissions-Policy
  (camera=self for QR, rest denied) to nginx.conf and .htaccess.
- Add tests/outgoing-message-integrity.test.mjs regression suite.
This commit is contained in:
lockbitchat
2026-06-18 16:48:29 -04:00
parent 6f36fce8c6
commit 42be55aaeb
7 changed files with 155 additions and 139 deletions
+44 -58
View File
@@ -1518,20 +1518,17 @@ this._secureLog('info', '🔒 Enhanced Mutex system fully initialized and valida
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;
}
}
// 3. No keyword/markup blocklist here.
// This validates OUTGOING free-text chat content. A word-level
// blocklist (e.g. "constructor", "global", "document.", "javascript:")
// silently rejected legitimate messages while providing no real
// protection: the rendering boundary is the receive-side DOMPurify
// pass in deliverMessageToUI(), and outgoing text is additionally run
// through EnhancedSecureCryptoUtils.sanitizeMessage() before encryption.
// Markup a user types is neutralised there, not by guessing keywords.
// 4. Sanitize string data
validationResult.sanitizedData = this._sanitizeInputString(data);
validationResult.isValid = true;
return validationResult;
@@ -1639,16 +1636,24 @@ this._secureLog('info', '🔒 Enhanced Mutex system fully initialized and valida
*/
_sanitizeInputString(str) {
if (typeof str !== 'string') return str;
// Remove null bytes
str = str.replace(/\0/g, '');
// Normalize whitespace
str = str.replace(/\s+/g, ' ');
// Trim
// Remove null bytes and C0/C1 control characters, but preserve the
// whitespace users actually type: newline (\n), carriage return (\r)
// and tab (\t). Collapsing all \s+ here destroyed multi-line messages
// and code snippets ("a\nb\nc" -> "a b c"), corrupting chat content.
str = str.replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F-\u009F]/g, '');
// Normalise line endings only. Internal spaces/tabs are preserved so
// code indentation, pasted keys and aligned text survive intact; the
// maxStringLength limit (and the receive-side 2000-char cap) bound abuse.
str = str.replace(/\r\n?/g, '\n');
// Collapse 3+ consecutive blank lines down to two.
str = str.replace(/\n{3,}/g, '\n\n');
// Trim leading/trailing whitespace only.
str = str.trim();
return str;
}
@@ -2089,41 +2094,12 @@ this._secureLog('info', '🔒 Enhanced Mutex system fully initialized and valida
rateLimitBurstSize: 10 // Burst size for rate limiting
};
// Malicious pattern detection
this._maliciousPatterns = [
// Enhanced script tag detection that handles edge cases
/<script\b[^>]*>[\s\S]*?<\/script\s*>/gi, // Standard </script>
/<script\b[^>]*>[\s\S]*?<\/script\s+[^>]*>/gi, // </script with attributes>
/<script\b[^>]*>[\s\S]*$/gi, // Malformed script tags without closing
// Additional dangerous tags
/<iframe\b[^>]*>[\s\S]*?<\/iframe\s*>/gi, // iframe tags
/<object\b[^>]*>[\s\S]*?<\/object\s*>/gi, // object tags
/<embed\b[^>]*>/gi, // embed tags
/<applet\b[^>]*>[\s\S]*?<\/applet\s*>/gi, // applet tags
/<style\b[^>]*>[\s\S]*?<\/style\s*>/gi, // style tags
// Dangerous protocols
/javascript\s*:/gi, // JavaScript protocol
/data\s*:/gi, // Data protocol
/vbscript\s*:/gi, // VBScript 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
];
// NOTE: A word/markup blocklist for outgoing chat text was removed.
// It rejected legitimate messages (plain words like "constructor",
// "global", "document.", or the literal text "javascript:") without
// adding security. XSS is prevented at the rendering boundary by the
// receive-side DOMPurify pass (deliverMessageToUI) and by sanitizeMessage()
// on the send path before encryption — not by keyword guessing.
// Comprehensive blacklist with all sensitive patterns
this._absoluteBlacklist = new Set([
@@ -3796,7 +3772,12 @@ this._secureLog('info', '🔒 Enhanced Mutex system fully initialized and valida
return aad;
} catch (error) {
this._secureLog('error', 'AAD validation failed', { error: error.message, aadString });
// Never log the raw AAD: it carries sessionId and keyFingerprint.
// Length is enough to diagnose malformed input without leaking secrets.
this._secureLog('error', 'AAD validation failed', {
error: error.message,
aadLength: typeof aadString === 'string' ? aadString.length : 0
});
throw new Error(`AAD validation failed: ${error.message}`);
}
}
@@ -13181,7 +13162,12 @@ class SecureKeyStorage {
return aad;
} catch (error) {
this._secureLog('error', 'AAD validation failed', { error: error.message, aadString });
// Never log the raw AAD: it carries sessionId and keyFingerprint.
// Length is enough to diagnose malformed input without leaking secrets.
this._secureLog('error', 'AAD validation failed', {
error: error.message,
aadLength: typeof aadString === 'string' ? aadString.length : 0
});
throw new Error(`AAD validation failed: ${error.message}`);
}
}