@instructure/platform-sanitize 0.3.15 → 0.3.17

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,4 +1,4 @@
1
- import u from "dompurify";
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",
@@ -285,13 +290,15 @@ const m = /* @__PURE__ */ new Set([
285
290
  "webkitallowfullscreen",
286
291
  "mozallowfullscreen",
287
292
  "scrolling",
293
+ // loading="lazy" is not in DOMPurify's default ALLOWED_ATTR and must be
294
+ // explicitly added. No security concern — it is a performance hint only.
295
+ "loading",
288
296
  // MathML 4 (W3C) annotation attributes for screen-reader accessibility.
289
297
  // MathCAT, JAWS, and NVDA use `intent` to know how to pronounce
290
298
  // expressions; `arg` labels sub-expressions referenced by intent.
291
299
  // These are plain string annotations — no URL-loading, no code execution.
292
300
  "intent",
293
- "arg",
294
- "loading"
301
+ "arg"
295
302
  ],
296
303
  // Rails UJS turns data-method/data-remote/etc. on clickable elements into
297
304
  // state-changing requests carrying the victim's CSRF token. Strip them so
@@ -314,14 +321,14 @@ const m = /* @__PURE__ */ new Set([
314
321
  };
315
322
  let a = null;
316
323
  function A() {
317
- return a || (a = typeof u == "function" ? u(window) : u, a.addHook("afterSanitizeAttributes", (e) => {
318
- if (!(e instanceof Element) || !e.hasAttribute("style")) return;
319
- const r = e.style, t = [];
320
- for (let n = 0; n < r.length; n++) {
321
- const i = r.item(n);
322
- m.has(i) || t.push(i);
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);
323
330
  }
324
- for (const n of t) r.removeProperty(n);
331
+ for (const n of e) t.removeProperty(n);
325
332
  const o = /* @__PURE__ */ new Set([
326
333
  "static",
327
334
  "relative",
@@ -331,54 +338,56 @@ function A() {
331
338
  "unset",
332
339
  "revert",
333
340
  "revert-layer"
334
- ]), s = r.getPropertyValue("position").trim().toLowerCase();
335
- s && !o.has(s) && r.removeProperty("position");
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");
336
345
  for (const n of h) {
337
- const i = r.getPropertyValue(n);
338
- i && y.test(i) && r.removeProperty(n);
346
+ const s = t.getPropertyValue(n);
347
+ s && y.test(s) && t.removeProperty(n);
339
348
  }
340
- r.length === 0 && e.removeAttribute("style");
341
- }), a.addHook("uponSanitizeAttribute", (e, r) => {
342
- if (!w.has(r.attrName)) return;
343
- const t = r.attrValue;
344
- /^\s*\/\//.test(t) ? (r.attrValue = t.trimStart().replace(/^\/\//, "https://"), r.keepAttr = !0) : /^\s*\\/.test(t) && (r.keepAttr = !1);
345
- }), a.addHook("afterSanitizeAttributes", (e) => {
346
- if (!(e instanceof Element) || !e.hasAttribute("srcset")) return;
347
- const t = (e.getAttribute("srcset") ?? "").split(","), o = (i) => i.trim().split(/\s+/)[0];
348
- if (t.some((i) => /^\s*\\/.test(o(i)))) {
349
- e.removeAttribute("srcset");
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");
350
359
  return;
351
360
  }
352
- let s = !1;
353
- const n = t.map((i) => {
354
- const l = o(i);
355
- if (!l.startsWith("//")) return i;
356
- s = !0;
357
- const d = i.indexOf(l);
358
- return i.slice(0, d) + "https://" + l.slice(2) + i.slice(d + l.length);
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);
359
368
  });
360
- s && e.setAttribute("srcset", n.join(","));
361
- }), a.addHook("afterSanitizeAttributes", (e) => {
369
+ l && r.setAttribute("srcset", u.join(","));
370
+ }), a.addHook("afterSanitizeAttributes", (r) => {
362
371
  var o;
363
- if (!(e instanceof Element) || e.tagName !== "A" && e.tagName !== "AREA" || ((o = e.getAttribute("target")) == null ? void 0 : o.toLowerCase()) !== "_blank") return;
364
- const r = e.getAttribute("rel") ?? "", t = new Set(r.split(/\s+/).filter(Boolean));
365
- t.add("noopener"), e.setAttribute("rel", [...t].join(" "));
366
- }), a.addHook("afterSanitizeAttributes", (e) => {
367
- if (!(e instanceof Element) || e.tagName !== "IFRAME" || !e.hasAttribute("sandbox")) return;
368
- const t = (e.getAttribute("sandbox") ?? "").toLowerCase().split(/\s+/).filter(Boolean), o = t.filter((s) => k.has(s));
369
- o.length !== t.length && e.setAttribute("sandbox", o.join(" "));
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(" "));
370
379
  }), a);
371
380
  }
