@fynixorg/ui 1.0.13 → 1.0.14
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/dist/hooks/nixAsync.d.ts +7 -1
- package/dist/hooks/nixAsync.d.ts.map +1 -1
- package/dist/hooks/nixAsync.js +84 -10
- package/dist/hooks/nixAsync.js.map +2 -2
- package/dist/hooks/nixAsyncCache.d.ts +6 -1
- package/dist/hooks/nixAsyncCache.d.ts.map +1 -1
- package/dist/hooks/nixAsyncCache.js +104 -26
- package/dist/hooks/nixAsyncCache.js.map +3 -3
- package/dist/hooks/nixComputed.d.ts.map +1 -1
- package/dist/hooks/nixComputed.js +4 -1
- package/dist/hooks/nixComputed.js.map +2 -2
- package/dist/hooks/nixEffect.d.ts.map +1 -1
- package/dist/hooks/nixEffect.js.map +2 -2
- package/dist/hooks/nixLocalStorage.d.ts +4 -1
- package/dist/hooks/nixLocalStorage.d.ts.map +1 -1
- package/dist/hooks/nixLocalStorage.js +118 -8
- package/dist/hooks/nixLocalStorage.js.map +2 -2
- package/dist/hooks/nixStore.d.ts +3 -0
- package/dist/hooks/nixStore.d.ts.map +1 -1
- package/dist/hooks/nixStore.js +57 -4
- package/dist/hooks/nixStore.js.map +2 -2
- package/dist/package.json +1 -1
- package/dist/plugins/vite-plugin-res.d.ts.map +1 -1
- package/dist/plugins/vite-plugin-res.js +239 -47
- package/dist/plugins/vite-plugin-res.js.map +3 -3
- package/dist/router/router.d.ts +13 -0
- package/dist/router/router.d.ts.map +1 -1
- package/dist/router/router.js +324 -16
- package/dist/router/router.js.map +2 -2
- package/dist/runtime.d.ts +62 -0
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +489 -13
- package/dist/runtime.js.map +2 -2
- package/package.json +1 -1
|
@@ -1,6 +1,98 @@
|
|
|
1
1
|
import { transform } from "esbuild";
|
|
2
2
|
import { normalizePath } from "vite";
|
|
3
3
|
import * as ts from "typescript";
|
|
4
|
+
function sanitizeIdentifier(identifier) {
|
|
5
|
+
if (typeof identifier !== "string")
|
|
6
|
+
return "";
|
|
7
|
+
const sanitized = identifier.replace(/[^a-zA-Z0-9_$]/g, "");
|
|
8
|
+
if (/^\d/.test(sanitized)) {
|
|
9
|
+
return "_" + sanitized;
|
|
10
|
+
}
|
|
11
|
+
const dangerousWords = [
|
|
12
|
+
"eval",
|
|
13
|
+
"Function",
|
|
14
|
+
"constructor",
|
|
15
|
+
"prototype",
|
|
16
|
+
"__proto__",
|
|
17
|
+
"window",
|
|
18
|
+
"global",
|
|
19
|
+
"process",
|
|
20
|
+
"require",
|
|
21
|
+
"import",
|
|
22
|
+
"export",
|
|
23
|
+
"document",
|
|
24
|
+
"location",
|
|
25
|
+
"alert",
|
|
26
|
+
"confirm",
|
|
27
|
+
"prompt",
|
|
28
|
+
];
|
|
29
|
+
if (dangerousWords.includes(sanitized)) {
|
|
30
|
+
return "safe_" + sanitized;
|
|
31
|
+
}
|
|
32
|
+
return sanitized || "defaultIdentifier";
|
|
33
|
+
}
|
|
34
|
+
function escapeHTML(str) {
|
|
35
|
+
if (typeof str !== "string")
|
|
36
|
+
return "";
|
|
37
|
+
return str
|
|
38
|
+
.replace(/&/g, "&")
|
|
39
|
+
.replace(/</g, "<")
|
|
40
|
+
.replace(/>/g, ">")
|
|
41
|
+
.replace(/"/g, """)
|
|
42
|
+
.replace(/'/g, "'")
|
|
43
|
+
.replace(/\//g, "/")
|
|
44
|
+
.replace(/`/g, "`")
|
|
45
|
+
.replace(/=/g, "=");
|
|
46
|
+
}
|
|
47
|
+
function sanitizeCSS(css) {
|
|
48
|
+
if (typeof css !== "string")
|
|
49
|
+
return "";
|
|
50
|
+
return css
|
|
51
|
+
.replace(/javascript:/gi, "")
|
|
52
|
+
.replace(/vbscript:/gi, "")
|
|
53
|
+
.replace(/data:/gi, "")
|
|
54
|
+
.replace(/expression\s*\(/gi, "")
|
|
55
|
+
.replace(/@import/gi, "")
|
|
56
|
+
.replace(/url\s*\(/gi, "")
|
|
57
|
+
.replace(/behavior\s*:/gi, "");
|
|
58
|
+
}
|
|
59
|
+
function sanitizePath(path) {
|
|
60
|
+
if (typeof path !== "string")
|
|
61
|
+
return "";
|
|
62
|
+
return path
|
|
63
|
+
.replace(/\.\./g, "")
|
|
64
|
+
.replace(/~/g, "")
|
|
65
|
+
.replace(/\\+/g, "/")
|
|
66
|
+
.replace(/\/+/g, "/")
|
|
67
|
+
.replace(/^\//g, "")
|
|
68
|
+
.replace(/\/$/, "");
|
|
69
|
+
}
|
|
70
|
+
function validateTemplateContent(content) {
|
|
71
|
+
if (typeof content !== "string")
|
|
72
|
+
return "";
|
|
73
|
+
const dangerousPatterns = [
|
|
74
|
+
/\$\{.*?\}/g,
|
|
75
|
+
/eval\s*\(/gi,
|
|
76
|
+
/Function\s*\(/gi,
|
|
77
|
+
/__proto__/gi,
|
|
78
|
+
/constructor/gi,
|
|
79
|
+
/import\s*\(/gi,
|
|
80
|
+
/require\s*\(/gi,
|
|
81
|
+
/process\./gi,
|
|
82
|
+
/global\./gi,
|
|
83
|
+
/<script/gi,
|
|
84
|
+
/javascript:/gi,
|
|
85
|
+
/vbscript:/gi,
|
|
86
|
+
/data:.*script/gi,
|
|
87
|
+
];
|
|
88
|
+
for (const pattern of dangerousPatterns) {
|
|
89
|
+
if (pattern.test(content)) {
|
|
90
|
+
console.warn(`[Security] Blocked dangerous pattern in template: ${pattern}`);
|
|
91
|
+
return "";
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return content;
|
|
95
|
+
}
|
|
4
96
|
const colors = {
|
|
5
97
|
reset: "\x1b[0m",
|
|
6
98
|
red: "\x1b[31m",
|
|
@@ -13,6 +105,13 @@ const colors = {
|
|
|
13
105
|
bold: "\x1b[1m",
|
|
14
106
|
};
|
|
15
107
|
function parseSFC(source) {
|
|
108
|
+
if (typeof source !== "string" || source.length > 1000000) {
|
|
109
|
+
throw new Error("Invalid or excessively large SFC source");
|
|
110
|
+
}
|
|
111
|
+
const sanitizedSource = validateTemplateContent(source);
|
|
112
|
+
if (!sanitizedSource) {
|
|
113
|
+
throw new Error("SFC contains potentially dangerous content");
|
|
114
|
+
}
|
|
16
115
|
const result = {
|
|
17
116
|
logic: "",
|
|
18
117
|
view: "",
|
|
@@ -25,11 +124,14 @@ function parseSFC(source) {
|
|
|
25
124
|
imports: [],
|
|
26
125
|
exports: [],
|
|
27
126
|
};
|
|
28
|
-
const logicMatch =
|
|
127
|
+
const logicMatch = sanitizedSource.match(/<logic\s+setup\s*=\s*["']?(ts|js)["']?\s*>([\s\S]*?)<\/logic>/i);
|
|
29
128
|
if (logicMatch && logicMatch[1] && logicMatch[2] !== undefined) {
|
|
30
129
|
result.hasLogic = true;
|
|
31
130
|
result.logicLang = logicMatch[1].toLowerCase();
|
|
32
131
|
const rawLogic = logicMatch[2].trim();
|
|
132
|
+
if (rawLogic.length > 100000) {
|
|
133
|
+
throw new Error("Logic block exceeds size limit");
|
|
134
|
+
}
|
|
33
135
|
const logicLines = rawLogic.split("\n");
|
|
34
136
|
const imports = [];
|
|
35
137
|
const exports = [];
|
|
@@ -40,6 +142,10 @@ function parseSFC(source) {
|
|
|
40
142
|
for (let i = 0; i < logicLines.length; i++) {
|
|
41
143
|
const line = logicLines[i] ?? "";
|
|
42
144
|
const trimmed = line.trim();
|
|
145
|
+
if (validateTemplateContent(line) === "") {
|
|
146
|
+
console.warn(`[Security] Skipped potentially dangerous line in logic block`);
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
43
149
|
if (inExportBlock) {
|
|
44
150
|
exportBuffer.push(line);
|
|
45
151
|
const openBraces = ((line && line.match(/{/g)) || []).length;
|
|
@@ -85,27 +191,61 @@ function parseSFC(source) {
|
|
|
85
191
|
result.exports = exports;
|
|
86
192
|
result.logic = otherLogic.join("\n");
|
|
87
193
|
}
|
|
88
|
-
const viewMatch =
|
|
194
|
+
const viewMatch = sanitizedSource.match(/<view\s*>([\s\S]*?)<\/view>/i);
|
|
89
195
|
if (viewMatch && viewMatch[1] !== undefined) {
|
|
196
|
+
const viewContent = viewMatch[1].trim();
|
|
197
|
+
if (viewContent.length > 50000) {
|
|
198
|
+
throw new Error("View block exceeds size limit");
|
|
199
|
+
}
|
|
90
200
|
result.hasView = true;
|
|
91
|
-
result.view =
|
|
201
|
+
result.view = viewContent;
|
|
92
202
|
}
|
|
93
|
-
const styleMatch =
|
|
203
|
+
const styleMatch = sanitizedSource.match(/<style(\s+scoped)?\s*>([\s\S]*?)<\/style>/i);
|
|
94
204
|
if (styleMatch && styleMatch[2] !== undefined) {
|
|
205
|
+
const styleContent = styleMatch[2].trim();
|
|
206
|
+
if (styleContent.length > 100000) {
|
|
207
|
+
throw new Error("Style block exceeds size limit");
|
|
208
|
+
}
|
|
95
209
|
result.hasStyle = true;
|
|
96
210
|
result.isStyleScoped = !!styleMatch[1];
|
|
97
|
-
result.style =
|
|
211
|
+
result.style = sanitizeCSS(styleContent);
|
|
98
212
|
}
|
|
99
213
|
return result;
|
|
100
214
|
}
|
|
101
215
|
function generateStyleId(filePath) {
|
|
216
|
+
const safePath = sanitizePath(filePath);
|
|
217
|
+
if (typeof crypto !== "undefined" && crypto.subtle) {
|
|
218
|
+
const encoder = new TextEncoder();
|
|
219
|
+
const data = encoder.encode(safePath + Date.now());
|
|
220
|
+
let dataHash = 0;
|
|
221
|
+
for (let i = 0; i < data.length; i++) {
|
|
222
|
+
const byte = data[i];
|
|
223
|
+
if (byte !== undefined) {
|
|
224
|
+
dataHash = (dataHash << 5) - dataHash + byte;
|
|
225
|
+
dataHash = dataHash & dataHash;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
const combinedInput = safePath + "_security_salt_" + dataHash;
|
|
229
|
+
let hash = 0;
|
|
230
|
+
for (let i = 0; i < combinedInput.length; i++) {
|
|
231
|
+
const char = combinedInput.charCodeAt(i);
|
|
232
|
+
hash = (hash << 5) - hash + char;
|
|
233
|
+
hash = hash & hash;
|
|
234
|
+
}
|
|
235
|
+
const timestamp = Date.now().toString(36);
|
|
236
|
+
const hashStr = Math.abs(hash).toString(36);
|
|
237
|
+
return sanitizeIdentifier(`fynix-${hashStr}-${timestamp}`);
|
|
238
|
+
}
|
|
102
239
|
let hash = 0;
|
|
103
|
-
|
|
104
|
-
|
|
240
|
+
const input = safePath + "_security_salt";
|
|
241
|
+
for (let i = 0; i < input.length; i++) {
|
|
242
|
+
const char = input.charCodeAt(i);
|
|
105
243
|
hash = (hash << 5) - hash + char;
|
|
106
244
|
hash = hash & hash;
|
|
107
245
|
}
|
|
108
|
-
|
|
246
|
+
const timestamp = Date.now().toString(36);
|
|
247
|
+
const hashStr = Math.abs(hash).toString(36);
|
|
248
|
+
return sanitizeIdentifier(`fynix-${hashStr}-${timestamp}`);
|
|
109
249
|
}
|
|
110
250
|
function scopeStyles(css, scopeId) {
|
|
111
251
|
const dataAttr = `[data-${scopeId}]`;
|
|
@@ -155,23 +295,34 @@ function scopeStyles(css, scopeId) {
|
|
|
155
295
|
return css;
|
|
156
296
|
}
|
|
157
297
|
function transformSFC(parsed, filePath, jsxFactory) {
|
|
158
|
-
|
|
298
|
+
if (!parsed || typeof parsed !== "object") {
|
|
299
|
+
throw new Error("Invalid parsed SFC result");
|
|
300
|
+
}
|
|
301
|
+
const safeFilePath = sanitizePath(filePath);
|
|
302
|
+
const safeJsxFactory = sanitizeIdentifier(jsxFactory);
|
|
303
|
+
if (!safeJsxFactory) {
|
|
304
|
+
throw new Error("Invalid JSX factory name");
|
|
305
|
+
}
|
|
306
|
+
const styleId = generateStyleId(safeFilePath);
|
|
159
307
|
const lines = [];
|
|
160
|
-
lines.push(`import { ${
|
|
308
|
+
lines.push(`import { ${safeJsxFactory} } from '@fynixorg/ui';`);
|
|
161
309
|
if (parsed.imports.length > 0) {
|
|
162
310
|
parsed.imports.forEach((importLine) => {
|
|
163
|
-
|
|
311
|
+
const validatedImport = validateTemplateContent(importLine);
|
|
312
|
+
if (validatedImport) {
|
|
313
|
+
lines.push(validatedImport);
|
|
314
|
+
}
|
|
164
315
|
});
|
|
165
316
|
}
|
|
166
317
|
lines.push("");
|
|
167
318
|
if (parsed.hasStyle) {
|
|
168
|
-
let processedStyle = parsed.style;
|
|
319
|
+
let processedStyle = sanitizeCSS(parsed.style);
|
|
169
320
|
if (parsed.isStyleScoped) {
|
|
170
|
-
processedStyle = scopeStyles(
|
|
321
|
+
processedStyle = scopeStyles(processedStyle, styleId);
|
|
171
322
|
}
|
|
172
323
|
lines.push(`// Inject styles`);
|
|
173
324
|
lines.push(`if (typeof document !== 'undefined') {`);
|
|
174
|
-
lines.push(` const styleId =
|
|
325
|
+
lines.push(` const styleId = ${JSON.stringify(styleId)};`);
|
|
175
326
|
lines.push(` if (!document.getElementById(styleId)) {`);
|
|
176
327
|
lines.push(` const styleEl = document.createElement('style');`);
|
|
177
328
|
lines.push(` styleEl.id = styleId;`);
|
|
@@ -183,7 +334,10 @@ function transformSFC(parsed, filePath, jsxFactory) {
|
|
|
183
334
|
}
|
|
184
335
|
if (parsed.exports.length > 0) {
|
|
185
336
|
parsed.exports.forEach((exportLine) => {
|
|
186
|
-
|
|
337
|
+
const validatedExport = validateTemplateContent(exportLine);
|
|
338
|
+
if (validatedExport) {
|
|
339
|
+
lines.push(validatedExport);
|
|
340
|
+
}
|
|
187
341
|
});
|
|
188
342
|
lines.push("");
|
|
189
343
|
}
|
|
@@ -197,44 +351,58 @@ function transformSFC(parsed, filePath, jsxFactory) {
|
|
|
197
351
|
lines.push(` // Component logic`);
|
|
198
352
|
const logicLines = parsed.logic.split("\n");
|
|
199
353
|
logicLines.forEach((line) => {
|
|
200
|
-
|
|
201
|
-
|
|
354
|
+
const validatedLine = validateTemplateContent(line);
|
|
355
|
+
if (validatedLine && validatedLine.trim()) {
|
|
356
|
+
lines.push(` ${validatedLine}`);
|
|
202
357
|
}
|
|
203
358
|
});
|
|
204
359
|
lines.push("");
|
|
205
360
|
}
|
|
206
361
|
if (parsed.exports.some((e) => e.trim().startsWith("export const meta"))) {
|
|
207
362
|
lines.push(` if (typeof document !== "undefined" && typeof meta !== "undefined") {`);
|
|
208
|
-
lines.push(` if (meta.title)
|
|
363
|
+
lines.push(` if (meta.title) {`);
|
|
364
|
+
lines.push(` // Import the escapeHTML function for secure processing`);
|
|
365
|
+
lines.push(` const escapeHTML = ${escapeHTML.toString()};`);
|
|
366
|
+
lines.push(` const safeTitle = escapeHTML(String(meta.title)).substring(0, 60);`);
|
|
367
|
+
lines.push(` document.title = safeTitle;`);
|
|
368
|
+
lines.push(` }`);
|
|
209
369
|
lines.push(` const _meta = meta as any;`);
|
|
370
|
+
lines.push(` const escapeHTML = ${escapeHTML.toString()};`);
|
|
210
371
|
lines.push(` const metaTags = [`);
|
|
211
|
-
lines.push(` _meta.description ? { name: "description", content: _meta.description } : null,`);
|
|
212
|
-
lines.push(` _meta.keywords ? { name: "keywords", content: _meta.keywords } : null,`);
|
|
213
|
-
lines.push(` _meta.ogTitle ? { property: "og:title", content: _meta.ogTitle } : null,`);
|
|
214
|
-
lines.push(` _meta.ogDescription ? { property: "og:description", content: _meta.ogDescription } : null,`);
|
|
215
|
-
lines.push(` _meta.ogImage ? { property: "og:image", content: _meta.ogImage } : null,`);
|
|
372
|
+
lines.push(` _meta.description ? { name: "description", content: escapeHTML(String(_meta.description || '')).substring(0, 300) } : null,`);
|
|
373
|
+
lines.push(` _meta.keywords ? { name: "keywords", content: escapeHTML(String(_meta.keywords || '')).substring(0, 200) } : null,`);
|
|
374
|
+
lines.push(` _meta.ogTitle ? { property: "og:title", content: escapeHTML(String(_meta.ogTitle || '')).substring(0, 60) } : null,`);
|
|
375
|
+
lines.push(` _meta.ogDescription ? { property: "og:description", content: escapeHTML(String(_meta.ogDescription || '')).substring(0, 300) } : null,`);
|
|
376
|
+
lines.push(` _meta.ogImage ? { property: "og:image", content: escapeHTML(String(_meta.ogImage || '')).substring(0, 500) } : null,`);
|
|
216
377
|
lines.push(` ].filter(Boolean);`);
|
|
217
378
|
lines.push(` metaTags.forEach((tagObj) => {`);
|
|
218
379
|
lines.push(` if (!tagObj) return;`);
|
|
219
380
|
lines.push(` const { name, property, content } = tagObj;`);
|
|
220
|
-
lines.push(` if (!content) return;`);
|
|
381
|
+
lines.push(` if (!content || typeof content !== 'string') return;`);
|
|
221
382
|
lines.push(` let tag;`);
|
|
222
383
|
lines.push(` if (name) {`);
|
|
223
|
-
lines.push(
|
|
384
|
+
lines.push(` const safeName = name.replace(/[^a-zA-Z0-9-_]/g, '');`);
|
|
385
|
+
lines.push(` if (!safeName) return;`);
|
|
386
|
+
lines.push(" tag = document.querySelector(`meta[name='${safeName}']`);");
|
|
224
387
|
lines.push(` if (!tag) {`);
|
|
225
388
|
lines.push(` tag = document.createElement("meta");`);
|
|
226
|
-
lines.push(` tag.setAttribute("name",
|
|
389
|
+
lines.push(` tag.setAttribute("name", safeName);`);
|
|
227
390
|
lines.push(` document.head.appendChild(tag);`);
|
|
228
391
|
lines.push(` }`);
|
|
229
392
|
lines.push(` } else if (property) {`);
|
|
230
|
-
lines.push(
|
|
393
|
+
lines.push(` const safeProperty = property.replace(/[^a-zA-Z0-9-_:]/g, '');`);
|
|
394
|
+
lines.push(` if (!safeProperty) return;`);
|
|
395
|
+
lines.push(" tag = document.querySelector(`meta[property='${safeProperty}']`);");
|
|
231
396
|
lines.push(` if (!tag) {`);
|
|
232
397
|
lines.push(` tag = document.createElement("meta");`);
|
|
233
|
-
lines.push(` tag.setAttribute("property",
|
|
398
|
+
lines.push(` tag.setAttribute("property", safeProperty);`);
|
|
234
399
|
lines.push(` document.head.appendChild(tag);`);
|
|
235
400
|
lines.push(` }`);
|
|
236
401
|
lines.push(` }`);
|
|
237
|
-
lines.push(` if (tag)
|
|
402
|
+
lines.push(` if (tag) {`);
|
|
403
|
+
lines.push(` // Escape content to prevent XSS using escapeHTML function`);
|
|
404
|
+
lines.push(` tag.setAttribute("content", content);`);
|
|
405
|
+
lines.push(` }`);
|
|
238
406
|
lines.push(` });`);
|
|
239
407
|
lines.push(` }`);
|
|
240
408
|
lines.push("");
|
|
@@ -242,7 +410,10 @@ function transformSFC(parsed, filePath, jsxFactory) {
|
|
|
242
410
|
if (parsed.hasView) {
|
|
243
411
|
lines.push(` // Component view`);
|
|
244
412
|
if (parsed.isStyleScoped) {
|
|
245
|
-
let viewContent = parsed.view.trim();
|
|
413
|
+
let viewContent = validateTemplateContent(parsed.view.trim());
|
|
414
|
+
if (!viewContent) {
|
|
415
|
+
throw new Error("View content contains dangerous patterns");
|
|
416
|
+
}
|
|
246
417
|
const showGeneratedCode = typeof globalThis !== "undefined" &&
|
|
247
418
|
globalThis.fynixShowGeneratedCode !== undefined
|
|
248
419
|
? globalThis.fynixShowGeneratedCode
|
|
@@ -258,15 +429,11 @@ function transformSFC(parsed, filePath, jsxFactory) {
|
|
|
258
429
|
const closingBracket = tagMatch[3];
|
|
259
430
|
if (showGeneratedCode) {
|
|
260
431
|
console.log(`${colors.magenta}[DEBUG] Matched tag:${colors.reset} ${openTag}`);
|
|
261
|
-
console.log(`${colors.magenta}[DEBUG] Attributes:${colors.reset} ${attributes}`);
|
|
262
432
|
}
|
|
263
|
-
const
|
|
433
|
+
const safeDataAttr = sanitizeIdentifier(`data-${styleId}`);
|
|
434
|
+
const modifiedStart = `${openTag}${attributes} ${safeDataAttr}=""${closingBracket}`;
|
|
264
435
|
const restOfView = viewContent.substring(tagMatch[0].length);
|
|
265
436
|
const modifiedView = modifiedStart + restOfView;
|
|
266
|
-
if (showGeneratedCode) {
|
|
267
|
-
console.log(`${colors.magenta}[DEBUG] Modified first line:${colors.reset}`);
|
|
268
|
-
console.log(modifiedView.split("\n")[0]);
|
|
269
|
-
}
|
|
270
437
|
lines.push(` return (`);
|
|
271
438
|
const viewLines = modifiedView.split("\n");
|
|
272
439
|
viewLines.forEach((line) => {
|
|
@@ -276,8 +443,8 @@ function transformSFC(parsed, filePath, jsxFactory) {
|
|
|
276
443
|
}
|
|
277
444
|
else {
|
|
278
445
|
lines.push(` return (`);
|
|
279
|
-
lines.push(` <div data-${styleId}="">`);
|
|
280
|
-
const viewLines =
|
|
446
|
+
lines.push(` <div ${sanitizeIdentifier(`data-${styleId}`)}="">`);
|
|
447
|
+
const viewLines = viewContent.split("\n");
|
|
281
448
|
viewLines.forEach((line) => {
|
|
282
449
|
lines.push(` ${line}`);
|
|
283
450
|
});
|
|
@@ -286,8 +453,12 @@ function transformSFC(parsed, filePath, jsxFactory) {
|
|
|
286
453
|
}
|
|
287
454
|
}
|
|
288
455
|
else {
|
|
456
|
+
const validatedView = validateTemplateContent(parsed.view);
|
|
457
|
+
if (!validatedView) {
|
|
458
|
+
throw new Error("View content contains dangerous patterns");
|
|
459
|
+
}
|
|
289
460
|
lines.push(` return (`);
|
|
290
|
-
const viewLines =
|
|
461
|
+
const viewLines = validatedView.split("\n");
|
|
291
462
|
viewLines.forEach((line) => {
|
|
292
463
|
lines.push(` ${line}`);
|
|
293
464
|
});
|
|
@@ -454,6 +625,11 @@ class TypeScriptChecker {
|
|
|
454
625
|
}
|
|
455
626
|
export default function fynixPlugin(options = {}) {
|
|
456
627
|
const { jsxFactory = "Fynix", jsxFragment = "Fynix.Fragment", include = [".ts", ".js", ".jsx", ".tsx", ".fnx"], exclude = ["node_modules"], sourcemap = true, esbuildOptions = {}, enableSFC = true, showGeneratedCode = false, typeCheck = false, tsConfig, } = options;
|
|
628
|
+
const safeJsxFactory = sanitizeIdentifier(jsxFactory);
|
|
629
|
+
const safeJsxFragment = sanitizeIdentifier(jsxFragment);
|
|
630
|
+
if (!safeJsxFactory || !safeJsxFragment) {
|
|
631
|
+
throw new Error("Invalid JSX factory or fragment names provided");
|
|
632
|
+
}
|
|
457
633
|
let typeChecker = null;
|
|
458
634
|
if (typeCheck) {
|
|
459
635
|
typeChecker = new TypeScriptChecker(tsConfig);
|
|
@@ -463,6 +639,14 @@ export default function fynixPlugin(options = {}) {
|
|
|
463
639
|
enforce: "pre",
|
|
464
640
|
async transform(code, id) {
|
|
465
641
|
const normalizedId = normalizePath(id);
|
|
642
|
+
const safePath = sanitizePath(normalizedId);
|
|
643
|
+
if (!safePath || safePath !== normalizedId.replace(/^.*[\/\\]/, "")) {
|
|
644
|
+
console.warn(`[Security] Potentially dangerous file path blocked: ${normalizedId}`);
|
|
645
|
+
return null;
|
|
646
|
+
}
|
|
647
|
+
if (code.length > 10485760) {
|
|
648
|
+
throw new Error(`File ${normalizedId} exceeds maximum size limit`);
|
|
649
|
+
}
|
|
466
650
|
const shouldExclude = exclude.some((pattern) => normalizedId.includes(pattern));
|
|
467
651
|
if (shouldExclude)
|
|
468
652
|
return null;
|
|
@@ -477,10 +661,15 @@ export default function fynixPlugin(options = {}) {
|
|
|
477
661
|
let codeToTransform = code;
|
|
478
662
|
let loader = "tsx";
|
|
479
663
|
let shouldTypeCheck = false;
|
|
664
|
+
const validatedCode = validateTemplateContent(code);
|
|
665
|
+
if (!validatedCode) {
|
|
666
|
+
throw new Error(`File contains potentially dangerous content: ${normalizedId}`);
|
|
667
|
+
}
|
|
668
|
+
codeToTransform = validatedCode;
|
|
480
669
|
if (normalizedId.endsWith(".fnx") && enableSFC) {
|
|
481
|
-
const parsed = parseSFC(
|
|
670
|
+
const parsed = parseSFC(codeToTransform);
|
|
482
671
|
validateSFC(parsed, normalizedId);
|
|
483
|
-
codeToTransform = transformSFC(parsed, normalizedId,
|
|
672
|
+
codeToTransform = transformSFC(parsed, normalizedId, safeJsxFactory);
|
|
484
673
|
if (showGeneratedCode) {
|
|
485
674
|
console.log(`\n${colors.cyan}${"=".repeat(80)}${colors.reset}`);
|
|
486
675
|
console.log(`${colors.cyan}[Fynix SFC]${colors.reset} Generated code for: ${colors.gray}${normalizedId}${colors.reset}`);
|
|
@@ -532,9 +721,12 @@ export default function fynixPlugin(options = {}) {
|
|
|
532
721
|
}
|
|
533
722
|
}
|
|
534
723
|
}
|
|
724
|
+
if (codeToTransform.length > 5242880) {
|
|
725
|
+
throw new Error(`Transformed code exceeds size limit for ${normalizedId}`);
|
|
726
|
+
}
|
|
535
727
|
const result = await transform(codeToTransform, {
|
|
536
728
|
loader,
|
|
537
|
-
jsxFactory,
|
|
729
|
+
jsxFactory: safeJsxFactory,
|
|
538
730
|
jsxFragment,
|
|
539
731
|
sourcemap,
|
|
540
732
|
sourcefile: id,
|
|
@@ -585,16 +777,16 @@ export default function fynixPlugin(options = {}) {
|
|
|
585
777
|
config() {
|
|
586
778
|
const config = {
|
|
587
779
|
esbuild: {
|
|
588
|
-
jsxFactory,
|
|
589
|
-
jsxFragment,
|
|
590
|
-
jsxInject: `import { ${
|
|
780
|
+
jsxFactory: safeJsxFactory,
|
|
781
|
+
jsxFragment: safeJsxFragment,
|
|
782
|
+
jsxInject: `import { ${safeJsxFactory} } from '@fynixorg/ui'`,
|
|
591
783
|
},
|
|
592
784
|
optimizeDeps: {
|
|
593
785
|
include: ["@fynixorg/ui"],
|
|
594
786
|
esbuildOptions: {
|
|
595
787
|
jsx: "transform",
|
|
596
|
-
jsxFactory,
|
|
597
|
-
jsxFragment,
|
|
788
|
+
jsxFactory: safeJsxFactory,
|
|
789
|
+
jsxFragment: safeJsxFragment,
|
|
598
790
|
},
|
|
599
791
|
},
|
|
600
792
|
resolve: {
|