@heliosgraphics/utils 6.0.0-alpha.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,558 @@
1
+ import { it, describe, expect } from "vitest"
2
+ import { sanitizeText } from "./sanitize"
3
+
4
+ describe("sanitize", () => {
5
+ describe("sanitizeText", () => {
6
+ const TEXT_1 = "a <b onClick=\"alert('hee hee')\">basic</b> tag"
7
+
8
+ it("removes an onclick tag", () => expect(sanitizeText(TEXT_1)).toEqual("a <b>basic</b> tag"))
9
+ it("returns empty for undefined", () => expect(sanitizeText(undefined)).toEqual(""))
10
+
11
+ // XSS Prevention Tests
12
+ it("removes script tags completely", () => {
13
+ const malicious = 'Hello <script>alert("XSS")</script> World'
14
+ expect(sanitizeText(malicious)).toEqual("Hello World")
15
+ })
16
+
17
+ it("removes script tags with attributes", () => {
18
+ const malicious = 'Hello <script type="text/javascript">alert("XSS")</script> World'
19
+ expect(sanitizeText(malicious)).toEqual("Hello World")
20
+ })
21
+
22
+ it("removes style tags", () => {
23
+ const malicious = "Hello <style>body{background:red}</style> World"
24
+ expect(sanitizeText(malicious)).toEqual("Hello World")
25
+ })
26
+
27
+ it("removes all event handlers", () => {
28
+ const tests = [
29
+ { input: '<div onclick="alert(1)">test</div>', expected: "<div>test</div>" },
30
+ { input: '<img onload="alert(1)" src="x">', expected: "" },
31
+ { input: '<body onpageshow="alert(1)">test</body>', expected: "test" },
32
+ { input: '<div onmouseover="evil()">hover</div>', expected: "<div>hover</div>" },
33
+ ]
34
+ tests.forEach(({ input, expected }) => {
35
+ expect(sanitizeText(input)).toEqual(expected)
36
+ })
37
+ })
38
+
39
+ it("removes javascript: URLs", () => {
40
+ const malicious = '<a href="javascript:alert(1)">click</a>'
41
+ expect(sanitizeText(malicious)).toEqual("click")
42
+ })
43
+
44
+ it("removes data: URLs", () => {
45
+ const malicious = '<img src="data:text/html,<script>alert(1)</script>">'
46
+ expect(sanitizeText(malicious)).toEqual("")
47
+ })
48
+
49
+ it("removes vbscript: URLs", () => {
50
+ const malicious = '<a href="vbscript:msgbox(1)">click</a>'
51
+ expect(sanitizeText(malicious)).toEqual("click")
52
+ })
53
+
54
+ // Advanced encoding bypass tests
55
+ it("handles Unicode escape sequences", () => {
56
+ const tests = [
57
+ { input: "<script>\\u0061\\u006c\\u0065\\u0072\\u0074(1)</script>", expected: "" },
58
+ { input: '<img src="\\x6a\\x61\\x76\\x61\\x73\\x63\\x72\\x69\\x70\\x74:alert(1)">', expected: "" },
59
+ { input: "Hello\\u003cscript\\u003ealert(1)\\u003c/script\\u003e", expected: "Hello" },
60
+ ]
61
+ tests.forEach(({ input, expected }) => {
62
+ expect(sanitizeText(input)).toEqual(expected)
63
+ })
64
+ })
65
+
66
+ it("handles URL encoding bypasses", () => {
67
+ const tests = [
68
+ { input: '<img src="%6a%61%76%61%73%63%72%69%70%74:alert(1)">', expected: "" },
69
+ {
70
+ input:
71
+ '<a href="%25%36%61%25%36%31%25%37%36%25%36%31%25%37%33%25%36%33%25%37%32%25%36%39%25%37%30%25%37%34:alert(1)">link</a>',
72
+ expected: "link",
73
+ },
74
+ { input: "test%3cscript%3ealert(1)%3c/script%3e", expected: "test" },
75
+ ]
76
+ tests.forEach(({ input, expected }) => {
77
+ expect(sanitizeText(input)).toEqual(expected)
78
+ })
79
+ })
80
+
81
+ it("handles CSS escape sequences", () => {
82
+ const tests = [
83
+ {
84
+ input: '<div style="\\6a \\61 \\76 \\61 \\73 \\63 \\72 \\69 \\70 \\74 :alert(1)">test</div>',
85
+ expected: "<div>test</div>",
86
+ },
87
+ { input: "<style>\\41 {color:red}</style>", expected: "" },
88
+ { input: "\\6a\\61\\76\\61\\73\\63\\72\\69\\70\\74:alert(1)", expected: ":(1)" },
89
+ ]
90
+ tests.forEach(({ input, expected }) => {
91
+ expect(sanitizeText(input)).toEqual(expected)
92
+ })
93
+ })
94
+
95
+ it("handles zero-width and control characters", () => {
96
+ const tests = [
97
+ { input: "<script>ale\u200Brt(1)</script>", expected: "" },
98
+ { input: "test\u202Ascript\u202C", expected: "testscript" },
99
+ { input: "hello\uFEFFworld", expected: "helloworld" },
100
+ { input: "test\u0000\u0001\u0002control", expected: "testcontrol" },
101
+ ]
102
+ tests.forEach(({ input, expected }) => {
103
+ expect(sanitizeText(input)).toEqual(expected)
104
+ })
105
+ })
106
+
107
+ it("handles Unicode normalization attacks", () => {
108
+ const tests = [
109
+ { input: "<scri\u0070t>alert(1)</script>", expected: "" },
110
+ { input: "java\u0073cript:alert(1)", expected: "(1)" },
111
+ ]
112
+ tests.forEach(({ input, expected }) => {
113
+ expect(sanitizeText(input)).toEqual(expected)
114
+ })
115
+ })
116
+
117
+ it("handles advanced protocol bypasses", () => {
118
+ const tests = [
119
+ { input: '<a href="java\u00A0script:alert(1)">link</a>', expected: "link" },
120
+ { input: '<img src="javascript:/*comment*/alert(1)">', expected: "" },
121
+ { input: '<a href="javascript://anything\nalert(1)">link</a>', expected: "link" },
122
+ { input: '<img src="javascript\\:alert(1)">', expected: "" },
123
+ { input: '<a href="j\ta\nv\ra\fscript:alert(1)">link</a>', expected: "link" },
124
+ { input: '<img src="livescript:alert(1)">', expected: "" },
125
+ { input: '<a href="mocha:alert(1)">link</a>', expected: "link" },
126
+ ]
127
+ tests.forEach(({ input, expected }) => {
128
+ expect(sanitizeText(input)).toEqual(expected)
129
+ })
130
+ })
131
+
132
+ it("handles advanced dangerous function patterns", () => {
133
+ const tests = [
134
+ { input: "setTimeout(alert, 1)", expected: "(, 1)" },
135
+ { input: 'Function("alert(1)")()', expected: '("(1)")()' },
136
+ { input: "globalThis.alert(1)", expected: "(1)" },
137
+ { input: 'window["constructor"]', expected: "" },
138
+ { input: "`${alert(1)}`", expected: "" },
139
+ { input: "String.fromCharCode(97,108,101,114,116)", expected: "(97,108,101,114,116)" },
140
+ { input: 'atob("YWxlcnQ=")', expected: '("YWxlcnQ=")' },
141
+ { input: 'decodeURIComponent("%61%6c%65%72%74")', expected: '("%61%6c%65%72%74")' },
142
+ ]
143
+ tests.forEach(({ input, expected }) => {
144
+ expect(sanitizeText(input)).toEqual(expected)
145
+ })
146
+ })
147
+
148
+ it("handles hex encoding with leading zeros", () => {
149
+ const tests = [
150
+ { input: "&#x00000061;lert(1)", expected: "(1)" },
151
+ { input: "&#0000097;lert(1)", expected: "(1)" },
152
+ ]
153
+ tests.forEach(({ input, expected }) => {
154
+ expect(sanitizeText(input)).toEqual(expected)
155
+ })
156
+ })
157
+
158
+ it("handles additional HTML entities", () => {
159
+ const tests = [
160
+ { input: "&lpar;alert&rpar;&semi;", expected: "();" },
161
+ { input: "&colon;&sol;&sol;comment&NewLine;alert&lpar;1&rpar;", expected: "://comment\n(1)" },
162
+ { input: "test&Tab;value&period;method&lpar;&rpar;", expected: "test\tvalue.method()" },
163
+ ]
164
+ tests.forEach(({ input, expected }) => {
165
+ const result = sanitizeText(input)
166
+ // Since 'alert' gets stripped by the function, adjust expectations
167
+ if (input.includes("alert")) {
168
+ expect(result).toBe(expected.replace("alert", ""))
169
+ } else {
170
+ expect(result).toBe(expected)
171
+ }
172
+ })
173
+ })
174
+
175
+ it("removes dangerous tags completely", () => {
176
+ const dangerous = [
177
+ '<iframe src="javascript:alert(1)"></iframe>',
178
+ '<object data="data:text/html,<script>alert(1)</script>"></object>',
179
+ '<embed src="javascript:alert(1)">',
180
+ '<form><input type="submit" formaction="javascript:alert(1)"></form>',
181
+ '<video><source onerror="alert(1)"></video>',
182
+ ]
183
+ dangerous.forEach((tag) => {
184
+ const result = sanitizeText(tag)
185
+ expect(result).not.toContain("<iframe")
186
+ expect(result).not.toContain("<object")
187
+ expect(result).not.toContain("<embed")
188
+ expect(result).not.toContain("<form")
189
+ expect(result).not.toContain("<video")
190
+ expect(result).not.toContain("<input")
191
+ expect(result).not.toContain("<source")
192
+ })
193
+ })
194
+
195
+ it("preserves safe HTML tags", () => {
196
+ const safe = "<p>Hello <b>bold</b> and <i>italic</i> and <em>emphasis</em></p>"
197
+ expect(sanitizeText(safe)).toEqual(safe)
198
+ })
199
+
200
+ it("preserves allowed attributes", () => {
201
+ const withAttrs = '<div class="safe" id="test" title="tooltip">content</div>'
202
+ expect(sanitizeText(withAttrs)).toEqual(withAttrs)
203
+ })
204
+
205
+ it("removes disallowed attributes", () => {
206
+ const withBadAttrs = '<div class="safe" onclick="alert(1)" data-bad="evil">content</div>'
207
+ expect(sanitizeText(withBadAttrs)).toEqual('<div class="safe">content</div>')
208
+ })
209
+
210
+ it("handles complex XSS attempts", () => {
211
+ const complex = `
212
+ <img src=x onerror=alert(1)>
213
+ <svg onload=alert(1)>
214
+ <details ontoggle=alert(1)>
215
+ <iframe srcdoc="<script>alert(1)</script>">
216
+ <object data="javascript:alert(1)">
217
+ <script>alert('XSS')</script>
218
+ <div style="background: url(javascript:alert(1))">
219
+ `
220
+ const result = sanitizeText(complex)
221
+ expect(result).not.toContain("alert")
222
+ expect(result).not.toContain("javascript")
223
+ expect(result).not.toContain("onerror")
224
+ expect(result).not.toContain("onload")
225
+ expect(result).not.toContain("<script")
226
+ expect(result).not.toContain("<iframe")
227
+ expect(result).not.toContain("<object")
228
+ })
229
+
230
+ it("handles edge cases safely", () => {
231
+ expect(sanitizeText("")).toEqual("")
232
+ expect(sanitizeText(null as unknown as string)).toEqual("")
233
+ expect(sanitizeText(" ")).toEqual("")
234
+ expect(sanitizeText("plain text")).toEqual("plain text")
235
+ })
236
+
237
+ // Advanced XSS Attack Vectors - Comprehensive Coverage
238
+ it("blocks HTML entity encoded attacks", () => {
239
+ const attacks = [
240
+ // HTML entity encoding
241
+ "<img src=x onerror=&#97;&#108;&#101;&#114;&#116;&#40;&#49;&#41;>",
242
+ '<div onclick="&#97;&#108;&#101;&#114;&#116;&#40;&#49;&#41;">click</div>',
243
+ // Hex encoding
244
+ "<img src=x onerror=&#x61;&#x6c;&#x65;&#x72;&#x74;&#x28;&#x31;&#x29;>",
245
+ // Mixed encoding
246
+ "<img src=x onerror=a&#108;ert(1)>",
247
+ ]
248
+ attacks.forEach((attack) => {
249
+ const result = sanitizeText(attack)
250
+ expect(result).not.toContain("alert")
251
+ expect(result).not.toContain("onerror")
252
+ expect(result).not.toContain("&#")
253
+ })
254
+ })
255
+
256
+ it("blocks case variation attacks", () => {
257
+ const attacks = [
258
+ "<SCRIPT>alert(1)</SCRIPT>",
259
+ "<ScRiPt>alert(1)</ScRiPt>",
260
+ "<IMG SRC=x ONERROR=alert(1)>",
261
+ '<DiV OnClick="alert(1)">test</DiV>',
262
+ '<IFrAmE src="javascript:alert(1)"></IFrAmE>',
263
+ ]
264
+ attacks.forEach((attack) => {
265
+ const result = sanitizeText(attack)
266
+ expect(result).not.toContain("alert")
267
+ expect(result).not.toContain("<script")
268
+ expect(result).not.toContain("<iframe")
269
+ expect(result).not.toMatch(/on\w+=/i)
270
+ })
271
+ })
272
+
273
+ it("blocks attribute quote variations", () => {
274
+ const attacks = [
275
+ // No quotes
276
+ "<img src=x onerror=alert(1)>",
277
+ // Mixed quotes
278
+ "<div onclick='alert(1)'>test</div>",
279
+ '<div onclick="alert(1)">test</div>',
280
+ // Backticks (less common but possible)
281
+ "<div onclick=`alert(1)`>test</div>",
282
+ // Spaces around equals
283
+ "<img src = x onerror = alert(1)>",
284
+ // Tab characters
285
+ "<img onclick = alert(1)>test</img>",
286
+ ]
287
+ attacks.forEach((attack) => {
288
+ const result = sanitizeText(attack)
289
+ expect(result).not.toContain("alert")
290
+ expect(result).not.toMatch(/on\w+\s*=/i)
291
+ })
292
+ })
293
+
294
+ it("blocks malformed HTML attacks", () => {
295
+ const attacks = [
296
+ // Unclosed tags
297
+ "<script>alert(1)",
298
+ "<img src=x onerror=alert(1)",
299
+ // Nested quotes
300
+ '<div onclick="alert(\\"XSS\\")">test</div>',
301
+ // Multiple attributes without spaces
302
+ "<img src=x onerror=alert(1)onload=alert(2)>",
303
+ // Broken attribute structure
304
+ "<div onclick=alert(1) onclick=alert(2)>test</div>",
305
+ // Comments in attributes
306
+ "<div on<!--comment-->click=alert(1)>test</div>",
307
+ // Null bytes (if they somehow make it through)
308
+ "<script>alert\\x00(1)</script>",
309
+ // Line breaks and tabs in dangerous content
310
+ "<script>\nalert(1)\n</script>",
311
+ "<img src=x\nonerror=alert(1)>",
312
+ ]
313
+ attacks.forEach((attack) => {
314
+ const result = sanitizeText(attack)
315
+ expect(result).not.toContain("alert")
316
+ expect(result).not.toContain("<script")
317
+ expect(result).not.toMatch(/on\w+\s*=/i)
318
+ })
319
+ })
320
+
321
+ it("blocks protocol-based attacks comprehensively", () => {
322
+ const protocols = [
323
+ "javascript:",
324
+ "JAVASCRIPT:",
325
+ "JaVaScRiPt:",
326
+ "data:",
327
+ "DATA:",
328
+ "vbscript:",
329
+ "VBSCRIPT:",
330
+ "VbScRiPt:",
331
+ "file:",
332
+ "FILE:",
333
+ // Protocol with encoding
334
+ "java&#115;cript:",
335
+ "java&#x73;cript:",
336
+ // Protocol with spaces/tabs
337
+ "java script:",
338
+ "java\tscript:",
339
+ "java\nscript:",
340
+ // Protocol variations
341
+ "javascript&colon;",
342
+ "javascript&#58;",
343
+ "javascript&#x3a;",
344
+ ]
345
+
346
+ protocols.forEach((protocol) => {
347
+ const attacks = [
348
+ `<a href="${protocol}alert(1)">click</a>`,
349
+ `<img src="${protocol}alert(1)">`,
350
+ `<iframe src="${protocol}alert(1)"></iframe>`,
351
+ `<object data="${protocol}alert(1)"></object>`,
352
+ ]
353
+
354
+ attacks.forEach((attack) => {
355
+ const result = sanitizeText(attack)
356
+ expect(result).not.toContain("alert")
357
+ expect(result).not.toContain("javascript")
358
+ expect(result).not.toContain("vbscript")
359
+ expect(result).not.toContain("data:")
360
+ expect(result).not.toContain("<iframe")
361
+ expect(result).not.toContain("<object")
362
+ })
363
+ })
364
+ })
365
+
366
+ it("blocks SVG-based XSS attacks", () => {
367
+ const svgAttacks = [
368
+ '<svg onload="alert(1)">',
369
+ "<svg><script>alert(1)</script></svg>",
370
+ '<svg><g onload="alert(1)"></g></svg>',
371
+ '<svg><animateTransform onbegin="alert(1)"></animateTransform></svg>',
372
+ '<svg><text onactivate="alert(1)">XSS</text></svg>',
373
+ '<svg><use href="javascript:alert(1)"></use></svg>',
374
+ ]
375
+
376
+ svgAttacks.forEach((attack) => {
377
+ const result = sanitizeText(attack)
378
+ expect(result).not.toContain("alert")
379
+ expect(result).not.toContain("<svg")
380
+ expect(result).not.toContain("<script")
381
+ expect(result).not.toMatch(/on\w+=/i)
382
+ })
383
+ })
384
+
385
+ it("blocks CSS-based XSS attempts", () => {
386
+ const cssAttacks = [
387
+ '<div style="background:url(javascript:alert(1))">test</div>',
388
+ '<div style="background-image:url(data:text/html,<script>alert(1)</script>)">test</div>',
389
+ "<style>body{background:url(javascript:alert(1))}</style>",
390
+ '<link rel="stylesheet" href="javascript:alert(1)">',
391
+ '<div style="width:expression(alert(1))">test</div>', // IE specific
392
+ '<div style="xss:expression(alert(1))">test</div>',
393
+ ]
394
+
395
+ cssAttacks.forEach((attack) => {
396
+ const result = sanitizeText(attack)
397
+ expect(result).not.toContain("alert")
398
+ expect(result).not.toContain("javascript")
399
+ expect(result).not.toContain("<style")
400
+ expect(result).not.toContain("<link")
401
+ expect(result).not.toContain("expression")
402
+ })
403
+ })
404
+
405
+ it("blocks meta and link tag attacks", () => {
406
+ const metaAttacks = [
407
+ '<meta http-equiv="refresh" content="0;url=javascript:alert(1)">',
408
+ '<link rel="import" href="javascript:alert(1)">',
409
+ '<meta charset="x" http-equiv="refresh" content="1;javascript:alert(1)">',
410
+ '<base href="javascript:alert(1)">',
411
+ '<isindex action="javascript:alert(1)">',
412
+ '<marquee onstart="alert(1)">XSS</marquee>',
413
+ ]
414
+
415
+ metaAttacks.forEach((attack) => {
416
+ const result = sanitizeText(attack)
417
+ expect(result).not.toContain("alert")
418
+ expect(result).not.toContain("javascript")
419
+ expect(result).not.toContain("<meta")
420
+ expect(result).not.toContain("<link")
421
+ expect(result).not.toContain("<base")
422
+ expect(result).not.toContain("<isindex")
423
+ expect(result).not.toContain("<marquee")
424
+ })
425
+ })
426
+
427
+ it("blocks form-based XSS attacks", () => {
428
+ const formAttacks = [
429
+ '<form action="javascript:alert(1)"><input type="submit"></form>',
430
+ '<input type="image" src="javascript:alert(1)">',
431
+ '<input type="submit" formaction="javascript:alert(1)">',
432
+ '<button formaction="javascript:alert(1)">click</button>',
433
+ '<select onchange="alert(1)"><option>test</option></select>',
434
+ '<textarea onfocus="alert(1)">content</textarea>',
435
+ '<keygen onfocus="alert(1)">',
436
+ ]
437
+
438
+ formAttacks.forEach((attack) => {
439
+ const result = sanitizeText(attack)
440
+ expect(result).not.toContain("alert")
441
+ expect(result).not.toContain("javascript")
442
+ expect(result).not.toContain("<form")
443
+ expect(result).not.toContain("<input")
444
+ expect(result).not.toContain("<button")
445
+ expect(result).not.toContain("<select")
446
+ expect(result).not.toContain("<textarea")
447
+ expect(result).not.toContain("<keygen")
448
+ })
449
+ })
450
+
451
+ it("blocks multimedia-based XSS attacks", () => {
452
+ const mediaAttacks = [
453
+ '<audio src="javascript:alert(1)">',
454
+ '<video src="javascript:alert(1)">',
455
+ '<audio><source src="javascript:alert(1)"></audio>',
456
+ '<video poster="javascript:alert(1)">',
457
+ '<track kind="captions" src="javascript:alert(1)">',
458
+ '<audio oncanplay="alert(1)">',
459
+ '<video onloadstart="alert(1)">',
460
+ '<source media="all and (max-width:0) and (pointer: coarse)" onerror="alert(1)">',
461
+ ]
462
+
463
+ mediaAttacks.forEach((attack) => {
464
+ const result = sanitizeText(attack)
465
+ expect(result).not.toContain("alert")
466
+ expect(result).not.toContain("javascript")
467
+ expect(result).not.toContain("<audio")
468
+ expect(result).not.toContain("<video")
469
+ expect(result).not.toContain("<source")
470
+ expect(result).not.toContain("<track")
471
+ })
472
+ })
473
+
474
+ it("blocks template and web component attacks", () => {
475
+ const templateAttacks = [
476
+ "<template><script>alert(1)</script></template>",
477
+ '<slot onclick="alert(1)">content</slot>',
478
+ '<custom-element onclick="alert(1)">test</custom-element>',
479
+ '<template id="x"><img src=x onerror="alert(1)"></template>',
480
+ "<shadow-root><script>alert(1)</script></shadow-root>",
481
+ ]
482
+
483
+ templateAttacks.forEach((attack) => {
484
+ const result = sanitizeText(attack)
485
+ expect(result).not.toContain("alert")
486
+ expect(result).not.toContain("<script")
487
+ expect(result).not.toContain("<template")
488
+ expect(result).not.toContain("<slot")
489
+ expect(result).not.toMatch(/on\w+=/i)
490
+ })
491
+ })
492
+
493
+ it("blocks whitespace and unicode evasion attempts", () => {
494
+ const evasionAttacks = [
495
+ // Various whitespace characters
496
+ "<img\tsrc=x\tonerror=alert(1)>",
497
+ "<img\nsrc=x\nonerror=alert(1)>",
498
+ "<img\rsrc=x\ronerror=alert(1)>",
499
+ "<img\f src=x\f onerror=alert(1)>",
500
+ // Zero-width characters (if they somehow make it through)
501
+ "<img src=x onerror=al\u200Bert(1)>",
502
+ "<script>al\u200Bert(1)</script>",
503
+ // Unicode normalization attacks
504
+ "<img src=x onerror=\u0061\u006C\u0065\u0072\u0074(1)>",
505
+ // Mixed unicode and ASCII
506
+ "<script>\u0061lert(1)</script>",
507
+ ]
508
+
509
+ evasionAttacks.forEach((attack) => {
510
+ const result = sanitizeText(attack)
511
+ expect(result).not.toContain("alert")
512
+ expect(result).not.toContain("<script")
513
+ expect(result).not.toMatch(/on\w+\s*=/i)
514
+ })
515
+ })
516
+
517
+ it("blocks OWASP Top 10 XSS vectors", () => {
518
+ const owaspVectors = [
519
+ // OWASP XSS Filter Evasion Cheat Sheet vectors
520
+ "<IMG SRC=javascript:alert(String.fromCharCode(88,83,83))>",
521
+ '<IMG SRC=javascript:alert("XSS")>',
522
+ "<IMG SRC=javascript:alert('XSS')>",
523
+ '<IMG """><SCRIPT>alert("XSS")</SCRIPT>">',
524
+ "<IMG SRC=javascript:alert(String.fromCharCode(88,83,83))>",
525
+ "<IFRAME SRC=\"javascript:alert('XSS');\"></IFRAME>",
526
+ "<BODY BACKGROUND=\"javascript:alert('XSS')\">",
527
+ "<BODY ONLOAD=alert('XSS')>",
528
+ "<IMG LOWSRC=\"javascript:alert('XSS')\">",
529
+ "<BGSOUND SRC=\"javascript:alert('XSS');\">",
530
+ "<BR SIZE=\"&{alert('XSS')}\">",
531
+ "<LAYER SRC=\"javascript:alert('XSS')\"></LAYER>",
532
+ '<LINK REL="stylesheet" HREF="javascript:alert(\'XSS\');">',
533
+ "<DIV STYLE=\"background-image:\\0075\\0072\\006C\\0028'\\006a\\0061\\0076\\0061\\0073\\0063\\0072\\0069\\0070\\0074\\003a\\0061\\006c\\0065\\0072\\0074\\0028.1027\\0058.1053\\0053\\0027\\0029'\\0029\">",
534
+ "<STYLE>@im\\port'\\ja\\vasc\\ript:alert(\"XSS\")';</STYLE>",
535
+ '<XSS STYLE="behavior: url(xss.htc);">',
536
+ '<STYLE>BODY{-moz-binding:url("http://ha.ckers.org/xssmoz.xml#xss")}</STYLE>',
537
+ ]
538
+
539
+ owaspVectors.forEach((vector) => {
540
+ const result = sanitizeText(vector)
541
+ expect(result).not.toContain("alert")
542
+ expect(result).not.toContain("javascript")
543
+ expect(result).not.toContain("<script")
544
+ expect(result).not.toContain("<iframe")
545
+ expect(result).not.toContain("<style")
546
+ expect(result).not.toContain("<link")
547
+ expect(result).not.toMatch(/on\w+\s*=/i)
548
+ })
549
+ })
550
+
551
+ it("handles normalize function edge case", () => {
552
+ const inputWithSpecialChars = "ABC<script>alert"
553
+ const result = sanitizeText(inputWithSpecialChars)
554
+ expect(result).not.toContain("script")
555
+ expect(result).not.toContain("alert")
556
+ })
557
+ })
558
+ })