@instructure/platform-sanitize 0.3.17 → 0.4.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 +93 -81
- package/dist/sanitizeHtml.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import
|
|
2
|
-
const
|
|
1
|
+
import f from "dompurify";
|
|
2
|
+
const w = /* @__PURE__ */ new Set([
|
|
3
3
|
// layout
|
|
4
4
|
"display",
|
|
5
5
|
"float",
|
|
@@ -241,7 +241,7 @@ const m = /* @__PURE__ */ new Set([
|
|
|
241
241
|
"caret-color",
|
|
242
242
|
"accent-color",
|
|
243
243
|
"appearance"
|
|
244
|
-
]),
|
|
244
|
+
]), h = /* @__PURE__ */ new Set([
|
|
245
245
|
"src",
|
|
246
246
|
"href",
|
|
247
247
|
"action",
|
|
@@ -252,16 +252,7 @@ const m = /* @__PURE__ */ new Set([
|
|
|
252
252
|
"cite",
|
|
253
253
|
"longdesc",
|
|
254
254
|
"xlink:href"
|
|
255
|
-
]),
|
|
256
|
-
"background",
|
|
257
|
-
"background-image",
|
|
258
|
-
"list-style",
|
|
259
|
-
"list-style-image",
|
|
260
|
-
"cursor",
|
|
261
|
-
// content: url(...) triggers an HTTP GET even on non-pseudo elements in some
|
|
262
|
-
// browsers; strip it as defense-in-depth against tracking-pixel exfiltration.
|
|
263
|
-
"content"
|
|
264
|
-
], y = /url\s*\(\s*['"]?(?:[a-z][a-z0-9+\-.]*:|\/\/)/i, k = /* @__PURE__ */ new Set([
|
|
255
|
+
]), y = ["content"], A = /url\s*\(\s*['"]?(?:[a-z][a-z0-9+\-.]*:|\/\/)/i, k = /* @__PURE__ */ new Set([
|
|
265
256
|
"allow-downloads",
|
|
266
257
|
"allow-forms",
|
|
267
258
|
"allow-modals",
|
|
@@ -274,8 +265,22 @@ const m = /* @__PURE__ */ new Set([
|
|
|
274
265
|
"allow-scripts",
|
|
275
266
|
"allow-storage-access-by-user-activation",
|
|
276
267
|
"allow-top-navigation-by-user-activation"
|
|
277
|
-
]),
|
|
278
|
-
|
|
268
|
+
]), p = {
|
|
269
|
+
// semantics and annotation are re-allowed because DOMPurify blocks all of
|
|
270
|
+
// mathMlDisallowed by default. semantics is a safe structural wrapper;
|
|
271
|
+
// annotation carries LaTeX source text (e.g. \frac{1}{2}); Canvas emits
|
|
272
|
+
// text-only content here, and any child elements would be hoisted out of
|
|
273
|
+
// the MathML context and sanitized as ordinary HTML.
|
|
274
|
+
//
|
|
275
|
+
// annotation-xml is re-added here and filtered by an afterSanitizeElements
|
|
276
|
+
// hook below: it is stripped when its encoding makes it an HTML integration
|
|
277
|
+
// point ("text/html" or "application/xhtml+xml" per the WHATWG spec), which
|
|
278
|
+
// is the mXSS namespace-confusion vector. Semantic encodings used by external
|
|
279
|
+
// tools (MathML-Content, MathML-Presentation from MathType/Wolfram Alpha,
|
|
280
|
+
// application/x-tex, …) are not integration points and are preserved, so this
|
|
281
|
+
// layer no longer strips the benign annotation-xml the backend and TinyMCE
|
|
282
|
+
// already allow.
|
|
283
|
+
ADD_TAGS: ["iframe", "semantics", "annotation", "annotation-xml"],
|
|
279
284
|
ADD_ATTR: [
|
|
280
285
|
"allowfullscreen",
|
|
281
286
|
"allow",
|
|
@@ -319,17 +324,18 @@ const m = /* @__PURE__ */ new Set([
|
|
|
319
324
|
// confusion attacks where fragments like <svg> could influence parse context.
|
|
320
325
|
FORCE_BODY: !0
|
|
321
326
|
};
|
|
322
|
-
let
|
|
323
|
-
function
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
327
|
+
let s = null;
|
|
328
|
+
function E() {
|
|
329
|
+
if (s) return s;
|
|
330
|
+
s = typeof f == "function" ? f(window) : f, s.addHook("afterSanitizeAttributes", (t) => {
|
|
331
|
+
if (!(t instanceof Element) || !t.hasAttribute("style")) return;
|
|
332
|
+
const e = t.style, r = [];
|
|
333
|
+
for (let i = 0; i < e.length; i++) {
|
|
334
|
+
const l = e.item(i);
|
|
335
|
+
w.has(l) || r.push(l);
|
|
330
336
|
}
|
|
331
|
-
for (const
|
|
332
|
-
const
|
|
337
|
+
for (const i of r) e.removeProperty(i);
|
|
338
|
+
const a = /* @__PURE__ */ new Set([
|
|
333
339
|
"static",
|
|
334
340
|
"relative",
|
|
335
341
|
"absolute",
|
|
@@ -338,53 +344,59 @@ function A() {
|
|
|
338
344
|
"unset",
|
|
339
345
|
"revert",
|
|
340
346
|
"revert-layer"
|
|
341
|
-
]),
|
|
342
|
-
|
|
343
|
-
const
|
|
344
|
-
|
|
345
|
-
for (const
|
|
346
|
-
const
|
|
347
|
-
|
|
347
|
+
]), c = e.getPropertyValue("position").trim().toLowerCase();
|
|
348
|
+
c && !a.has(c) && e.removeProperty("position");
|
|
349
|
+
const d = /* @__PURE__ */ new Set(["initial", "inherit", "unset", "revert", "revert-layer"]), o = e.getPropertyValue("opacity").trim().toLowerCase();
|
|
350
|
+
o && !d.has(o) && (o.endsWith("%") ? parseFloat(o) / 100 : parseFloat(o)) < 0.05 && e.removeProperty("opacity");
|
|
351
|
+
for (const i of y) {
|
|
352
|
+
const l = e.getPropertyValue(i);
|
|
353
|
+
l && A.test(l) && e.removeProperty(i);
|
|
348
354
|
}
|
|
349
|
-
|
|
350
|
-
}),
|
|
351
|
-
if (!
|
|
352
|
-
const
|
|
353
|
-
/^\s*\/\//.test(
|
|
354
|
-
}),
|
|
355
|
-
if (!(
|
|
356
|
-
const
|
|
357
|
-
if (
|
|
358
|
-
|
|
355
|
+
e.length === 0 && t.removeAttribute("style");
|
|
356
|
+
}), s.addHook("uponSanitizeAttribute", (t, e) => {
|
|
357
|
+
if (!h.has(e.attrName)) return;
|
|
358
|
+
const r = e.attrValue;
|
|
359
|
+
/^\s*\/\//.test(r) ? (e.attrValue = r.trimStart().replace(/^\/\//, "https://"), e.keepAttr = !0) : /^\s*\\/.test(r) && (e.keepAttr = !1);
|
|
360
|
+
}), s.addHook("afterSanitizeAttributes", (t) => {
|
|
361
|
+
if (!(t instanceof Element) || !t.hasAttribute("srcset")) return;
|
|
362
|
+
const r = (t.getAttribute("srcset") ?? "").split(","), a = (o) => o.trim().split(/\s+/)[0];
|
|
363
|
+
if (r.some((o) => /^\s*\\/.test(a(o)))) {
|
|
364
|
+
t.removeAttribute("srcset");
|
|
359
365
|
return;
|
|
360
366
|
}
|
|
361
|
-
let
|
|
362
|
-
const
|
|
363
|
-
const
|
|
364
|
-
if (!
|
|
365
|
-
|
|
366
|
-
const
|
|
367
|
-
return
|
|
367
|
+
let c = !1;
|
|
368
|
+
const d = r.map((o) => {
|
|
369
|
+
const i = a(o);
|
|
370
|
+
if (!i.startsWith("//")) return o;
|
|
371
|
+
c = !0;
|
|
372
|
+
const l = o.indexOf(i);
|
|
373
|
+
return o.slice(0, l) + "https://" + i.slice(2) + o.slice(l + i.length);
|
|
368
374
|
});
|
|
369
|
-
|
|
370
|
-
}),
|
|
371
|
-
var
|
|
372
|
-
if (!(
|
|
373
|
-
const
|
|
374
|
-
|
|
375
|
-
}),
|
|
376
|
-
if (!(
|
|
377
|
-
const
|
|
378
|
-
|
|
379
|
-
})
|
|
375
|
+
c && t.setAttribute("srcset", d.join(","));
|
|
376
|
+
}), s.addHook("afterSanitizeAttributes", (t) => {
|
|
377
|
+
var a;
|
|
378
|
+
if (!(t instanceof Element) || t.tagName !== "A" && t.tagName !== "AREA" || ((a = t.getAttribute("target")) == null ? void 0 : a.toLowerCase()) !== "_blank") return;
|
|
379
|
+
const e = t.getAttribute("rel") ?? "", r = new Set(e.split(/\s+/).filter(Boolean));
|
|
380
|
+
r.add("noopener"), t.setAttribute("rel", [...r].join(" "));
|
|
381
|
+
}), s.addHook("afterSanitizeAttributes", (t) => {
|
|
382
|
+
if (!(t instanceof Element) || t.tagName !== "IFRAME" || !t.hasAttribute("sandbox")) return;
|
|
383
|
+
const r = (t.getAttribute("sandbox") ?? "").toLowerCase().split(/\s+/).filter(Boolean), a = r.filter((c) => k.has(c));
|
|
384
|
+
a.length !== r.length && t.setAttribute("sandbox", a.join(" "));
|
|
385
|
+
});
|
|
386
|
+
const n = /* @__PURE__ */ new Set(["text/html", "application/xhtml+xml"]);
|
|
387
|
+
return s.addHook("afterSanitizeElements", (t) => {
|
|
388
|
+
if (!(t instanceof Element) || t.tagName.toLowerCase() !== "annotation-xml") return;
|
|
389
|
+
const e = (t.getAttribute("encoding") ?? "").toLowerCase().trim();
|
|
390
|
+
n.has(e) && t.remove();
|
|
391
|
+
}), s;
|
|
380
392
|
}
|
|
381
|
-
function
|
|
393
|
+
function _(n, t) {
|
|
382
394
|
if (typeof window > "u")
|
|
383
395
|
throw new Error("sanitizeHtml requires a DOM environment (window is not defined)");
|
|
384
|
-
const e = t != null && t.allowFormAttributeNames ? { ...
|
|
385
|
-
return
|
|
396
|
+
const e = t != null && t.allowFormAttributeNames ? { ...p, SANITIZE_DOM: !1 } : p;
|
|
397
|
+
return E().sanitize(n ?? "", e);
|
|
386
398
|
}
|
|
387
|
-
const
|
|
399
|
+
const m = /* @__PURE__ */ new Set(["http:", "https:", "mailto:", "tel:"]), u = "http://platform-sanitize.invalid/", g = /^\s*\/\//, b = (
|
|
388
400
|
// oxlint-disable-next-line no-control-regex -- intentional security guard
|
|
389
401
|
/^[\u0000-\u0020\u007F-\u00A0\u2000-\u200F\u2028\u2029\u202F\u205F\u2060\u3000\uFEFF]*(?:javascript|data|vbscript|file):/i
|
|
390
402
|
), S = {
|
|
@@ -400,49 +412,49 @@ const p = /* @__PURE__ */ new Set(["http:", "https:", "mailto:", "tel:"]), c = "
|
|
|
400
412
|
`
|
|
401
413
|
// java
script:
|
|
402
414
|
};
|
|
403
|
-
function
|
|
404
|
-
return
|
|
405
|
-
const
|
|
406
|
-
if (!Number.isFinite(
|
|
415
|
+
function x(n) {
|
|
416
|
+
return n.replace(/&#(\d+);/g, (t, e) => {
|
|
417
|
+
const r = Number(e);
|
|
418
|
+
if (!Number.isFinite(r) || r < 0 || r > 1114111) return "";
|
|
407
419
|
try {
|
|
408
|
-
return String.fromCodePoint(
|
|
420
|
+
return String.fromCodePoint(r);
|
|
409
421
|
} catch {
|
|
410
422
|
return "";
|
|
411
423
|
}
|
|
412
424
|
}).replace(/&#x([\da-f]+);/gi, (t, e) => {
|
|
413
|
-
const
|
|
414
|
-
if (!Number.isFinite(
|
|
425
|
+
const r = parseInt(e, 16);
|
|
426
|
+
if (!Number.isFinite(r) || r < 0 || r > 1114111) return "";
|
|
415
427
|
try {
|
|
416
|
-
return String.fromCodePoint(
|
|
428
|
+
return String.fromCodePoint(r);
|
|
417
429
|
} catch {
|
|
418
430
|
return "";
|
|
419
431
|
}
|
|
420
432
|
}).replace(/&([A-Za-z][A-Za-z0-9]*);/g, (t, e) => S[e] ?? t);
|
|
421
433
|
}
|
|
422
|
-
function R(
|
|
423
|
-
if (!
|
|
424
|
-
const t =
|
|
434
|
+
function R(n) {
|
|
435
|
+
if (!n || !n.trim()) return "about:blank";
|
|
436
|
+
const t = n.replace(/\\/g, "/");
|
|
425
437
|
if (g.test(t) || b.test(t)) return "about:blank";
|
|
426
438
|
if (/&[#A-Za-z]/.test(t)) {
|
|
427
|
-
const e =
|
|
439
|
+
const e = x(t);
|
|
428
440
|
if (g.test(e) || b.test(e))
|
|
429
441
|
return "about:blank";
|
|
430
442
|
try {
|
|
431
|
-
const
|
|
432
|
-
if (!
|
|
443
|
+
const r = new URL(e, u);
|
|
444
|
+
if (!r.href.startsWith(u) && !m.has(r.protocol))
|
|
433
445
|
return "about:blank";
|
|
434
446
|
} catch {
|
|
435
447
|
return "about:blank";
|
|
436
448
|
}
|
|
437
449
|
}
|
|
438
450
|
try {
|
|
439
|
-
const e = new URL(t,
|
|
440
|
-
return !
|
|
451
|
+
const e = new URL(t, u);
|
|
452
|
+
return !m.has(e.protocol) || (e.protocol === "http:" || e.protocol === "https:") && (e.username || e.password) ? "about:blank" : e.href.startsWith(u) ? n : t.replace(/[\x00-\x1F\u2028\u2029]/g, "").replace(/%250[9ad]/gi, "").replace(/%0[9ad]/gi, "");
|
|
441
453
|
} catch {
|
|
442
454
|
return "about:blank";
|
|
443
455
|
}
|
|
444
456
|
}
|
|
445
457
|
export {
|
|
446
|
-
|
|
458
|
+
_ as sanitizeHtml,
|
|
447
459
|
R as sanitizeUrl
|
|
448
460
|
};
|
|
@@ -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":"AA8fA,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"}
|