@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/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, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
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, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
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
|
|
2432
|
-
|
|
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,
|
|
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"}
|
package/runtime/sanitizer.d.ts
CHANGED
|
@@ -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;
|
|
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"}
|
package/tool-template/index.js
CHANGED