mirror of
https://github.com/root-fr/jmap-webmail.git
synced 2026-09-26 00:01:19 +00:00
- Add unified email sanitization utility (lib/email-sanitization.ts) - Safe HTML parsing using DOMParser instead of innerHTML - Consistent DOMPurify configuration across components - Separate signature sanitization with stricter rules - Add secure email validation (lib/validation.ts) - RFC 5322 compliant validation - Block header injection attempts (\r\n<>) - Validate comma-separated email lists - Fix HTML signature XSS (identity-form.tsx) - Sanitize signatures before storage and rendering - Add live preview with safe rendering - Input length limits for defense in depth - Fix innerHTML XSS (email-viewer.tsx, thread-conversation-view.tsx) - Replace unsafe innerHTML with DOMParser for format detection - Use shared EMAIL_SANITIZE_CONFIG - Add <meta>, <link>, <base> to forbidden tags Security issues resolved: - H1: Stored XSS in HTML signatures (CVSS 7.1) - H2: XSS during HTML parsing (CVSS 6.8) - M1: Email validation bypass (CVSS 4.3) - M3: Inconsistent sanitization (CVSS 5.0) Zero new dependencies. All tests passing.
69 lines
1.8 KiB
TypeScript
69 lines
1.8 KiB
TypeScript
/**
|
|
* RFC 5322 compliant email validation with security enhancements
|
|
*/
|
|
export function isValidEmail(email: string): boolean {
|
|
// Length check
|
|
if (!email || email.length > 254) return false;
|
|
|
|
// Security: Block control characters and header injection
|
|
if (/[\r\n\0<>]/.test(email)) return false;
|
|
|
|
// RFC 5322 compliant regex (simplified but secure)
|
|
const emailRegex = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
|
|
|
|
if (!emailRegex.test(email)) return false;
|
|
|
|
// Additional checks
|
|
const [localPart, domain] = email.split('@');
|
|
|
|
// Local part max 64 chars
|
|
if (localPart.length > 64) return false;
|
|
|
|
// Domain validation
|
|
if (domain.length > 255) return false;
|
|
if (domain.startsWith('.') || domain.endsWith('.')) return false;
|
|
if (domain.includes('..')) return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Validate comma-separated email list
|
|
* @returns Object with validation result and invalid emails
|
|
*/
|
|
export function validateEmailList(csv: string): {
|
|
valid: boolean;
|
|
invalidEmails: string[];
|
|
} {
|
|
if (!csv?.trim()) {
|
|
return { valid: true, invalidEmails: [] };
|
|
}
|
|
|
|
const emails = csv.split(',').map(e => e.trim()).filter(Boolean);
|
|
const invalid = emails.filter(e => !isValidEmail(e));
|
|
|
|
return {
|
|
valid: invalid.length === 0,
|
|
invalidEmails: invalid
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Get user-friendly validation error message
|
|
*/
|
|
export function getEmailValidationError(email: string): string | null {
|
|
if (!email?.trim()) return 'Email address is required';
|
|
|
|
if (email.length > 254) return 'Email address is too long (max 254 characters)';
|
|
|
|
if (/[\r\n\0<>]/.test(email)) {
|
|
return 'Email address contains invalid characters';
|
|
}
|
|
|
|
if (!isValidEmail(email)) {
|
|
return 'Please enter a valid email address';
|
|
}
|
|
|
|
return null;
|
|
}
|