@heliosgraphics/utils 6.0.0-alpha.10 → 6.0.0-alpha.12
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.
- package/classnames.ts +2 -2
- package/clipboard.ts +7 -3
- package/colors.ts +14 -7
- package/debounce.ts +21 -7
- package/equals.ts +22 -42
- package/index.ts +1 -1
- package/package.json +22 -4
- package/sanitize.ts +1 -1
- package/strings.ts +1 -1
- package/throttle.ts +16 -5
- package/uuid.ts +6 -7
- package/validations.ts +1 -1
- package/classnames.spec.ts +0 -17
- package/clipboard.spec.ts +0 -40
- package/colors.spec.ts +0 -40
- package/debounce.spec.ts +0 -101
- package/equals.spec.ts +0 -168
- package/sanitize.spec.ts +0 -558
- package/sleep.spec.ts +0 -59
- package/slug.spec.ts +0 -15
- package/strings.spec.ts +0 -77
- package/throttle.spec.ts +0 -142
- package/tsconfig.json +0 -40
- package/uuid.spec.ts +0 -32
- package/validations.spec.ts +0 -26
package/sanitize.spec.ts
DELETED
|
@@ -1,558 +0,0 @@
|
|
|
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: "alert(1)", expected: "(1)" },
|
|
151
|
-
{ input: "alert(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: "(alert);", expected: "();" },
|
|
161
|
-
{ input: "://comment
alert(1)", expected: "://comment\n(1)" },
|
|
162
|
-
{ input: "test	value.method()", 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=alert(1)>",
|
|
242
|
-
'<div onclick="alert(1)">click</div>',
|
|
243
|
-
// Hex encoding
|
|
244
|
-
"<img src=x onerror=alert(1)>",
|
|
245
|
-
// Mixed encoding
|
|
246
|
-
"<img src=x onerror=alert(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
|
-
"javascript:",
|
|
335
|
-
"javascript:",
|
|
336
|
-
// Protocol with spaces/tabs
|
|
337
|
-
"java script:",
|
|
338
|
-
"java\tscript:",
|
|
339
|
-
"java\nscript:",
|
|
340
|
-
// Protocol variations
|
|
341
|
-
"javascript:",
|
|
342
|
-
"javascript:",
|
|
343
|
-
"javascript:",
|
|
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
|
-
})
|
package/sleep.spec.ts
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest"
|
|
2
|
-
import { sleep } from "./sleep"
|
|
3
|
-
|
|
4
|
-
describe("sleep", () => {
|
|
5
|
-
it("returns a Promise", () => {
|
|
6
|
-
const result = sleep(1)
|
|
7
|
-
expect(result).toBeInstanceOf(Promise)
|
|
8
|
-
})
|
|
9
|
-
|
|
10
|
-
it("resolves with undefined", async () => {
|
|
11
|
-
const result = await sleep(1)
|
|
12
|
-
expect(result).toBeUndefined()
|
|
13
|
-
})
|
|
14
|
-
|
|
15
|
-
it("accepts number parameter", () => {
|
|
16
|
-
expect(() => sleep(100)).not.toThrow()
|
|
17
|
-
expect(() => sleep(0)).not.toThrow()
|
|
18
|
-
expect(() => sleep(1.5)).not.toThrow()
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
it("resolves after minimal delay", async () => {
|
|
22
|
-
const start = Date.now()
|
|
23
|
-
await sleep(1)
|
|
24
|
-
const elapsed = Date.now() - start
|
|
25
|
-
expect(elapsed).toBeGreaterThanOrEqual(0)
|
|
26
|
-
})
|
|
27
|
-
|
|
28
|
-
it("handles zero milliseconds", async () => {
|
|
29
|
-
const start = Date.now()
|
|
30
|
-
await sleep(0)
|
|
31
|
-
const elapsed = Date.now() - start
|
|
32
|
-
expect(elapsed).toBeGreaterThanOrEqual(0)
|
|
33
|
-
})
|
|
34
|
-
|
|
35
|
-
it("handles fractional milliseconds", async () => {
|
|
36
|
-
const start = Date.now()
|
|
37
|
-
await sleep(0.5)
|
|
38
|
-
const elapsed = Date.now() - start
|
|
39
|
-
expect(elapsed).toBeGreaterThanOrEqual(0)
|
|
40
|
-
})
|
|
41
|
-
|
|
42
|
-
it("handles negative values", async () => {
|
|
43
|
-
const start = Date.now()
|
|
44
|
-
await sleep(-1)
|
|
45
|
-
const elapsed = Date.now() - start
|
|
46
|
-
expect(elapsed).toBeGreaterThanOrEqual(0)
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
it("can be used with Promise.all", async () => {
|
|
50
|
-
const promises = [sleep(1), sleep(2), sleep(3)]
|
|
51
|
-
const results = await Promise.all(promises)
|
|
52
|
-
expect(results).toEqual([undefined, undefined, undefined])
|
|
53
|
-
})
|
|
54
|
-
|
|
55
|
-
it("maintains proper typing", () => {
|
|
56
|
-
const promise: Promise<void> = sleep(1)
|
|
57
|
-
expect(promise).toBeInstanceOf(Promise)
|
|
58
|
-
})
|
|
59
|
-
})
|
package/slug.spec.ts
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { it, describe, expect } from "vitest"
|
|
2
|
-
import { getSlug } from "./slug"
|
|
3
|
-
|
|
4
|
-
describe("slug", () => {
|
|
5
|
-
describe("getSlug", () => {
|
|
6
|
-
it("returns valid from string with dashes", () => expect(getSlug("--B—uRn--")).toEqual("-burn-"))
|
|
7
|
-
it("returns valid from special string", () => expect(getSlug("#$%^B#uR#n-")).toEqual("burn-"))
|
|
8
|
-
it("returns valid from parens string", () =>
|
|
9
|
-
expect(getSlug("Gaussian Blur [1](2){3}")).toEqual("gaussian-blur-123"))
|
|
10
|
-
it("replaces àáäâèéëêìíïîòóöôùúüûñç", () =>
|
|
11
|
-
expect(getSlug("àáäâèéëêìíïîòóöôùúüûñç")).toEqual("aaaaeeeeiiiioooouuuunc"))
|
|
12
|
-
it("fails silently from undefined", () => expect(getSlug(undefined)).toEqual(""))
|
|
13
|
-
it("fails silently from null", () => expect(getSlug(null as unknown as string)).toEqual(""))
|
|
14
|
-
})
|
|
15
|
-
})
|