@frontmcp/uipack 0.8.0 → 0.9.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.
@@ -23,6 +23,424 @@ var init_utils = __esm({
23
23
  }
24
24
  });
25
25
 
26
+ // libs/uipack/src/runtime/sanitizer.ts
27
+ import DOMPurify from "dompurify";
28
+ function isEmail(value) {
29
+ return PII_PATTERNS.email.test(value);
30
+ }
31
+ function isPhone(value) {
32
+ return PII_PATTERNS.phone.test(value);
33
+ }
34
+ function isCreditCard(value) {
35
+ const digits = value.replace(/[-\s]/g, "");
36
+ return digits.length >= 13 && digits.length <= 19 && PII_PATTERNS.creditCard.test(value);
37
+ }
38
+ function isSSN(value) {
39
+ return PII_PATTERNS.ssn.test(value);
40
+ }
41
+ function isIPv4(value) {
42
+ return PII_PATTERNS.ipv4.test(value);
43
+ }
44
+ function detectPIIType(value) {
45
+ if (isEmail(value)) return "EMAIL";
46
+ if (isCreditCard(value)) return "CARD";
47
+ if (isSSN(value)) return "ID";
48
+ if (isPhone(value)) return "PHONE";
49
+ if (isIPv4(value)) return "IP";
50
+ return null;
51
+ }
52
+ function redactPIIFromText(text) {
53
+ if (text.length > MAX_PII_TEXT_LENGTH) {
54
+ return text;
55
+ }
56
+ let result = text;
57
+ result = result.replace(PII_PATTERNS.creditCardInText, REDACTION_TOKENS.CARD);
58
+ result = result.replace(PII_PATTERNS.ssnInText, REDACTION_TOKENS.ID);
59
+ result = result.replace(PII_PATTERNS.emailInText, REDACTION_TOKENS.EMAIL);
60
+ result = result.replace(PII_PATTERNS.phoneInText, REDACTION_TOKENS.PHONE);
61
+ result = result.replace(PII_PATTERNS.ipv4InText, REDACTION_TOKENS.IP);
62
+ return result;
63
+ }
64
+ function sanitizeValue(key, value, path, options, depth, visited) {
65
+ const maxDepth = options.maxDepth ?? 10;
66
+ if (depth > maxDepth) {
67
+ return value;
68
+ }
69
+ if (value === null || value === void 0) {
70
+ return value;
71
+ }
72
+ if (typeof options.mode === "function") {
73
+ return options.mode(key, value, path);
74
+ }
75
+ if (Array.isArray(options.mode)) {
76
+ const lowerKey = key.toLowerCase();
77
+ if (options.mode.some((f) => f.toLowerCase() === lowerKey)) {
78
+ return REDACTION_TOKENS.REDACTED;
79
+ }
80
+ }
81
+ if (typeof value === "string") {
82
+ if (options.mode === true) {
83
+ const piiType = detectPIIType(value);
84
+ if (piiType) {
85
+ return REDACTION_TOKENS[piiType];
86
+ }
87
+ if (options.sanitizeInText !== false) {
88
+ const redacted = redactPIIFromText(value);
89
+ if (redacted !== value) {
90
+ return redacted;
91
+ }
92
+ }
93
+ }
94
+ return value;
95
+ }
96
+ if (Array.isArray(value)) {
97
+ if (visited.has(value)) {
98
+ return REDACTION_TOKENS.REDACTED;
99
+ }
100
+ visited.add(value);
101
+ return value.map(
102
+ (item, index) => sanitizeValue(String(index), item, [...path, String(index)], options, depth + 1, visited)
103
+ );
104
+ }
105
+ if (typeof value === "object") {
106
+ if (visited.has(value)) {
107
+ return REDACTION_TOKENS.REDACTED;
108
+ }
109
+ visited.add(value);
110
+ const result = {};
111
+ for (const [k, v] of Object.entries(value)) {
112
+ result[k] = sanitizeValue(k, v, [...path, k], options, depth + 1, visited);
113
+ }
114
+ return result;
115
+ }
116
+ return value;
117
+ }
118
+ function sanitizeInput(input, mode = true) {
119
+ if (!input || typeof input !== "object") {
120
+ return {};
121
+ }
122
+ const options = {
123
+ mode,
124
+ maxDepth: 10,
125
+ sanitizeInText: true
126
+ };
127
+ const visited = /* @__PURE__ */ new WeakSet();
128
+ return sanitizeValue("", input, [], options, 0, visited);
129
+ }
130
+ function createSanitizer(mode = true) {
131
+ return (input) => sanitizeInput(input, mode);
132
+ }
133
+ function detectPII(input, options) {
134
+ const maxDepth = options?.maxDepth ?? 10;
135
+ const fields = [];
136
+ const visited = /* @__PURE__ */ new WeakSet();
137
+ const check = (value, path, depth) => {
138
+ if (depth > maxDepth) return;
139
+ if (value === null || value === void 0) return;
140
+ if (typeof value === "string") {
141
+ if (detectPIIType(value)) {
142
+ fields.push(path.join("."));
143
+ }
144
+ return;
145
+ }
146
+ if (Array.isArray(value)) {
147
+ if (visited.has(value)) return;
148
+ visited.add(value);
149
+ value.forEach((item, index) => check(item, [...path, String(index)], depth + 1));
150
+ return;
151
+ }
152
+ if (typeof value === "object") {
153
+ if (visited.has(value)) return;
154
+ visited.add(value);
155
+ for (const [k, v] of Object.entries(value)) {
156
+ check(v, [...path, k], depth + 1);
157
+ }
158
+ }
159
+ };
160
+ check(input, [], 0);
161
+ return {
162
+ hasPII: fields.length > 0,
163
+ fields
164
+ };
165
+ }
166
+ function sanitizeHtmlContent(html) {
167
+ if (typeof window !== "undefined" && typeof document !== "undefined") {
168
+ return DOMPurify.sanitize(html);
169
+ }
170
+ return sanitizeHtmlViaParser(html);
171
+ }
172
+ function sanitizeHtmlViaParser(html) {
173
+ if (html.length > MAX_HTML_LENGTH) {
174
+ return html.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
175
+ }
176
+ const result = [];
177
+ let i = 0;
178
+ const len = html.length;
179
+ while (i < len) {
180
+ if (html[i] === "<") {
181
+ const tagEnd = findTagEnd(html, i);
182
+ if (tagEnd === -1) {
183
+ result.push(html[i]);
184
+ i++;
185
+ continue;
186
+ }
187
+ const tagContent = html.slice(i + 1, tagEnd);
188
+ const tagInfo = parseTagContent(tagContent);
189
+ if (!tagInfo) {
190
+ result.push(html[i]);
191
+ i++;
192
+ continue;
193
+ }
194
+ const { tagName, isClosing, isSelfClosing, attributes } = tagInfo;
195
+ const tagLower = tagName.toLowerCase();
196
+ if (HTML_DANGEROUS_TAGS.has(tagLower)) {
197
+ if (!isClosing && !isSelfClosing) {
198
+ const closeTag = `</${tagLower}`;
199
+ const closeIdx = html.toLowerCase().indexOf(closeTag, tagEnd + 1);
200
+ if (closeIdx !== -1) {
201
+ const closeEnd = html.indexOf(">", closeIdx);
202
+ i = closeEnd !== -1 ? closeEnd + 1 : tagEnd + 1;
203
+ } else {
204
+ i = tagEnd + 1;
205
+ }
206
+ } else {
207
+ i = tagEnd + 1;
208
+ }
209
+ continue;
210
+ }
211
+ const safeAttrs = sanitizeAttributes(attributes);
212
+ if (isClosing) {
213
+ result.push(`</${tagName}>`);
214
+ } else if (isSelfClosing) {
215
+ result.push(`<${tagName}${safeAttrs} />`);
216
+ } else {
217
+ result.push(`<${tagName}${safeAttrs}>`);
218
+ }
219
+ i = tagEnd + 1;
220
+ } else {
221
+ result.push(html[i]);
222
+ i++;
223
+ }
224
+ }
225
+ return result.join("");
226
+ }
227
+ function findTagEnd(html, start) {
228
+ let i = start + 1;
229
+ let inQuote = null;
230
+ while (i < html.length) {
231
+ if (inQuote) {
232
+ if (html[i] === inQuote) inQuote = null;
233
+ } else {
234
+ if (html[i] === '"' || html[i] === "'") {
235
+ inQuote = html[i];
236
+ } else if (html[i] === ">") {
237
+ return i;
238
+ }
239
+ }
240
+ i++;
241
+ }
242
+ return -1;
243
+ }
244
+ function parseTagContent(content) {
245
+ const trimmed = content.trim();
246
+ if (!trimmed) return null;
247
+ let idx = 0;
248
+ const isClosing = trimmed[0] === "/";
249
+ if (isClosing) idx++;
250
+ while (idx < trimmed.length && /\s/.test(trimmed[idx])) idx++;
251
+ const nameStart = idx;
252
+ while (idx < trimmed.length && /[a-zA-Z0-9-]/.test(trimmed[idx])) idx++;
253
+ const tagName = trimmed.slice(nameStart, idx);
254
+ if (!tagName) return null;
255
+ const isSelfClosing = trimmed.endsWith("/");
256
+ const attributes = [];
257
+ const attrPart = isSelfClosing ? trimmed.slice(idx, -1) : trimmed.slice(idx);
258
+ let attrIdx = 0;
259
+ while (attrIdx < attrPart.length) {
260
+ while (attrIdx < attrPart.length && /\s/.test(attrPart[attrIdx])) attrIdx++;
261
+ if (attrIdx >= attrPart.length) break;
262
+ const attrNameStart = attrIdx;
263
+ while (attrIdx < attrPart.length && /[a-zA-Z0-9_-]/.test(attrPart[attrIdx])) attrIdx++;
264
+ const attrName = attrPart.slice(attrNameStart, attrIdx);
265
+ if (!attrName) {
266
+ attrIdx++;
267
+ continue;
268
+ }
269
+ while (attrIdx < attrPart.length && /\s/.test(attrPart[attrIdx])) attrIdx++;
270
+ let attrValue = "";
271
+ if (attrIdx < attrPart.length && attrPart[attrIdx] === "=") {
272
+ attrIdx++;
273
+ while (attrIdx < attrPart.length && /\s/.test(attrPart[attrIdx])) attrIdx++;
274
+ if (attrIdx < attrPart.length && (attrPart[attrIdx] === '"' || attrPart[attrIdx] === "'")) {
275
+ const quote = attrPart[attrIdx];
276
+ attrIdx++;
277
+ const valueStart = attrIdx;
278
+ while (attrIdx < attrPart.length && attrPart[attrIdx] !== quote) attrIdx++;
279
+ attrValue = attrPart.slice(valueStart, attrIdx);
280
+ if (attrIdx < attrPart.length) attrIdx++;
281
+ } else {
282
+ const valueStart = attrIdx;
283
+ while (attrIdx < attrPart.length && !/\s/.test(attrPart[attrIdx])) attrIdx++;
284
+ attrValue = attrPart.slice(valueStart, attrIdx);
285
+ }
286
+ }
287
+ attributes.push({ name: attrName, value: attrValue });
288
+ }
289
+ return { tagName, isClosing, isSelfClosing, attributes };
290
+ }
291
+ function sanitizeAttributes(attributes) {
292
+ const safe = [];
293
+ for (const { name, value } of attributes) {
294
+ const nameLower = name.toLowerCase();
295
+ if (nameLower.startsWith("on") || HTML_EVENT_HANDLERS.has(nameLower)) {
296
+ continue;
297
+ }
298
+ if (["href", "src", "action", "formaction", "data", "poster", "codebase"].includes(nameLower)) {
299
+ const valueLower = value.toLowerCase().trim();
300
+ if (HTML_DANGEROUS_SCHEMES.some((scheme) => valueLower.startsWith(scheme))) {
301
+ continue;
302
+ }
303
+ }
304
+ if (nameLower === "style") {
305
+ const styleLower = value.toLowerCase();
306
+ if (styleLower.includes("expression(") || styleLower.includes("javascript:") || styleLower.includes("url(")) {
307
+ continue;
308
+ }
309
+ }
310
+ const escapedValue = value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
311
+ safe.push(` ${name}="${escapedValue}"`);
312
+ }
313
+ return safe.join("");
314
+ }
315
+ var REDACTION_TOKENS, PII_PATTERNS, MAX_PII_TEXT_LENGTH, HTML_EVENT_HANDLERS, HTML_DANGEROUS_TAGS, HTML_DANGEROUS_SCHEMES, MAX_HTML_LENGTH;
316
+ var init_sanitizer = __esm({
317
+ "libs/uipack/src/runtime/sanitizer.ts"() {
318
+ "use strict";
319
+ REDACTION_TOKENS = {
320
+ EMAIL: "[EMAIL]",
321
+ PHONE: "[PHONE]",
322
+ CARD: "[CARD]",
323
+ ID: "[ID]",
324
+ IP: "[IP]",
325
+ REDACTED: "[REDACTED]"
326
+ };
327
+ PII_PATTERNS = {
328
+ // Email: user@domain.tld
329
+ email: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/,
330
+ // Email embedded in text
331
+ emailInText: /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g,
332
+ // Phone: Various formats (US-centric but flexible)
333
+ phone: /^[+]?[(]?[0-9]{1,4}[)]?[-\s.]?[0-9]{1,4}[-\s.]?[0-9]{1,9}$/,
334
+ // Phone embedded in text
335
+ phoneInText: /(?:\+?1[-.\s]?)?\(?[0-9]{3}\)?[-.\s]?[0-9]{3}[-.\s]?[0-9]{4}/g,
336
+ // Credit card: 13-19 digits (with optional separators)
337
+ creditCard: /^(?:[0-9]{4}[-\s]?){3,4}[0-9]{1,4}$/,
338
+ // Credit card embedded in text
339
+ creditCardInText: /\b(?:[0-9]{4}[-\s]?){3,4}[0-9]{1,4}\b/g,
340
+ // SSN: XXX-XX-XXXX
341
+ ssn: /^[0-9]{3}[-]?[0-9]{2}[-]?[0-9]{4}$/,
342
+ // SSN embedded in text
343
+ ssnInText: /\b[0-9]{3}[-]?[0-9]{2}[-]?[0-9]{4}\b/g,
344
+ // IPv4 address
345
+ ipv4: /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/,
346
+ // IPv4 embedded in text
347
+ ipv4InText: /\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b/g
348
+ };
349
+ MAX_PII_TEXT_LENGTH = 1e5;
350
+ HTML_EVENT_HANDLERS = /* @__PURE__ */ new Set([
351
+ "onabort",
352
+ "onafterprint",
353
+ "onauxclick",
354
+ "onbeforematch",
355
+ "onbeforeprint",
356
+ "onbeforetoggle",
357
+ "onbeforeunload",
358
+ "onblur",
359
+ "oncancel",
360
+ "oncanplay",
361
+ "oncanplaythrough",
362
+ "onchange",
363
+ "onclick",
364
+ "onclose",
365
+ "oncontextlost",
366
+ "oncontextmenu",
367
+ "oncontextrestored",
368
+ "oncopy",
369
+ "oncuechange",
370
+ "oncut",
371
+ "ondblclick",
372
+ "ondrag",
373
+ "ondragend",
374
+ "ondragenter",
375
+ "ondragleave",
376
+ "ondragover",
377
+ "ondragstart",
378
+ "ondrop",
379
+ "ondurationchange",
380
+ "onemptied",
381
+ "onended",
382
+ "onerror",
383
+ "onfocus",
384
+ "onformdata",
385
+ "onhashchange",
386
+ "oninput",
387
+ "oninvalid",
388
+ "onkeydown",
389
+ "onkeypress",
390
+ "onkeyup",
391
+ "onlanguagechange",
392
+ "onload",
393
+ "onloadeddata",
394
+ "onloadedmetadata",
395
+ "onloadstart",
396
+ "onmessage",
397
+ "onmessageerror",
398
+ "onmousedown",
399
+ "onmouseenter",
400
+ "onmouseleave",
401
+ "onmousemove",
402
+ "onmouseout",
403
+ "onmouseover",
404
+ "onmouseup",
405
+ "onoffline",
406
+ "ononline",
407
+ "onpagehide",
408
+ "onpageshow",
409
+ "onpaste",
410
+ "onpause",
411
+ "onplay",
412
+ "onplaying",
413
+ "onpopstate",
414
+ "onprogress",
415
+ "onratechange",
416
+ "onrejectionhandled",
417
+ "onreset",
418
+ "onresize",
419
+ "onscroll",
420
+ "onscrollend",
421
+ "onsecuritypolicyviolation",
422
+ "onseeked",
423
+ "onseeking",
424
+ "onselect",
425
+ "onslotchange",
426
+ "onstalled",
427
+ "onstorage",
428
+ "onsubmit",
429
+ "onsuspend",
430
+ "ontimeupdate",
431
+ "ontoggle",
432
+ "onunhandledrejection",
433
+ "onunload",
434
+ "onvolumechange",
435
+ "onwaiting",
436
+ "onwheel"
437
+ ]);
438
+ HTML_DANGEROUS_TAGS = /* @__PURE__ */ new Set(["script", "style", "iframe", "object", "embed", "applet", "base"]);
439
+ HTML_DANGEROUS_SCHEMES = ["javascript:", "data:", "vbscript:"];
440
+ MAX_HTML_LENGTH = 5e5;
441
+ }
442
+ });
443
+
26
444
  // libs/uipack/src/handlebars/helpers.ts
