react-next-editor-js 0.1.0

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.
Files changed (102) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +877 -0
  3. package/dist/chunk-3QWXTDLY.cjs +486 -0
  4. package/dist/chunk-3QWXTDLY.cjs.map +1 -0
  5. package/dist/chunk-5F6SPYCN.cjs +180 -0
  6. package/dist/chunk-5F6SPYCN.cjs.map +1 -0
  7. package/dist/chunk-6NTSXJX4.js +174 -0
  8. package/dist/chunk-6NTSXJX4.js.map +1 -0
  9. package/dist/chunk-7VYJDBH7.js +261 -0
  10. package/dist/chunk-7VYJDBH7.js.map +1 -0
  11. package/dist/chunk-DBSFCCBG.cjs +1712 -0
  12. package/dist/chunk-DBSFCCBG.cjs.map +1 -0
  13. package/dist/chunk-EFE6RHDL.cjs +4 -0
  14. package/dist/chunk-EFE6RHDL.cjs.map +1 -0
  15. package/dist/chunk-G6YRIEK4.js +3 -0
  16. package/dist/chunk-G6YRIEK4.js.map +1 -0
  17. package/dist/chunk-GFNFJ3FL.cjs +119 -0
  18. package/dist/chunk-GFNFJ3FL.cjs.map +1 -0
  19. package/dist/chunk-IG2YLUFW.js +114 -0
  20. package/dist/chunk-IG2YLUFW.js.map +1 -0
  21. package/dist/chunk-JQXTWLHL.js +176 -0
  22. package/dist/chunk-JQXTWLHL.js.map +1 -0
  23. package/dist/chunk-NJCEHQV3.cjs +454 -0
  24. package/dist/chunk-NJCEHQV3.cjs.map +1 -0
  25. package/dist/chunk-O4GTLC3T.js +478 -0
  26. package/dist/chunk-O4GTLC3T.js.map +1 -0
  27. package/dist/chunk-ODHABIIC.cjs +82 -0
  28. package/dist/chunk-ODHABIIC.cjs.map +1 -0
  29. package/dist/chunk-PZ5AY32C.js +9 -0
  30. package/dist/chunk-PZ5AY32C.js.map +1 -0
  31. package/dist/chunk-Q7SFCCGT.cjs +11 -0
  32. package/dist/chunk-Q7SFCCGT.cjs.map +1 -0
  33. package/dist/chunk-QIUIYBCZ.js +80 -0
  34. package/dist/chunk-QIUIYBCZ.js.map +1 -0
  35. package/dist/chunk-QROUNVQK.js +450 -0
  36. package/dist/chunk-QROUNVQK.js.map +1 -0
  37. package/dist/chunk-T6FR37IC.js +41 -0
  38. package/dist/chunk-T6FR37IC.js.map +1 -0
  39. package/dist/chunk-TI44I654.cjs +265 -0
  40. package/dist/chunk-TI44I654.cjs.map +1 -0
  41. package/dist/chunk-TXPLBAH5.cjs +47 -0
  42. package/dist/chunk-TXPLBAH5.cjs.map +1 -0
  43. package/dist/chunk-U3O54IYI.cjs +187 -0
  44. package/dist/chunk-U3O54IYI.cjs.map +1 -0
  45. package/dist/chunk-VLC7SZMT.js +1669 -0
  46. package/dist/chunk-VLC7SZMT.js.map +1 -0
  47. package/dist/core/index.cjs +232 -0
  48. package/dist/core/index.cjs.map +1 -0
  49. package/dist/core/index.d.cts +122 -0
  50. package/dist/core/index.d.ts +122 -0
  51. package/dist/core/index.js +7 -0
  52. package/dist/core/index.js.map +1 -0
  53. package/dist/defaults-EQD5QKCU.js +4 -0
  54. package/dist/defaults-EQD5QKCU.js.map +1 -0
  55. package/dist/defaults-MLYXD2BG.cjs +49 -0
  56. package/dist/defaults-MLYXD2BG.cjs.map +1 -0
  57. package/dist/docx-BUrf4PFj.d.ts +49 -0
  58. package/dist/docx-DLfSdvXm.d.cts +49 -0
  59. package/dist/docx-LDETXV3L.js +5 -0
  60. package/dist/docx-LDETXV3L.js.map +1 -0
  61. package/dist/docx-N2LKIOK3.cjs +14 -0
  62. package/dist/docx-N2LKIOK3.cjs.map +1 -0
  63. package/dist/export/index.cjs +54 -0
  64. package/dist/export/index.cjs.map +1 -0
  65. package/dist/export/index.d.cts +60 -0
  66. package/dist/export/index.d.ts +60 -0
  67. package/dist/export/index.js +9 -0
  68. package/dist/export/index.js.map +1 -0
  69. package/dist/html-5BXJPQU3.js +7 -0
  70. package/dist/html-5BXJPQU3.js.map +1 -0
  71. package/dist/html-KU2KHLRF.cjs +24 -0
  72. package/dist/html-KU2KHLRF.cjs.map +1 -0
  73. package/dist/import/index.cjs +15 -0
  74. package/dist/import/index.cjs.map +1 -0
  75. package/dist/import/index.d.cts +37 -0
  76. package/dist/import/index.d.ts +37 -0
  77. package/dist/import/index.js +6 -0
  78. package/dist/import/index.js.map +1 -0
  79. package/dist/index.cjs +1035 -0
  80. package/dist/index.cjs.map +1 -0
  81. package/dist/index.d.cts +248 -0
  82. package/dist/index.d.ts +248 -0
  83. package/dist/index.js +885 -0
  84. package/dist/index.js.map +1 -0
  85. package/dist/persistence/index.cjs +37 -0
  86. package/dist/persistence/index.cjs.map +1 -0
  87. package/dist/persistence/index.d.cts +279 -0
  88. package/dist/persistence/index.d.ts +279 -0
  89. package/dist/persistence/index.js +4 -0
  90. package/dist/persistence/index.js.map +1 -0
  91. package/dist/sanitize-7IZ-SW1f.d.ts +361 -0
  92. package/dist/sanitize-CvmgqbsA.d.cts +361 -0
  93. package/dist/server/index.cjs +400 -0
  94. package/dist/server/index.cjs.map +1 -0
  95. package/dist/server/index.d.cts +229 -0
  96. package/dist/server/index.d.ts +229 -0
  97. package/dist/server/index.js +390 -0
  98. package/dist/server/index.js.map +1 -0
  99. package/dist/styles.css +680 -0
  100. package/dist/types-B4z0Quvv.d.cts +193 -0
  101. package/dist/types-B4z0Quvv.d.ts +193 -0
  102. package/package.json +183 -0
