@instructure/platform-sanitize 0.3.15 → 0.4.0
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 +72 -72
- package/dist/sanitizeHtml.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import d from "dompurify";
|
|
2
2
|
const m = /* @__PURE__ */ new Set([
|
|
3
3
|
// layout
|
|
4
4
|
"display",
|
|
@@ -8,6 +8,7 @@ const m = /* @__PURE__ */ new Set([
|
|
|
8
8
|
"overflow-x",
|
|
9
9
|
"overflow-y",
|
|
10
10
|
"visibility",
|
|
11
|
+
"opacity",
|
|
11
12
|
"cursor",
|
|
12
13
|
"direction",
|
|
13
14
|
"user-select",
|
|
@@ -205,6 +206,10 @@ const m = /* @__PURE__ */ new Set([
|
|
|
205
206
|
"animation-direction",
|
|
206
207
|
"animation-fill-mode",
|
|
207
208
|
"animation-play-state",
|
|
209
|
+
// transforms — no value filter needed; .user_content has overflow-y:hidden and overflow-x:auto
|
|
210
|
+
// (canvas-lms g/412928), which together contain transformed content within the container on
|
|
211
|
+
// both axes and prevent overlay of Canvas UI.
|
|
212
|
+
"transform",
|
|
208
213
|
// columns
|
|
209
214
|
"column-count",
|
|
210
215
|
"column-width",
|
|
@@ -247,16 +252,7 @@ const m = /* @__PURE__ */ new Set([
|
|
|
247
252
|
"cite",
|
|
248
253
|
"longdesc",
|
|
249
254
|
"xlink:href"
|
|
250
|
-
]), h = [
|
|
251
|
-
"background",
|
|
252
|
-
"background-image",
|
|
253
|
-
"list-style",
|
|
254
|
-
"list-style-image",
|
|
255
|
-
"cursor",
|
|
256
|
-
// content: url(...) triggers an HTTP GET even on non-pseudo elements in some
|
|
257
|
-
// browsers; strip it as defense-in-depth against tracking-pixel exfiltration.
|
|
258
|
-
"content"
|
|
259
|
-
], y = /url\s*\(\s*['"]?(?:[a-z][a-z0-9+\-.]*:|\/\/)/i, k = /* @__PURE__ */ new Set([
|
|
255
|
+
]), h = ["content"], y = /url\s*\(\s*['"]?(?:[a-z][a-z0-9+\-.]*:|\/\/)/i, A = /* @__PURE__ */ new Set([
|
|
260
256
|
"allow-downloads",
|
|
261
257
|
"allow-forms",
|
|
262
258
|
"allow-modals",
|
|
@@ -285,13 +281,15 @@ const m = /* @__PURE__ */ new Set([
|
|
|
285
281
|
"webkitallowfullscreen",
|
|
286
282
|
"mozallowfullscreen",
|
|
287
283
|
"scrolling",
|
|
284
|
+
// loading="lazy" is not in DOMPurify's default ALLOWED_ATTR and must be
|
|
285
|
+
// explicitly added. No security concern — it is a performance hint only.
|
|
286
|
+
"loading",
|
|
288
287
|
// MathML 4 (W3C) annotation attributes for screen-reader accessibility.
|
|
289
288
|
// MathCAT, JAWS, and NVDA use `intent` to know how to pronounce
|
|
290
289
|
// expressions; `arg` labels sub-expressions referenced by intent.
|
|
291
290
|
// These are plain string annotations — no URL-loading, no code execution.
|
|
292
291
|
"intent",
|
|
293
|
-
"arg"
|
|
294
|
-
"loading"
|
|
292
|
+
"arg"
|
|
295
293
|
],
|
|
296
294
|
// Rails UJS turns data-method/data-remote/etc. on clickable elements into
|
|
297
295
|
// state-changing requests carrying the victim's CSRF token. Strip them so
|
|
@@ -313,15 +311,15 @@ const m = /* @__PURE__ */ new Set([
|
|
|
313
311
|
FORCE_BODY: !0
|
|
314
312
|
};
|
|
315
313
|
let a = null;
|
|
316
|
-
function
|
|
317
|
-
return a || (a = typeof
|
|
318
|
-
if (!(
|
|
319
|
-
const
|
|
320
|
-
for (let n = 0; n <
|
|
321
|
-
const
|
|
322
|
-
m.has(
|
|
314
|
+
function k() {
|
|
315
|
+
return a || (a = typeof d == "function" ? d(window) : d, a.addHook("afterSanitizeAttributes", (r) => {
|
|
316
|
+
if (!(r instanceof Element) || !r.hasAttribute("style")) return;
|
|
317
|
+
const t = r.style, e = [];
|
|
318
|
+
for (let n = 0; n < t.length; n++) {
|
|
319
|
+
const s = t.item(n);
|
|
320
|
+
m.has(s) || e.push(s);
|
|
323
321
|
}
|
|
324
|
-
for (const n of
|
|
322
|
+
for (const n of e) t.removeProperty(n);
|
|
325
323
|
const o = /* @__PURE__ */ new Set([
|
|
326
324
|
"static",
|
|
327
325
|
"relative",
|
|
@@ -331,54 +329,56 @@ function A() {
|
|
|
331
329
|
"unset",
|
|
332
330
|
"revert",
|
|
333
331
|
"revert-layer"
|
|
334
|
-
]),
|
|
335
|
-
|
|
332
|
+
]), l = t.getPropertyValue("position").trim().toLowerCase();
|
|
333
|
+
l && !o.has(l) && t.removeProperty("position");
|
|
334
|
+
const u = /* @__PURE__ */ new Set(["initial", "inherit", "unset", "revert", "revert-layer"]), i = t.getPropertyValue("opacity").trim().toLowerCase();
|
|
335
|
+
i && !u.has(i) && (i.endsWith("%") ? parseFloat(i) / 100 : parseFloat(i)) < 0.05 && t.removeProperty("opacity");
|
|
336
336
|
for (const n of h) {
|
|
337
|
-
const
|
|
338
|
-
|
|
337
|
+
const s = t.getPropertyValue(n);
|
|
338
|
+
s && y.test(s) && t.removeProperty(n);
|
|
339
339
|
}
|
|
340
|
-
|
|
341
|
-
}), a.addHook("uponSanitizeAttribute", (
|
|
342
|
-
if (!w.has(
|
|
343
|
-
const
|
|
344
|
-
/^\s*\/\//.test(
|
|
345
|
-
}), a.addHook("afterSanitizeAttributes", (
|
|
346
|
-
if (!(
|
|
347
|
-
const
|
|
348
|
-
if (
|
|
349
|
-
|
|
340
|
+
t.length === 0 && r.removeAttribute("style");
|
|
341
|
+
}), a.addHook("uponSanitizeAttribute", (r, t) => {
|
|
342
|
+
if (!w.has(t.attrName)) return;
|
|
343
|
+
const e = t.attrValue;
|
|
344
|
+
/^\s*\/\//.test(e) ? (t.attrValue = e.trimStart().replace(/^\/\//, "https://"), t.keepAttr = !0) : /^\s*\\/.test(e) && (t.keepAttr = !1);
|
|
345
|
+
}), a.addHook("afterSanitizeAttributes", (r) => {
|
|
346
|
+
if (!(r instanceof Element) || !r.hasAttribute("srcset")) return;
|
|
347
|
+
const e = (r.getAttribute("srcset") ?? "").split(","), o = (i) => i.trim().split(/\s+/)[0];
|
|
348
|
+
if (e.some((i) => /^\s*\\/.test(o(i)))) {
|
|
349
|
+
r.removeAttribute("srcset");
|
|
350
350
|
return;
|
|
351
351
|
}
|
|
352
|
-
let
|
|
353
|
-
const
|
|
354
|
-
const
|
|
355
|
-
if (!
|
|
356
|
-
|
|
357
|
-
const
|
|
358
|
-
return i.slice(0,
|
|
352
|
+
let l = !1;
|
|
353
|
+
const u = e.map((i) => {
|
|
354
|
+
const n = o(i);
|
|
355
|
+
if (!n.startsWith("//")) return i;
|
|
356
|
+
l = !0;
|
|
357
|
+
const s = i.indexOf(n);
|
|
358
|
+
return i.slice(0, s) + "https://" + n.slice(2) + i.slice(s + n.length);
|
|
359
359
|
});
|
|
360
|
-
|
|
361
|
-
}), a.addHook("afterSanitizeAttributes", (
|
|
360
|
+
l && r.setAttribute("srcset", u.join(","));
|
|
361
|
+
}), a.addHook("afterSanitizeAttributes", (r) => {
|
|
362
362
|
var o;
|
|
363
|
-
if (!(
|
|
364
|
-
const
|
|
365
|
-
|
|
366
|
-
}), a.addHook("afterSanitizeAttributes", (
|
|
367
|
-
if (!(
|
|
368
|
-
const
|
|
369
|
-
o.length !==
|
|
363
|
+
if (!(r instanceof Element) || r.tagName !== "A" && r.tagName !== "AREA" || ((o = r.getAttribute("target")) == null ? void 0 : o.toLowerCase()) !== "_blank") return;
|
|
364
|
+
const t = r.getAttribute("rel") ?? "", e = new Set(t.split(/\s+/).filter(Boolean));
|
|
365
|
+
e.add("noopener"), r.setAttribute("rel", [...e].join(" "));
|
|
366
|
+
}), a.addHook("afterSanitizeAttributes", (r) => {
|
|
367
|
+
if (!(r instanceof Element) || r.tagName !== "IFRAME" || !r.hasAttribute("sandbox")) return;
|
|
368
|
+
const e = (r.getAttribute("sandbox") ?? "").toLowerCase().split(/\s+/).filter(Boolean), o = e.filter((l) => A.has(l));
|
|
369
|
+
o.length !== e.length && r.setAttribute("sandbox", o.join(" "));
|
|
370
370
|
}), a);
|
|
371
371
|
}
|
|
372
|
-
function v(
|
|
372
|
+
function v(r, t) {
|
|
373
373
|
if (typeof window > "u")
|
|
374
374
|
throw new Error("sanitizeHtml requires a DOM environment (window is not defined)");
|
|
375
|
-
const
|
|
376
|
-
return
|
|
375
|
+
const e = t != null && t.allowFormAttributeNames ? { ...f, SANITIZE_DOM: !1 } : f;
|
|
376
|
+
return k().sanitize(r ?? "", e);
|
|
377
377
|
}
|
|
378
|
-
const p = /* @__PURE__ */ new Set(["http:", "https:", "mailto:", "tel:"]), c = "http://platform-sanitize.invalid/",
|
|
378
|
+
const p = /* @__PURE__ */ new Set(["http:", "https:", "mailto:", "tel:"]), c = "http://platform-sanitize.invalid/", b = /^\s*\/\//, g = (
|
|
379
379
|
// oxlint-disable-next-line no-control-regex -- intentional security guard
|
|
380
380
|
/^[\u0000-\u0020\u007F-\u00A0\u2000-\u200F\u2028\u2029\u202F\u205F\u2060\u3000\uFEFF]*(?:javascript|data|vbscript|file):/i
|
|
381
|
-
),
|
|
381
|
+
), S = {
|
|
382
382
|
colon: ":",
|
|
383
383
|
// javascript:alert(1) → javascript:alert(1)
|
|
384
384
|
sol: "/",
|
|
@@ -391,35 +391,35 @@ const p = /* @__PURE__ */ new Set(["http:", "https:", "mailto:", "tel:"]), c = "
|
|
|
391
391
|
`
|
|
392
392
|
// java
script:
|
|
393
393
|
};
|
|
394
|
-
function E(
|
|
395
|
-
return
|
|
396
|
-
const o = Number(
|
|
394
|
+
function E(r) {
|
|
395
|
+
return r.replace(/&#(\d+);/g, (t, e) => {
|
|
396
|
+
const o = Number(e);
|
|
397
397
|
if (!Number.isFinite(o) || o < 0 || o > 1114111) return "";
|
|
398
398
|
try {
|
|
399
399
|
return String.fromCodePoint(o);
|
|
400
400
|
} catch {
|
|
401
401
|
return "";
|
|
402
402
|
}
|
|
403
|
-
}).replace(/&#x([\da-f]+);/gi, (
|
|
404
|
-
const o = parseInt(
|
|
403
|
+
}).replace(/&#x([\da-f]+);/gi, (t, e) => {
|
|
404
|
+
const o = parseInt(e, 16);
|
|
405
405
|
if (!Number.isFinite(o) || o < 0 || o > 1114111) return "";
|
|
406
406
|
try {
|
|
407
407
|
return String.fromCodePoint(o);
|
|
408
408
|
} catch {
|
|
409
409
|
return "";
|
|
410
410
|
}
|
|
411
|
-
}).replace(/&([A-Za-z][A-Za-z0-9]*);/g, (
|
|
411
|
+
}).replace(/&([A-Za-z][A-Za-z0-9]*);/g, (t, e) => S[e] ?? t);
|
|
412
412
|
}
|
|
413
|
-
function R(
|
|
414
|
-
if (!
|
|
415
|
-
const
|
|
416
|
-
if (
|
|
417
|
-
if (/&[#A-Za-z]/.test(
|
|
418
|
-
const
|
|
419
|
-
if (
|
|
413
|
+
function R(r) {
|
|
414
|
+
if (!r || !r.trim()) return "about:blank";
|
|
415
|
+
const t = r.replace(/\\/g, "/");
|
|
416
|
+
if (b.test(t) || g.test(t)) return "about:blank";
|
|
417
|
+
if (/&[#A-Za-z]/.test(t)) {
|
|
418
|
+
const e = E(t);
|
|
419
|
+
if (b.test(e) || g.test(e))
|
|
420
420
|
return "about:blank";
|
|
421
421
|
try {
|
|
422
|
-
const o = new URL(
|
|
422
|
+
const o = new URL(e, c);
|
|
423
423
|
if (!o.href.startsWith(c) && !p.has(o.protocol))
|
|
424
424
|
return "about:blank";
|
|
425
425
|
} catch {
|
|
@@ -427,8 +427,8 @@ function R(e) {
|
|
|
427
427
|
}
|
|
428
428
|
}
|
|
429
429
|
try {
|
|
430
|
-
const
|
|
431
|
-
return !p.has(
|
|
430
|
+
const e = new URL(t, c);
|
|
431
|
+
return !p.has(e.protocol) || (e.protocol === "http:" || e.protocol === "https:") && (e.username || e.password) ? "about:blank" : e.href.startsWith(c) ? r : t.replace(/[\x00-\x1F\u2028\u2029]/g, "").replace(/%250[9ad]/gi, "").replace(/%0[9ad]/gi, "");
|
|
432
432
|
} catch {
|
|
433
433
|
return "about:blank";
|
|
434
434
|
}
|
|
@@ -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":"AA6dA,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"}
|