27
445
  function formatDate(date, format) {
28
446
  if (date === null || date === void 0) {
@@ -641,6 +1059,7 @@ var HtmlRendererAdapter;
641
1059
  var init_html_adapter = __esm({
642
1060
  "libs/uipack/src/runtime/adapters/html.adapter.ts"() {
643
1061
  "use strict";
1062
+ init_sanitizer();
644
1063
  HtmlRendererAdapter = class {
645
1064
  type = "html";
646
1065
  // Lazy-loaded Handlebars renderer
@@ -666,7 +1085,7 @@ var init_html_adapter = __esm({
666
1085
  async renderToDOM(content, target, context, _options) {
667
1086
  try {
668
1087
  const html = await this.render(content, context);
669
- target.innerHTML = html;
1088
+ target.innerHTML = sanitizeHtmlContent(html);
670
1089
  target.dispatchEvent(
671
1090
  new CustomEvent("frontmcp:rendered", {
672
1091
  bubbles: true,
@@ -2359,8 +2778,14 @@ var MCP_BRIDGE_RUNTIME = `
2359
2778
  </script>
2360
2779
  `;
2361
2780
  function getMCPBridgeScript() {
2362
- const match = MCP_BRIDGE_RUNTIME.match(/<script>([\s\S]*?)<\/script>/);
2363
- return match ? match[1].trim() : "";
2781
+ const openTag = "<script>";
2782
+ const closeTag = "</script>";
2783
+ const startIdx = MCP_BRIDGE_RUNTIME.indexOf(openTag);
2784
+ const endIdx = MCP_BRIDGE_RUNTIME.lastIndexOf(closeTag);
2785
+ if (startIdx === -1 || endIdx === -1 || endIdx <= startIdx) {
2786
+ return "";
2787
+ }
2788
+ return MCP_BRIDGE_RUNTIME.slice(startIdx + openTag.length, endIdx).trim();
2364
2789
  }
2365
2790
  function isMCPBridgeSupported() {
2366
2791
  if (typeof window === "undefined") return false;
@@ -2969,179 +3394,7 @@ function buildThemeCss(theme) {
2969
3394
 
2970
3395
  // libs/uipack/src/runtime/wrapper.ts
2971
3396
  init_utils();
2972
-
2973
- // libs/uipack/src/runtime/sanitizer.ts
2974
- var REDACTION_TOKENS = {
2975
- EMAIL: "[EMAIL]",
2976
- PHONE: "[PHONE]",
2977
- CARD: "[CARD]",
2978
- ID: "[ID]",
2979
- IP: "[IP]",
2980
- REDACTED: "[REDACTED]"
2981
- };
2982
- var PII_PATTERNS = {
2983
- // Email: user@domain.tld
2984
- email: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/,
2985
- // Email embedded in text
2986
- emailInText: /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g,
2987
- // Phone: Various formats (US-centric but flexible)
2988
- phone: /^[+]?[(]?[0-9]{1,4}[)]?[-\s.]?[0-9]{1,4}[-\s.]?[0-9]{1,9}$/,
2989
- // Phone embedded in text
2990
- phoneInText: /(?:\+?1[-.\s]?)?\(?[0-9]{3}\)?[-.\s]?[0-9]{3}[-.\s]?[0-9]{4}/g,
2991
- // Credit card: 13-19 digits (with optional separators)
2992
- creditCard: /^(?:[0-9]{4}[-\s]?){3,4}[0-9]{1,4}$/,
2993
- // Credit card embedded in text
2994
- creditCardInText: /\b(?:[0-9]{4}[-\s]?){3,4}[0-9]{1,4}\b/g,
2995
- // SSN: XXX-XX-XXXX
2996
- ssn: /^[0-9]{3}[-]?[0-9]{2}[-]?[0-9]{4}$/,
2997
- // SSN embedded in text
2998
- ssnInText: /\b[0-9]{3}[-]?[0-9]{2}[-]?[0-9]{4}\b/g,
2999
- // IPv4 address
3000
- ipv4: /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/,
3001
- // IPv4 embedded in text
3002
- ipv4InText: /\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b/g
3003
- };
3004
- function isEmail(value) {
3005
- return PII_PATTERNS.email.test(value);
3006
- }
3007
- function isPhone(value) {
3008
- return PII_PATTERNS.phone.test(value);
3009
- }
3010
- function isCreditCard(value) {
3011
- const digits = value.replace(/[-\s]/g, "");
3012
- return digits.length >= 13 && digits.length <= 19 && PII_PATTERNS.creditCard.test(value);
3013
- }
3014
- function isSSN(value) {
3015
- return PII_PATTERNS.ssn.test(value);
3016
- }
3017
- function isIPv4(value) {
3018
- return PII_PATTERNS.ipv4.test(value);
3019
- }
3020
- function detectPIIType(value) {
3021
- if (isEmail(value)) return "EMAIL";
3022
- if (isCreditCard(value)) return "CARD";
3023
- if (isSSN(value)) return "ID";
3024
- if (isPhone(value)) return "PHONE";
3025
- if (isIPv4(value)) return "IP";
3026
- return null;
3027
- }
3028
- var MAX_PII_TEXT_LENGTH = 1e5;
3029
- function redactPIIFromText(text) {
3030
- if (text.length > MAX_PII_TEXT_LENGTH) {
3031
- return text;
3032
- }
3033
- let result = text;
3034
- result = result.replace(PII_PATTERNS.creditCardInText, REDACTION_TOKENS.CARD);
3035
- result = result.replace(PII_PATTERNS.ssnInText, REDACTION_TOKENS.ID);
3036
- result = result.replace(PII_PATTERNS.emailInText, REDACTION_TOKENS.EMAIL);
3037
- result = result.replace(PII_PATTERNS.phoneInText, REDACTION_TOKENS.PHONE);
3038
- result = result.replace(PII_PATTERNS.ipv4InText, REDACTION_TOKENS.IP);
3039
- return result;
3040
- }
3041
- function sanitizeValue(key, value, path, options, depth, visited) {
3042
- const maxDepth = options.maxDepth ?? 10;
3043
- if (depth > maxDepth) {
3044
- return value;
3045
- }
3046
- if (value === null || value === void 0) {
3047
- return value;
3048
- }
3049
- if (typeof options.mode === "function") {
3050
- return options.mode(key, value, path);
3051
- }
3052
- if (Array.isArray(options.mode)) {
3053
- const lowerKey = key.toLowerCase();
3054
- if (options.mode.some((f) => f.toLowerCase() === lowerKey)) {
3055
- return REDACTION_TOKENS.REDACTED;
3056
- }
3057
- }
3058
- if (typeof value === "string") {
3059
- if (options.mode === true) {
3060
- const piiType = detectPIIType(value);
3061
- if (piiType) {
3062
- return REDACTION_TOKENS[piiType];
3063
- }
3064
- if (options.sanitizeInText !== false) {
3065
- const redacted = redactPIIFromText(value);
3066
- if (redacted !== value) {
3067
- return redacted;
3068
- }
3069
- }
3070
- }
3071
- return value;
3072
- }
3073
- if (Array.isArray(value)) {
3074
- if (visited.has(value)) {
3075
- return REDACTION_TOKENS.REDACTED;
3076
- }
3077
- visited.add(value);
3078
- return value.map(
3079
- (item, index) => sanitizeValue(String(index), item, [...path, String(index)], options, depth + 1, visited)
3080
- );
3081
- }
3082
- if (typeof value === "object") {
3083
- if (visited.has(value)) {
3084
- return REDACTION_TOKENS.REDACTED;
3085
- }
3086
- visited.add(value);
3087
- const result = {};
3088
- for (const [k, v] of Object.entries(value)) {
3089
- result[k] = sanitizeValue(k, v, [...path, k], options, depth + 1, visited);
3090
- }
3091
- return result;
3092
- }
3093
- return value;
3094
- }
3095
- function sanitizeInput(input, mode = true) {
3096
- if (!input || typeof input !== "object") {
3097
- return {};
3098
- }
3099
- const options = {
3100
- mode,
3101
- maxDepth: 10,
3102
- sanitizeInText: true
3103
- };
3104
- const visited = /* @__PURE__ */ new WeakSet();
3105
- return sanitizeValue("", input, [], options, 0, visited);
3106
- }
3107
- function createSanitizer(mode = true) {
3108
- return (input) => sanitizeInput(input, mode);
3109
- }
3110
- function detectPII(input, options) {
3111
- const maxDepth = options?.maxDepth ?? 10;
3112
- const fields = [];
3113
- const visited = /* @__PURE__ */ new WeakSet();
3114
- const check = (value, path, depth) => {
3115
- if (depth > maxDepth) return;
3116
- if (value === null || value === void 0) return;
3117
- if (typeof value === "string") {
3118
- if (detectPIIType(value)) {
3119
- fields.push(path.join("."));
3120
- }
3121
- return;
3122
- }
3123
- if (Array.isArray(value)) {
3124
- if (visited.has(value)) return;
3125
- visited.add(value);
3126
- value.forEach((item, index) => check(item, [...path, String(index)], depth + 1));
3127
- return;
3128
- }
3129
- if (typeof value === "object") {
3130
- if (visited.has(value)) return;
3131
- visited.add(value);
3132
- for (const [k, v] of Object.entries(value)) {
3133
- check(v, [...path, k], depth + 1);
3134
- }
3135
- }
3136
- };
3137
- check(input, [], 0);
3138
- return {
3139
- hasPII: fields.length > 0,
3140
- fields
3141
- };
3142
- }
3143
-
3144
- // libs/uipack/src/runtime/wrapper.ts
3397
+ init_sanitizer();
3145
3398
  function createTemplateHelpers() {
3146
3399
  let idCounter2 = 0;
3147
3400
  return {
@@ -4729,6 +4982,9 @@ function wrapToolUIForClaude(options) {
4729
4982
  </html>`;
4730
4983
  }
4731
4984
 
4985
+ // libs/uipack/src/runtime/index.ts
4986
+ init_sanitizer();
4987
+
4732
4988
  // libs/uipack/src/runtime/adapters/index.ts
4733
4989
  init_html_adapter();
4734
4990
  init_mdx_adapter();
@@ -5092,6 +5348,7 @@ export {
5092
5348
  loadAdapter,
5093
5349
  redactPIIFromText,
5094
5350
  sanitizeCSPDomains,
5351
+ sanitizeHtmlContent,
5095
5352
  sanitizeInput,
5096
5353
  validateCSPDomain,
5097
5354
  wrapHybridWidgetShell,
@@ -2642,6 +2642,7 @@ function buildThemeCss(theme) {
2642
2642
  init_utils();
2643
2643
 
2644
2644
  // libs/uipack/src/runtime/sanitizer.ts
2645
+ import DOMPurify from "dompurify";
2645
2646
  var REDACTION_TOKENS = {
2646
2647
  EMAIL: "[EMAIL]",
2647
2648
  PHONE: "[PHONE]",