@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 CHANGED
@@ -1,5 +1,5 @@
1
- import d from "dompurify";
2
- const m = /* @__PURE__ */ new Set([
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
- ]), w = /* @__PURE__ */ new Set([
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
- ]), h = [
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
- ]), f = {
278
- ADD_TAGS: ["iframe"],
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 a = null;
323
- function A() {
324
- return a || (a = typeof d == "function" ? d(window) : d, a.addHook("afterSanitizeAttributes", (r) => {
325
- if (!(r instanceof Element) || !r.hasAttribute("style")) return;
326
- const t = r.style, e = [];
327
- for (let n = 0; n < t.length; n++) {
328
- const s = t.item(n);
329
- m.has(s) || e.push(s);
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 n of e) t.removeProperty(n);
332
- const o = /* @__PURE__ */ new Set([
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
- ]), l = t.getPropertyValue("position").trim().toLowerCase();
342
- l && !o.has(l) && t.removeProperty("position");
343
- const u = /* @__PURE__ */ new Set(["initial", "inherit", "unset", "revert", "revert-layer"]), i = t.getPropertyValue("opacity").trim().toLowerCase();
344
- i && !u.has(i) && (i.endsWith("%") ? parseFloat(i) / 100 : parseFloat(i)) < 0.05 && t.removeProperty("opacity");
345
- for (const n of h) {
346
- const s = t.getPropertyValue(n);
347
- s && y.test(s) && t.removeProperty(n);
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
- t.length === 0 && r.removeAttribute("style");
350
- }), a.addHook("uponSanitizeAttribute", (r, t) => {
351
- if (!w.has(t.attrName)) return;
352
- const e = t.attrValue;
353
- /^\s*\/\//.test(e) ? (t.attrValue = e.trimStart().replace(/^\/\//, "https://"), t.keepAttr = !0) : /^\s*\\/.test(e) && (t.keepAttr = !1);
354
- }), a.addHook("afterSanitizeAttributes", (r) => {
355
- if (!(r instanceof Element) || !r.hasAttribute("srcset")) return;
356
- const e = (r.getAttribute("srcset") ?? "").split(","), o = (i) => i.trim().split(/\s+/)[0];
357
- if (e.some((i) => /^\s*\\/.test(o(i)))) {
358
- r.removeAttribute("srcset");
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 l = !1;
362
- const u = e.map((i) => {
363
- const n = o(i);
364
- if (!n.startsWith("//")) return i;
365
- l = !0;
366
- const s = i.indexOf(n);
367
- return i.slice(0, s) + "https://" + n.slice(2) + i.slice(s + n.length);
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
- l && r.setAttribute("srcset", u.join(","));
370
- }), a.addHook("afterSanitizeAttributes", (r) => {
371
- var o;
372
- if (!(r instanceof Element) || r.tagName !== "A" && r.tagName !== "AREA" || ((o = r.getAttribute("target")) == null ? void 0 : o.toLowerCase()) !== "_blank") return;
373
- const t = r.getAttribute("rel") ?? "", e = new Set(t.split(/\s+/).filter(Boolean));
374
- e.add("noopener"), r.setAttribute("rel", [...e].join(" "));
375
- }), a.addHook("afterSanitizeAttributes", (r) => {
376
- if (!(r instanceof Element) || r.tagName !== "IFRAME" || !r.hasAttribute("sandbox")) return;
377
- const e = (r.getAttribute("sandbox") ?? "").toLowerCase().split(/\s+/).filter(Boolean), o = e.filter((l) => k.has(l));
378
- o.length !== e.length && r.setAttribute("sandbox", o.join(" "));
379
- }), a);
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 v(r, t) {
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 ? { ...f, SANITIZE_DOM: !1 } : f;
385
- return A().sanitize(r ?? "", e);
396
+ const e = t != null && t.allowFormAttributeNames ? { ...p, SANITIZE_DOM: !1 } : p;
397
+ return E().sanitize(n ?? "", e);
386
398
  }
387
- const p = /* @__PURE__ */ new Set(["http:", "https:", "mailto:", "tel:"]), c = "http://platform-sanitize.invalid/", g = /^\s*\/\//, b = (
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&NewLine;script:
402
414
  };
403
- function E(r) {
404
- return r.replace(/&#(\d+);/g, (t, e) => {
405
- const o = Number(e);
406
- if (!Number.isFinite(o) || o < 0 || o > 1114111) return "";
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(o);
420
+ return String.fromCodePoint(r);
409
421
  } catch {
410
422
  return "";
411
423
  }
412
424
  }).replace(/&#x([\da-f]+);/gi, (t, e) => {
413
- const o = parseInt(e, 16);
414
- if (!Number.isFinite(o) || o < 0 || o > 1114111) return "";
425
+ const r = parseInt(e, 16);
426
+ if (!Number.isFinite(r) || r < 0 || r > 1114111) return "";
415
427
  try {
416
- return String.fromCodePoint(o);
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(r) {
423
- if (!r || !r.trim()) return "about:blank";
424
- const t = r.replace(/\\/g, "/");
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 = E(t);
439
+ const e = x(t);
428
440
  if (g.test(e) || b.test(e))
429
441
  return "about:blank";
430
442
  try {
431
- const o = new URL(e, c);
432
- if (!o.href.startsWith(c) && !p.has(o.protocol))
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, c);
440
- 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, "");
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
- v as sanitizeHtml,
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":"AAgeA,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"}
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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@instructure/platform-sanitize",
3
- "version": "0.3.17",
3
+ "version": "0.4.1",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",