/** * Comprehensive Unit Tests for Sanitization Utilities * * These tests ensure XSS and injection attack prevention */ import { describe, it, expect } from 'vitest'; import { sanitizeHTML, sanitizeURL, sanitizePlainText, containsSuspiciousContent } from '@/lib/sanitize'; describe('sanitizeURL', () => { describe('Valid URLs', () => { it('should allow valid http URLs', () => { expect(sanitizeURL('http://example.com')).toBe('http://example.com'); }); it('should allow valid https URLs', () => { expect(sanitizeURL('https://example.com/path?query=value')).toBe('https://example.com/path?query=value'); }); it('should allow valid mailto URLs', () => { expect(sanitizeURL('mailto:user@example.com')).toBe('mailto:user@example.com'); }); it('should allow URLs with special characters in query strings', () => { const url = 'https://example.com/search?q=test%20query&sort=desc'; expect(sanitizeURL(url)).toBe(url); }); it('should allow URLs with fragments', () => { const url = 'https://example.com/page#section'; expect(sanitizeURL(url)).toBe(url); }); it('should allow URLs with authentication', () => { const url = 'https://user:pass@example.com/path'; expect(sanitizeURL(url)).toBe(url); }); it('should allow URLs with ports', () => { const url = 'https://example.com:8080/path'; expect(sanitizeURL(url)).toBe(url); }); }); describe('Dangerous Protocols - XSS Prevention', () => { it('should block javascript: protocol', () => { expect(sanitizeURL('javascript:alert("XSS")')).toBe('#'); }); it('should block javascript: protocol with uppercase', () => { expect(sanitizeURL('JAVASCRIPT:alert("XSS")')).toBe('#'); }); it('should block javascript: protocol with mixed case', () => { expect(sanitizeURL('JaVaScRiPt:alert("XSS")')).toBe('#'); }); it('should block data: protocol', () => { expect(sanitizeURL('data:text/html,')).toBe('#'); }); it('should block data: protocol with base64', () => { expect(sanitizeURL('data:text/html;base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4=')).toBe('#'); }); it('should block vbscript: protocol', () => { expect(sanitizeURL('vbscript:msgbox("XSS")')).toBe('#'); }); it('should block file: protocol', () => { expect(sanitizeURL('file:///etc/passwd')).toBe('#'); }); it('should block ftp: protocol', () => { expect(sanitizeURL('ftp://example.com/file')).toBe('#'); }); }); describe('Edge Cases', () => { it('should handle invalid URLs', () => { expect(sanitizeURL('not a url')).toBe('#'); expect(sanitizeURL('')).toBe('#'); expect(sanitizeURL(' ')).toBe('#'); }); it('should handle null/undefined gracefully', () => { expect(sanitizeURL(null as any)).toBe('#'); expect(sanitizeURL(undefined as any)).toBe('#'); }); it('should handle malformed URLs', () => { expect(sanitizeURL('http://')).toBe('#'); expect(sanitizeURL('https://')).toBe('#'); expect(sanitizeURL('://')).toBe('#'); }); it('should handle URLs with only protocol', () => { expect(sanitizeURL('http:')).toBe('#'); expect(sanitizeURL('https:')).toBe('#'); }); it('should handle relative URLs', () => { expect(sanitizeURL('/path/to/page')).toBe('#'); expect(sanitizeURL('./relative')).toBe('#'); expect(sanitizeURL('../parent')).toBe('#'); }); it('should handle URLs with whitespace (URL constructor allows it)', () => { // Note: URL constructor successfully parses URLs with surrounding whitespace const result = sanitizeURL(' https://example.com '); // Either returns the URL as-is or we could trim it first expect(result).toBe(' https://example.com '); }); it('should handle empty or whitespace-only strings', () => { expect(sanitizeURL('')).toBe('#'); expect(sanitizeURL(' ')).toBe('#'); expect(sanitizeURL('\n\t')).toBe('#'); }); }); }); describe('sanitizePlainText', () => { describe('HTML Entity Escaping', () => { it('should escape script tags', () => { expect(sanitizePlainText('')) .toBe('<script>alert("XSS")</script>'); }); it('should escape ampersands', () => { expect(sanitizePlainText('Tom & Jerry')).toBe('Tom & Jerry'); }); it('should escape double quotes', () => { expect(sanitizePlainText('"Hello"')).toContain('"'); }); it('should escape single quotes', () => { expect(sanitizePlainText("'World'")).toContain('''); }); it('should escape less than symbols', () => { expect(sanitizePlainText('5 < 10')).toBe('5 < 10'); }); it('should escape greater than symbols', () => { expect(sanitizePlainText('10 > 5')).toBe('10 > 5'); }); it('should escape forward slashes', () => { expect(sanitizePlainText('path/to/file')).toBe('path/to/file'); }); it('should escape all special characters together', () => { const input = '
©
'; const output = sanitizePlainText(input); expect(output).not.toContain('<'); expect(output).not.toContain('>'); expect(output).not.toContain('"'); }); }); describe('XSS Attack Vectors', () => { it('should neutralize img onerror attacks', () => { const attack = ''; const result = sanitizePlainText(attack); // The escaped version will contain the text 'onerror' but in safe escaped form expect(result).toContain('<img'); expect(result).toContain('"'); // Ensure no executable HTML/script tags remain expect(result).not.toContain(''); expect(result).not.toContain('<'); }); it('should neutralize iframe attacks', () => { const attack = ''; const result = sanitizePlainText(attack); expect(result).not.toContain(' { const attack = ''; const result = sanitizePlainText(attack); expect(result).toContain('<button'); expect(result).toContain('"'); // Ensure no executable HTML/script tags remain expect(result).not.toContain(''); expect(result).not.toContain('<'); }); it('should neutralize SVG-based XSS', () => { const attack = ''; const result = sanitizePlainText(attack); expect(result).toContain('<svg'); expect(result).toContain('"'); // Ensure no executable HTML/script tags remain expect(result).not.toContain(''); expect(result).not.toContain('<'); }); }); describe('Safe Content', () => { it('should handle plain text without changes', () => { expect(sanitizePlainText('Hello World')).toBe('Hello World'); }); it('should handle empty strings', () => { expect(sanitizePlainText('')).toBe(''); }); it('should handle text with numbers', () => { expect(sanitizePlainText('Price: $19.99')).toBe('Price: $19.99'); }); it('should handle text with newlines', () => { expect(sanitizePlainText('Line 1\nLine 2')).toBe('Line 1\nLine 2'); }); it('should handle Unicode characters', () => { expect(sanitizePlainText('Hello δΈ–η•Œ 🌍')).toBe('Hello δΈ–η•Œ 🌍'); }); }); describe('Edge Cases', () => { it('should handle null/undefined', () => { expect(sanitizePlainText(null as any)).toBe(''); expect(sanitizePlainText(undefined as any)).toBe(''); }); it('should handle numbers', () => { expect(sanitizePlainText(123 as any)).toBe(''); }); it('should handle objects', () => { expect(sanitizePlainText({} as any)).toBe(''); }); it('should handle very long strings', () => { const longString = 'a'.repeat(10000); const result = sanitizePlainText(longString); expect(result.length).toBe(10000); }); }); }); describe('containsSuspiciousContent', () => { describe('Script Tag Detection', () => { it('should detect script tags', () => { expect(containsSuspiciousContent('')).toBe(true); }); it('should detect uppercase script tags', () => { expect(containsSuspiciousContent('')).toBe(true); }); it('should detect mixed case script tags', () => { expect(containsSuspiciousContent('')).toBe(true); }); it('should detect script tags with attributes', () => { expect(containsSuspiciousContent('')).toBe(true); }); it('should detect self-closing script tags', () => { expect(containsSuspiciousContent('')).toBe(true); }); }); describe('Safe Content', () => { it('should not flag safe content', () => { expect(containsSuspiciousContent('This is a safe message')).toBe(false); }); it('should not flag email addresses', () => { expect(containsSuspiciousContent('Email: user@example.com')).toBe(false); }); it('should not flag safe HTML-like text', () => { expect(containsSuspiciousContent('The tag

is safe')).toBe(false); }); it('should not flag normal URLs', () => { expect(containsSuspiciousContent('https://example.com')).toBe(false); }); it('should not flag markdown-like syntax', () => { expect(containsSuspiciousContent('[Link](https://example.com)')).toBe(false); }); }); describe('Edge Cases', () => { it('should handle null/undefined', () => { expect(containsSuspiciousContent(null as any)).toBe(false); expect(containsSuspiciousContent(undefined as any)).toBe(false); }); it('should handle empty strings', () => { expect(containsSuspiciousContent('')).toBe(false); }); it('should handle numbers', () => { expect(containsSuspiciousContent(123 as any)).toBe(false); }); it('should handle objects', () => { expect(containsSuspiciousContent({} as any)).toBe(false); }); }); }); describe('sanitizeHTML', () => { describe('Safe Tags', () => { it('should allow paragraph tags', () => { const html = '

Hello world

'; const result = sanitizeHTML(html); expect(result).toContain('

'); expect(result).toContain('Hello world'); }); it('should allow strong tags', () => { const html = '

Hello world

'; const result = sanitizeHTML(html); expect(result).toContain(''); }); it('should allow emphasis tags', () => { const html = '

Hello world

'; const result = sanitizeHTML(html); expect(result).toContain(''); }); it('should allow underline tags', () => { const html = '

Hello world

'; const result = sanitizeHTML(html); expect(result).toContain(''); }); it('should allow lists', () => { const html = '
  • Item 1
  • Item 2
'; const result = sanitizeHTML(html); expect(result).toContain('
    '); expect(result).toContain('
  • '); }); it('should allow ordered lists', () => { const html = '
    1. First
    2. Second
    '; const result = sanitizeHTML(html); expect(result).toContain('
      '); expect(result).toContain('
    1. '); }); it('should allow line breaks', () => { const html = 'Line 1
      Line 2'; const result = sanitizeHTML(html); expect(result).toContain('
      '); }); }); describe('Dangerous Content Removal', () => { it('should remove script tags', () => { const html = '

      Hello

      '; const result = sanitizeHTML(html); expect(result).not.toContain('