@@ -0,0 +1,180 @@
1
+ 'use strict';
2
+
3
+ // src/security/sanitize.ts
4
+ var SAFE_LINK_SCHEMES = /* @__PURE__ */ new Set(["http:", "https:", "mailto:", "tel:", "ftp:"]);
5
+ var SAFE_IMAGE_SCHEMES = /* @__PURE__ */ new Set(["http:", "https:", "blob:"]);
6
+ var MAX_DATA_URI_LENGTH = 35e5;
7
+ var MAX_URL_LENGTH = 16384;
8
+ var DANGEROUS_SCHEME = /^(javascript|vbscript|data|file):/i;
9
+ function stripControlChars(value) {
10
+ let out = "";
11
+ for (let i = 0; i < value.length; i++) {
12
+ const code = value.charCodeAt(i);
13
+ if (code <= 32 || code >= 127 && code <= 159) continue;
14
+ out += value[i];
15
+ }
16
+ return out;
17
+ }
18
+ function sanitizeUrl(raw) {
19
+ if (!raw) return null;
20
+ if (raw.length > MAX_URL_LENGTH) return null;
21
+ const value = raw.trim();
22
+ if (!value) return null;
23
+ const cleaned = stripControlChars(value);
24
+ if (!cleaned) return null;
25
+ if (DANGEROUS_SCHEME.test(cleaned)) return null;
26
+ if (cleaned.startsWith("//")) return `https:${cleaned}`;
27
+ if (cleaned.startsWith("/") || cleaned.startsWith("#") || cleaned.startsWith("?") || cleaned.startsWith("./") || cleaned.startsWith("../")) {
28
+ return cleaned;
29
+ }
30
+ const schemeMatch = /^([a-z][a-z0-9+.-]*):/i.exec(cleaned);
31
+ if (schemeMatch) {
32
+ const scheme = `${schemeMatch[1].toLowerCase()}:`;
33
+ return SAFE_LINK_SCHEMES.has(scheme) ? cleaned : null;
34
+ }
35
+ return cleaned;
36
+ }
37
+ function sanitizeImageSrc(raw) {
38
+ if (!raw) return null;
39
+ if (raw.length > MAX_DATA_URI_LENGTH) return null;
40
+ const value = raw.trim();
41
+ if (!value) return null;
42
+ const cleaned = stripControlChars(value);
43
+ if (!cleaned) return null;
44
+ if (/^data:/i.test(cleaned)) {
45
+ if (cleaned.length > MAX_DATA_URI_LENGTH) return null;
46
+ if (/^data:image\/(png|jpe?g|gif|webp|bmp|x-icon|vnd\.microsoft\.icon);/i.test(cleaned)) {
47
+ return cleaned;
48
+ }
49
+ return null;
50
+ }
51
+ if (cleaned.startsWith("//")) return `https:${cleaned}`;
52
+ if (cleaned.startsWith("/") || cleaned.startsWith("./") || cleaned.startsWith("../")) {
53
+ return cleaned;
54
+ }
55
+ const schemeMatch = /^([a-z][a-z0-9+.-]*):/i.exec(cleaned);
56
+ if (schemeMatch) {
57
+ const scheme = `${schemeMatch[1].toLowerCase()}:`;
58
+ return SAFE_IMAGE_SCHEMES.has(scheme) ? cleaned : null;
59
+ }
60
+ return cleaned;
61
+ }
62
+ var ALLOWED_HTML_TAGS = [
63
+ "p",
64
+ "br",
65
+ "span",
66
+ "div",
67
+ "h1",
68
+ "h2",
69
+ "h3",
70
+ "h4",
71
+ "h5",
72
+ "h6",
73
+ "strong",
74
+ "b",
75
+ "em",
76
+ "i",
77
+ "u",
78
+ "s",
79
+ "del",
80
+ "strike",
81
+ "sub",
82
+ "sup",
83
+ "mark",
84
+ "code",
85
+ "pre",
86
+ "blockquote",
87
+ "ul",
88
+ "ol",
89
+ "li",
90
+ "a",
91
+ "img",
92
+ "table",
93
+ "thead",
94
+ "tbody",
95
+ "tfoot",
96
+ "tr",
97
+ "th",
98
+ "td",
99
+ "hr",
100
+ "caption",
101
+ "colgroup",
102
+ "col"
103
+ ];
104
+ var ALLOWED_HTML_ATTRS = [
105
+ "href",
106
+ "title",
107
+ "target",
108
+ "src",
109
+ "alt",
110
+ "width",
111
+ "height",
112
+ "colspan",
113
+ "rowspan",
114
+ "style",
115
+ "align",
116
+ "start",
117
+ "type",
118
+ "data-checked"
119
+ ];
120
+ var purifierPromise = null;
121
+ var cachedPurify = null;
122
+ async function getPurifier() {
123
+ if (purifierPromise) return purifierPromise;
124
+ purifierPromise = (async () => {
125
+ if (typeof window === "undefined") return null;
126
+ const mod = await import('dompurify');
127
+ const factory = mod.default ?? mod;
128
+ const purify = factory(window);
129
+ purify.addHook("afterSanitizeAttributes", (node) => {
130
+ const el = node;
131
+ if (el.hasAttribute && el.hasAttribute("href")) {
132
+ const safe = sanitizeUrl(el.getAttribute("href"));
133
+ if (safe) el.setAttribute("href", safe);
134
+ else el.removeAttribute("href");
135
+ }
136
+ if (el.tagName === "IMG" && el.hasAttribute("src")) {
137
+ const safe = sanitizeImageSrc(el.getAttribute("src"));
138
+ if (safe) el.setAttribute("src", safe);
139
+ else el.removeAttribute("src");
140
+ }
141
+ if (el.tagName === "A") {
142
+ el.setAttribute("rel", "noopener noreferrer nofollow");
143
+ }
144
+ });
145
+ const fn = (html) => purify.sanitize(html, {
146
+ // An explicit allow-list (do not combine with USE_PROFILES, which would
147
+ // widen the tag set and conflict with ALLOWED_TAGS).
148
+ ALLOWED_TAGS: ALLOWED_HTML_TAGS,
149
+ ALLOWED_ATTR: ALLOWED_HTML_ATTRS,
150
+ FORBID_TAGS: ["script", "style", "iframe", "object", "embed", "form", "svg", "math"],
151
+ FORBID_ATTR: ["srcset"],
152
+ ALLOW_DATA_ATTR: false,
153
+ ALLOW_UNKNOWN_PROTOCOLS: false
154
+ });
155
+ cachedPurify = fn;
156
+ return fn;
157
+ })();
158
+ return purifierPromise;
159
+ }
160
+ async function preloadSanitizer() {
161
+ await getPurifier();
162
+ }
163
+ function basicScrubHtml(html) {
164
+ return html.replace(/<\s*(script|style|iframe|object|embed|svg|math)\b[\s\S]*?<\s*\/\s*\1\s*>/gi, "").replace(/<\s*(script|style|iframe|object|embed|svg|math)\b[^>]*>/gi, "").replace(/\son[a-z]+\s*=\s*"[^"]*"/gi, "").replace(/\son[a-z]+\s*=\s*'[^']*'/gi, "").replace(/\son[a-z]+\s*=\s*[^\s>]+/gi, "").replace(/(href|src)\s*=\s*"\s*(?:javascript|vbscript|data:text\/html)[^"]*"/gi, '$1="#"').replace(/(href|src)\s*=\s*'\s*(?:javascript|vbscript|data:text\/html)[^']*'/gi, "$1='#'");
165
+ }
166
+ function sanitizeHtmlSync(html) {
167
+ return cachedPurify ? cachedPurify(html) : basicScrubHtml(html);
168
+ }
169
+ async function sanitizeHtml(html) {
170
+ const purify = await getPurifier();
171
+ return purify ? purify(html) : basicScrubHtml(html);
172
+ }
173
+
174
+ exports.preloadSanitizer = preloadSanitizer;
175
+ exports.sanitizeHtml = sanitizeHtml;
176
+ exports.sanitizeHtmlSync = sanitizeHtmlSync;
177
+ exports.sanitizeImageSrc = sanitizeImageSrc;
178
+ exports.sanitizeUrl = sanitizeUrl;
179
+ //# sourceMappingURL=chunk-5F6SPYCN.cjs.map
180
+ //# sourceMappingURL=chunk-5F6SPYCN.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/security/sanitize.ts"],"names":[],"mappings":";;;AAQA,IAAM,iBAAA,uBAAwB,GAAA,CAAI,CAAC,SAAS,QAAA,EAAU,SAAA,EAAW,MAAA,EAAQ,MAAM,CAAC,CAAA;AAGhF,IAAM,qCAAqB,IAAI,GAAA,CAAI,CAAC,OAAA,EAAS,QAAA,EAAU,OAAO,CAAC,CAAA;AAG/D,IAAM,mBAAA,GAAsB,IAAA;AAE5B,IAAM,cAAA,GAAiB,KAAA;AAEvB,IAAM,gBAAA,GAAmB,oCAAA;AAOzB,SAAS,kBAAkB,KAAA,EAAuB;AAChD,EAAA,IAAI,GAAA,GAAM,EAAA;AACV,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,KAAA,CAAM,QAAQ,CAAA,EAAA,EAAK;AACrC,IAAA,MAAM,IAAA,GAAO,KAAA,CAAM,UAAA,CAAW,CAAC,CAAA;AAE/B,IAAA,IAAI,IAAA,IAAQ,EAAA,IAAS,IAAA,IAAQ,GAAA,IAAQ,QAAQ,GAAA,EAAO;AACpD,IAAA,GAAA,IAAO,MAAM,CAAC,CAAA;AAAA,EAChB;AACA,EAAA,OAAO,GAAA;AACT;AAOO,SAAS,YAAY,GAAA,EAA+C;AACzE,EAAA,IAAI,CAAC,KAAK,OAAO,IAAA;AACjB,EAAA,IAAI,GAAA,CAAI,MAAA,GAAS,cAAA,EAAgB,OAAO,IAAA;AACxC,EAAA,MAAM,KAAA,GAAQ,IAAI,IAAA,EAAK;AACvB,EAAA,IAAI,CAAC,OAAO,OAAO,IAAA;AAEnB,EAAA,MAAM,OAAA,GAAU,kBAAkB,KAAK,CAAA;AACvC,EAAA,IAAI,CAAC,SAAS,OAAO,IAAA;AACrB,EAAA,IAAI,gBAAA,CAAiB,IAAA,CAAK,OAAO,CAAA,EAAG,OAAO,IAAA;AAG3C,EAAA,IAAI,QAAQ,UAAA,CAAW,IAAI,CAAA,EAAG,OAAO,SAAS,OAAO,CAAA,CAAA;AAErD,EAAA,IACE,QAAQ,UAAA,CAAW,GAAG,KACtB,OAAA,CAAQ,UAAA,CAAW,GAAG,CAAA,IACtB,OAAA,CAAQ,WAAW,GAAG,CAAA,IACtB,QAAQ,UAAA,CAAW,IAAI,KACvB,OAAA,CAAQ,UAAA,CAAW,KAAK,CAAA,EACxB;AACA,IAAA,OAAO,OAAA;AAAA,EACT;AAGA,EAAA,MAAM,WAAA,GAAc,wBAAA,CAAyB,IAAA,CAAK,OAAO,CAAA;AACzD,EAAA,IAAI,WAAA,EAAa;AACf,IAAA,MAAM,SAAS,CAAA,EAAG,WAAA,CAAY,CAAC,CAAA,CAAG,aAAa,CAAA,CAAA,CAAA;AAC/C,IAAA,OAAO,iBAAA,CAAkB,GAAA,CAAI,MAAM,CAAA,GAAI,OAAA,GAAU,IAAA;AAAA,EACnD;AAGA,EAAA,OAAO,OAAA;AACT;AAMO,SAAS,iBAAiB,GAAA,EAA+C;AAC9E,EAAA,IAAI,CAAC,KAAK,OAAO,IAAA;AAEjB,EAAA,IAAI,GAAA,CAAI,MAAA,GAAS,mBAAA,EAAqB,OAAO,IAAA;AAC7C,EAAA,MAAM,KAAA,GAAQ,IAAI,IAAA,EAAK;AACvB,EAAA,IAAI,CAAC,OAAO,OAAO,IAAA;AACnB,EAAA,MAAM,OAAA,GAAU,kBAAkB,KAAK,CAAA;AACvC,EAAA,IAAI,CAAC,SAAS,OAAO,IAAA;AAErB,EAAA,IAAI,SAAA,CAAU,IAAA,CAAK,OAAO,CAAA,EAAG;AAG3B,IAAA,IAAI,OAAA,CAAQ,MAAA,GAAS,mBAAA,EAAqB,OAAO,IAAA;AAEjD,IAAA,IAAI,qEAAA,CAAsE,IAAA,CAAK,OAAO,CAAA,EAAG;AACvF,MAAA,OAAO,OAAA;AAAA,IACT;AACA,IAAA,OAAO,IAAA;AAAA,EACT;AAEA,EAAA,IAAI,QAAQ,UAAA,CAAW,IAAI,CAAA,EAAG,OAAO,SAAS,OAAO,CAAA,CAAA;AACrD,EAAA,IAAI,OAAA,CAAQ,UAAA,CAAW,GAAG,CAAA,IAAK,OAAA,CAAQ,UAAA,CAAW,IAAI,CAAA,IAAK,OAAA,CAAQ,UAAA,CAAW,KAAK,CAAA,EAAG;AACpF,IAAA,OAAO,OAAA;AAAA,EACT;AAEA,EAAA,MAAM,WAAA,GAAc,wBAAA,CAAyB,IAAA,CAAK,OAAO,CAAA;AACzD,EAAA,IAAI,WAAA,EAAa;AACf,IAAA,MAAM,SAAS,CAAA,EAAG,WAAA,CAAY,CAAC,CAAA,CAAG,aAAa,CAAA,CAAA,CAAA;AAC/C,IAAA,OAAO,kBAAA,CAAmB,GAAA,CAAI,MAAM,CAAA,GAAI,OAAA,GAAU,IAAA;AAAA,EACpD;AACA,EAAA,OAAO,OAAA;AACT;AAGA,IAAM,iBAAA,GAAoB;AAAA,EACxB,GAAA;AAAA,EACA,IAAA;AAAA,EACA,MAAA;AAAA,EACA,KAAA;AAAA,EACA,IAAA;AAAA,EACA,IAAA;AAAA,EACA,IAAA;AAAA,EACA,IAAA;AAAA,EACA,IAAA;AAAA,EACA,IAAA;AAAA,EACA,QAAA;AAAA,EACA,GAAA;AAAA,EACA,IAAA;AAAA,EACA,GAAA;AAAA,EACA,GAAA;AAAA,EACA,GAAA;AAAA,EACA,KAAA;AAAA,EACA,QAAA;AAAA,EACA,KAAA;AAAA,EACA,KAAA;AAAA,EACA,MAAA;AAAA,EACA,MAAA;AAAA,EACA,KAAA;AAAA,EACA,YAAA;AAAA,EACA,IAAA;AAAA,EACA,IAAA;AAAA,EACA,IAAA;AAAA,EACA,GAAA;AAAA,EACA,KAAA;AAAA,EACA,OAAA;AAAA,EACA,OAAA;AAAA,EACA,OAAA;AAAA,EACA,OAAA;AAAA,EACA,IAAA;AAAA,EACA,IAAA;AAAA,EACA,IAAA;AAAA,EACA,IAAA;AAAA,EACA,SAAA;AAAA,EACA,UAAA;AAAA,EACA;AACF,CAAA;AAEA,IAAM,kBAAA,GAAqB;AAAA,EACzB,MAAA;AAAA,EACA,OAAA;AAAA,EACA,QAAA;AAAA,EACA,KAAA;AAAA,EACA,KAAA;AAAA,EACA,OAAA;AAAA,EACA,QAAA;AAAA,EACA,SAAA;AAAA,EACA,SAAA;AAAA,EACA,OAAA;AAAA,EACA,OAAA;AAAA,EACA,OAAA;AAAA,EACA,MAAA;AAAA,EACA;AACF,CAAA;AASA,IAAI,eAAA,GAAqE,IAAA;AAEzE,IAAI,YAAA,GAAkD,IAAA;AAQtD,eAAe,WAAA,GAA0D;AACvE,EAAA,IAAI,iBAAiB,OAAO,eAAA;AAC5B,EAAA,eAAA,GAAA,CAAmB,YAAY;AAC7B,IAAA,IAAI,OAAO,MAAA,KAAW,WAAA,EAAa,OAAO,IAAA;AAC1C,IAAA,MAAM,GAAA,GAAM,MAAM,OAAO,WAAW,CAAA;AACpC,IAAA,MAAM,OAAA,GAAW,IAAI,OAAA,IAAW,GAAA;AAChC,IAAA,MAAM,MAAA,GAAS,QAAQ,MAA+C,CAAA;AAEtE,IAAA,MAAA,CAAO,OAAA,CAAQ,yBAAA,EAA2B,CAAC,IAAA,KAAS;AAClD,MAAA,MAAM,EAAA,GAAK,IAAA;AACX,MAAA,IAAI,EAAA,CAAG,YAAA,IAAgB,EAAA,CAAG,YAAA,CAAa,MAAM,CAAA,EAAG;AAC9C,QAAA,MAAM,IAAA,GAAO,WAAA,CAAY,EAAA,CAAG,YAAA,CAAa,MAAM,CAAC,CAAA;AAChD,QAAA,IAAI,IAAA,EAAM,EAAA,CAAG,YAAA,CAAa,MAAA,EAAQ,IAAI,CAAA;AAAA,aACjC,EAAA,CAAG,gBAAgB,MAAM,CAAA;AAAA,MAChC;AACA,MAAA,IAAI,GAAG,OAAA,KAAY,KAAA,IAAS,EAAA,CAAG,YAAA,CAAa,KAAK,CAAA,EAAG;AAClD,QAAA,MAAM,IAAA,GAAO,gBAAA,CAAiB,EAAA,CAAG,YAAA,CAAa,KAAK,CAAC,CAAA;AACpD,QAAA,IAAI,IAAA,EAAM,EAAA,CAAG,YAAA,CAAa,KAAA,EAAO,IAAI,CAAA;AAAA,aAChC,EAAA,CAAG,gBAAgB,KAAK,CAAA;AAAA,MAC/B;AACA,MAAA,IAAI,EAAA,CAAG,YAAY,GAAA,EAAK;AACtB,QAAA,EAAA,CAAG,YAAA,CAAa,OAAO,8BAA8B,CAAA;AAAA,MACvD;AAAA,IACF,CAAC,CAAA;AACD,IAAA,MAAM,EAAA,GAAK,CAAC,IAAA,KACV,MAAA,CAAO,SAAS,IAAA,EAAM;AAAA;AAAA;AAAA,MAGpB,YAAA,EAAc,iBAAA;AAAA,MACd,YAAA,EAAc,kBAAA;AAAA,MACd,WAAA,EAAa,CAAC,QAAA,EAAU,OAAA,EAAS,UAAU,QAAA,EAAU,OAAA,EAAS,MAAA,EAAQ,KAAA,EAAO,MAAM,CAAA;AAAA,MACnF,WAAA,EAAa,CAAC,QAAQ,CAAA;AAAA,MACtB,eAAA,EAAiB,KAAA;AAAA,MACjB,uBAAA,EAAyB;AAAA,KAC1B,CAAA;AACH,IAAA,YAAA,GAAe,EAAA;AACf,IAAA,OAAO,EAAA;AAAA,EACT,CAAA,GAAG;AACH,EAAA,OAAO,eAAA;AACT;AAMA,eAAsB,gBAAA,GAAkC;AACtD,EAAA,MAAM,WAAA,EAAY;AACpB;AAUO,SAAS,eAAe,IAAA,EAAsB;AACnD,EAAA,OAAO,IAAA,CACJ,OAAA,CAAQ,4EAAA,EAA8E,EAAE,CAAA,CACxF,OAAA,CAAQ,2DAAA,EAA6D,EAAE,CAAA,CACvE,OAAA,CAAQ,4BAAA,EAA8B,EAAE,CAAA,CACxC,OAAA,CAAQ,4BAAA,EAA8B,EAAE,CAAA,CACxC,OAAA,CAAQ,4BAAA,EAA8B,EAAE,CAAA,CACxC,OAAA,CAAQ,sEAAA,EAAwE,QAAQ,CAAA,CACxF,OAAA,CAAQ,sEAAA,EAAwE,QAAQ,CAAA;AAC7F;AAOO,SAAS,iBAAiB,IAAA,EAAsB;AACrD,EAAA,OAAO,YAAA,GAAe,YAAA,CAAa,IAAI,CAAA,GAAI,eAAe,IAAI,CAAA;AAChE;AAOA,eAAsB,aAAa,IAAA,EAA+B;AAChE,EAAA,MAAM,MAAA,GAAS,MAAM,WAAA,EAAY;AACjC,EAAA,OAAO,MAAA,GAAS,MAAA,CAAO,IAAI,CAAA,GAAI,eAAe,IAAI,CAAA;AACpD","file":"chunk-5F6SPYCN.cjs","sourcesContent":["/**\n * Security primitives (§5.12). These functions are the single ingress point for\n * untrusted content: pasted/imported HTML, link and image URLs. They are\n * dependency-light and (for URLs) DOM-free so the schema can be imported in\n * Node for export/tests without pulling a DOM library.\n */\n\n/** URL schemes permitted in hyperlinks. Everything else is rejected. */\nconst SAFE_LINK_SCHEMES = new Set(['http:', 'https:', 'mailto:', 'tel:', 'ftp:']);\n\n/** URL schemes permitted for images. `data:` is allowed only for image MIME types. */\nconst SAFE_IMAGE_SCHEMES = new Set(['http:', 'https:', 'blob:']);\n\n/** Maximum length of an inline `data:` image URI (characters). */\nconst MAX_DATA_URI_LENGTH = 3_500_000;\n/** Maximum length of a hyperlink URL (characters). */\nconst MAX_URL_LENGTH = 16_384;\n\nconst DANGEROUS_SCHEME = /^(javascript|vbscript|data|file):/i;\n\n/**\n * Strip control characters and spaces used to obfuscate a URL scheme\n * (e.g. \"java\\tscript:\"). Implemented with char-code inspection to avoid\n * embedding control characters in source.\n */\nfunction stripControlChars(value: string): string {\n let out = '';\n for (let i = 0; i < value.length; i++) {\n const code = value.charCodeAt(i);\n // Drop C0/C1 control ranges and the space character.\n if (code <= 0x20 || (code >= 0x7f && code <= 0x9f)) continue;\n out += value[i];\n }\n return out;\n}\n\n/**\n * Validate and normalize a hyperlink URL. Returns the cleaned URL, or `null` if\n * the URL is missing or uses an unsafe scheme (e.g. `javascript:`). Relative and\n * fragment/anchor URLs are allowed (F-12.2, F-12.5).\n */\nexport function sanitizeUrl(raw: string | null | undefined): string | null {\n if (!raw) return null;\n if (raw.length > MAX_URL_LENGTH) return null;\n const value = raw.trim();\n if (!value) return null;\n\n const cleaned = stripControlChars(value);\n if (!cleaned) return null;\n if (DANGEROUS_SCHEME.test(cleaned)) return null;\n\n // Protocol-relative URLs are upgraded to https (checked before single-'/').\n if (cleaned.startsWith('//')) return `https:${cleaned}`;\n // Relative URLs and anchors are safe to keep.\n if (\n cleaned.startsWith('/') ||\n cleaned.startsWith('#') ||\n cleaned.startsWith('?') ||\n cleaned.startsWith('./') ||\n cleaned.startsWith('../')\n ) {\n return cleaned;\n }\n\n // If it has a scheme, it must be in the allow-list.\n const schemeMatch = /^([a-z][a-z0-9+.-]*):/i.exec(cleaned);\n if (schemeMatch) {\n const scheme = `${schemeMatch[1]!.toLowerCase()}:`;\n return SAFE_LINK_SCHEMES.has(scheme) ? cleaned : null;\n }\n\n // No scheme and not obviously relative: treat as a bare host/path.\n return cleaned;\n}\n\n/**\n * Validate an image source. Accepts http(s)/blob URLs and `data:image/*` URIs,\n * rejecting SVG data URIs and any active-content scheme (F-12.5).\n */\nexport function sanitizeImageSrc(raw: string | null | undefined): string | null {\n if (!raw) return null;\n // Reject oversized input up front (before the O(n) control-char scan).\n if (raw.length > MAX_DATA_URI_LENGTH) return null;\n const value = raw.trim();\n if (!value) return null;\n const cleaned = stripControlChars(value);\n if (!cleaned) return null;\n\n if (/^data:/i.test(cleaned)) {\n // Bound the size of inline data URIs to avoid memory/DoS from oversized\n // payloads embedded in a document (~3.4 MB of base64 ≈ 2.5 MB binary).\n if (cleaned.length > MAX_DATA_URI_LENGTH) return null;\n // Permit only raster image data URIs; reject SVG (can carry script).\n if (/^data:image\\/(png|jpe?g|gif|webp|bmp|x-icon|vnd\\.microsoft\\.icon);/i.test(cleaned)) {\n return cleaned;\n }\n return null;\n }\n\n if (cleaned.startsWith('//')) return `https:${cleaned}`;\n if (cleaned.startsWith('/') || cleaned.startsWith('./') || cleaned.startsWith('../')) {\n return cleaned;\n }\n\n const schemeMatch = /^([a-z][a-z0-9+.-]*):/i.exec(cleaned);\n if (schemeMatch) {\n const scheme = `${schemeMatch[1]!.toLowerCase()}:`;\n return SAFE_IMAGE_SCHEMES.has(scheme) ? cleaned : null;\n }\n return cleaned;\n}\n\n/** Allowed tags for pasted HTML — a conservative, document-oriented subset. */\nconst ALLOWED_HTML_TAGS = [\n 'p',\n 'br',\n 'span',\n 'div',\n 'h1',\n 'h2',\n 'h3',\n 'h4',\n 'h5',\n 'h6',\n 'strong',\n 'b',\n 'em',\n 'i',\n 'u',\n 's',\n 'del',\n 'strike',\n 'sub',\n 'sup',\n 'mark',\n 'code',\n 'pre',\n 'blockquote',\n 'ul',\n 'ol',\n 'li',\n 'a',\n 'img',\n 'table',\n 'thead',\n 'tbody',\n 'tfoot',\n 'tr',\n 'th',\n 'td',\n 'hr',\n 'caption',\n 'colgroup',\n 'col',\n];\n\nconst ALLOWED_HTML_ATTRS = [\n 'href',\n 'title',\n 'target',\n 'src',\n 'alt',\n 'width',\n 'height',\n 'colspan',\n 'rowspan',\n 'style',\n 'align',\n 'start',\n 'type',\n 'data-checked',\n];\n\n/** Minimal structural type for the DOMPurify instance we use (avoids hard dep on its types). */\ninterface DOMPurifyInstance {\n sanitize(dirty: string, config?: Record<string, unknown>): string;\n addHook(entryPoint: string, hook: (node: unknown) => void): void;\n}\ntype DOMPurifyFactory = (window: Window & typeof globalThis) => DOMPurifyInstance;\n\nlet purifierPromise: Promise<((html: string) => string) | null> | null = null;\n/** Resolved synchronous purify function, cached after the first async load. */\nlet cachedPurify: ((html: string) => string) | null = null;\n\n/**\n * Lazily build a DOMPurify-backed HTML sanitizer. Returns a function that cleans\n * HTML, or `null` if no DOM is available (in which case callers fall back to\n * ProseMirror's own DOM parsing of the already-browser-provided fragment). This\n * is lazy so importing the schema in Node never loads a DOM library.\n */\nasync function getPurifier(): Promise<((html: string) => string) | null> {\n if (purifierPromise) return purifierPromise;\n purifierPromise = (async () => {\n if (typeof window === 'undefined') return null;\n const mod = await import('dompurify');\n const factory = (mod.default ?? mod) as unknown as DOMPurifyFactory;\n const purify = factory(window as unknown as Window & typeof globalThis);\n // Force-strip event handlers and dangerous protocols defensively.\n purify.addHook('afterSanitizeAttributes', (node) => {\n const el = node as Element;\n if (el.hasAttribute && el.hasAttribute('href')) {\n const safe = sanitizeUrl(el.getAttribute('href'));\n if (safe) el.setAttribute('href', safe);\n else el.removeAttribute('href');\n }\n if (el.tagName === 'IMG' && el.hasAttribute('src')) {\n const safe = sanitizeImageSrc(el.getAttribute('src'));\n if (safe) el.setAttribute('src', safe);\n else el.removeAttribute('src');\n }\n if (el.tagName === 'A') {\n el.setAttribute('rel', 'noopener noreferrer nofollow');\n }\n });\n const fn = (html: string) =>\n purify.sanitize(html, {\n // An explicit allow-list (do not combine with USE_PROFILES, which would\n // widen the tag set and conflict with ALLOWED_TAGS).\n ALLOWED_TAGS: ALLOWED_HTML_TAGS,\n ALLOWED_ATTR: ALLOWED_HTML_ATTRS,\n FORBID_TAGS: ['script', 'style', 'iframe', 'object', 'embed', 'form', 'svg', 'math'],\n FORBID_ATTR: ['srcset'],\n ALLOW_DATA_ATTR: false,\n ALLOW_UNKNOWN_PROTOCOLS: false,\n });\n cachedPurify = fn;\n return fn;\n })();\n return purifierPromise;\n}\n\n/**\n * Preload the DOM sanitizer so a later synchronous paste can be cleaned without\n * waiting. Safe to call in the browser on editor mount. No-op in Node.\n */\nexport async function preloadSanitizer(): Promise<void> {\n await getPurifier();\n}\n\n/**\n * Last-resort, regex-based HTML scrub used when DOMPurify is not (yet) available\n * (e.g. the first paste before the async load completes, or a non-DOM runtime).\n * It removes script/style/active-content blocks, inline event handlers, and\n * dangerous URL schemes. This is defense-in-depth only: ProseMirror parses paste\n * input in an inert document and the schema whitelists nodes, so content never\n * executes regardless.\n */\nexport function basicScrubHtml(html: string): string {\n return html\n .replace(/<\\s*(script|style|iframe|object|embed|svg|math)\\b[\\s\\S]*?<\\s*\\/\\s*\\1\\s*>/gi, '')\n .replace(/<\\s*(script|style|iframe|object|embed|svg|math)\\b[^>]*>/gi, '')\n .replace(/\\son[a-z]+\\s*=\\s*\"[^\"]*\"/gi, '')\n .replace(/\\son[a-z]+\\s*=\\s*'[^']*'/gi, '')\n .replace(/\\son[a-z]+\\s*=\\s*[^\\s>]+/gi, '')\n .replace(/(href|src)\\s*=\\s*\"\\s*(?:javascript|vbscript|data:text\\/html)[^\"]*\"/gi, '$1=\"#\"')\n .replace(/(href|src)\\s*=\\s*'\\s*(?:javascript|vbscript|data:text\\/html)[^']*'/gi, \"$1='#'\");\n}\n\n/**\n * Sanitize an HTML string synchronously, best-effort: uses the cached DOM\n * sanitizer if it has been loaded, otherwise applies {@link basicScrubHtml} as a\n * fallback. Use for the paste transform (which must be synchronous).\n */\nexport function sanitizeHtmlSync(html: string): string {\n return cachedPurify ? cachedPurify(html) : basicScrubHtml(html);\n}\n\n/**\n * Sanitize an HTML string for safe parsing into the editor. No script, inline\n * event handlers, or active content survives (F-12.1, F-12.2). When no DOM\n * sanitizer is available a regex scrub is applied as a fallback.\n */\nexport async function sanitizeHtml(html: string): Promise<string> {\n const purify = await getPurifier();\n return purify ? purify(html) : basicScrubHtml(html);\n}\n"]}
@@ -0,0 +1,174 @@
1
+ // src/security/sanitize.ts
2
+ var SAFE_LINK_SCHEMES = /* @__PURE__ */ new Set(["http:", "https:", "mailto:", "tel:", "ftp:"]);
3
+ var SAFE_IMAGE_SCHEMES = /* @__PURE__ */ new Set(["http:", "https:", "blob:"]);
4
+ var MAX_DATA_URI_LENGTH = 35e5;
5
+ var MAX_URL_LENGTH = 16384;
6
+ var DANGEROUS_SCHEME = /^(javascript|vbscript|data|file):/i;
7
+ function stripControlChars(value) {
8
+ let out = "";
9
+ for (let i = 0; i < value.length; i++) {
10
+ const code = value.charCodeAt(i);
11
+ if (code <= 32 || code >= 127 && code <= 159) continue;
12
+ out += value[i];
13
+ }
14
+ return out;
15
+ }
16
+ function sanitizeUrl(raw) {
17
+ if (!raw) return null;
18
+ if (raw.length > MAX_URL_LENGTH) return null;
19
+ const value = raw.trim();
20
+ if (!value) return null;
21
+ const cleaned = stripControlChars(value);
22
+ if (!cleaned) return null;
23
+ if (DANGEROUS_SCHEME.test(cleaned)) return null;
24
+ if (cleaned.startsWith("//")) return `https:${cleaned}`;
25
+ if (cleaned.startsWith("/") || cleaned.startsWith("#") || cleaned.startsWith("?") || cleaned.startsWith("./") || cleaned.startsWith("../")) {
26
+ return cleaned;
27
+ }
28
+ const schemeMatch = /^([a-z][a-z0-9+.-]*):/i.exec(cleaned);
29
+ if (schemeMatch) {
30
+ const scheme = `${schemeMatch[1].toLowerCase()}:`;
31
+ return SAFE_LINK_SCHEMES.has(scheme) ? cleaned : null;
32
+ }
33
+ return cleaned;
34
+ }
35
+ function sanitizeImageSrc(raw) {
36
+ if (!raw) return null;
37
+ if (raw.length > MAX_DATA_URI_LENGTH) return null;
38
+ const value = raw.trim();
39
+ if (!value) return null;
40
+ const cleaned = stripControlChars(value);
41
+ if (!cleaned) return null;
42
+ if (/^data:/i.test(cleaned)) {
43
+ if (cleaned.length > MAX_DATA_URI_LENGTH) return null;
44
+ if (/^data:image\/(png|jpe?g|gif|webp|bmp|x-icon|vnd\.microsoft\.icon);/i.test(cleaned)) {
45
+ return cleaned;
46
+ }
47
+ return null;
48
+ }
49
+ if (cleaned.startsWith("//")) return `https:${cleaned}`;
50
+ if (cleaned.startsWith("/") || cleaned.startsWith("./") || cleaned.startsWith("../")) {
51
+ return cleaned;
52
+ }
53
+ const schemeMatch = /^([a-z][a-z0-9+.-]*):/i.exec(cleaned);
54
+ if (schemeMatch) {
55
+ const scheme = `${schemeMatch[1].toLowerCase()}:`;
56
+ return SAFE_IMAGE_SCHEMES.has(scheme) ? cleaned : null;
57
+ }
58
+ return cleaned;
59
+ }
60
+ var ALLOWED_HTML_TAGS = [
61
+ "p",
62
+ "br",
63
+ "span",
64
+ "div",
65
+ "h1",
66
+ "h2",
67
+ "h3",
68
+ "h4",
69
+ "h5",
70
+ "h6",
71
+ "strong",
72
+ "b",
73
+ "em",
74
+ "i",
75
+ "u",
76
+ "s",
77
+ "del",
78
+ "strike",
79
+ "sub",
80
+ "sup",
81
+ "mark",
82
+ "code",
83
+ "pre",
84
+ "blockquote",
85
+ "ul",
86
+ "ol",
87
+ "li",
88
+ "a",
89
+ "img",
90
+ "table",
91
+ "thead",
92
+ "tbody",
93
+ "tfoot",
94
+ "tr",
95
+ "th",
96
+ "td",
97
+ "hr",
98
+ "caption",
99
+ "colgroup",
100
+ "col"
101
+ ];
102
+ var ALLOWED_HTML_ATTRS = [
103
+ "href",
104
+ "title",
105
+ "target",
106
+ "src",
107
+ "alt",
108
+ "width",
109
+ "height",
110
+ "colspan",
111
+ "rowspan",
112
+ "style",
113
+ "align",
114
+ "start",
115
+ "type",
116
+ "data-checked"
117
+ ];
118
+ var purifierPromise = null;
119
+ var cachedPurify = null;
120
+ async function getPurifier() {
121
+ if (purifierPromise) return purifierPromise;
122
+ purifierPromise = (async () => {
123
+ if (typeof window === "undefined") return null;
124
+ const mod = await import('dompurify');
125
+ const factory = mod.default ?? mod;
126
+ const purify = factory(window);
127
+ purify.addHook("afterSanitizeAttributes", (node) => {
128
+ const el = node;
129
+ if (el.hasAttribute && el.hasAttribute("href")) {
130
+ const safe = sanitizeUrl(el.getAttribute("href"));
131
+ if (safe) el.setAttribute("href", safe);
132
+ else el.removeAttribute("href");
133
+ }
134
+ if (el.tagName === "IMG" && el.hasAttribute("src")) {
135
+ const safe = sanitizeImageSrc(el.getAttribute("src"));
136
+ if (safe) el.setAttribute("src", safe);
137
+ else el.removeAttribute("src");
138
+ }
139
+ if (el.tagName === "A") {
140
+ el.setAttribute("rel", "noopener noreferrer nofollow");
141
+ }
142
+ });
143
+ const fn = (html) => purify.sanitize(html, {
144
+ // An explicit allow-list (do not combine with USE_PROFILES, which would
145
+ // widen the tag set and conflict with ALLOWED_TAGS).
146
+ ALLOWED_TAGS: ALLOWED_HTML_TAGS,
147
+ ALLOWED_ATTR: ALLOWED_HTML_ATTRS,
148
+ FORBID_TAGS: ["script", "style", "iframe", "object", "embed", "form", "svg", "math"],
149
+ FORBID_ATTR: ["srcset"],
150
+ ALLOW_DATA_ATTR: false,
151
+ ALLOW_UNKNOWN_PROTOCOLS: false
152
+ });
153
+ cachedPurify = fn;
154
+ return fn;
155
+ })();
156
+ return purifierPromise;
157
+ }
158
+ async function preloadSanitizer() {
159
+ await getPurifier();
160
+ }
161
+ function basicScrubHtml(html) {
162
+ return html.replace(/<\s*(script|style|iframe|object|embed|svg|math)\b[\s\S]*?<\s*\/\s*\1\s*>/gi, "").replace(/<\s*(script|style|iframe|object|embed|svg|math)\b[^>]*>/gi, "").replace(/\son[a-z]+\s*=\s*"[^"]*"/gi, "").replace(/\son[a-z]+\s*=\s*'[^']*'/gi, "").replace(/\son[a-z]+\s*=\s*[^\s>]+/gi, "").replace(/(href|src)\s*=\s*"\s*(?:javascript|vbscript|data:text\/html)[^"]*"/gi, '$1="#"').replace(/(href|src)\s*=\s*'\s*(?:javascript|vbscript|data:text\/html)[^']*'/gi, "$1='#'");
163
+ }
164
+ function sanitizeHtmlSync(html) {
165
+ return cachedPurify ? cachedPurify(html) : basicScrubHtml(html);
166
+ }
167
+ async function sanitizeHtml(html) {
168
+ const purify = await getPurifier();
169
+ return purify ? purify(html) : basicScrubHtml(html);
170
+ }
171
+
172
+ export { preloadSanitizer, sanitizeHtml, sanitizeHtmlSync, sanitizeImageSrc, sanitizeUrl };
173
+ //# sourceMappingURL=chunk-6NTSXJX4.js.map
174
+ //# sourceMappingURL=chunk-6NTSXJX4.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/security/sanitize.ts"],"names":[],"mappings":"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","file":"chunk-6NTSXJX4.js","sourcesContent":["/**\n * Security primitives (§5.12). These functions are the single ingress point for\n * untrusted content: pasted/imported HTML, link and image URLs. They are\n * dependency-light and (for URLs) DOM-free so the schema can be imported in\n * Node for export/tests without pulling a DOM library.\n */\n\n/** URL schemes permitted in hyperlinks. Everything else is rejected. */\nconst SAFE_LINK_SCHEMES = new Set(['http:', 'https:', 'mailto:', 'tel:', 'ftp:']);\n\n/** URL schemes permitted for images. `data:` is allowed only for image MIME types. */\nconst SAFE_IMAGE_SCHEMES = new Set(['http:', 'https:', 'blob:']);\n\n/** Maximum length of an inline `data:` image URI (characters). */\nconst MAX_DATA_URI_LENGTH = 3_500_000;\n/** Maximum length of a hyperlink URL (characters). */\nconst MAX_URL_LENGTH = 16_384;\n\nconst DANGEROUS_SCHEME = /^(javascript|vbscript|data|file):/i;\n\n/**\n * Strip control characters and spaces used to obfuscate a URL scheme\n * (e.g. \"java\\tscript:\"). Implemented with char-code inspection to avoid\n * embedding control characters in source.\n */\nfunction stripControlChars(value: string): string {\n let out = '';\n for (let i = 0; i < value.length; i++) {\n const code = value.charCodeAt(i);\n // Drop C0/C1 control ranges and the space character.\n if (code <= 0x20 || (code >= 0x7f && code <= 0x9f)) continue;\n out += value[i];\n }\n return out;\n}\n\n/**\n * Validate and normalize a hyperlink URL. Returns the cleaned URL, or `null` if\n * the URL is missing or uses an unsafe scheme (e.g. `javascript:`). Relative and\n * fragment/anchor URLs are allowed (F-12.2, F-12.5).\n */\nexport function sanitizeUrl(raw: string | null | undefined): string | null {\n if (!raw) return null;\n if (raw.length > MAX_URL_LENGTH) return null;\n const value = raw.trim();\n if (!value) return null;\n\n const cleaned = stripControlChars(value);\n if (!cleaned) return null;\n if (DANGEROUS_SCHEME.test(cleaned)) return null;\n\n // Protocol-relative URLs are upgraded to https (checked before single-'/').\n if (cleaned.startsWith('//')) return `https:${cleaned}`;\n // Relative URLs and anchors are safe to keep.\n if (\n cleaned.startsWith('/') ||\n cleaned.startsWith('#') ||\n cleaned.startsWith('?') ||\n cleaned.startsWith('./') ||\n cleaned.startsWith('../')\n ) {\n return cleaned;\n }\n\n // If it has a scheme, it must be in the allow-list.\n const schemeMatch = /^([a-z][a-z0-9+.-]*):/i.exec(cleaned);\n if (schemeMatch) {\n const scheme = `${schemeMatch[1]!.toLowerCase()}:`;\n return SAFE_LINK_SCHEMES.has(scheme) ? cleaned : null;\n }\n\n // No scheme and not obviously relative: treat as a bare host/path.\n return cleaned;\n}\n\n/**\n * Validate an image source. Accepts http(s)/blob URLs and `data:image/*` URIs,\n * rejecting SVG data URIs and any active-content scheme (F-12.5).\n */\nexport function sanitizeImageSrc(raw: string | null | undefined): string | null {\n if (!raw) return null;\n // Reject oversized input up front (before the O(n) control-char scan).\n if (raw.length > MAX_DATA_URI_LENGTH) return null;\n const value = raw.trim();\n if (!value) return null;\n const cleaned = stripControlChars(value);\n if (!cleaned) return null;\n\n if (/^data:/i.test(cleaned)) {\n // Bound the size of inline data URIs to avoid memory/DoS from oversized\n // payloads embedded in a document (~3.4 MB of base64 ≈ 2.5 MB binary).\n if (cleaned.length > MAX_DATA_URI_LENGTH) return null;\n // Permit only raster image data URIs; reject SVG (can carry script).\n if (/^data:image\\/(png|jpe?g|gif|webp|bmp|x-icon|vnd\\.microsoft\\.icon);/i.test(cleaned)) {\n return cleaned;\n }\n return null;\n }\n\n if (cleaned.startsWith('//')) return `https:${cleaned}`;\n if (cleaned.startsWith('/') || cleaned.startsWith('./') || cleaned.startsWith('../')) {\n return cleaned;\n }\n\n const schemeMatch = /^([a-z][a-z0-9+.-]*):/i.exec(cleaned);\n if (schemeMatch) {\n const scheme = `${schemeMatch[1]!.toLowerCase()}:`;\n return SAFE_IMAGE_SCHEMES.has(scheme) ? cleaned : null;\n }\n return cleaned;\n}\n\n/** Allowed tags for pasted HTML — a conservative, document-oriented subset. */\nconst ALLOWED_HTML_TAGS = [\n 'p',\n 'br',\n 'span',\n 'div',\n 'h1',\n 'h2',\n 'h3',\n 'h4',\n 'h5',\n 'h6',\n 'strong',\n 'b',\n 'em',\n 'i',\n 'u',\n 's',\n 'del',\n 'strike',\n 'sub',\n 'sup',\n 'mark',\n 'code',\n 'pre',\n 'blockquote',\n 'ul',\n 'ol',\n 'li',\n 'a',\n 'img',\n 'table',\n 'thead',\n 'tbody',\n 'tfoot',\n 'tr',\n 'th',\n 'td',\n 'hr',\n 'caption',\n 'colgroup',\n 'col',\n];\n\nconst ALLOWED_HTML_ATTRS = [\n 'href',\n 'title',\n 'target',\n 'src',\n 'alt',\n 'width',\n 'height',\n 'colspan',\n 'rowspan',\n 'style',\n 'align',\n 'start',\n 'type',\n 'data-checked',\n];\n\n/** Minimal structural type for the DOMPurify instance we use (avoids hard dep on its types). */\ninterface DOMPurifyInstance {\n sanitize(dirty: string, config?: Record<string, unknown>): string;\n addHook(entryPoint: string, hook: (node: unknown) => void): void;\n}\ntype DOMPurifyFactory = (window: Window & typeof globalThis) => DOMPurifyInstance;\n\nlet purifierPromise: Promise<((html: string) => string) | null> | null = null;\n/** Resolved synchronous purify function, cached after the first async load. */\nlet cachedPurify: ((html: string) => string) | null = null;\n\n/**\n * Lazily build a DOMPurify-backed HTML sanitizer. Returns a function that cleans\n * HTML, or `null` if no DOM is available (in which case callers fall back to\n * ProseMirror's own DOM parsing of the already-browser-provided fragment). This\n * is lazy so importing the schema in Node never loads a DOM library.\n */\nasync function getPurifier(): Promise<((html: string) => string) | null> {\n if (purifierPromise) return purifierPromise;\n purifierPromise = (async () => {\n if (typeof window === 'undefined') return null;\n const mod = await import('dompurify');\n const factory = (mod.default ?? mod) as unknown as DOMPurifyFactory;\n const purify = factory(window as unknown as Window & typeof globalThis);\n // Force-strip event handlers and dangerous protocols defensively.\n purify.addHook('afterSanitizeAttributes', (node) => {\n const el = node as Element;\n if (el.hasAttribute && el.hasAttribute('href')) {\n const safe = sanitizeUrl(el.getAttribute('href'));\n if (safe) el.setAttribute('href', safe);\n else el.removeAttribute('href');\n }\n if (el.tagName === 'IMG' && el.hasAttribute('src')) {\n const safe = sanitizeImageSrc(el.getAttribute('src'));\n if (safe) el.setAttribute('src', safe);\n else el.removeAttribute('src');\n }\n if (el.tagName === 'A') {\n el.setAttribute('rel', 'noopener noreferrer nofollow');\n }\n });\n const fn = (html: string) =>\n purify.sanitize(html, {\n // An explicit allow-list (do not combine with USE_PROFILES, which would\n // widen the tag set and conflict with ALLOWED_TAGS).\n ALLOWED_TAGS: ALLOWED_HTML_TAGS,\n ALLOWED_ATTR: ALLOWED_HTML_ATTRS,\n FORBID_TAGS: ['script', 'style', 'iframe', 'object', 'embed', 'form', 'svg', 'math'],\n FORBID_ATTR: ['srcset'],\n ALLOW_DATA_ATTR: false,\n ALLOW_UNKNOWN_PROTOCOLS: false,\n });\n cachedPurify = fn;\n return fn;\n })();\n return purifierPromise;\n}\n\n/**\n * Preload the DOM sanitizer so a later synchronous paste can be cleaned without\n * waiting. Safe to call in the browser on editor mount. No-op in Node.\n */\nexport async function preloadSanitizer(): Promise<void> {\n await getPurifier();\n}\n\n/**\n * Last-resort, regex-based HTML scrub used when DOMPurify is not (yet) available\n * (e.g. the first paste before the async load completes, or a non-DOM runtime).\n * It removes script/style/active-content blocks, inline event handlers, and\n * dangerous URL schemes. This is defense-in-depth only: ProseMirror parses paste\n * input in an inert document and the schema whitelists nodes, so content never\n * executes regardless.\n */\nexport function basicScrubHtml(html: string): string {\n return html\n .replace(/<\\s*(script|style|iframe|object|embed|svg|math)\\b[\\s\\S]*?<\\s*\\/\\s*\\1\\s*>/gi, '')\n .replace(/<\\s*(script|style|iframe|object|embed|svg|math)\\b[^>]*>/gi, '')\n .replace(/\\son[a-z]+\\s*=\\s*\"[^\"]*\"/gi, '')\n .replace(/\\son[a-z]+\\s*=\\s*'[^']*'/gi, '')\n .replace(/\\son[a-z]+\\s*=\\s*[^\\s>]+/gi, '')\n .replace(/(href|src)\\s*=\\s*\"\\s*(?:javascript|vbscript|data:text\\/html)[^\"]*\"/gi, '$1=\"#\"')\n .replace(/(href|src)\\s*=\\s*'\\s*(?:javascript|vbscript|data:text\\/html)[^']*'/gi, \"$1='#'\");\n}\n\n/**\n * Sanitize an HTML string synchronously, best-effort: uses the cached DOM\n * sanitizer if it has been loaded, otherwise applies {@link basicScrubHtml} as a\n * fallback. Use for the paste transform (which must be synchronous).\n */\nexport function sanitizeHtmlSync(html: string): string {\n return cachedPurify ? cachedPurify(html) : basicScrubHtml(html);\n}\n\n/**\n * Sanitize an HTML string for safe parsing into the editor. No script, inline\n * event handlers, or active content survives (F-12.1, F-12.2). When no DOM\n * sanitizer is available a regex scrub is applied as a fallback.\n */\nexport async function sanitizeHtml(html: string): Promise<string> {\n const purify = await getPurifier();\n return purify ? purify(html) : basicScrubHtml(html);\n}\n"]}