@instructure/platform-sanitize 0.5.0 → 0.5.1
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/dist/index.js +109 -97
- package/dist/sanitizeHtml.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,5 +1,63 @@
|
|
|
1
1
|
import f from "dompurify";
|
|
2
|
-
const
|
|
2
|
+
const m = /* @__PURE__ */ new Set(["http:", "https:", "mailto:", "tel:"]), u = "http://platform-sanitize.invalid/", p = /^\s*\/\//, b = (
|
|
3
|
+
// oxlint-disable-next-line no-control-regex -- intentional security guard
|
|
4
|
+
/^[\u0000-\u0020\u007F-\u00A0\u2000-\u200F\u2028\u2029\u202F\u205F\u2060\u3000\uFEFF]*(?:javascript|data|vbscript|file):/i
|
|
5
|
+
), h = {
|
|
6
|
+
colon: ":",
|
|
7
|
+
// javascript:alert(1) → javascript:alert(1)
|
|
8
|
+
sol: "/",
|
|
9
|
+
// //evil.com → //evil.com (protocol-relative)
|
|
10
|
+
bsol: "\\",
|
|
11
|
+
// \\evil.com → \\evil.com
|
|
12
|
+
Tab: " ",
|
|
13
|
+
// java	script: — WHATWG strips mid-scheme tabs
|
|
14
|
+
NewLine: `
|
|
15
|
+
`
|
|
16
|
+
// java
script:
|
|
17
|
+
};
|
|
18
|
+
function w(s) {
|
|
19
|
+
return s.replace(/&#(\d+);/g, (t, e) => {
|
|
20
|
+
const r = Number(e);
|
|
21
|
+
if (!Number.isFinite(r) || r < 0 || r > 1114111) return "";
|
|
22
|
+
try {
|
|
23
|
+
return String.fromCodePoint(r);
|
|
24
|
+
} catch {
|
|
25
|
+
return "";
|
|
26
|
+
}
|
|
27
|
+
}).replace(/&#x([\da-f]+);/gi, (t, e) => {
|
|
28
|
+
const r = parseInt(e, 16);
|
|
29
|
+
if (!Number.isFinite(r) || r < 0 || r > 1114111) return "";
|
|
30
|
+
try {
|
|
31
|
+
return String.fromCodePoint(r);
|
|
32
|
+
} catch {
|
|
33
|
+
return "";
|
|
34
|
+
}
|
|
35
|
+
}).replace(/&([A-Za-z][A-Za-z0-9]*);/g, (t, e) => h[e] ?? t);
|
|
36
|
+
}
|
|
37
|
+
function A(s) {
|
|
38
|
+
if (!s || !s.trim()) return "about:blank";
|
|
39
|
+
const t = s.replace(/\\/g, "/");
|
|
40
|
+
if (p.test(t) || b.test(t)) return "about:blank";
|
|
41
|
+
if (/&[#A-Za-z]/.test(t)) {
|
|
42
|
+
const e = w(t);
|
|
43
|
+
if (p.test(e) || b.test(e))
|
|
44
|
+
return "about:blank";
|
|
45
|
+
try {
|
|
46
|
+
const r = new URL(e, u);
|
|
47
|
+
if (!r.href.startsWith(u) && !m.has(r.protocol))
|
|
48
|
+
return "about:blank";
|
|
49
|
+
} catch {
|
|
50
|
+
return "about:blank";
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
const e = new URL(t, u);
|
|
55
|
+
return !m.has(e.protocol) || (e.protocol === "http:" || e.protocol === "https:") && (e.username || e.password) ? "about:blank" : e.href.startsWith(u) ? s : t.replace(/[\x00-\x1F\u2028\u2029]/g, "").replace(/%250[9ad]/gi, "").replace(/%0[9ad]/gi, "");
|
|
56
|
+
} catch {
|
|
57
|
+
return "about:blank";
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const y = /* @__PURE__ */ new Set([
|
|
3
61
|
// layout
|
|
4
62
|
"display",
|
|
5
63
|
"float",
|
|
@@ -241,7 +299,7 @@ const w = /* @__PURE__ */ new Set([
|
|
|
241
299
|
"caret-color",
|
|
242
300
|
"accent-color",
|
|
243
301
|
"appearance"
|
|
244
|
-
]),
|
|
302
|
+
]), S = /* @__PURE__ */ new Set([
|
|
245
303
|
"src",
|
|
246
304
|
"href",
|
|
247
305
|
"action",
|
|
@@ -252,7 +310,7 @@ const w = /* @__PURE__ */ new Set([
|
|
|
252
310
|
"cite",
|
|
253
311
|
"longdesc",
|
|
254
312
|
"xlink:href"
|
|
255
|
-
]),
|
|
313
|
+
]), k = ["content"], E = /url\s*\(\s*['"]?(?:[a-z][a-z0-9+\-.]*:|\/\/)/i, v = /* @__PURE__ */ new Set([
|
|
256
314
|
"allow-downloads",
|
|
257
315
|
"allow-forms",
|
|
258
316
|
"allow-modals",
|
|
@@ -265,7 +323,14 @@ const w = /* @__PURE__ */ new Set([
|
|
|
265
323
|
"allow-scripts",
|
|
266
324
|
"allow-storage-access-by-user-activation",
|
|
267
325
|
"allow-top-navigation-by-user-activation"
|
|
268
|
-
]),
|
|
326
|
+
]), x = {
|
|
327
|
+
"data-url": "data-custom-url",
|
|
328
|
+
"data-method": "data-custom-method",
|
|
329
|
+
"data-remote": "data-custom-remote",
|
|
330
|
+
"data-remove": "data-custom-remove",
|
|
331
|
+
"data-confirm": "data-custom-confirm",
|
|
332
|
+
"data-disable-with": "data-custom-disable-with"
|
|
333
|
+
}, _ = /* @__PURE__ */ new Set(["data-item-href"]), g = {
|
|
269
334
|
// semantics and annotation are re-allowed because DOMPurify blocks all of
|
|
270
335
|
// mathMlDisallowed by default. semantics is a safe structural wrapper;
|
|
271
336
|
// annotation carries LaTeX source text (e.g. \frac{1}{2}); Canvas emits
|
|
@@ -330,10 +395,6 @@ const w = /* @__PURE__ */ new Set([
|
|
|
330
395
|
"intent",
|
|
331
396
|
"arg"
|
|
332
397
|
],
|
|
333
|
-
// Rails UJS turns data-method/data-remote/etc. on clickable elements into
|
|
334
|
-
// state-changing requests carrying the victim's CSRF token. Strip them so
|
|
335
|
-
// user-authored HTML cannot become a CSRF gadget when UJS is on the page.
|
|
336
|
-
FORBID_ATTR: ["data-method", "data-remote", "data-url", "data-confirm", "data-disable-with"],
|
|
337
398
|
// In browsers that support the Trusted Types API (Chromium, Firefox),
|
|
338
399
|
// DOMPurify returns a TrustedHTML object rather than a plain string. The
|
|
339
400
|
// exported sanitizeHtml signature declares string — a deliberate lie that
|
|
@@ -349,18 +410,18 @@ const w = /* @__PURE__ */ new Set([
|
|
|
349
410
|
// confusion attacks where fragments like <svg> could influence parse context.
|
|
350
411
|
FORCE_BODY: !0
|
|
351
412
|
};
|
|
352
|
-
let
|
|
353
|
-
function
|
|
354
|
-
if (
|
|
355
|
-
|
|
413
|
+
let a = null;
|
|
414
|
+
function R() {
|
|
415
|
+
if (a) return a;
|
|
416
|
+
a = typeof f == "function" ? f(window) : f, a.addHook("afterSanitizeAttributes", (t) => {
|
|
356
417
|
if (!(t instanceof Element) || !t.hasAttribute("style")) return;
|
|
357
418
|
const e = t.style, r = [];
|
|
358
419
|
for (let i = 0; i < e.length; i++) {
|
|
359
420
|
const l = e.item(i);
|
|
360
|
-
|
|
421
|
+
y.has(l) || r.push(l);
|
|
361
422
|
}
|
|
362
423
|
for (const i of r) e.removeProperty(i);
|
|
363
|
-
const
|
|
424
|
+
const n = /* @__PURE__ */ new Set([
|
|
364
425
|
"static",
|
|
365
426
|
"relative",
|
|
366
427
|
"absolute",
|
|
@@ -370,120 +431,71 @@ function E() {
|
|
|
370
431
|
"revert",
|
|
371
432
|
"revert-layer"
|
|
372
433
|
]), c = e.getPropertyValue("position").trim().toLowerCase();
|
|
373
|
-
c && !
|
|
434
|
+
c && !n.has(c) && e.removeProperty("position");
|
|
374
435
|
const d = /* @__PURE__ */ new Set(["initial", "inherit", "unset", "revert", "revert-layer"]), o = e.getPropertyValue("opacity").trim().toLowerCase();
|
|
375
436
|
o && !d.has(o) && (o.endsWith("%") ? parseFloat(o) / 100 : parseFloat(o)) < 0.05 && e.removeProperty("opacity");
|
|
376
|
-
for (const i of
|
|
437
|
+
for (const i of k) {
|
|
377
438
|
const l = e.getPropertyValue(i);
|
|
378
|
-
l &&
|
|
439
|
+
l && E.test(l) && e.removeProperty(i);
|
|
379
440
|
}
|
|
380
441
|
e.length === 0 && t.removeAttribute("style");
|
|
381
|
-
}),
|
|
382
|
-
if (!
|
|
442
|
+
}), a.addHook("uponSanitizeAttribute", (t, e) => {
|
|
443
|
+
if (!S.has(e.attrName)) return;
|
|
383
444
|
const r = e.attrValue;
|
|
384
445
|
/^\s*\/\//.test(r) ? (e.attrValue = r.trimStart().replace(/^\/\//, "https://"), e.keepAttr = !0) : /^\s*\\/.test(r) && (e.keepAttr = !1);
|
|
385
|
-
}),
|
|
446
|
+
}), a.addHook("uponSanitizeAttribute", (t, e) => {
|
|
447
|
+
if (!_.has(e.attrName)) return;
|
|
448
|
+
const r = e.attrValue;
|
|
449
|
+
if (!r.trim()) return;
|
|
450
|
+
const n = /^\s*\/\//.test(r) ? r.trimStart().replace(/^\/\//, "https://") : r;
|
|
451
|
+
e.attrValue = A(n);
|
|
452
|
+
}), a.addHook("uponSanitizeAttribute", (t, e) => {
|
|
453
|
+
const r = x[e.attrName];
|
|
454
|
+
!r || !(t instanceof Element) || (t.hasAttribute(r) || t.setAttribute(r, e.attrValue), e.keepAttr = !1);
|
|
455
|
+
}), a.addHook("afterSanitizeAttributes", (t) => {
|
|
386
456
|
if (!(t instanceof Element) || !t.hasAttribute("srcset")) return;
|
|
387
|
-
const r = (t.getAttribute("srcset") ?? "").split(","),
|
|
388
|
-
if (r.some((o) => /^\s*\\/.test(
|
|
457
|
+
const r = (t.getAttribute("srcset") ?? "").split(","), n = (o) => o.trim().split(/\s+/)[0];
|
|
458
|
+
if (r.some((o) => /^\s*\\/.test(n(o)))) {
|
|
389
459
|
t.removeAttribute("srcset");
|
|
390
460
|
return;
|
|
391
461
|
}
|
|
392
462
|
let c = !1;
|
|
393
463
|
const d = r.map((o) => {
|
|
394
|
-
const i =
|
|
464
|
+
const i = n(o);
|
|
395
465
|
if (!i.startsWith("//")) return o;
|
|
396
466
|
c = !0;
|
|
397
467
|
const l = o.indexOf(i);
|
|
398
468
|
return o.slice(0, l) + "https://" + i.slice(2) + o.slice(l + i.length);
|
|
399
469
|
});
|
|
400
470
|
c && t.setAttribute("srcset", d.join(","));
|
|
401
|
-
}),
|
|
402
|
-
var
|
|
403
|
-
if (!(t instanceof Element) || t.tagName !== "A" && t.tagName !== "AREA" || ((
|
|
471
|
+
}), a.addHook("afterSanitizeAttributes", (t) => {
|
|
472
|
+
var n;
|
|
473
|
+
if (!(t instanceof Element) || t.tagName !== "A" && t.tagName !== "AREA" || ((n = t.getAttribute("target")) == null ? void 0 : n.toLowerCase()) !== "_blank") return;
|
|
404
474
|
const e = t.getAttribute("rel") ?? "", r = new Set(e.split(/\s+/).filter(Boolean));
|
|
405
475
|
r.add("noopener"), t.setAttribute("rel", [...r].join(" "));
|
|
406
|
-
}),
|
|
476
|
+
}), a.addHook("afterSanitizeAttributes", (t) => {
|
|
407
477
|
if (!(t instanceof Element) || t.tagName !== "IFRAME" || !t.hasAttribute("sandbox")) return;
|
|
408
|
-
const r = (t.getAttribute("sandbox") ?? "").toLowerCase().split(/\s+/).filter(Boolean),
|
|
409
|
-
|
|
410
|
-
}),
|
|
478
|
+
const r = (t.getAttribute("sandbox") ?? "").toLowerCase().split(/\s+/).filter(Boolean), n = r.filter((c) => v.has(c));
|
|
479
|
+
n.length !== r.length && t.setAttribute("sandbox", n.join(" "));
|
|
480
|
+
}), a.addHook("afterSanitizeAttributes", (t) => {
|
|
411
481
|
if (!(t instanceof Element) || t.tagName !== "PARAM") return;
|
|
412
482
|
const e = t.getAttribute("value");
|
|
413
483
|
e && /^\s*(?:javascript|vbscript):/i.test(e) && t.removeAttribute("value");
|
|
414
484
|
});
|
|
415
|
-
const
|
|
416
|
-
return
|
|
485
|
+
const s = /* @__PURE__ */ new Set(["text/html", "application/xhtml+xml"]);
|
|
486
|
+
return a.addHook("afterSanitizeElements", (t) => {
|
|
417
487
|
if (!(t instanceof Element) || t.tagName.toLowerCase() !== "annotation-xml") return;
|
|
418
488
|
const e = (t.getAttribute("encoding") ?? "").toLowerCase().trim();
|
|
419
|
-
|
|
420
|
-
}),
|
|
489
|
+
s.has(e) && t.remove();
|
|
490
|
+
}), a;
|
|
421
491
|
}
|
|
422
|
-
function
|
|
492
|
+
function N(s, t) {
|
|
423
493
|
if (typeof window > "u")
|
|
424
494
|
throw new Error("sanitizeHtml requires a DOM environment (window is not defined)");
|
|
425
|
-
const e = t != null && t.allowFormAttributeNames ? { ...
|
|
426
|
-
return
|
|
427
|
-
}
|
|
428
|
-
const m = /* @__PURE__ */ new Set(["http:", "https:", "mailto:", "tel:"]), u = "http://platform-sanitize.invalid/", b = /^\s*\/\//, g = (
|
|
429
|
-
// oxlint-disable-next-line no-control-regex -- intentional security guard
|
|
430
|
-
/^[\u0000-\u0020\u007F-\u00A0\u2000-\u200F\u2028\u2029\u202F\u205F\u2060\u3000\uFEFF]*(?:javascript|data|vbscript|file):/i
|
|
431
|
-
), S = {
|
|
432
|
-
colon: ":",
|
|
433
|
-
// javascript:alert(1) → javascript:alert(1)
|
|
434
|
-
sol: "/",
|
|
435
|
-
// //evil.com → //evil.com (protocol-relative)
|
|
436
|
-
bsol: "\\",
|
|
437
|
-
// \\evil.com → \\evil.com
|
|
438
|
-
Tab: " ",
|
|
439
|
-
// java	script: — WHATWG strips mid-scheme tabs
|
|
440
|
-
NewLine: `
|
|
441
|
-
`
|
|
442
|
-
// java
script:
|
|
443
|
-
};
|
|
444
|
-
function x(a) {
|
|
445
|
-
return a.replace(/&#(\d+);/g, (t, e) => {
|
|
446
|
-
const r = Number(e);
|
|
447
|
-
if (!Number.isFinite(r) || r < 0 || r > 1114111) return "";
|
|
448
|
-
try {
|
|
449
|
-
return String.fromCodePoint(r);
|
|
450
|
-
} catch {
|
|
451
|
-
return "";
|
|
452
|
-
}
|
|
453
|
-
}).replace(/&#x([\da-f]+);/gi, (t, e) => {
|
|
454
|
-
const r = parseInt(e, 16);
|
|
455
|
-
if (!Number.isFinite(r) || r < 0 || r > 1114111) return "";
|
|
456
|
-
try {
|
|
457
|
-
return String.fromCodePoint(r);
|
|
458
|
-
} catch {
|
|
459
|
-
return "";
|
|
460
|
-
}
|
|
461
|
-
}).replace(/&([A-Za-z][A-Za-z0-9]*);/g, (t, e) => S[e] ?? t);
|
|
462
|
-
}
|
|
463
|
-
function _(a) {
|
|
464
|
-
if (!a || !a.trim()) return "about:blank";
|
|
465
|
-
const t = a.replace(/\\/g, "/");
|
|
466
|
-
if (b.test(t) || g.test(t)) return "about:blank";
|
|
467
|
-
if (/&[#A-Za-z]/.test(t)) {
|
|
468
|
-
const e = x(t);
|
|
469
|
-
if (b.test(e) || g.test(e))
|
|
470
|
-
return "about:blank";
|
|
471
|
-
try {
|
|
472
|
-
const r = new URL(e, u);
|
|
473
|
-
if (!r.href.startsWith(u) && !m.has(r.protocol))
|
|
474
|
-
return "about:blank";
|
|
475
|
-
} catch {
|
|
476
|
-
return "about:blank";
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
try {
|
|
480
|
-
const e = new URL(t, u);
|
|
481
|
-
return !m.has(e.protocol) || (e.protocol === "http:" || e.protocol === "https:") && (e.username || e.password) ? "about:blank" : e.href.startsWith(u) ? a : t.replace(/[\x00-\x1F\u2028\u2029]/g, "").replace(/%250[9ad]/gi, "").replace(/%0[9ad]/gi, "");
|
|
482
|
-
} catch {
|
|
483
|
-
return "about:blank";
|
|
484
|
-
}
|
|
495
|
+
const e = t != null && t.allowFormAttributeNames ? { ...g, SANITIZE_DOM: !1 } : g;
|
|
496
|
+
return R().sanitize(s ?? "", e);
|
|
485
497
|
}
|
|
486
498
|
export {
|
|
487
|
-
|
|
488
|
-
|
|
499
|
+
N as sanitizeHtml,
|
|
500
|
+
A as sanitizeUrl
|
|
489
501
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sanitizeHtml.d.ts","sourceRoot":"","sources":["../src/sanitizeHtml.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"sanitizeHtml.d.ts","sourceRoot":"","sources":["../src/sanitizeHtml.ts"],"names":[],"mappings":"AA+kBA,wBAAgB,YAAY,CAC1B,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAC/B,OAAO,CAAC,EAAE;IAAE,uBAAuB,CAAC,EAAE,OAAO,CAAA;CAAE,GAC9C,MAAM,CASR"}
|