372
- function v(e, r) {
381
+ function v(r, t) {
373
382
  if (typeof window > "u")
374
383
  throw new Error("sanitizeHtml requires a DOM environment (window is not defined)");
375
- const t = r != null && r.allowFormAttributeNames ? { ...f, SANITIZE_DOM: !1 } : f;
376
- return A().sanitize(e ?? "", t);
384
+ const e = t != null && t.allowFormAttributeNames ? { ...f, SANITIZE_DOM: !1 } : f;
385
+ return A().sanitize(r ?? "", e);
377
386
  }
378
387
  const p = /* @__PURE__ */ new Set(["http:", "https:", "mailto:", "tel:"]), c = "http://platform-sanitize.invalid/", g = /^\s*\/\//, b = (
379
388
  // oxlint-disable-next-line no-control-regex -- intentional security guard
380
389
  /^[\u0000-\u0020\u007F-\u00A0\u2000-\u200F\u2028\u2029\u202F\u205F\u2060\u3000\uFEFF]*(?:javascript|data|vbscript|file):/i
381
- ), x = {
390
+ ), S = {
382
391
  colon: ":",
383
392
  // javascript&colon;alert(1) → javascript:alert(1)
384
393
  sol: "/",
@@ -391,35 +400,35 @@ const p = /* @__PURE__ */ new Set(["http:", "https:", "mailto:", "tel:"]), c = "
391
400
  `
392
401
  // java&NewLine;script:
393
402
  };
394
- function E(e) {
395
- return e.replace(/&#(\d+);/g, (r, t) => {
396
- const o = Number(t);
403
+ function E(r) {
404
+ return r.replace(/&#(\d+);/g, (t, e) => {
405
+ const o = Number(e);
397
406
  if (!Number.isFinite(o) || o < 0 || o > 1114111) return "";
398
407
  try {
399
408
  return String.fromCodePoint(o);
400
409
  } catch {
401
410
  return "";
402
411
  }
403
- }).replace(/&#x([\da-f]+);/gi, (r, t) => {
404
- const o = parseInt(t, 16);
412
+ }).replace(/&#x([\da-f]+);/gi, (t, e) => {
413
+ const o = parseInt(e, 16);
405
414
  if (!Number.isFinite(o) || o < 0 || o > 1114111) return "";
406
415
  try {
407
416
  return String.fromCodePoint(o);
408
417
  } catch {
409
418
  return "";
410
419
  }
411
- }).replace(/&([A-Za-z][A-Za-z0-9]*);/g, (r, t) => x[t] ?? r);
420
+ }).replace(/&([A-Za-z][A-Za-z0-9]*);/g, (t, e) => S[e] ?? t);
412
421
  }
413
- function R(e) {
414
- if (!e || !e.trim()) return "about:blank";
415
- const r = e.replace(/\\/g, "/");
416
- if (g.test(r) || b.test(r)) return "about:blank";
417
- if (/&[#A-Za-z]/.test(r)) {
418
- const t = E(r);
419
- if (g.test(t) || b.test(t))
422
+ function R(r) {
423
+ if (!r || !r.trim()) return "about:blank";
424
+ const t = r.replace(/\\/g, "/");
425
+ if (g.test(t) || b.test(t)) return "about:blank";
426
+ if (/&[#A-Za-z]/.test(t)) {
427
+ const e = E(t);
428
+ if (g.test(e) || b.test(e))
420
429
  return "about:blank";
421
430
  try {
422
- const o = new URL(t, c);
431
+ const o = new URL(e, c);
423
432
  if (!o.href.startsWith(c) && !p.has(o.protocol))
424
433
  return "about:blank";
425
434
  } catch {
@@ -427,8 +436,8 @@ function R(e) {
427
436
  }
428
437
  }
429
438
  try {
430
- const t = new URL(r, c);
431
- return !p.has(t.protocol) || (t.protocol === "http:" || t.protocol === "https:") && (t.username || t.password) ? "about:blank" : t.href.startsWith(c) ? e : r.replace(/[\x00-\x1F\u2028\u2029]/g, "").replace(/%250[9ad]/gi, "").replace(/%0[9ad]/gi, "");
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, "");
432
441
  } catch {
433
442
  return "about:blank";
434
443
  }
@@ -1 +1 @@
1
- {"version":3,"file":"sanitizeHtml.d.ts","sourceRoot":"","sources":["../src/sanitizeHtml.ts"],"names":[],"mappings":"AA6cA,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":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@instructure/platform-sanitize",
3
- "version": "0.3.15",
3
+ "version": "0.3.17",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",