@instructure/platform-sanitize 0.3.14 → 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,5 +1,5 @@
1
- import c from "dompurify";
2
- const p = /* @__PURE__ */ new Set([
1
+ import d from "dompurify";
2
+ const m = /* @__PURE__ */ new Set([
3
3
  // layout
4
4
  "display",
5
5
  "float",
@@ -8,6 +8,7 @@ const p = /* @__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 p = /* @__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",
@@ -236,7 +241,7 @@ const p = /* @__PURE__ */ new Set([
236
241
  "caret-color",
237
242
  "accent-color",
238
243
  "appearance"
239
- ]), m = /* @__PURE__ */ new Set([
244
+ ]), w = /* @__PURE__ */ new Set([
240
245
  "src",
241
246
  "href",
242
247
  "action",
@@ -247,7 +252,7 @@ const p = /* @__PURE__ */ new Set([
247
252
  "cite",
248
253
  "longdesc",
249
254
  "xlink:href"
250
- ]), u = /^\s*(\/\/|\\)/, w = [
255
+ ]), h = [
251
256
  "background",
252
257
  "background-image",
253
258
  "list-style",
@@ -256,7 +261,7 @@ const p = /* @__PURE__ */ new Set([
256
261
  // content: url(...) triggers an HTTP GET even on non-pseudo elements in some
257
262
  // browsers; strip it as defense-in-depth against tracking-pixel exfiltration.
258
263
  "content"
259
- ], h = /url\s*\(\s*['"]?(?:[a-z][a-z0-9+\-.]*:|\/\/)/i, y = /* @__PURE__ */ new Set([
264
+ ], y = /url\s*\(\s*['"]?(?:[a-z][a-z0-9+\-.]*:|\/\/)/i, k = /* @__PURE__ */ new Set([
260
265
  "allow-downloads",
261
266
  "allow-forms",
262
267
  "allow-modals",
@@ -269,7 +274,7 @@ const p = /* @__PURE__ */ new Set([
269
274
  "allow-scripts",
270
275
  "allow-storage-access-by-user-activation",
271
276
  "allow-top-navigation-by-user-activation"
272
- ]), d = {
277
+ ]), f = {
273
278
  ADD_TAGS: ["iframe"],
274
279
  ADD_ATTR: [
275
280
  "allowfullscreen",
@@ -285,13 +290,15 @@ const p = /* @__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
@@ -312,17 +319,17 @@ const p = /* @__PURE__ */ new Set([
312
319
  // confusion attacks where fragments like <svg> could influence parse context.
313
320
  FORCE_BODY: !0
314
321
  };
315
- let i = null;
316
- function k() {
317
- return i || (i = typeof c == "function" ? c(window) : c, i.addHook("afterSanitizeAttributes", (t) => {
318
- if (!(t instanceof Element) || !t.hasAttribute("style")) return;
319
- const e = t.style, o = [];
320
- for (let n = 0; n < e.length; n++) {
321
- const a = e.item(n);
322
- p.has(a) || o.push(a);
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);
323
330
  }
324
- for (const n of o) e.removeProperty(n);
325
- const r = /* @__PURE__ */ new Set([
331
+ for (const n of e) t.removeProperty(n);
332
+ const o = /* @__PURE__ */ new Set([
326
333
  "static",
327
334
  "relative",
328
335
  "absolute",
@@ -331,39 +338,56 @@ function k() {
331
338
  "unset",
332
339
  "revert",
333
340
  "revert-layer"
334
- ]), s = e.getPropertyValue("position").trim().toLowerCase();
335
- s && !r.has(s) && e.removeProperty("position");
336
- for (const n of w) {
337
- const a = e.getPropertyValue(n);
338
- a && h.test(a) && e.removeProperty(n);
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);
339
348
  }
340
- e.length === 0 && t.removeAttribute("style");
341
- }), i.addHook("uponSanitizeAttribute", (t, e) => {
342
- m.has(e.attrName) && u.test(e.attrValue) && (e.keepAttr = !1);
343
- }), i.addHook("afterSanitizeAttributes", (t) => {
344
- if (!(t instanceof Element) || !t.hasAttribute("srcset")) return;
345
- (t.getAttribute("srcset") ?? "").split(",").map((r) => r.trim().split(/\s+/)[0]).some((r) => u.test(r)) && t.removeAttribute("srcset");
346
- }), i.addHook("afterSanitizeAttributes", (t) => {
347
- var r;
348
- if (!(t instanceof Element) || t.tagName !== "A" && t.tagName !== "AREA" || ((r = t.getAttribute("target")) == null ? void 0 : r.toLowerCase()) !== "_blank") return;
349
- const e = t.getAttribute("rel") ?? "", o = new Set(e.split(/\s+/).filter(Boolean));
350
- o.add("noopener"), t.setAttribute("rel", [...o].join(" "));
351
- }), i.addHook("afterSanitizeAttributes", (t) => {
352
- if (!(t instanceof Element) || t.tagName !== "IFRAME" || !t.hasAttribute("sandbox")) return;
353
- const o = (t.getAttribute("sandbox") ?? "").toLowerCase().split(/\s+/).filter(Boolean), r = o.filter((s) => y.has(s));
354
- r.length !== o.length && t.setAttribute("sandbox", r.join(" "));
355
- }), i);
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");
359
+ return;
360
+ }
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);
368
+ });
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);
356
380
  }
357
- function R(t, e) {
381
+ function v(r, t) {
358
382
  if (typeof window > "u")
359
383
  throw new Error("sanitizeHtml requires a DOM environment (window is not defined)");
360
- const o = e != null && e.allowFormAttributeNames ? { ...d, SANITIZE_DOM: !1 } : d;
361
- return k().sanitize(t ?? "", o);
384
+ const e = t != null && t.allowFormAttributeNames ? { ...f, SANITIZE_DOM: !1 } : f;
385
+ return A().sanitize(r ?? "", e);
362
386
  }
363
- const f = /* @__PURE__ */ new Set(["http:", "https:", "mailto:", "tel:"]), l = "http://platform-sanitize.invalid/", b = /^\s*\/\//, g = (
387
+ const p = /* @__PURE__ */ new Set(["http:", "https:", "mailto:", "tel:"]), c = "http://platform-sanitize.invalid/", g = /^\s*\/\//, b = (
364
388
  // oxlint-disable-next-line no-control-regex -- intentional security guard
365
389
  /^[\u0000-\u0020\u007F-\u00A0\u2000-\u200F\u2028\u2029\u202F\u205F\u2060\u3000\uFEFF]*(?:javascript|data|vbscript|file):/i
366
- ), A = {
390
+ ), S = {
367
391
  colon: ":",
368
392
  // javascript&colon;alert(1) → javascript:alert(1)
369
393
  sol: "/",
@@ -376,49 +400,49 @@ const f = /* @__PURE__ */ new Set(["http:", "https:", "mailto:", "tel:"]), l = "
376
400
  `
377
401
  // java&NewLine;script:
378
402
  };
379
- function E(t) {
380
- return t.replace(/&#(\d+);/g, (e, o) => {
381
- const r = Number(o);
382
- if (!Number.isFinite(r) || r < 0 || r > 1114111) return "";
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 "";
383
407
  try {
384
- return String.fromCodePoint(r);
408
+ return String.fromCodePoint(o);
385
409
  } catch {
386
410
  return "";
387
411
  }
388
- }).replace(/&#x([\da-f]+);/gi, (e, o) => {
389
- const r = parseInt(o, 16);
390
- if (!Number.isFinite(r) || r < 0 || r > 1114111) return "";
412
+ }).replace(/&#x([\da-f]+);/gi, (t, e) => {
413
+ const o = parseInt(e, 16);
414
+ if (!Number.isFinite(o) || o < 0 || o > 1114111) return "";
391
415
  try {
392
- return String.fromCodePoint(r);
416
+ return String.fromCodePoint(o);
393
417
  } catch {
394
418
  return "";
395
419
  }
396
- }).replace(/&([A-Za-z][A-Za-z0-9]*);/g, (e, o) => A[o] ?? e);
420
+ }).replace(/&([A-Za-z][A-Za-z0-9]*);/g, (t, e) => S[e] ?? t);
397
421
  }
398
- function S(t) {
399
- if (!t || !t.trim()) return "about:blank";
400
- const e = t.replace(/\\/g, "/");
401
- if (b.test(e) || g.test(e)) return "about:blank";
402
- if (/&[#A-Za-z]/.test(e)) {
403
- const o = E(e);
404
- if (b.test(o) || g.test(o))
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))
405
429
  return "about:blank";
406
430
  try {
407
- const r = new URL(o, l);
408
- if (!r.href.startsWith(l) && !f.has(r.protocol))
431
+ const o = new URL(e, c);
432
+ if (!o.href.startsWith(c) && !p.has(o.protocol))
409
433
  return "about:blank";
410
434
  } catch {
411
435
  return "about:blank";
412
436
  }
413
437
  }
414
438
  try {
415
- const o = new URL(e, l);
416
- return !f.has(o.protocol) || (o.protocol === "http:" || o.protocol === "https:") && (o.username || o.password) ? "about:blank" : o.href.startsWith(l) ? t : e.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, "");
417
441
  } catch {
418
442
  return "about:blank";
419
443
  }
420
444
  }
421
445
  export {
422
- R as sanitizeHtml,
423
- S as sanitizeUrl
446
+ v as sanitizeHtml,
447
+ R as sanitizeUrl
424
448
  };
@@ -1 +1 @@
1
- {"version":3,"file":"sanitizeHtml.d.ts","sourceRoot":"","sources":["../src/sanitizeHtml.ts"],"names":[],"mappings":"AAybA,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"}
@@ -1 +1 @@
1
- {"version":3,"file":"sanitizeUrl.d.ts","sourceRoot":"","sources":["../src/sanitizeUrl.ts"],"names":[],"mappings":"AAoFA,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAwDlE"}
1
+ {"version":3,"file":"sanitizeUrl.d.ts","sourceRoot":"","sources":["../src/sanitizeUrl.ts"],"names":[],"mappings":"AAsFA,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAwDlE"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@instructure/platform-sanitize",
3
- "version": "0.3.14",
3
+ "version": "0.3.17",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",