Files
tkmind_go/ui/desktop/src/utils/htmlSecurity.test.ts
T

270 lines
8.3 KiB
TypeScript

import { describe, it, expect } from 'vitest';
import { containsHTML, wrapHTMLInCodeBlock } from '../utils/htmlSecurity';
describe('HTML Security Detection', () => {
describe('containsHTML', () => {
describe('should detect dangerous HTML tags', () => {
it('detects script tags', () => {
expect(containsHTML('<script>alert("xss")</script>')).toBe(true);
expect(containsHTML('<script src="evil.js"></script>')).toBe(true);
expect(containsHTML('<script>')).toBe(true);
});
it('detects style tags', () => {
expect(containsHTML('<style>body { display: none; }</style>')).toBe(true);
expect(containsHTML('<style>')).toBe(true);
});
it('detects iframe tags', () => {
expect(containsHTML('<iframe src="evil.com"></iframe>')).toBe(true);
expect(containsHTML('<iframe>')).toBe(true);
});
it('detects form elements', () => {
expect(containsHTML('<form action="/submit"></form>')).toBe(true);
expect(containsHTML('<input type="text" name="password">')).toBe(true);
expect(containsHTML('<button onclick="evil()">Click</button>')).toBe(true);
});
it('detects layout-affecting tags', () => {
expect(containsHTML('<div class="container">content</div>')).toBe(true);
expect(containsHTML('<span style="color:red">text</span>')).toBe(true);
expect(containsHTML('<br/>')).toBe(true);
expect(containsHTML('<hr>')).toBe(true);
expect(containsHTML('<img src="image.jpg" alt="test">')).toBe(true);
});
it('detects HTML comments', () => {
expect(containsHTML('<!-- this is a comment -->')).toBe(true);
expect(containsHTML('<!-- multi\nline\ncomment -->')).toBe(true);
});
});
describe('should NOT detect safe content', () => {
it('ignores auto-links', () => {
expect(containsHTML('<https://example.com>')).toBe(false);
expect(containsHTML('<http://test.org>')).toBe(false);
expect(containsHTML('<https://block.dev/docs>')).toBe(false);
});
it('ignores email addresses', () => {
expect(containsHTML('<user@example.com>')).toBe(false);
expect(containsHTML('<admin@block.dev>')).toBe(false);
expect(containsHTML('<test.email+tag@domain.co.uk>')).toBe(false);
});
it('ignores TypeScript generics and placeholders', () => {
expect(containsHTML('Array<T>')).toBe(false);
expect(containsHTML('Promise<string>')).toBe(false);
expect(containsHTML('<project-root>')).toBe(false);
expect(containsHTML('<filename>')).toBe(false);
expect(containsHTML('<<not a tag>>')).toBe(false);
});
it('ignores content already in code blocks', () => {
expect(containsHTML('```html\n<div>safe</div>\n```')).toBe(false);
expect(containsHTML('`<script>safe</script>`')).toBe(false);
expect(containsHTML('Here is `<br/>` in inline code')).toBe(false);
});
it('ignores plain text', () => {
expect(containsHTML('This is just plain text')).toBe(false);
expect(containsHTML('No HTML here!')).toBe(false);
expect(containsHTML('')).toBe(false);
});
it('ignores mathematical expressions', () => {
expect(containsHTML('x < y && y > z')).toBe(false);
expect(containsHTML('if (a < b && c > d)')).toBe(false);
});
});
describe('edge cases', () => {
it('handles mixed content correctly', () => {
// Real HTML mixed with safe content
expect(containsHTML('Visit <https://example.com> and <div>click here</div>')).toBe(true);
// Only safe content
expect(containsHTML('Email <user@test.com> about <project-root> setup')).toBe(false);
});
it('handles malformed HTML', () => {
expect(containsHTML('<div unclosed')).toBe(false); // This doesn't match our regex pattern
expect(containsHTML('<>')).toBe(false);
expect(containsHTML('< div >')).toBe(false);
});
});
});
describe('wrapHTMLInCodeBlock', () => {
describe('should wrap dangerous HTML', () => {
it('wraps single line HTML', () => {
const input = '<script>alert("xss")</script>';
const expected = '```html\n<script>alert("xss")</script>\n```';
expect(wrapHTMLInCodeBlock(input)).toBe(expected);
});
it('wraps HTML comments', () => {
const input = '<!-- malicious comment -->';
const expected = '```html\n<!-- malicious comment -->\n```';
expect(wrapHTMLInCodeBlock(input)).toBe(expected);
});
it('wraps mixed content selectively', () => {
const input = `Normal text
<div>This should be wrapped</div>
More normal text`;
const expected = `Normal text
\`\`\`html
<div>This should be wrapped</div>
\`\`\`
More normal text`;
expect(wrapHTMLInCodeBlock(input)).toBe(expected);
});
});
describe('should preserve safe content', () => {
it('preserves auto-links', () => {
const input = 'Visit <https://example.com> for more info';
expect(wrapHTMLInCodeBlock(input)).toBe(input);
});
it('preserves email addresses', () => {
const input = 'Contact <admin@example.com> for help';
expect(wrapHTMLInCodeBlock(input)).toBe(input);
});
it('preserves TypeScript generics', () => {
const input = 'const arr: Array<string> = []';
expect(wrapHTMLInCodeBlock(input)).toBe(input);
});
it('preserves existing code blocks', () => {
const input = `# Title
\`\`\`javascript
const x = "<div>this is safe</div>";
\`\`\`
Normal text`;
expect(wrapHTMLInCodeBlock(input)).toBe(input);
});
it('preserves inline code', () => {
const input = 'Use `<br/>` for line breaks';
expect(wrapHTMLInCodeBlock(input)).toBe(input);
});
});
describe('complex scenarios', () => {
it('handles multiple HTML lines correctly', () => {
const input = `# Test Message
Normal paragraph
<div>First HTML line</div>
<span>Second HTML line</span>
More normal text`;
const expected = `# Test Message
Normal paragraph
\`\`\`html
<div>First HTML line</div>
\`\`\`
\`\`\`html
<span>Second HTML line</span>
\`\`\`
More normal text`;
expect(wrapHTMLInCodeBlock(input)).toBe(expected);
});
it('respects existing code block boundaries', () => {
const input = `Before code block
\`\`\`html
<div>This is already safe</div>
<script>This is also safe in here</script>
\`\`\`
<div>This should be wrapped</div>`;
const expected = `Before code block
\`\`\`html
<div>This is already safe</div>
<script>This is also safe in here</script>
\`\`\`
\`\`\`html
<div>This should be wrapped</div>
\`\`\``;
expect(wrapHTMLInCodeBlock(input)).toBe(expected);
});
it('handles the test suite scenarios', () => {
// Test Message 1: One-liners
const test1 = `<https://example.com>
<user@example.com>
\`<T>\``;
expect(wrapHTMLInCodeBlock(test1)).toBe(test1);
// Test Message 2: Mixed content - our function wraps the entire line, not just the HTML part
const test2 = `Here's a link <https://example.com> and HTML <div>content</div>`;
const expected2 = `\`\`\`html
Here's a link <https://example.com> and HTML <div>content</div>
\`\`\``;
expect(wrapHTMLInCodeBlock(test2)).toBe(expected2);
// Test Message 7: Comment-only
const test7 = `<!-- top-level html comment -->`;
const expected7 = `\`\`\`html
<!-- top-level html comment -->
\`\`\``;
expect(wrapHTMLInCodeBlock(test7)).toBe(expected7);
});
});
describe('edge cases', () => {
it('handles empty input', () => {
expect(wrapHTMLInCodeBlock('')).toBe('');
});
it('handles only whitespace', () => {
const input = ' \n \n ';
expect(wrapHTMLInCodeBlock(input)).toBe(input);
});
it('handles nested code block scenarios', () => {
const input = `\`\`\`
<div>safe in code block</div>
\`\`\`
<div>unsafe outside</div>
\`\`\`
<span>also safe in code block</span>
\`\`\``;
const expected = `\`\`\`
<div>safe in code block</div>
\`\`\`
\`\`\`html
<div>unsafe outside</div>
\`\`\`
\`\`\`
<span>also safe in code block</span>
\`\`\``;
expect(wrapHTMLInCodeBlock(input)).toBe(expected);
});
});
});
});