@instructure/platform-sanitize 0.4.0 → 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,7 +252,7 @@ const m = /* @__PURE__ */ new Set([
252
252
  "cite",
253
253
  "longdesc",
254
254
  "xlink:href"
255
- ]), h = ["content"], y = /url\s*\(\s*['"]?(?:[a-z][a-z0-9+\-.]*:|\/\/)/i, A = /* @__PURE__ */ new Set([
255
+ ]), y = ["content"], A = /url\s*\(\s*['"]?(?:[a-z][a-z0-9+\-.]*:|\/\/)/i, k = /* @__PURE__ */ new Set([
256
256
  "allow-downloads",
257
257
  "allow-forms",
258
258
  "allow-modals",
@@ -265,8 +265,22 @@ const m = /* @__PURE__ */ new Set([
265
265
  "allow-scripts",
266
266
  "allow-storage-access-by-user-activation",
267
267
  "allow-top-navigation-by-user-activation"
268
- ]), f = {
269
- 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"],
270
284
  ADD_ATTR: [
271
285
  "allowfullscreen",
272
286
  "allow",
@@ -310,17 +324,18 @@ const m = /* @__PURE__ */ new Set([
310
324
  // confusion attacks where fragments like <svg> could influence parse context.
311
325
  FORCE_BODY: !0
312
326
  };
313
- let a = null;
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);
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);
321
336
  }
322
- for (const n of e) t.removeProperty(n);
323
- const o = /* @__PURE__ */ new Set([
337
+ for (const i of r) e.removeProperty(i);
338
+ const a = /* @__PURE__ */ new Set([
324
339
  "static",
325
340
  "relative",
326
341
  "absolute",
@@ -329,53 +344,59 @@ function k() {
329
344
  "unset",
330
345
  "revert",
331
346
  "revert-layer"
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
- for (const n of h) {
337
- const s = t.getPropertyValue(n);
338
- 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);
339
354
  }
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");
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");
350
365
  return;
351
366
  }
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);
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);
359
374
  });
360
- l && r.setAttribute("srcset", u.join(","));
361
- }), a.addHook("afterSanitizeAttributes", (r) => {
362
- var o;
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
- }), 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;
371
392
  }
372
- function v(r, t) {
393
+ function _(n, t) {
373
394
  if (typeof window > "u")
374
395
  throw new Error("sanitizeHtml requires a DOM environment (window is not defined)");
375
- const e = t != null && t.allowFormAttributeNames ? { ...f, SANITIZE_DOM: !1 } : f;
376
- return k().sanitize(r ?? "", e);
396
+ const e = t != null && t.allowFormAttributeNames ? { ...p, SANITIZE_DOM: !1 } : p;
397
+ return E().sanitize(n ?? "", e);
377
398
  }
378
- const p = /* @__PURE__ */ new Set(["http:", "https:", "mailto:", "tel:"]), c = "http://platform-sanitize.invalid/", b = /^\s*\/\//, g = (
399
+ const m = /* @__PURE__ */ new Set(["http:", "https:", "mailto:", "tel:"]), u = "http://platform-sanitize.invalid/", g = /^\s*\/\//, b = (
379
400
  // oxlint-disable-next-line no-control-regex -- intentional security guard
380
401
  /^[\u0000-\u0020\u007F-\u00A0\u2000-\u200F\u2028\u2029\u202F\u205F\u2060\u3000\uFEFF]*(?:javascript|data|vbscript|file):/i
381
402
  ), S = {
@@ -391,49 +412,49 @@ const p = /* @__PURE__ */ new Set(["http:", "https:", "mailto:", "tel:"]), c = "
391
412
  `
392
413
  // java&NewLine;script:
393
414
  };
394
- function E(r) {
395
- return r.replace(/&#(\d+);/g, (t, e) => {
396
- const o = Number(e);
397
- 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 "";
398
419
  try {
399
- return String.fromCodePoint(o);
420
+ return String.fromCodePoint(r);
400
421
  } catch {
401
422
  return "";
402
423
  }
403
424
  }).replace(/&#x([\da-f]+);/gi, (t, e) => {
404
- const o = parseInt(e, 16);
405
- 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 "";
406
427
  try {
407
- return String.fromCodePoint(o);
428
+ return String.fromCodePoint(r);
408
429
  } catch {
409
430
  return "";
410
431
  }
411
432
  }).replace(/&([A-Za-z][A-Za-z0-9]*);/g, (t, e) => S[e] ?? t);
412
433
  }
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";
434
+ function R(n) {
435
+ if (!n || !n.trim()) return "about:blank";
436
+ const t = n.replace(/\\/g, "/");
437
+ if (g.test(t) || b.test(t)) return "about:blank";
417
438
  if (/&[#A-Za-z]/.test(t)) {
418
- const e = E(t);
419
- if (b.test(e) || g.test(e))
439
+ const e = x(t);
440
+ if (g.test(e) || b.test(e))
420
441
  return "about:blank";
421
442
  try {
422
- const o = new URL(e, c);
423
- 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))
424
445
  return "about:blank";
425
446
  } catch {
426
447
  return "about:blank";
427
448
  }
428
449
  }
429
450
  try {
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, "");
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, "");
432
453
  } catch {
433
454
  return "about:blank";
434
455
  }
435
456
  }
436
457
  export {
437
- v as sanitizeHtml,
458
+ _ as sanitizeHtml,
438
459
  R as sanitizeUrl
439
460
  };
@@ -1 +1 @@
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"}
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.4.0",
3
+ "version": "0.4.1",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",