@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/README.md +46 -106
- package/bundler/index.js +2 -2
- package/esm/bundler/index.mjs +1 -1
- package/esm/index.mjs +190 -175
- package/esm/package.json +4 -3
- package/esm/runtime/index.mjs +433 -176
- package/esm/tool-template/index.mjs +1 -0
- package/index.js +190 -175
- package/package.json +4 -3
- package/runtime/adapters/html.adapter.d.ts.map +1 -1
- package/runtime/index.d.ts +1 -1
- package/runtime/index.d.ts.map +1 -1
- package/runtime/index.js +434 -176
- package/runtime/mcp-bridge.d.ts.map +1 -1
- package/runtime/sanitizer.d.ts +8 -0
- package/runtime/sanitizer.d.ts.map +1 -1
- package/tool-template/index.js +1 -0
package/esm/runtime/index.mjs
CHANGED
|
@@ -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, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
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, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
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
|
|
2363
|
-
|
|
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,
|