@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.
package/runtime/index.js CHANGED
@@ -39,6 +39,424 @@ var init_utils = __esm({
39
39
  }
40
40
  });
41
41
 
42
+ // libs/uipack/src/runtime/sanitizer.ts
43
+ function isEmail(value) {
44
+ return PII_PATTERNS.email.test(value);
45
+ }
46
+ function isPhone(value) {
47
+ return PII_PATTERNS.phone.test(value);
48
+ }
49
+ function isCreditCard(value) {
50
+ const digits = value.replace(/[-\s]/g, "");
51
+ return digits.length >= 13 && digits.length <= 19 && PII_PATTERNS.creditCard.test(value);
52
+ }
53
+ function isSSN(value) {
54
+ return PII_PATTERNS.ssn.test(value);
55
+ }
56
+ function isIPv4(value) {
57
+ return PII_PATTERNS.ipv4.test(value);
58
+ }
59
+ function detectPIIType(value) {
60
+ if (isEmail(value)) return "EMAIL";
61
+ if (isCreditCard(value)) return "CARD";
62
+ if (isSSN(value)) return "ID";
63
+ if (isPhone(value)) return "PHONE";
64
+ if (isIPv4(value)) return "IP";
65
+ return null;
66
+ }
67
+ function redactPIIFromText(text) {
68
+ if (text.length > MAX_PII_TEXT_LENGTH) {
69
+ return text;
70
+ }
71
+ let result = text;
72
+ result = result.replace(PII_PATTERNS.creditCardInText, REDACTION_TOKENS.CARD);
73
+ result = result.replace(PII_PATTERNS.ssnInText, REDACTION_TOKENS.ID);
74
+ result = result.replace(PII_PATTERNS.emailInText, REDACTION_TOKENS.EMAIL);
75
+ result = result.replace(PII_PATTERNS.phoneInText, REDACTION_TOKENS.PHONE);
76
+ result = result.replace(PII_PATTERNS.ipv4InText, REDACTION_TOKENS.IP);
77
+ return result;
78
+ }
79
+ function sanitizeValue(key, value, path, options, depth, visited) {
80
+ const maxDepth = options.maxDepth ?? 10;
81
+ if (depth > maxDepth) {
82
+ return value;
83
+ }
84
+ if (value === null || value === void 0) {
85
+ return value;
86
+ }
87
+ if (typeof options.mode === "function") {
88
+ return options.mode(key, value, path);
89
+ }
90
+ if (Array.isArray(options.mode)) {
91
+ const lowerKey = key.toLowerCase();
92
+ if (options.mode.some((f) => f.toLowerCase() === lowerKey)) {
93
+ return REDACTION_TOKENS.REDACTED;
94
+ }
95
+ }
96
+ if (typeof value === "string") {
97
+ if (options.mode === true) {
98
+ const piiType = detectPIIType(value);
99
+ if (piiType) {
100
+ return REDACTION_TOKENS[piiType];
101
+ }
102
+ if (options.sanitizeInText !== false) {
103
+ const redacted = redactPIIFromText(value);
104
+ if (redacted !== value) {
105
+ return redacted;
106
+ }
107
+ }
108
+ }
109
+ return value;
110
+ }
111
+ if (Array.isArray(value)) {
112
+ if (visited.has(value)) {
113
+ return REDACTION_TOKENS.REDACTED;
114
+ }
115
+ visited.add(value);
116
+ return value.map(
117
+ (item, index) => sanitizeValue(String(index), item, [...path, String(index)], options, depth + 1, visited)
118
+ );
119
+ }
120
+ if (typeof value === "object") {
121
+ if (visited.has(value)) {
122
+ return REDACTION_TOKENS.REDACTED;
123
+ }
124
+ visited.add(value);
125
+ const result = {};
126
+ for (const [k, v] of Object.entries(value)) {
127
+ result[k] = sanitizeValue(k, v, [...path, k], options, depth + 1, visited);
128
+ }
129
+ return result;
130
+ }
131
+ return value;
132
+ }
133
+ function sanitizeInput(input, mode = true) {
134
+ if (!input || typeof input !== "object") {
135
+ return {};
136
+ }
137
+ const options = {
138
+ mode,
139
+ maxDepth: 10,
140
+ sanitizeInText: true
141
+ };
142
+ const visited = /* @__PURE__ */ new WeakSet();
143
+ return sanitizeValue("", input, [], options, 0, visited);
144
+ }
145
+ function createSanitizer(mode = true) {
146
+ return (input) => sanitizeInput(input, mode);
147
+ }
148
+ function detectPII(input, options) {
149
+ const maxDepth = options?.maxDepth ?? 10;
150
+ const fields = [];
151
+ const visited = /* @__PURE__ */ new WeakSet();
152
+ const check = (value, path, depth) => {
153
+ if (depth > maxDepth) return;
154
+ if (value === null || value === void 0) return;
155
+ if (typeof value === "string") {
156
+ if (detectPIIType(value)) {
157
+ fields.push(path.join("."));
158
+ }
159
+ return;
160
+ }
161
+ if (Array.isArray(value)) {
162
+ if (visited.has(value)) return;
163
+ visited.add(value);
164
+ value.forEach((item, index) => check(item, [...path, String(index)], depth + 1));
165
+ return;
166
+ }
167
+ if (typeof value === "object") {
168
+ if (visited.has(value)) return;
169
+ visited.add(value);
170
+ for (const [k, v] of Object.entries(value)) {
171
+ check(v, [...path, k], depth + 1);
172
+ }
173
+ }
174
+ };
175
+ check(input, [], 0);
176
+ return {
177
+ hasPII: fields.length > 0,
178
+ fields
179
+ };
180
+ }
181
+ function sanitizeHtmlContent(html) {
182
+ if (typeof window !== "undefined" && typeof document !== "undefined") {
183
+ return import_dompurify.default.sanitize(html);
184
+ }
185
+ return sanitizeHtmlViaParser(html);
186
+ }
187
+ function sanitizeHtmlViaParser(html) {
188
+ if (html.length > MAX_HTML_LENGTH) {
189
+ return html.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
190
+ }
191
+ const result = [];
192
+ let i = 0;
193
+ const len = html.length;
194
+ while (i < len) {
195
+ if (html[i] === "<") {
196
+ const tagEnd = findTagEnd(html, i);
197
+ if (tagEnd === -1) {
198
+ result.push(html[i]);
199
+ i++;
200
+ continue;
201
+ }
202
+ const tagContent = html.slice(i + 1, tagEnd);
203
+ const tagInfo = parseTagContent(tagContent);
204
+ if (!tagInfo) {
205
+ result.push(html[i]);
206
+ i++;
207
+ continue;
208
+ }
209
+ const { tagName, isClosing, isSelfClosing, attributes } = tagInfo;
210
+ const tagLower = tagName.toLowerCase();
211
+ if (HTML_DANGEROUS_TAGS.has(tagLower)) {
212
+ if (!isClosing && !isSelfClosing) {
213
+ const closeTag = `</${tagLower}`;
214
+ const closeIdx = html.toLowerCase().indexOf(closeTag, tagEnd + 1);
215
+ if (closeIdx !== -1) {
216
+ const closeEnd = html.indexOf(">", closeIdx);
217
+ i = closeEnd !== -1 ? closeEnd + 1 : tagEnd + 1;
218
+ } else {
219
+ i = tagEnd + 1;
220
+ }
221
+ } else {
222
+ i = tagEnd + 1;
223
+ }
224
+ continue;
225
+ }
226
+ const safeAttrs = sanitizeAttributes(attributes);
227
+ if (isClosing) {
228
+ result.push(`</${tagName}>`);
229
+ } else if (isSelfClosing) {
230
+ result.push(`<${tagName}${safeAttrs} />`);
231
+ } else {
232
+ result.push(`<${tagName}${safeAttrs}>`);
233
+ }
234
+ i = tagEnd + 1;
235
+ } else {
236
+ result.push(html[i]);
237
+ i++;
238
+ }
239
+ }
240
+ return result.join("");
241
+ }
242
+ function findTagEnd(html, start) {
243
+ let i = start + 1;
244
+ let inQuote = null;
245
+ while (i < html.length) {
246
+ if (inQuote) {
247
+ if (html[i] === inQuote) inQuote = null;
248
+ } else {
249
+ if (html[i] === '"' || html[i] === "'") {
250
+ inQuote = html[i];
251
+ } else if (html[i] === ">") {
252
+ return i;
253
+ }
254
+ }
255
+ i++;
256
+ }
257
+ return -1;
258
+ }
259
+ function parseTagContent(content) {
260
+ const trimmed = content.trim();
261
+ if (!trimmed) return null;
262
+ let idx = 0;
263
+ const isClosing = trimmed[0] === "/";
264
+ if (isClosing) idx++;
265
+ while (idx < trimmed.length && /\s/.test(trimmed[idx])) idx++;
266
+ const nameStart = idx;
267
+ while (idx < trimmed.length && /[a-zA-Z0-9-]/.test(trimmed[idx])) idx++;
268
+ const tagName = trimmed.slice(nameStart, idx);
269
+ if (!tagName) return null;
270
+ const isSelfClosing = trimmed.endsWith("/");
271
+ const attributes = [];
272
+ const attrPart = isSelfClosing ? trimmed.slice(idx, -1) : trimmed.slice(idx);
273
+ let attrIdx = 0;
274
+ while (attrIdx < attrPart.length) {
275
+ while (attrIdx < attrPart.length && /\s/.test(attrPart[attrIdx])) attrIdx++;
276
+ if (attrIdx >= attrPart.length) break;
277
+ const attrNameStart = attrIdx;
278
+ while (attrIdx < attrPart.length && /[a-zA-Z0-9_-]/.test(attrPart[attrIdx])) attrIdx++;
279
+ const attrName = attrPart.slice(attrNameStart, attrIdx);
280
+ if (!attrName) {
281
+ attrIdx++;
282
+ continue;
283
+ }
284
+ while (attrIdx < attrPart.length && /\s/.test(attrPart[attrIdx])) attrIdx++;
285
+ let attrValue = "";
286
+ if (attrIdx < attrPart.length && attrPart[attrIdx] === "=") {
287
+ attrIdx++;
288
+ while (attrIdx < attrPart.length && /\s/.test(attrPart[attrIdx])) attrIdx++;
289
+ if (attrIdx < attrPart.length && (attrPart[attrIdx] === '"' || attrPart[attrIdx] === "'")) {
290
+ const quote = attrPart[attrIdx];
291
+ attrIdx++;
292
+ const valueStart = attrIdx;
293
+ while (attrIdx < attrPart.length && attrPart[attrIdx] !== quote) attrIdx++;
294
+ attrValue = attrPart.slice(valueStart, attrIdx);
295
+ if (attrIdx < attrPart.length) attrIdx++;
296
+ } else {
297
+ const valueStart = attrIdx;
298
+ while (attrIdx < attrPart.length && !/\s/.test(attrPart[attrIdx])) attrIdx++;
299
+ attrValue = attrPart.slice(valueStart, attrIdx);
300
+ }
301
+ }
302
+ attributes.push({ name: attrName, value: attrValue });
303
+ }
304
+ return { tagName, isClosing, isSelfClosing, attributes };
305
+ }
306
+ function sanitizeAttributes(attributes) {
307
+ const safe = [];
308
+ for (const { name, value } of attributes) {
309
+ const nameLower = name.toLowerCase();
310
+ if (nameLower.startsWith("on") || HTML_EVENT_HANDLERS.has(nameLower)) {
311
+ continue;
312
+ }
313
+ if (["href", "src", "action", "formaction", "data", "poster", "codebase"].includes(nameLower)) {
314
+ const valueLower = value.toLowerCase().trim();
315
+ if (HTML_DANGEROUS_SCHEMES.some((scheme) => valueLower.startsWith(scheme))) {
316
+ continue;
317
+ }
318
+ }
319
+ if (nameLower === "style") {
320
+ const styleLower = value.toLowerCase();
321
+ if (styleLower.includes("expression(") || styleLower.includes("javascript:") || styleLower.includes("url(")) {
322
+ continue;
323
+ }
324
+ }
325
+ const escapedValue = value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
326
+ safe.push(` ${name}="${escapedValue}"`);
327
+ }
328
+ return safe.join("");
329
+ }
330
+ var import_dompurify, REDACTION_TOKENS, PII_PATTERNS, MAX_PII_TEXT_LENGTH, HTML_EVENT_HANDLERS, HTML_DANGEROUS_TAGS, HTML_DANGEROUS_SCHEMES, MAX_HTML_LENGTH;
331
+ var init_sanitizer = __esm({
332
+ "libs/uipack/src/runtime/sanitizer.ts"() {
333
+ "use strict";
334
+ import_dompurify = __toESM(require("dompurify"));
335
+ REDACTION_TOKENS = {
336
+ EMAIL: "[EMAIL]",
337
+ PHONE: "[PHONE]",
338
+ CARD: "[CARD]",
339
+ ID: "[ID]",
340
+ IP: "[IP]",
341
+ REDACTED: "[REDACTED]"
342
+ };
343
+ PII_PATTERNS = {
344
+ // Email: user@domain.tld
345
+ email: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/,
346
+ // Email embedded in text
347
+ emailInText: /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g,
348
+ // Phone: Various formats (US-centric but flexible)
349
+ phone: /^[+]?[(]?[0-9]{1,4}[)]?[-\s.]?[0-9]{1,4}[-\s.]?[0-9]{1,9}$/,
350
+ // Phone embedded in text
351
+ phoneInText: /(?:\+?1[-.\s]?)?\(?[0-9]{3}\)?[-.\s]?[0-9]{3}[-.\s]?[0-9]{4}/g,
352
+ // Credit card: 13-19 digits (with optional separators)
353
+ creditCard: /^(?:[0-9]{4}[-\s]?){3,4}[0-9]{1,4}$/,
354
+ // Credit card embedded in text
355
+ creditCardInText: /\b(?:[0-9]{4}[-\s]?){3,4}[0-9]{1,4}\b/g,
356
+ // SSN: XXX-XX-XXXX
357
+ ssn: /^[0-9]{3}[-]?[0-9]{2}[-]?[0-9]{4}$/,
358
+ // SSN embedded in text
359
+ ssnInText: /\b[0-9]{3}[-]?[0-9]{2}[-]?[0-9]{4}\b/g,
360
+ // IPv4 address
361
+ 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]?)$/,
362
+ // IPv4 embedded in text
363
+ 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
364
+ };
365
+ MAX_PII_TEXT_LENGTH = 1e5;
366
+ HTML_EVENT_HANDLERS = /* @__PURE__ */ new Set([
367
+ "onabort",
368
+ "onafterprint",
369
+ "onauxclick",
370
+ "onbeforematch",
371
+ "onbeforeprint",
372
+ "onbeforetoggle",
373
+ "onbeforeunload",
374
+ "onblur",
375
+ "oncancel",
376
+ "oncanplay",
377
+ "oncanplaythrough",
378
+ "onchange",
379
+ "onclick",
380
+ "onclose",
381
+ "oncontextlost",
382
+ "oncontextmenu",
383
+ "oncontextrestored",
384
+ "oncopy",
385
+ "oncuechange",
386
+ "oncut",
387
+ "ondblclick",
388
+ "ondrag",
389
+ "ondragend",
390
+ "ondragenter",
391
+ "ondragleave",
392
+ "ondragover",
393
+ "ondragstart",
394
+ "ondrop",
395
+ "ondurationchange",
396
+ "onemptied",
397
+ "onended",
398
+ "onerror",
399
+ "onfocus",
400
+ "onformdata",
401
+ "onhashchange",
402
+ "oninput",
403
+ "oninvalid",
404
+ "onkeydown",
405
+ "onkeypress",
406
+ "onkeyup",
407
+ "onlanguagechange",
408
+ "onload",
409
+ "onloadeddata",
410
+ "onloadedmetadata",
411
+ "onloadstart",
412
+ "onmessage",
413
+ "onmessageerror",
414
+ "onmousedown",
415
+ "onmouseenter",
416
+ "onmouseleave",
417
+ "onmousemove",
418
+ "onmouseout",
419
+ "onmouseover",
420
+ "onmouseup",
421
+ "onoffline",
422
+ "ononline",
423
+ "onpagehide",
424
+ "onpageshow",
425
+ "onpaste",
426
+ "onpause",
427
+ "onplay",
428
+ "onplaying",
429
+ "onpopstate",
430
+ "onprogress",
431
+ "onratechange",
432
+ "onrejectionhandled",
433
+ "onreset",
434
+ "onresize",
435
+ "onscroll",
436
+ "onscrollend",
437
+ "onsecuritypolicyviolation",
438
+ "onseeked",
439
+ "onseeking",
440
+ "onselect",
441
+ "onslotchange",
442
+ "onstalled",
443
+ "onstorage",
444
+ "onsubmit",
445
+ "onsuspend",
446
+ "ontimeupdate",
447
+ "ontoggle",
448
+ "onunhandledrejection",
449
+ "onunload",
450
+ "onvolumechange",
451
+ "onwaiting",
452
+ "onwheel"
453
+ ]);
454
+ HTML_DANGEROUS_TAGS = /* @__PURE__ */ new Set(["script", "style", "iframe", "object", "embed", "applet", "base"]);
455
+ HTML_DANGEROUS_SCHEMES = ["javascript:", "data:", "vbscript:"];
456
+ MAX_HTML_LENGTH = 5e5;
457
+ }
458
+ });
459
+
42
460
  // libs/uipack/src/handlebars/helpers.ts
