wu-framework 1.0.5 → 1.1.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 +773 -366
- package/package.json +34 -9
- package/src/adapters/angular.d.ts +154 -0
- package/src/adapters/angular.js +642 -0
- package/src/adapters/index.js +157 -0
- package/src/adapters/lit.d.ts +120 -0
- package/src/adapters/lit.js +726 -0
- package/src/adapters/preact.d.ts +108 -0
- package/src/adapters/preact.js +665 -0
- package/src/adapters/react.d.ts +212 -0
- package/src/adapters/react.js +513 -0
- package/src/adapters/solid.d.ts +101 -0
- package/src/adapters/solid.js +591 -0
- package/src/adapters/svelte.d.ts +166 -0
- package/src/adapters/svelte.js +803 -0
- package/src/adapters/vanilla.d.ts +179 -0
- package/src/adapters/vanilla.js +791 -0
- package/src/adapters/vue.d.ts +299 -0
- package/src/adapters/vue.js +570 -0
- package/src/core/wu-cache.js +87 -15
- package/src/core/wu-event-bus.js +147 -58
- package/src/core/wu-manifest.js +139 -8
- package/src/core/wu-plugin.js +201 -71
- package/src/index.js +1 -1
package/src/core/wu-event-bus.js
CHANGED
|
@@ -1,46 +1,156 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* 📡 WU-EVENT-BUS:
|
|
2
|
+
* 📡 WU-EVENT-BUS: SECURE PUB/SUB SYSTEM
|
|
3
3
|
*
|
|
4
4
|
* Sistema de eventos para comunicación entre microfrontends
|
|
5
|
-
* - Pub/Sub pattern
|
|
5
|
+
* - Pub/Sub pattern con validación de origen
|
|
6
6
|
* - Event namespaces
|
|
7
7
|
* - Wildcards
|
|
8
8
|
* - Event replay
|
|
9
|
-
* -
|
|
9
|
+
* - Verificación de apps autorizadas
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
export class WuEventBus {
|
|
13
13
|
constructor() {
|
|
14
|
-
this.listeners = new Map();
|
|
15
|
-
this.history = [];
|
|
14
|
+
this.listeners = new Map();
|
|
15
|
+
this.history = [];
|
|
16
|
+
|
|
17
|
+
// 🔐 SEGURIDAD: Registro de apps autorizadas con tokens
|
|
18
|
+
this.authorizedApps = new Map(); // appName -> { token, permissions }
|
|
19
|
+
this.trustedEvents = new Set(['wu:*', 'system:*']); // Eventos del sistema
|
|
20
|
+
|
|
16
21
|
this.config = {
|
|
17
22
|
maxHistory: 100,
|
|
18
23
|
enableReplay: true,
|
|
19
24
|
enableWildcards: true,
|
|
20
|
-
logEvents: false
|
|
25
|
+
logEvents: false,
|
|
26
|
+
// 🔐 Opciones de seguridad
|
|
27
|
+
strictMode: false, // Si true, rechaza eventos de apps no autorizadas
|
|
28
|
+
validateOrigin: true // Valida que appName sea una app registrada
|
|
21
29
|
};
|
|
22
30
|
|
|
23
31
|
this.stats = {
|
|
24
32
|
emitted: 0,
|
|
25
|
-
subscriptions: 0
|
|
33
|
+
subscriptions: 0,
|
|
34
|
+
rejected: 0 // Eventos rechazados por seguridad
|
|
26
35
|
};
|
|
36
|
+
}
|
|
27
37
|
|
|
28
|
-
|
|
38
|
+
/**
|
|
39
|
+
* 🔐 REGISTER APP: Registrar app autorizada para emitir eventos
|
|
40
|
+
* @param {string} appName - Nombre de la app
|
|
41
|
+
* @param {Object} options - { permissions: ['event:*'], token }
|
|
42
|
+
* @returns {string} Token de autorización
|
|
43
|
+
*/
|
|
44
|
+
registerApp(appName, options = {}) {
|
|
45
|
+
const token = options.token || this._generateToken();
|
|
46
|
+
|
|
47
|
+
this.authorizedApps.set(appName, {
|
|
48
|
+
token,
|
|
49
|
+
permissions: options.permissions || ['*'], // Por defecto puede emitir todo
|
|
50
|
+
registeredAt: Date.now()
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
return token;
|
|
29
54
|
}
|
|
30
55
|
|
|
31
56
|
/**
|
|
32
|
-
*
|
|
57
|
+
* 🔓 UNREGISTER APP: Desregistrar app
|
|
58
|
+
* @param {string} appName
|
|
59
|
+
*/
|
|
60
|
+
unregisterApp(appName) {
|
|
61
|
+
this.authorizedApps.delete(appName);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* 🔐 VALIDATE ORIGIN: Verificar que el emisor está autorizado
|
|
66
|
+
* @param {string} eventName
|
|
67
|
+
* @param {string} appName
|
|
68
|
+
* @param {string} token
|
|
69
|
+
* @returns {boolean}
|
|
70
|
+
*/
|
|
71
|
+
_validateOrigin(eventName, appName, token) {
|
|
72
|
+
// Eventos del sistema siempre permitidos
|
|
73
|
+
if (this._isSystemEvent(eventName)) {
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Si no está en modo estricto, permitir todo
|
|
78
|
+
if (!this.config.strictMode) {
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Verificar que la app esté registrada
|
|
83
|
+
const appInfo = this.authorizedApps.get(appName);
|
|
84
|
+
if (!appInfo) {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Verificar token si se proporciona
|
|
89
|
+
if (token && appInfo.token !== token) {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Verificar permisos
|
|
94
|
+
return this._hasPermission(appInfo.permissions, eventName);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* 🔐 HAS PERMISSION: Verificar si la app tiene permiso para el evento
|
|
99
|
+
*/
|
|
100
|
+
_hasPermission(permissions, eventName) {
|
|
101
|
+
if (permissions.includes('*')) return true;
|
|
102
|
+
|
|
103
|
+
return permissions.some(pattern => {
|
|
104
|
+
if (pattern === eventName) return true;
|
|
105
|
+
if (pattern.includes('*')) {
|
|
106
|
+
return this.matchesWildcard(eventName, pattern);
|
|
107
|
+
}
|
|
108
|
+
return false;
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* 🔐 IS SYSTEM EVENT: Verificar si es un evento del sistema
|
|
114
|
+
*/
|
|
115
|
+
_isSystemEvent(eventName) {
|
|
116
|
+
return eventName.startsWith('wu:') ||
|
|
117
|
+
eventName.startsWith('system:') ||
|
|
118
|
+
eventName.startsWith('app:');
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* 🔐 GENERATE TOKEN: Generar token único
|
|
123
|
+
*/
|
|
124
|
+
_generateToken() {
|
|
125
|
+
return `wu_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* 📢 EMIT: Emitir evento con validación de origen
|
|
33
130
|
* @param {string} eventName - Nombre del evento
|
|
34
131
|
* @param {*} data - Datos del evento
|
|
35
|
-
* @param {Object} options -
|
|
132
|
+
* @param {Object} options - { appName, timestamp, meta, token }
|
|
36
133
|
*/
|
|
37
134
|
emit(eventName, data, options = {}) {
|
|
135
|
+
const appName = options.appName || 'unknown';
|
|
136
|
+
|
|
137
|
+
// 🔐 Validar origen si está habilitado
|
|
138
|
+
if (this.config.validateOrigin && this.config.strictMode) {
|
|
139
|
+
if (!this._validateOrigin(eventName, appName, options.token)) {
|
|
140
|
+
this.stats.rejected++;
|
|
141
|
+
console.warn(`[WuEventBus] 🚫 Event rejected: ${eventName} from ${appName} (unauthorized)`);
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
38
146
|
const event = {
|
|
39
147
|
name: eventName,
|
|
40
148
|
data,
|
|
41
149
|
timestamp: options.timestamp || Date.now(),
|
|
42
|
-
appName
|
|
43
|
-
meta: options.meta || {}
|
|
150
|
+
appName,
|
|
151
|
+
meta: options.meta || {},
|
|
152
|
+
// 🔐 Marcar si el origen fue verificado
|
|
153
|
+
verified: this.authorizedApps.has(appName)
|
|
44
154
|
};
|
|
45
155
|
|
|
46
156
|
// Agregar a historial
|
|
@@ -71,13 +181,11 @@ export class WuEventBus {
|
|
|
71
181
|
}
|
|
72
182
|
|
|
73
183
|
this.stats.emitted++;
|
|
184
|
+
return true;
|
|
74
185
|
}
|
|
75
186
|
|
|
76
187
|
/**
|
|
77
188
|
* 👂 ON: Suscribirse a evento
|
|
78
|
-
* @param {string} eventName - Nombre del evento (puede usar wildcards: 'app.*', '*.update')
|
|
79
|
-
* @param {Function} callback - Callback a ejecutar
|
|
80
|
-
* @returns {Function} Función para desuscribirse
|
|
81
189
|
*/
|
|
82
190
|
on(eventName, callback) {
|
|
83
191
|
if (!this.listeners.has(eventName)) {
|
|
@@ -87,48 +195,36 @@ export class WuEventBus {
|
|
|
87
195
|
this.listeners.get(eventName).add(callback);
|
|
88
196
|
this.stats.subscriptions++;
|
|
89
197
|
|
|
90
|
-
// Retornar función de desuscripción
|
|
91
198
|
return () => this.off(eventName, callback);
|
|
92
199
|
}
|
|
93
200
|
|
|
94
201
|
/**
|
|
95
202
|
* 🔇 OFF: Desuscribirse de evento
|
|
96
|
-
* @param {string} eventName - Nombre del evento
|
|
97
|
-
* @param {Function} callback - Callback a remover
|
|
98
203
|
*/
|
|
99
204
|
off(eventName, callback) {
|
|
100
205
|
const listeners = this.listeners.get(eventName);
|
|
101
206
|
if (listeners) {
|
|
102
207
|
listeners.delete(callback);
|
|
103
|
-
|
|
104
|
-
// Limpiar si no quedan listeners
|
|
105
208
|
if (listeners.size === 0) {
|
|
106
209
|
this.listeners.delete(eventName);
|
|
107
210
|
}
|
|
108
|
-
|
|
109
211
|
this.stats.subscriptions--;
|
|
110
212
|
}
|
|
111
213
|
}
|
|
112
214
|
|
|
113
215
|
/**
|
|
114
216
|
* 🎯 ONCE: Suscribirse una sola vez
|
|
115
|
-
* @param {string} eventName - Nombre del evento
|
|
116
|
-
* @param {Function} callback - Callback a ejecutar
|
|
117
|
-
* @returns {Function} Función para desuscribirse
|
|
118
217
|
*/
|
|
119
218
|
once(eventName, callback) {
|
|
120
219
|
const wrappedCallback = (event) => {
|
|
121
220
|
callback(event);
|
|
122
221
|
this.off(eventName, wrappedCallback);
|
|
123
222
|
};
|
|
124
|
-
|
|
125
223
|
return this.on(eventName, wrappedCallback);
|
|
126
224
|
}
|
|
127
225
|
|
|
128
226
|
/**
|
|
129
|
-
* 🌟 WILDCARD LISTENERS
|
|
130
|
-
* @param {string} eventName - Nombre del evento emitido
|
|
131
|
-
* @param {Object} event - Objeto del evento
|
|
227
|
+
* 🌟 WILDCARD LISTENERS
|
|
132
228
|
*/
|
|
133
229
|
notifyWildcardListeners(eventName, event) {
|
|
134
230
|
for (const [pattern, listeners] of this.listeners) {
|
|
@@ -145,41 +241,29 @@ export class WuEventBus {
|
|
|
145
241
|
}
|
|
146
242
|
|
|
147
243
|
/**
|
|
148
|
-
* 🎯 MATCHES WILDCARD
|
|
149
|
-
* @param {string} eventName - Nombre del evento
|
|
150
|
-
* @param {string} pattern - Patrón con wildcards
|
|
151
|
-
* @returns {boolean}
|
|
244
|
+
* 🎯 MATCHES WILDCARD
|
|
152
245
|
*/
|
|
153
246
|
matchesWildcard(eventName, pattern) {
|
|
154
|
-
// Si no hay wildcard, ya se procesó en listeners exactos
|
|
155
247
|
if (!pattern.includes('*')) return false;
|
|
156
|
-
|
|
157
|
-
// Convertir pattern a regex
|
|
158
248
|
const regexPattern = pattern
|
|
159
249
|
.replace(/\./g, '\\.')
|
|
160
250
|
.replace(/\*/g, '.*');
|
|
161
|
-
|
|
162
251
|
const regex = new RegExp(`^${regexPattern}$`);
|
|
163
252
|
return regex.test(eventName);
|
|
164
253
|
}
|
|
165
254
|
|
|
166
255
|
/**
|
|
167
|
-
* 📝 ADD TO HISTORY
|
|
168
|
-
* @param {Object} event - Evento
|
|
256
|
+
* 📝 ADD TO HISTORY
|
|
169
257
|
*/
|
|
170
258
|
addToHistory(event) {
|
|
171
259
|
this.history.push(event);
|
|
172
|
-
|
|
173
|
-
// Mantener tamaño máximo
|
|
174
260
|
if (this.history.length > this.config.maxHistory) {
|
|
175
261
|
this.history.shift();
|
|
176
262
|
}
|
|
177
263
|
}
|
|
178
264
|
|
|
179
265
|
/**
|
|
180
|
-
* 🔄 REPLAY
|
|
181
|
-
* @param {string} eventNameOrPattern - Nombre o patrón de eventos a reproducir
|
|
182
|
-
* @param {Function} callback - Callback para cada evento
|
|
266
|
+
* 🔄 REPLAY
|
|
183
267
|
*/
|
|
184
268
|
replay(eventNameOrPattern, callback) {
|
|
185
269
|
const events = this.history.filter(event => {
|
|
@@ -189,8 +273,6 @@ export class WuEventBus {
|
|
|
189
273
|
return event.name === eventNameOrPattern;
|
|
190
274
|
});
|
|
191
275
|
|
|
192
|
-
console.log(`[WuEventBus] 🔄 Replaying ${events.length} events for ${eventNameOrPattern}`);
|
|
193
|
-
|
|
194
276
|
events.forEach(event => {
|
|
195
277
|
try {
|
|
196
278
|
callback(event);
|
|
@@ -201,13 +283,11 @@ export class WuEventBus {
|
|
|
201
283
|
}
|
|
202
284
|
|
|
203
285
|
/**
|
|
204
|
-
* 🧹 CLEAR HISTORY
|
|
205
|
-
* @param {string} eventNameOrPattern - Patrón de eventos a limpiar (opcional)
|
|
286
|
+
* 🧹 CLEAR HISTORY
|
|
206
287
|
*/
|
|
207
288
|
clearHistory(eventNameOrPattern) {
|
|
208
289
|
if (!eventNameOrPattern) {
|
|
209
290
|
this.history = [];
|
|
210
|
-
console.log('[WuEventBus] 🧹 Event history cleared');
|
|
211
291
|
return;
|
|
212
292
|
}
|
|
213
293
|
|
|
@@ -220,14 +300,14 @@ export class WuEventBus {
|
|
|
220
300
|
}
|
|
221
301
|
|
|
222
302
|
/**
|
|
223
|
-
* 📊 GET STATS
|
|
224
|
-
* @returns {Object}
|
|
303
|
+
* 📊 GET STATS
|
|
225
304
|
*/
|
|
226
305
|
getStats() {
|
|
227
306
|
return {
|
|
228
307
|
...this.stats,
|
|
229
308
|
activeListeners: this.listeners.size,
|
|
230
309
|
historySize: this.history.length,
|
|
310
|
+
authorizedApps: this.authorizedApps.size,
|
|
231
311
|
listenersByEvent: Array.from(this.listeners.entries()).map(([event, listeners]) => ({
|
|
232
312
|
event,
|
|
233
313
|
listeners: listeners.size
|
|
@@ -236,22 +316,31 @@ export class WuEventBus {
|
|
|
236
316
|
}
|
|
237
317
|
|
|
238
318
|
/**
|
|
239
|
-
* ⚙️ CONFIGURE
|
|
240
|
-
* @param {Object} config - Nueva configuración
|
|
319
|
+
* ⚙️ CONFIGURE
|
|
241
320
|
*/
|
|
242
321
|
configure(config) {
|
|
243
|
-
this.config = {
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
322
|
+
this.config = { ...this.config, ...config };
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* 🔐 ENABLE STRICT MODE: Activar modo estricto de seguridad
|
|
327
|
+
*/
|
|
328
|
+
enableStrictMode() {
|
|
329
|
+
this.config.strictMode = true;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* 🔓 DISABLE STRICT MODE
|
|
334
|
+
*/
|
|
335
|
+
disableStrictMode() {
|
|
336
|
+
this.config.strictMode = false;
|
|
247
337
|
}
|
|
248
338
|
|
|
249
339
|
/**
|
|
250
|
-
* 🗑️ REMOVE ALL
|
|
340
|
+
* 🗑️ REMOVE ALL
|
|
251
341
|
*/
|
|
252
342
|
removeAll() {
|
|
253
343
|
this.listeners.clear();
|
|
254
344
|
this.stats.subscriptions = 0;
|
|
255
|
-
console.log('[WuEventBus] 🗑️ All listeners removed');
|
|
256
345
|
}
|
|
257
346
|
}
|
package/src/core/wu-manifest.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* 📋 WU-MANIFEST:
|
|
3
|
-
*
|
|
2
|
+
* 📋 WU-MANIFEST: SECURE MANIFEST SYSTEM
|
|
3
|
+
* Validación estricta de wu.json para seguridad
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
export class WuManifest {
|
|
@@ -8,10 +8,33 @@ export class WuManifest {
|
|
|
8
8
|
this.cache = new Map();
|
|
9
9
|
this.schemas = new Map();
|
|
10
10
|
|
|
11
|
-
//
|
|
12
|
-
this.
|
|
11
|
+
// 🔐 Configuración de seguridad
|
|
12
|
+
this.security = {
|
|
13
|
+
maxManifestSize: 100 * 1024, // 100KB máximo
|
|
14
|
+
maxNameLength: 50,
|
|
15
|
+
maxEntryLength: 200,
|
|
16
|
+
maxExports: 100,
|
|
17
|
+
maxImports: 50,
|
|
18
|
+
maxRoutes: 100,
|
|
19
|
+
// Patrones peligrosos en paths
|
|
20
|
+
dangerousPatterns: [
|
|
21
|
+
/\.\./, // Path traversal
|
|
22
|
+
/^\/etc\//, // System paths
|
|
23
|
+
/^\/proc\//,
|
|
24
|
+
/^file:\/\//, // File protocol
|
|
25
|
+
/javascript:/i, // JS injection
|
|
26
|
+
/data:/i, // Data URLs
|
|
27
|
+
/<script/i, // Script tags
|
|
28
|
+
/on\w+\s*=/i // Event handlers
|
|
29
|
+
],
|
|
30
|
+
// Dominios bloqueados
|
|
31
|
+
blockedDomains: [
|
|
32
|
+
'evil.com',
|
|
33
|
+
'malware.com'
|
|
34
|
+
]
|
|
35
|
+
};
|
|
13
36
|
|
|
14
|
-
|
|
37
|
+
this.defineSchema();
|
|
15
38
|
}
|
|
16
39
|
|
|
17
40
|
/**
|
|
@@ -67,7 +90,19 @@ export class WuManifest {
|
|
|
67
90
|
}
|
|
68
91
|
|
|
69
92
|
const manifestText = await response.text();
|
|
70
|
-
|
|
93
|
+
|
|
94
|
+
// 🔐 Validar tamaño del manifest
|
|
95
|
+
if (manifestText.length > this.security.maxManifestSize) {
|
|
96
|
+
throw new Error(`Manifest too large (${manifestText.length} bytes, max ${this.security.maxManifestSize})`);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// 🔐 Intentar parsear JSON de forma segura
|
|
100
|
+
let manifest;
|
|
101
|
+
try {
|
|
102
|
+
manifest = JSON.parse(manifestText);
|
|
103
|
+
} catch (parseError) {
|
|
104
|
+
throw new Error(`Invalid JSON in manifest: ${parseError.message}`);
|
|
105
|
+
}
|
|
71
106
|
|
|
72
107
|
// Validar manifest
|
|
73
108
|
const validatedManifest = this.validate(manifest);
|
|
@@ -133,13 +168,64 @@ export class WuManifest {
|
|
|
133
168
|
}
|
|
134
169
|
|
|
135
170
|
/**
|
|
136
|
-
*
|
|
171
|
+
* 🔐 SANITIZE STRING: Limpiar string de caracteres peligrosos
|
|
172
|
+
*/
|
|
173
|
+
_sanitizeString(str) {
|
|
174
|
+
if (typeof str !== 'string') return '';
|
|
175
|
+
return str
|
|
176
|
+
.replace(/[<>'"]/g, '') // Remove HTML chars
|
|
177
|
+
.replace(/[\x00-\x1F\x7F]/g, '') // Remove control chars
|
|
178
|
+
.trim();
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* 🔐 CHECK DANGEROUS PATTERNS: Verificar patrones peligrosos
|
|
183
|
+
*/
|
|
184
|
+
_hasDangerousPatterns(str) {
|
|
185
|
+
if (typeof str !== 'string') return false;
|
|
186
|
+
return this.security.dangerousPatterns.some(pattern => pattern.test(str));
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* 🔐 VALIDATE URL: Verificar que URL es segura
|
|
191
|
+
*/
|
|
192
|
+
_isUrlSafe(url) {
|
|
193
|
+
if (typeof url !== 'string') return false;
|
|
194
|
+
|
|
195
|
+
// Verificar patrones peligrosos
|
|
196
|
+
if (this._hasDangerousPatterns(url)) {
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Verificar dominios bloqueados
|
|
201
|
+
try {
|
|
202
|
+
const urlObj = new URL(url, 'http://localhost');
|
|
203
|
+
if (this.security.blockedDomains.some(d => urlObj.hostname.includes(d))) {
|
|
204
|
+
return false;
|
|
205
|
+
}
|
|
206
|
+
} catch {
|
|
207
|
+
// Si no es URL válida, verificar como path
|
|
208
|
+
if (this._hasDangerousPatterns(url)) {
|
|
209
|
+
return false;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return true;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Validar manifest contra schema con validación de seguridad
|
|
137
218
|
* @param {Object} manifest - Manifest a validar
|
|
138
219
|
* @returns {Object} Manifest validado
|
|
139
220
|
*/
|
|
140
221
|
validate(manifest) {
|
|
141
222
|
const schema = this.schemas.get('wu.json');
|
|
142
223
|
|
|
224
|
+
// 🔐 Verificar que manifest es un objeto
|
|
225
|
+
if (!manifest || typeof manifest !== 'object' || Array.isArray(manifest)) {
|
|
226
|
+
throw new Error('Manifest must be a valid object');
|
|
227
|
+
}
|
|
228
|
+
|
|
143
229
|
// Verificar campos requeridos
|
|
144
230
|
for (const field of schema.required) {
|
|
145
231
|
if (!manifest[field]) {
|
|
@@ -147,23 +233,68 @@ export class WuManifest {
|
|
|
147
233
|
}
|
|
148
234
|
}
|
|
149
235
|
|
|
236
|
+
// 🔐 Validar nombre
|
|
237
|
+
if (typeof manifest.name !== 'string') {
|
|
238
|
+
throw new Error('name must be a string');
|
|
239
|
+
}
|
|
240
|
+
if (manifest.name.length > this.security.maxNameLength) {
|
|
241
|
+
throw new Error(`name too long (max ${this.security.maxNameLength} chars)`);
|
|
242
|
+
}
|
|
243
|
+
if (this._hasDangerousPatterns(manifest.name)) {
|
|
244
|
+
throw new Error('name contains dangerous patterns');
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// 🔐 Validar entry
|
|
248
|
+
if (typeof manifest.entry !== 'string') {
|
|
249
|
+
throw new Error('entry must be a string');
|
|
250
|
+
}
|
|
251
|
+
if (manifest.entry.length > this.security.maxEntryLength) {
|
|
252
|
+
throw new Error(`entry too long (max ${this.security.maxEntryLength} chars)`);
|
|
253
|
+
}
|
|
254
|
+
if (!this._isUrlSafe(manifest.entry)) {
|
|
255
|
+
throw new Error('entry contains dangerous patterns');
|
|
256
|
+
}
|
|
257
|
+
|
|
150
258
|
// Verificar tipos en sección wu
|
|
151
259
|
if (manifest.wu) {
|
|
152
260
|
const wu = manifest.wu;
|
|
153
|
-
const wuSchema = schema.wu;
|
|
154
261
|
|
|
155
262
|
if (wu.exports && typeof wu.exports !== 'object') {
|
|
156
263
|
throw new Error('wu.exports must be an object');
|
|
157
264
|
}
|
|
158
265
|
|
|
266
|
+
// 🔐 Validar límites de exports
|
|
267
|
+
if (wu.exports && Object.keys(wu.exports).length > this.security.maxExports) {
|
|
268
|
+
throw new Error(`Too many exports (max ${this.security.maxExports})`);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// 🔐 Validar cada export path
|
|
272
|
+
if (wu.exports) {
|
|
273
|
+
for (const [key, path] of Object.entries(wu.exports)) {
|
|
274
|
+
if (!this._isUrlSafe(path)) {
|
|
275
|
+
throw new Error(`Dangerous export path: ${key}`);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
159
280
|
if (wu.imports && !Array.isArray(wu.imports)) {
|
|
160
281
|
throw new Error('wu.imports must be an array');
|
|
161
282
|
}
|
|
162
283
|
|
|
284
|
+
// 🔐 Validar límites de imports
|
|
285
|
+
if (wu.imports && wu.imports.length > this.security.maxImports) {
|
|
286
|
+
throw new Error(`Too many imports (max ${this.security.maxImports})`);
|
|
287
|
+
}
|
|
288
|
+
|
|
163
289
|
if (wu.routes && !Array.isArray(wu.routes)) {
|
|
164
290
|
throw new Error('wu.routes must be an array');
|
|
165
291
|
}
|
|
166
292
|
|
|
293
|
+
// 🔐 Validar límites de routes
|
|
294
|
+
if (wu.routes && wu.routes.length > this.security.maxRoutes) {
|
|
295
|
+
throw new Error(`Too many routes (max ${this.security.maxRoutes})`);
|
|
296
|
+
}
|
|
297
|
+
|
|
167
298
|
if (wu.permissions && !Array.isArray(wu.permissions)) {
|
|
168
299
|
throw new Error('wu.permissions must be an array');
|
|
169
300
|
}
|