@instructure/platform-sanitize 0.3.13 → 0.3.15

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 u from "dompurify";
2
+ const m = /* @__PURE__ */ new Set([
3
3
  // layout
4
4
  "display",
5
5
  "float",
@@ -228,14 +228,15 @@ const p = /* @__PURE__ */ new Set([
228
228
  "counter-increment",
229
229
  "content",
230
230
  // UI / interaction
231
- // pointer-events is included: RCE uses it legitimately (e.g. non-interactive
232
- // decorative overlays). It is not in the overlay-phishing class because it
233
- // cannot reposition elements — position/z-index remain blocked.
231
+ // pointer-events and z-index are safe: neither can reposition elements
232
+ // outside the container. The overlay-phishing risk comes from position:fixed/sticky,
233
+ // which SAFE_POSITION_VALUES strips.
234
234
  "pointer-events",
235
+ "z-index",
235
236
  "caret-color",
236
237
  "accent-color",
237
238
  "appearance"
238
- ]), m = /* @__PURE__ */ new Set([
239
+ ]), w = /* @__PURE__ */ new Set([
239
240
  "src",
240
241
  "href",
241
242
  "action",
@@ -246,7 +247,7 @@ const p = /* @__PURE__ */ new Set([
246
247
  "cite",
247
248
  "longdesc",
248
249
  "xlink:href"
249
- ]), u = /^\s*(\/\/|\\)/, w = [
250
+ ]), h = [
250
251
  "background",
251
252
  "background-image",
252
253
  "list-style",
@@ -255,7 +256,7 @@ const p = /* @__PURE__ */ new Set([
255
256
  // content: url(...) triggers an HTTP GET even on non-pseudo elements in some
256
257
  // browsers; strip it as defense-in-depth against tracking-pixel exfiltration.
257
258
  "content"
258
- ], h = /url\s*\(\s*['"]?(?:[a-z][a-z0-9+\-.]*:|\/\/)/i, y = /* @__PURE__ */ new Set([
259
+ ], y = /url\s*\(\s*['"]?(?:[a-z][a-z0-9+\-.]*:|\/\/)/i, k = /* @__PURE__ */ new Set([
259
260
  "allow-downloads",
260
261
  "allow-forms",
261
262
  "allow-modals",
@@ -268,7 +269,7 @@ const p = /* @__PURE__ */ new Set([
268
269
  "allow-scripts",
269
270
  "allow-storage-access-by-user-activation",
270
271
  "allow-top-navigation-by-user-activation"
271
- ]), d = {
272
+ ]), f = {
272
273
  ADD_TAGS: ["iframe"],
273
274
  ADD_ATTR: [
274
275
  "allowfullscreen",
@@ -289,7 +290,8 @@ const p = /* @__PURE__ */ new Set([
289
290
  // expressions; `arg` labels sub-expressions referenced by intent.
290
291
  // These are plain string annotations — no URL-loading, no code execution.
291
292
  "intent",
292
- "arg"
293
+ "arg",
294
+ "loading"
293
295
  ],
294
296
  // Rails UJS turns data-method/data-remote/etc. on clickable elements into
295
297
  // state-changing requests carrying the victim's CSRF token. Strip them so
@@ -310,17 +312,17 @@ const p = /* @__PURE__ */ new Set([
310
312
  // confusion attacks where fragments like <svg> could influence parse context.
311
313
  FORCE_BODY: !0
312
314
  };
313
- let i = null;
314
- function k() {
315
- return i || (i = typeof c == "function" ? c(window) : c, i.addHook("afterSanitizeAttributes", (t) => {
316
- if (!(t instanceof Element) || !t.hasAttribute("style")) return;
317
- const e = t.style, o = [];
318
- for (let n = 0; n < e.length; n++) {
319
- const a = e.item(n);
320
- p.has(a) || o.push(a);
315
+ let a = null;
316
+ 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);
321
323
  }
322
- for (const n of o) e.removeProperty(n);
323
- const r = /* @__PURE__ */ new Set([
324
+ for (const n of t) r.removeProperty(n);
325
+ const o = /* @__PURE__ */ new Set([
324
326
  "static",
325
327
  "relative",
326
328
  "absolute",
@@ -329,39 +331,54 @@ function k() {
329
331
  "unset",
330
332
  "revert",
331
333
  "revert-layer"
332
- ]), s = e.getPropertyValue("position").trim().toLowerCase();
333
- s && !r.has(s) && e.removeProperty("position");
334
- for (const n of w) {
335
- const a = e.getPropertyValue(n);
336
- a && h.test(a) && e.removeProperty(n);
334
+ ]), s = r.getPropertyValue("position").trim().toLowerCase();
335
+ s && !o.has(s) && r.removeProperty("position");
336
+ for (const n of h) {
337
+ const i = r.getPropertyValue(n);
338
+ i && y.test(i) && r.removeProperty(n);
337
339
  }
338
- e.length === 0 && t.removeAttribute("style");
339
- }), i.addHook("uponSanitizeAttribute", (t, e) => {
340
- m.has(e.attrName) && u.test(e.attrValue) && (e.keepAttr = !1);
341
- }), i.addHook("afterSanitizeAttributes", (t) => {
342
- if (!(t instanceof Element) || !t.hasAttribute("srcset")) return;
343
- (t.getAttribute("srcset") ?? "").split(",").map((r) => r.trim().split(/\s+/)[0]).some((r) => u.test(r)) && t.removeAttribute("srcset");
344
- }), i.addHook("afterSanitizeAttributes", (t) => {
345
- var r;
346
- if (!(t instanceof Element) || t.tagName !== "A" && t.tagName !== "AREA" || ((r = t.getAttribute("target")) == null ? void 0 : r.toLowerCase()) !== "_blank") return;
347
- const e = t.getAttribute("rel") ?? "", o = new Set(e.split(/\s+/).filter(Boolean));
348
- o.add("noopener"), t.setAttribute("rel", [...o].join(" "));
349
- }), i.addHook("afterSanitizeAttributes", (t) => {
350
- if (!(t instanceof Element) || t.tagName !== "IFRAME" || !t.hasAttribute("sandbox")) return;
351
- const o = (t.getAttribute("sandbox") ?? "").toLowerCase().split(/\s+/).filter(Boolean), r = o.filter((s) => y.has(s));
352
- r.length !== o.length && t.setAttribute("sandbox", r.join(" "));
353
- }), i);
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");
350
+ return;
351
+ }
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);
359
+ });
360
+ s && e.setAttribute("srcset", n.join(","));
361
+ }), a.addHook("afterSanitizeAttributes", (e) => {
362
+ 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(" "));
370
+ }), a);
354
371
  }
355
- function R(t, e) {
372
+ function v(e, r) {
356
373
  if (typeof window > "u")
357
374
  throw new Error("sanitizeHtml requires a DOM environment (window is not defined)");
358
- const o = e != null && e.allowFormAttributeNames ? { ...d, SANITIZE_DOM: !1 } : d;
359
- return k().sanitize(t ?? "", o);
375
+ const t = r != null && r.allowFormAttributeNames ? { ...f, SANITIZE_DOM: !1 } : f;
376
+ return A().sanitize(e ?? "", t);
360
377
  }
361
- const f = /* @__PURE__ */ new Set(["http:", "https:", "mailto:", "tel:"]), l = "http://platform-sanitize.invalid/", b = /^\s*\/\//, g = (
378
+ const p = /* @__PURE__ */ new Set(["http:", "https:", "mailto:", "tel:"]), c = "http://platform-sanitize.invalid/", g = /^\s*\/\//, b = (
362
379
  // oxlint-disable-next-line no-control-regex -- intentional security guard
363
380
  /^[\u0000-\u0020\u007F-\u00A0\u2000-\u200F\u2028\u2029\u202F\u205F\u2060\u3000\uFEFF]*(?:javascript|data|vbscript|file):/i
364
- ), A = {
381
+ ), x = {
365
382
  colon: ":",
366
383
  // javascript&colon;alert(1) → javascript:alert(1)
367
384
  sol: "/",
@@ -374,49 +391,49 @@ const f = /* @__PURE__ */ new Set(["http:", "https:", "mailto:", "tel:"]), l = "
374
391
  `
375
392
  // java&NewLine;script:
376
393
  };
377
- function E(t) {
378
- return t.replace(/&#(\d+);/g, (e, o) => {
379
- const r = Number(o);
380
- if (!Number.isFinite(r) || r < 0 || r > 1114111) return "";
394
+ function E(e) {
395
+ return e.replace(/&#(\d+);/g, (r, t) => {
396
+ const o = Number(t);
397
+ if (!Number.isFinite(o) || o < 0 || o > 1114111) return "";
381
398
  try {
382
- return String.fromCodePoint(r);
399
+ return String.fromCodePoint(o);
383
400
  } catch {
384
401
  return "";
385
402
  }
386
- }).replace(/&#x([\da-f]+);/gi, (e, o) => {
387
- const r = parseInt(o, 16);
388
- if (!Number.isFinite(r) || r < 0 || r > 1114111) return "";
403
+ }).replace(/&#x([\da-f]+);/gi, (r, t) => {
404
+ const o = parseInt(t, 16);
405
+ if (!Number.isFinite(o) || o < 0 || o > 1114111) return "";
389
406
  try {
390
- return String.fromCodePoint(r);
407
+ return String.fromCodePoint(o);
391
408
  } catch {
392
409
  return "";
393
410
  }
394
- }).replace(/&([A-Za-z][A-Za-z0-9]*);/g, (e, o) => A[o] ?? e);
411
+ }).replace(/&([A-Za-z][A-Za-z0-9]*);/g, (r, t) => x[t] ?? r);
395
412
  }
396
- function S(t) {
397
- if (!t || !t.trim()) return "about:blank";
398
- const e = t.replace(/\\/g, "/");
399
- if (b.test(e) || g.test(e)) return "about:blank";
400
- if (/&[#A-Za-z]/.test(e)) {
401
- const o = E(e);
402
- if (b.test(o) || g.test(o))
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))
403
420
  return "about:blank";
404
421
  try {
405
- const r = new URL(o, l);
406
- if (!r.href.startsWith(l) && !f.has(r.protocol))
422
+ const o = new URL(t, c);
423
+ if (!o.href.startsWith(c) && !p.has(o.protocol))
407
424
  return "about:blank";
408
425
  } catch {
409
426
  return "about:blank";
410
427
  }
411
428
  }
412
429
  try {
413
- const o = new URL(e, l);
414
- 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, "");
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, "");
415
432
  } catch {
416
433
  return "about:blank";
417
434
  }
418
435
  }
419
436
  export {
420
- R as sanitizeHtml,
421
- S as sanitizeUrl
437
+ v as sanitizeHtml,
438
+ R as sanitizeUrl
422
439
  };
@@ -1 +1 @@
1
- {"version":3,"file":"sanitizeHtml.d.ts","sourceRoot":"","sources":["../src/sanitizeHtml.ts"],"names":[],"mappings":"AAubA,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":"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 +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.13",
3
+ "version": "0.3.15",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",