43
461
  function formatDate(date, format) {
44
462
  if (date === null || date === void 0) {
@@ -657,6 +1075,7 @@ var HtmlRendererAdapter;
657
1075
  var init_html_adapter = __esm({
658
1076
  "libs/uipack/src/runtime/adapters/html.adapter.ts"() {
659
1077
  "use strict";
1078
+ init_sanitizer();
660
1079
  HtmlRendererAdapter = class {
661
1080
  type = "html";
662
1081
  // Lazy-loaded Handlebars renderer
@@ -682,7 +1101,7 @@ var init_html_adapter = __esm({
682
1101
  async renderToDOM(content, target, context, _options) {
683
1102
  try {
684
1103
  const html = await this.render(content, context);
685
- target.innerHTML = html;
1104
+ target.innerHTML = sanitizeHtmlContent(html);
686
1105
  target.dispatchEvent(
687
1106
  new CustomEvent("frontmcp:rendered", {
688
1107
  bubbles: true,
@@ -981,6 +1400,7 @@ __export(runtime_exports, {
981
1400
  loadAdapter: () => loadAdapter,
982
1401
  redactPIIFromText: () => redactPIIFromText,
983
1402
  sanitizeCSPDomains: () => sanitizeCSPDomains,
1403
+ sanitizeHtmlContent: () => sanitizeHtmlContent,
984
1404
  sanitizeInput: () => sanitizeInput,
985
1405
  validateCSPDomain: () => validateCSPDomain,
986
1406
  wrapHybridWidgetShell: () => wrapHybridWidgetShell,
@@ -2428,8 +2848,14 @@ var MCP_BRIDGE_RUNTIME = `
2428
2848
  </script>
2429
2849
  `;
2430
2850
  function getMCPBridgeScript() {
2431
- const match = MCP_BRIDGE_RUNTIME.match(/<script>([\s\S]*?)<\/script>/);
2432
- return match ? match[1].trim() : "";
2851
+ const openTag = "<script>";
2852
+ const closeTag = "</script>";
2853
+ const startIdx = MCP_BRIDGE_RUNTIME.indexOf(openTag);
2854
+ const endIdx = MCP_BRIDGE_RUNTIME.lastIndexOf(closeTag);
2855
+ if (startIdx === -1 || endIdx === -1 || endIdx <= startIdx) {
2856
+ return "";
2857
+ }
2858
+ return MCP_BRIDGE_RUNTIME.slice(startIdx + openTag.length, endIdx).trim();
2433
2859
  }
2434
2860
  function isMCPBridgeSupported() {
2435
2861
  if (typeof window === "undefined") return false;
@@ -3038,179 +3464,7 @@ function buildThemeCss(theme) {
3038
3464
 
3039
3465
  // libs/uipack/src/runtime/wrapper.ts
3040
3466
  init_utils();
3041
-
3042
- // libs/uipack/src/runtime/sanitizer.ts
3043
- var REDACTION_TOKENS = {
3044
- EMAIL: "[EMAIL]",
3045
- PHONE: "[PHONE]",
3046
- CARD: "[CARD]",
3047
- ID: "[ID]",
3048
- IP: "[IP]",
3049
- REDACTED: "[REDACTED]"
3050
- };
3051
- var PII_PATTERNS = {
3052
- // Email: user@domain.tld
3053
- email: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/,
3054
- // Email embedded in text
3055
- emailInText: /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g,
3056
- // Phone: Various formats (US-centric but flexible)
3057
- phone: /^[+]?[(]?[0-9]{1,4}[)]?[-\s.]?[0-9]{1,4}[-\s.]?[0-9]{1,9}$/,
3058
- // Phone embedded in text
3059
- phoneInText: /(?:\+?1[-.\s]?)?\(?[0-9]{3}\)?[-.\s]?[0-9]{3}[-.\s]?[0-9]{4}/g,
3060
- // Credit card: 13-19 digits (with optional separators)
3061
- creditCard: /^(?:[0-9]{4}[-\s]?){3,4}[0-9]{1,4}$/,
3062
- // Credit card embedded in text
3063
- creditCardInText: /\b(?:[0-9]{4}[-\s]?){3,4}[0-9]{1,4}\b/g,
3064
- // SSN: XXX-XX-XXXX
3065
- ssn: /^[0-9]{3}[-]?[0-9]{2}[-]?[0-9]{4}$/,
3066
- // SSN embedded in text
3067
- ssnInText: /\b[0-9]{3}[-]?[0-9]{2}[-]?[0-9]{4}\b/g,
3068
- // IPv4 address
3069
- 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]?)$/,
3070
- // IPv4 embedded in text
3071
- 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
3072
- };
3073
- function isEmail(value) {
3074
- return PII_PATTERNS.email.test(value);
3075
- }
3076
- function isPhone(value) {
3077
- return PII_PATTERNS.phone.test(value);
3078
- }
3079
- function isCreditCard(value) {
3080
- const digits = value.replace(/[-\s]/g, "");
3081
- return digits.length >= 13 && digits.length <= 19 && PII_PATTERNS.creditCard.test(value);
3082
- }
3083
- function isSSN(value) {
3084
- return PII_PATTERNS.ssn.test(value);
3085
- }
3086
- function isIPv4(value) {
3087
- return PII_PATTERNS.ipv4.test(value);
3088
- }
3089
- function detectPIIType(value) {
3090
- if (isEmail(value)) return "EMAIL";
3091
- if (isCreditCard(value)) return "CARD";
3092
- if (isSSN(value)) return "ID";
3093
- if (isPhone(value)) return "PHONE";
3094
- if (isIPv4(value)) return "IP";
3095
- return null;
3096
- }
3097
- var MAX_PII_TEXT_LENGTH = 1e5;
3098
- function redactPIIFromText(text) {
3099
- if (text.length > MAX_PII_TEXT_LENGTH) {
3100
- return text;
3101
- }
3102
- let result = text;
3103
- result = result.replace(PII_PATTERNS.creditCardInText, REDACTION_TOKENS.CARD);
3104
- result = result.replace(PII_PATTERNS.ssnInText, REDACTION_TOKENS.ID);
3105
- result = result.replace(PII_PATTERNS.emailInText, REDACTION_TOKENS.EMAIL);
3106
- result = result.replace(PII_PATTERNS.phoneInText, REDACTION_TOKENS.PHONE);
3107
- result = result.replace(PII_PATTERNS.ipv4InText, REDACTION_TOKENS.IP);
3108
- return result;
3109
- }
3110
- function sanitizeValue(key, value, path, options, depth, visited) {
3111
- const maxDepth = options.maxDepth ?? 10;
3112
- if (depth > maxDepth) {
3113
- return value;
3114
- }
3115
- if (value === null || value === void 0) {
3116
- return value;
3117
- }
3118
- if (typeof options.mode === "function") {
3119
- return options.mode(key, value, path);
3120
- }
3121
- if (Array.isArray(options.mode)) {
3122
- const lowerKey = key.toLowerCase();
3123
- if (options.mode.some((f) => f.toLowerCase() === lowerKey)) {
3124
- return REDACTION_TOKENS.REDACTED;
3125
- }
3126
- }
3127
- if (typeof value === "string") {
3128
- if (options.mode === true) {
3129
- const piiType = detectPIIType(value);
3130
- if (piiType) {
3131
- return REDACTION_TOKENS[piiType];
3132
- }
3133
- if (options.sanitizeInText !== false) {
3134
- const redacted = redactPIIFromText(value);
3135
- if (redacted !== value) {
3136
- return redacted;
3137
- }
3138
- }
3139
- }
3140
- return value;
3141
- }
3142
- if (Array.isArray(value)) {
3143
- if (visited.has(value)) {
3144
- return REDACTION_TOKENS.REDACTED;
3145
- }
3146
- visited.add(value);
3147
- return value.map(
3148
- (item, index) => sanitizeValue(String(index), item, [...path, String(index)], options, depth + 1, visited)
3149
- );
3150
- }
3151
- if (typeof value === "object") {
3152
- if (visited.has(value)) {
3153
- return REDACTION_TOKENS.REDACTED;
3154
- }
3155
- visited.add(value);
3156
- const result = {};
3157
- for (const [k, v] of Object.entries(value)) {
3158
- result[k] = sanitizeValue(k, v, [...path, k], options, depth + 1, visited);
3159
- }
3160
- return result;
3161
- }
3162
- return value;
3163
- }
3164
- function sanitizeInput(input, mode = true) {
3165
- if (!input || typeof input !== "object") {
3166
- return {};
3167
- }
3168
- const options = {
3169
- mode,
3170
- maxDepth: 10,
3171
- sanitizeInText: true
3172
- };
3173
- const visited = /* @__PURE__ */ new WeakSet();
3174
- return sanitizeValue("", input, [], options, 0, visited);
3175
- }
3176
- function createSanitizer(mode = true) {
3177
- return (input) => sanitizeInput(input, mode);
3178
- }
3179
- function detectPII(input, options) {
3180
- const maxDepth = options?.maxDepth ?? 10;
3181
- const fields = [];
3182
- const visited = /* @__PURE__ */ new WeakSet();
3183
- const check = (value, path, depth) => {
3184
- if (depth > maxDepth) return;
3185
- if (value === null || value === void 0) return;
3186
- if (typeof value === "string") {
3187
- if (detectPIIType(value)) {
3188
- fields.push(path.join("."));
3189
- }
3190
- return;
3191
- }
3192
- if (Array.isArray(value)) {
3193
- if (visited.has(value)) return;
3194
- visited.add(value);
3195
- value.forEach((item, index) => check(item, [...path, String(index)], depth + 1));
3196
- return;
3197
- }
3198
- if (typeof value === "object") {
3199
- if (visited.has(value)) return;
3200
- visited.add(value);
3201
- for (const [k, v] of Object.entries(value)) {
3202
- check(v, [...path, k], depth + 1);
3203
- }
3204
- }
3205
- };
3206
- check(input, [], 0);
3207
- return {
3208
- hasPII: fields.length > 0,
3209
- fields
3210
- };
3211
- }
3212
-
3213
- // libs/uipack/src/runtime/wrapper.ts
3467
+ init_sanitizer();
3214
3468
  function createTemplateHelpers() {
3215
3469
  let idCounter2 = 0;
3216
3470
  return {
@@ -4798,6 +5052,9 @@ function wrapToolUIForClaude(options) {
4798
5052
  </html>`;
4799
5053
  }
4800
5054
 
5055
+ // libs/uipack/src/runtime/index.ts
5056
+ init_sanitizer();
5057
+
4801
5058
  // libs/uipack/src/runtime/adapters/index.ts
4802
5059
  init_html_adapter();
4803
5060
  init_mdx_adapter();
@@ -5162,6 +5419,7 @@ function generateBootstrapScript() {
5162
5419
  loadAdapter,
5163
5420
  redactPIIFromText,
5164
5421
  sanitizeCSPDomains,
5422
+ sanitizeHtmlContent,
5165
5423
  sanitizeInput,
5166
5424
  validateCSPDomain,
5167
5425
  wrapHybridWidgetShell,
@@ -1 +1 @@
1
- {"version":3,"file":"mcp-bridge.d.ts","sourceRoot":"","sources":["../../src/runtime/mcp-bridge.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAIH;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,kBAAkB,koeA8c9B,CAAC;AAEF;;;GAGG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,CAI3C;AAED;;GAEG;AACH,wBAAgB,oBAAoB,IAAI,OAAO,CAW9C;AAMD;;;;;;;;;GASG;AACH,eAAO,MAAM,uBAAuB,QAA+B,CAAC;AAEpE;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,uBAAuB;;;;;CAAqB,CAAC;AAE1D;;;;;;;;;;;;;GAaG;AACH,OAAO,EAAE,kBAAkB,IAAI,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAG/E,YAAY,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC"}
1
+ {"version":3,"file":"mcp-bridge.d.ts","sourceRoot":"","sources":["../../src/runtime/mcp-bridge.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAIH;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,kBAAkB,koeA8c9B,CAAC;AAEF;;;GAGG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,CAa3C;AAED;;GAEG;AACH,wBAAgB,oBAAoB,IAAI,OAAO,CAW9C;AAMD;;;;;;;;;GASG;AACH,eAAO,MAAM,uBAAuB,QAA+B,CAAC;AAEpE;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,uBAAuB;;;;;CAAqB,CAAC;AAE1D;;;;;;;;;;;;;GAaG;AACH,OAAO,EAAE,kBAAkB,IAAI,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAG/E,YAAY,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC"}
@@ -169,4 +169,12 @@ export declare function detectPII(input: Record<string, unknown>, options?: {
169
169
  hasPII: boolean;
170
170
  fields: string[];
171
171
  };
172
+ /**
173
+ * Sanitize HTML content to prevent XSS attacks.
174
+ * Uses DOMPurify for robust sanitization when available (browser), falls back to parser-based approach.
175
+ *
176
+ * @param html - HTML string to sanitize
177
+ * @returns Sanitized HTML string
178
+ */
179
+ export declare function sanitizeHtmlContent(html: string): string;
172
180
  //# sourceMappingURL=sanitizer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"sanitizer.d.ts","sourceRoot":"","sources":["../../src/runtime/sanitizer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAEH;;GAEG;AACH,eAAO,MAAM,gBAAgB;;;;;;;CAOnB,CAAC;AAEX;;GAEG;AACH,eAAO,MAAM,YAAY;;;;;;;;;;;CA8BxB,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC;AAEnF;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B;;;;;OAKG;IACH,IAAI,EAAE,IAAI,GAAG,MAAM,EAAE,GAAG,WAAW,CAAC;IAEpC;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;;OAGG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED;;GAEG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAE9C;AAED;;GAEG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAE9C;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAInD;AAED;;GAEG;AACH,wBAAgB,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAE5C;AAED;;GAEG;AACH,wBAAgB,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAE7C;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,OAAO,gBAAgB,GAAG,IAAI,CAOjF;AAQD;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAgBtD;AA0FD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,aAAa,CAC3B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,IAAI,GAAE,IAAI,GAAG,MAAM,EAAE,GAAG,WAAkB,GACzC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAczB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,eAAe,CAC7B,IAAI,GAAE,IAAI,GAAG,MAAM,EAAE,GAAG,WAAkB,GACzC,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAE7D;AAED;;;;;;;;GAQG;AACH,wBAAgB,SAAS,CACvB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,OAAO,CAAC,EAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,GAC9B;IACD,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB,CA0CA"}
1
+ {"version":3,"file":"sanitizer.d.ts","sourceRoot":"","sources":["../../src/runtime/sanitizer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAIH;;GAEG;AACH,eAAO,MAAM,gBAAgB;;;;;;;CAOnB,CAAC;AAEX;;GAEG;AACH,eAAO,MAAM,YAAY;;;;;;;;;;;CA8BxB,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC;AAEnF;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B;;;;;OAKG;IACH,IAAI,EAAE,IAAI,GAAG,MAAM,EAAE,GAAG,WAAW,CAAC;IAEpC;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;;OAGG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED;;GAEG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAE9C;AAED;;GAEG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAE9C;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAInD;AAED;;GAEG;AACH,wBAAgB,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAE5C;AAED;;GAEG;AACH,wBAAgB,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAE7C;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,OAAO,gBAAgB,GAAG,IAAI,CAOjF;AAQD;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAgBtD;AA0FD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,aAAa,CAC3B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,IAAI,GAAE,IAAI,GAAG,MAAM,EAAE,GAAG,WAAkB,GACzC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAczB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,eAAe,CAC7B,IAAI,GAAE,IAAI,GAAG,MAAM,EAAE,GAAG,WAAkB,GACzC,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAE7D;AAED;;;;;;;;GAQG;AACH,wBAAgB,SAAS,CACvB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,OAAO,CAAC,EAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,GAC9B;IACD,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB,CA0CA;AA4GD;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAUxD"}
@@ -2679,6 +2679,7 @@ function buildThemeCss(theme) {
2679
2679
  init_utils();
2680
2680
 
2681
2681
  // libs/uipack/src/runtime/sanitizer.ts
2682
+ var import_dompurify = __toESM(require("dompurify"));
2682
2683
  var REDACTION_TOKENS = {
2683
2684
  EMAIL: "[EMAIL]",
2684
2685
  PHONE: "[PHONE]",