main fe6ee00

security-tests.ts RawHistoryFolder

1import { formatText, validateMessageContent, createGradientText } from './text-formatter';
2import { validateTextStyle, DEFAULT_TEXT_STYLE, type TextStyle } from '../types/text-formatting';
3
4// XSS Test Vectors
5const XSS_TEST_VECTORS = [
6	// Script injection
7	'<script>alert("XSS")</script>',
8	'<script src="malicious.js"></script>',
9	'<img src="x" onerror="alert(\'XSS\')">',
10	'<svg onload="alert(\'XSS\')">',
11	'<iframe src="javascript:alert(\'XSS\')"></iframe>',
12
13	// Event handlers
14	'<div onclick="alert(\'XSS\')">Click me</div>',
15	'<input onfocus="alert(\'XSS\')" autofocus>',
16	'<body onload="alert(\'XSS\')">',
17	'<a href="javascript:alert(\'XSS\')">Link</a>',
18
19	// CSS injection
20	'<style>body{background:url("javascript:alert(\'XSS\')")}</style>',
21	'<div style="background-image:url(javascript:alert(\'XSS\'))">',
22	'<div style="expression(alert(\'XSS\'))">',
23
24	// Data URIs
25	'<img src="data:text/html,<script>alert(\'XSS\')</script>">',
26	'<iframe src="data:text/html,<script>alert(\'XSS\')</script>">',
27
28	// HTML injection
29	'<object data="malicious.swf">',
30	'<embed src="malicious.swf">',
31	'<applet code="malicious.class">',
32
33	// BBCode injection attempts
34	'[script]alert("XSS")[/script]',
35	'[img=javascript:alert("XSS")]',
36	'[url=javascript:alert("XSS")]Link[/url]',
37
38	// Markdown injection attempts
39	'![XSS](javascript:alert("XSS"))',
40	'[XSS](javascript:alert("XSS"))',
41
42	// Unicode and encoding bypasses
43	'<script>alert(&#34;XSS&#34;)</script>',
44	'<script>alert(String.fromCharCode(88,83,83))</script>',
45	'<img src="x" onerror="&#97;&#108;&#101;&#114;&#116;&#40;&#39;&#88;&#83;&#83;&#39;&#41;">',
46
47	// CSS expression injection
48	'<div style="width:expression(alert(\'XSS\'))">',
49	'<div style="background:url(vbscript:alert(\'XSS\'))">',
50
51	// Mixed content attacks
52	'Normal text <script>alert("XSS")</script> more text',
53	'[b]Bold <script>alert("XSS")</script> text[/b]',
54	'**Bold <img src="x" onerror="alert(\'XSS\')"> text**'
55];
56
57// CSS Injection Test Vectors
58const CSS_INJECTION_VECTORS = [
59	'color: red; background: url(javascript:alert("XSS"))',
60	'font-family: "Arial"; } body { background: red; .fake {',
61	'font-size: expression(alert("XSS"))',
62	'background: url("data:text/html,<script>alert(\'XSS\')</script>")',
63	'color: red; } .injected { background: red',
64	'@import url("malicious.css")',
65	'behavior: url("malicious.htc")'
66];
67
68// Test Results Interface
69interface SecurityTestResult {
70	input: string;
71	output: string;
72	passed: boolean;
73	vulnerabilities: string[];
74	testType: string;
75}
76
77interface SecurityTestSuite {
78	xssTests: SecurityTestResult[];
79	cssInjectionTests: SecurityTestResult[];
80	validationTests: SecurityTestResult[];
81	gradientTests: SecurityTestResult[];
82	summary: {
83		totalTests: number;
84		passed: number;
85		failed: number;
86		vulnerabilities: string[];
87	};
88}
89
90// Check if output contains dangerous patterns
91function detectVulnerabilities(output: string): string[] {
92	const vulnerabilities: string[] = [];
93
94	// Script tags
95	if (/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi.test(output)) {
96		vulnerabilities.push('Script tag injection');
97	}
98
99	// Event handlers
100	if (/on\w+\s*=/gi.test(output)) {
101		vulnerabilities.push('Event handler injection');
102	}
103
104	// JavaScript URLs
105	if (/javascript\s*:/gi.test(output)) {
106		vulnerabilities.push('JavaScript URL injection');
107	}
108
109	// Data URLs with HTML/JS
110	if (/data\s*:\s*text\/html/gi.test(output)) {
111		vulnerabilities.push('Data URL HTML injection');
112	}
113
114	// CSS expressions
115	if (/expression\s*\(/gi.test(output)) {
116		vulnerabilities.push('CSS expression injection');
117	}
118
119	// Dangerous CSS URLs
120	if (/url\s*\(\s*['"]*javascript:/gi.test(output)) {
121		vulnerabilities.push('CSS JavaScript URL injection');
122	}
123
124	// Iframe with dangerous src
125	if (/<iframe[^>]*src\s*=\s*['"]*(?:javascript:|data:)/gi.test(output)) {
126		vulnerabilities.push('Dangerous iframe injection');
127	}
128
129	// Object/embed tags
130	if (/<(?:object|embed|applet)\b/gi.test(output)) {
131		vulnerabilities.push('Dangerous object/embed tag');
132	}
133
134	// Style tag injection
135	if (/<style\b[^<]*(?:(?!<\/style>)<[^<]*)*<\/style>/gi.test(output)) {
136		vulnerabilities.push('Style tag injection');
137	}
138
139	return vulnerabilities;
140}
141
142// Run XSS tests
143async function runXSSTests(): Promise<SecurityTestResult[]> {
144	const results: SecurityTestResult[] = [];
145
146	for (const vector of XSS_TEST_VECTORS) {
147		try {
148			const output = await formatText(vector, DEFAULT_TEXT_STYLE, true);
149			const vulnerabilities = detectVulnerabilities(output);
150
151			results.push({
152				input: vector,
153				output,
154				passed: vulnerabilities.length === 0,
155				vulnerabilities,
156				testType: 'XSS'
157			});
158		} catch (error) {
159			results.push({
160				input: vector,
161				output: `Error: ${error}`,
162				passed: true, // Errors are acceptable for malicious input
163				vulnerabilities: [],
164				testType: 'XSS'
165			});
166		}
167	}
168
169	return results;
170}
171
172// Run CSS injection tests
173async function runCSSInjectionTests(): Promise<SecurityTestResult[]> {
174	const results: SecurityTestResult[] = [];
175
176	for (const vector of CSS_INJECTION_VECTORS) {
177		try {
178			const testStyle = { ...DEFAULT_TEXT_STYLE, color: 'red' };
179			const output = await formatText(`Test with style`, testStyle, true);
180			const vulnerabilities = detectVulnerabilities(output);
181
182			results.push({
183				input: vector,
184				output,
185				passed: vulnerabilities.length === 0,
186				vulnerabilities,
187				testType: 'CSS Injection'
188			});
189		} catch (error) {
190			results.push({
191				input: vector,
192				output: `Error: ${error}`,
193				passed: true,
194				vulnerabilities: [],
195				testType: 'CSS Injection'
196			});
197		}
198	}
199
200	return results;
201}
202
203// Run validation tests
204function runValidationTests(): SecurityTestResult[] {
205	const results: SecurityTestResult[] = [];
206
207	const testCases = [
208		'Normal text',
209		'Text with <script>alert("XSS")</script>',
210		'Very long text '.repeat(200), // Test length limits
211		'', // Empty text
212		'Text with\nnewlines\nand\ttabs',
213		'Special chars: <>&"\'`',
214		'Unicode: 你好世界 🌍 🎉',
215		'Mixed: Normal **bold** and <script>alert("XSS")</script>'
216	];
217
218	for (const testCase of testCases) {
219		const validation = validateMessageContent(testCase);
220		const vulnerabilities = detectVulnerabilities(validation.sanitizedContent);
221
222		results.push({
223			input: testCase,
224			output: validation.sanitizedContent,
225			passed: validation.isValid && vulnerabilities.length === 0,
226			vulnerabilities: [...validation.errors, ...vulnerabilities],
227			testType: 'Validation'
228		});
229	}
230
231	return results;
232}
233
234// Run gradient text security tests
235function runGradientTests(): SecurityTestResult[] {
236	const results: SecurityTestResult[] = [];
237
238	const testCases = [
239		'Normal gradient text',
240		'<script>alert("XSS")</script>',
241		'Text with "quotes" and \'apostrophes\'',
242		'Very long gradient text '.repeat(50),
243		'Special chars: <>&\'"',
244		''
245	];
246
247	const testColors = ['#FF0000', '#00FF00', '#0000FF'];
248
249	for (const testCase of testCases) {
250		try {
251			const output = createGradientText(testCase, testColors);
252			const vulnerabilities = detectVulnerabilities(output);
253
254			results.push({
255				input: testCase,
256				output,
257				passed: vulnerabilities.length === 0,
258				vulnerabilities,
259				testType: 'Gradient'
260			});
261		} catch (error) {
262			results.push({
263				input: testCase,
264				output: `Error: ${error}`,
265				passed: true,
266				vulnerabilities: [],
267				testType: 'Gradient'
268			});
269		}
270	}
271
272	return results;
273}
274
275// Run complete security test suite
276export async function runSecurityTestSuite(): Promise<SecurityTestSuite> {
277	console.log('🔒 Running comprehensive security test suite...');
278
279	const xssTests = await runXSSTests();
280	const cssInjectionTests = await runCSSInjectionTests();
281	const validationTests = runValidationTests();
282	const gradientTests = runGradientTests();
283
284	const allTests = [...xssTests, ...cssInjectionTests, ...validationTests, ...gradientTests];
285	const passed = allTests.filter((test) => test.passed).length;
286	const failed = allTests.length - passed;
287
288	// Collect all unique vulnerabilities
289	const allVulnerabilities = new Set<string>();
290	allTests.forEach((test) => {
291		test.vulnerabilities.forEach((vuln) => allVulnerabilities.add(vuln));
292	});
293
294	const results: SecurityTestSuite = {
295		xssTests,
296		cssInjectionTests,
297		validationTests,
298		gradientTests,
299		summary: {
300			totalTests: allTests.length,
301			passed,
302			failed,
303			vulnerabilities: Array.from(allVulnerabilities)
304		}
305	};
306
307	console.log(`✅ Security tests completed:`);
308	console.log(`   Total tests: ${results.summary.totalTests}`);
309	console.log(`   Passed: ${results.summary.passed}`);
310	console.log(`   Failed: ${results.summary.failed}`);
311
312	if (results.summary.vulnerabilities.length > 0) {
313		console.warn(`⚠️  Vulnerabilities detected:`, results.summary.vulnerabilities);
314	} else {
315		console.log(`🛡️  No vulnerabilities detected!`);
316	}
317
318	return results;
319}
320
321// Quick security check function
322export async function quickSecurityCheck(input: string): Promise<boolean> {
323	try {
324		const output = await formatText(input, DEFAULT_TEXT_STYLE, true);
325		const vulnerabilities = detectVulnerabilities(output);
326		return vulnerabilities.length === 0;
327	} catch {
328		// Errors on malicious input are acceptable
329		return true;
330	}
331}
332
333// Style validation tests
334export function testStyleValidation(): boolean {
335	const testCases = [
336		// Valid styles
337		{ fontFamily: 'tahoma', fontSize: 14, color: 'red', bold: true },
338		{ fontFamily: 'verdana', fontSize: 16, color: 'blue', italic: true },
339
340		// Invalid styles (should be sanitized)
341		{ fontFamily: 'malicious-font', fontSize: 999, color: 'invalid-color' },
342		{ fontFamily: 'tahoma', fontSize: -10, color: 'red' },
343		{ fontFamily: 'tahoma', fontSize: 'invalid' as unknown as number, color: 'red' }
344	];
345
346	for (const testCase of testCases) {
347		try {
348			const validated = validateTextStyle(testCase as Partial<TextStyle>);
349
350			// Check that validated style has safe values
351			if (!validated.fontFamily || validated.fontSize < 8 || validated.fontSize > 72) {
352				console.error('Style validation failed for:', testCase);
353				return false;
354			}
355		} catch (error) {
356			console.error('Style validation error:', error);
357			return false;
358		}
359	}
360
361	return true;
362}
363
364// Export test utilities
365export {
366	XSS_TEST_VECTORS,
367	CSS_INJECTION_VECTORS,
368	detectVulnerabilities,
369	type SecurityTestResult,
370	type SecurityTestSuite
371};

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 3

Tags 9

↑↓ moveenter openesc close