@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 +91 -67
- package/dist/sanitizeHtml.d.ts.map +1 -1
- package/dist/sanitizeUrl.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import
|
|
2
|
-
const
|
|
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
|
-
]),
|
|
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
|
-
]),
|
|
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
|
-
],
|
|
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
|
-
]),
|
|
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
|
|
316
|
-
function
|
|
317
|
-
return
|
|
318
|
-
if (!(
|
|
319
|
-
const
|
|
320
|
-
for (let n = 0; n <
|
|
321
|
-
const
|
|
322
|
-
|
|
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
|
|
325
|
-
const
|
|
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
|
-
]),
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
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
|
-
|
|
341
|
-
}),
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
if (
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
const
|
|
354
|
-
|
|
355
|
-
|
|
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
|
|
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
|
|
361
|
-
return
|
|
384
|
+
const e = t != null && t.allowFormAttributeNames ? { ...f, SANITIZE_DOM: !1 } : f;
|
|
385
|
+
return A().sanitize(r ?? "", e);
|
|
362
386
|
}
|
|
363
|
-
const
|
|
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
|
-
),
|
|
390
|
+
), S = {
|
|
367
391
|
colon: ":",
|
|
368
392
|
// javascript: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
script:
|
|
378
402
|
};
|
|
379
|
-
function E(
|
|
380
|
-
return
|
|
381
|
-
const
|
|
382
|
-
if (!Number.isFinite(
|
|
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(
|
|
408
|
+
return String.fromCodePoint(o);
|
|
385
409
|
} catch {
|
|
386
410
|
return "";
|
|
387
411
|
}
|
|
388
|
-
}).replace(/&#x([\da-f]+);/gi, (
|
|
389
|
-
const
|
|
390
|
-
if (!Number.isFinite(
|
|
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(
|
|
416
|
+
return String.fromCodePoint(o);
|
|
393
417
|
} catch {
|
|
394
418
|
return "";
|
|
395
419
|
}
|
|
396
|
-
}).replace(/&([A-Za-z][A-Za-z0-9]*);/g, (
|
|
420
|
+
}).replace(/&([A-Za-z][A-Za-z0-9]*);/g, (t, e) => S[e] ?? t);
|
|
397
421
|
}
|
|
398
|
-
function
|
|
399
|
-
if (!
|
|
400
|
-
const
|
|
401
|
-
if (
|
|
402
|
-
if (/&[#A-Za-z]/.test(
|
|
403
|
-
const
|
|
404
|
-
if (
|
|
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
|
|
408
|
-
if (!
|
|
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
|
|
416
|
-
return !
|
|
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
|
-
|
|
423
|
-
|
|
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":"
|
|
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":"
|
|
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"}
|