wu-framework 1.0.5 → 1.0.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wu-framework",
3
- "version": "1.0.5",
3
+ "version": "1.0.6",
4
4
  "description": "🚀 Universal Microfrontends Framework - Framework agnostic, zero config, Shadow DOM isolation",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -1,16 +1,10 @@
1
1
  /**
2
- * 💾 WU-CACHE: INTERNAL FRAMEWORK CACHING
2
+ * 💾 WU-CACHE: SECURE INTERNAL CACHING
3
3
  *
4
- * Sistema de caché INTERNO para recursos del framework:
5
- * - Manifests de microfrontends
6
- * - Módulos cargados
7
- * - Configuraciones de apps
8
- *
9
- * Features:
10
- * - Cache persistente (localStorage/sessionStorage)
11
- * - Cache en memoria (Map)
12
- * - TTL (Time To Live) configurable
13
- * - LRU (Least Recently Used) eviction
4
+ * Sistema de caché INTERNO con rate limiting
5
+ * - Rate limiting para prevenir abuso
6
+ * - Cache persistente y en memoria
7
+ * - TTL y LRU eviction
14
8
  *
15
9
  * ⚠️ USO INTERNO: No exponer en API pública
16
10
  */
@@ -22,15 +16,26 @@ export class WuCache {
22
16
  maxItems: options.maxItems || 100,
23
17
  defaultTTL: options.defaultTTL || 3600000, // 1 hour
24
18
  persistent: options.persistent !== false,
25
- storage: options.storage || 'memory', // 'memory' | 'localStorage' | 'sessionStorage'
19
+ storage: options.storage || 'memory',
26
20
  compression: options.compression || false
27
21
  };
28
22
 
23
+ // 🔐 Rate limiting configuration
24
+ this.rateLimiting = {
25
+ enabled: options.rateLimiting !== false,
26
+ maxOpsPerSecond: options.maxOpsPerSecond || 100,
27
+ windowMs: 1000, // 1 second window
28
+ cooldownMs: options.cooldownMs || 5000, // 5 second cooldown after limit
29
+ operations: [],
30
+ inCooldown: false,
31
+ cooldownUntil: 0
32
+ };
33
+
29
34
  // Memory cache
30
35
  this.memoryCache = new Map();
31
36
 
32
37
  // LRU tracking
33
- this.accessOrder = new Map(); // key -> timestamp
38
+ this.accessOrder = new Map();
34
39
 
35
40
  // Statistics
36
41
  this.stats = {
@@ -38,10 +43,67 @@ export class WuCache {
38
43
  misses: 0,
39
44
  sets: 0,
40
45
  evictions: 0,
41
- size: 0
46
+ size: 0,
47
+ rateLimited: 0 // 🔐 Contador de operaciones rechazadas
42
48
  };
49
+ }
50
+
51
+ /**
52
+ * 🔐 CHECK RATE LIMIT: Verificar si la operación está permitida
53
+ * @returns {boolean} true si la operación está permitida
54
+ */
55
+ _checkRateLimit() {
56
+ if (!this.rateLimiting.enabled) return true;
57
+
58
+ const now = Date.now();
59
+
60
+ // Verificar si estamos en cooldown
61
+ if (this.rateLimiting.inCooldown) {
62
+ if (now < this.rateLimiting.cooldownUntil) {
63
+ this.stats.rateLimited++;
64
+ return false;
65
+ }
66
+ // Cooldown terminado
67
+ this.rateLimiting.inCooldown = false;
68
+ this.rateLimiting.operations = [];
69
+ }
70
+
71
+ // Limpiar operaciones antiguas (fuera de la ventana)
72
+ const windowStart = now - this.rateLimiting.windowMs;
73
+ this.rateLimiting.operations = this.rateLimiting.operations.filter(
74
+ ts => ts > windowStart
75
+ );
76
+
77
+ // Verificar límite
78
+ if (this.rateLimiting.operations.length >= this.rateLimiting.maxOpsPerSecond) {
79
+ // Activar cooldown
80
+ this.rateLimiting.inCooldown = true;
81
+ this.rateLimiting.cooldownUntil = now + this.rateLimiting.cooldownMs;
82
+ this.stats.rateLimited++;
83
+ console.warn(`[WuCache] 🚫 Rate limit exceeded. Cooldown for ${this.rateLimiting.cooldownMs}ms`);
84
+ return false;
85
+ }
43
86
 
44
- console.log('[WuCache] 💾 Advanced cache system initialized');
87
+ // Registrar operación
88
+ this.rateLimiting.operations.push(now);
89
+ return true;
90
+ }
91
+
92
+ /**
93
+ * 🔐 GET RATE LIMIT STATUS
94
+ */
95
+ getRateLimitStatus() {
96
+ const now = Date.now();
97
+ return {
98
+ enabled: this.rateLimiting.enabled,
99
+ inCooldown: this.rateLimiting.inCooldown,
100
+ cooldownRemaining: this.rateLimiting.inCooldown
101
+ ? Math.max(0, this.rateLimiting.cooldownUntil - now)
102
+ : 0,
103
+ currentOps: this.rateLimiting.operations.length,
104
+ maxOps: this.rateLimiting.maxOpsPerSecond,
105
+ rateLimited: this.stats.rateLimited
106
+ };
45
107
  }
46
108
 
47
109
  /**
@@ -50,6 +112,11 @@ export class WuCache {
50
112
  * @returns {*} Valor cacheado o null
51
113
  */
52
114
  get(key) {
115
+ // 🔐 Check rate limit
116
+ if (!this._checkRateLimit()) {
117
+ return null; // Silently fail on rate limit
118
+ }
119
+
53
120
  // 1. Buscar en memoria
54
121
  if (this.memoryCache.has(key)) {
55
122
  const entry = this.memoryCache.get(key);
@@ -92,6 +159,11 @@ export class WuCache {
92
159
  * @returns {boolean}
93
160
  */
94
161
  set(key, value, ttl) {
162
+ // 🔐 Check rate limit
163
+ if (!this._checkRateLimit()) {
164
+ return false; // Reject on rate limit
165
+ }
166
+
95
167
  try {
96
168
  const entry = {
97
169
  key,
@@ -1,46 +1,156 @@
1
1
  /**
2
- * 📡 WU-EVENT-BUS: ADVANCED PUB/SUB SYSTEM
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
- * - Performance optimizado
9
+ * - Verificación de apps autorizadas
10
10
  */
11
11
 
12
12
  export class WuEventBus {
13
13
  constructor() {
14
- this.listeners = new Map(); // eventName -> Set<callback>
15
- this.history = []; // Event history para replay
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
- console.log('[WuEventBus] 📡 Advanced event bus initialized');
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
- * 📢 EMIT: Emitir evento
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 - Opciones { appName, timestamp, meta }
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: options.appName || 'unknown',
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: Notificar listeners con wildcards
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: Verificar si evento coincide con patrón 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: Agregar evento al historial
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: Reproducir eventos del historial
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: Limpiar historial de eventos
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: Obtener estadísticas
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: Configurar event bus
240
- * @param {Object} config - Nueva configuración
319
+ * ⚙️ CONFIGURE
241
320
  */
242
321
  configure(config) {
243
- this.config = {
244
- ...this.config,
245
- ...config
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: Remover todos los listeners
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
  }
@@ -1,6 +1,6 @@
1
1
  /**
2
- * 📋 WU-MANIFEST: SISTEMA DE MANIFIESTOS SIMPLIFICADO
3
- * Maneja wu.json sin complicaciones innecesarias
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
- // Definir schema básico
12
- this.defineSchema();
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
- console.log('[WuManifest] 📋 Manifest system initialized');
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
- const manifest = JSON.parse(manifestText);
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
- * Validar manifest contra schema
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
  }
@@ -1,53 +1,168 @@
1
1
  /**
2
- * 🔌 WU-PLUGIN: EXTENSIBLE PLUGIN SYSTEM
2
+ * 🔌 WU-PLUGIN: SECURE PLUGIN SYSTEM
3
3
  *
4
- * Sistema de plugins para extender wu-framework sin modificar el core
4
+ * Sistema de plugins con sandboxing de seguridad
5
5
  * - Plugin lifecycle (install, beforeMount, afterMount, etc.)
6
- * - Dependency injection
7
- * - Plugin communication
6
+ * - Sandboxed API (plugins no tienen acceso completo al core)
7
+ * - Permission system
8
+ * - Timeout protection
8
9
  */
9
10
 
10
11
  export class WuPluginSystem {
11
12
  constructor(core) {
12
- this.core = core;
13
- this.plugins = new Map(); // name -> plugin instance
14
- this.hooks = new Map(); // hook name -> [callbacks]
13
+ this._core = core; // Privado - no expuesto a plugins
14
+ this.plugins = new Map();
15
+ this.hooks = new Map();
15
16
 
16
17
  // Hooks disponibles
17
18
  this.availableHooks = [
18
- 'beforeInit',
19
- 'afterInit',
20
- 'beforeMount',
21
- 'afterMount',
22
- 'beforeUnmount',
23
- 'afterUnmount',
24
- 'onError',
25
- 'onDestroy'
19
+ 'beforeInit', 'afterInit',
20
+ 'beforeMount', 'afterMount',
21
+ 'beforeUnmount', 'afterUnmount',
22
+ 'onError', 'onDestroy'
26
23
  ];
27
24
 
28
- // Inicializar hooks
25
+ // 🔐 Permisos disponibles
26
+ this.availablePermissions = [
27
+ 'mount', // Puede montar/desmontar apps
28
+ 'events', // Puede emitir/escuchar eventos
29
+ 'store', // Puede leer/escribir store
30
+ 'apps', // Puede ver lista de apps
31
+ 'config', // Puede modificar configuración
32
+ 'unsafe' // Acceso completo (peligroso)
33
+ ];
34
+
35
+ // 🔐 Timeout para hooks (evita que plugins bloqueen)
36
+ this.hookTimeout = 5000; // 5 segundos
37
+
29
38
  this.availableHooks.forEach(hook => {
30
39
  this.hooks.set(hook, []);
31
40
  });
41
+ }
42
+
43
+ /**
44
+ * 🔐 CREATE SANDBOXED API: Crea API limitada para el plugin
45
+ * @param {Array} permissions - Permisos del plugin
46
+ * @returns {Object} API sandboxeada
47
+ */
48
+ _createSandboxedApi(permissions) {
49
+ const api = {
50
+ // Info básica siempre disponible
51
+ version: this._core.version,
52
+ info: this._core.info,
53
+
54
+ // 📊 Métodos de solo lectura
55
+ getAppInfo: (appName) => {
56
+ const mounted = this._core.mounted.get(appName);
57
+ if (!mounted) return null;
58
+ return {
59
+ name: appName,
60
+ state: mounted.state,
61
+ timestamp: mounted.timestamp
62
+ };
63
+ },
64
+
65
+ getMountedApps: () => {
66
+ return Array.from(this._core.mounted.keys());
67
+ },
68
+
69
+ getStats: () => this._core.getStats()
70
+ };
71
+
72
+ // 🔐 Agregar métodos según permisos
73
+ if (permissions.includes('events') || permissions.includes('unsafe')) {
74
+ api.emit = (event, data) => this._core.eventBus.emit(event, data, { appName: 'plugin' });
75
+ api.on = (event, cb) => this._core.eventBus.on(event, cb);
76
+ api.off = (event, cb) => this._core.eventBus.off(event, cb);
77
+ }
78
+
79
+ if (permissions.includes('store') || permissions.includes('unsafe')) {
80
+ api.getState = (path) => this._core.store.get(path);
81
+ api.setState = (path, value) => this._core.store.set(path, value);
82
+ }
83
+
84
+ if (permissions.includes('mount') || permissions.includes('unsafe')) {
85
+ api.mount = (appName, container) => this._core.mount(appName, container);
86
+ api.unmount = (appName) => this._core.unmount(appName);
87
+ }
88
+
89
+ if (permissions.includes('config') || permissions.includes('unsafe')) {
90
+ api.configure = (config) => {
91
+ // Solo permitir configuración segura
92
+ const safeKeys = ['debug', 'logLevel'];
93
+ const safeConfig = {};
94
+ for (const key of safeKeys) {
95
+ if (key in config) safeConfig[key] = config[key];
96
+ }
97
+ Object.assign(this._core, safeConfig);
98
+ };
99
+ }
100
+
101
+ // 🚨 Acceso completo solo con permiso 'unsafe'
102
+ if (permissions.includes('unsafe')) {
103
+ api._unsafeCore = this._core;
104
+ console.warn('[WuPlugin] ⚠️ Plugin has unsafe access to core!');
105
+ }
106
+
107
+ // Congelar API para evitar modificaciones
108
+ return Object.freeze(api);
109
+ }
110
+
111
+ /**
112
+ * 🔐 VALIDATE PLUGIN: Validar estructura del plugin
113
+ * @param {Object} plugin
114
+ * @returns {boolean}
115
+ */
116
+ _validatePlugin(plugin) {
117
+ if (!plugin || typeof plugin !== 'object') {
118
+ throw new Error('[WuPlugin] Invalid plugin: must be an object');
119
+ }
120
+
121
+ if (!plugin.name || typeof plugin.name !== 'string') {
122
+ throw new Error('[WuPlugin] Invalid plugin: must have a name (string)');
123
+ }
124
+
125
+ if (plugin.name.length > 50) {
126
+ throw new Error('[WuPlugin] Invalid plugin: name too long (max 50 chars)');
127
+ }
128
+
129
+ // Validar que los hooks sean funciones
130
+ for (const hookName of this.availableHooks) {
131
+ if (plugin[hookName] && typeof plugin[hookName] !== 'function') {
132
+ throw new Error(`[WuPlugin] Invalid plugin: ${hookName} must be a function`);
133
+ }
134
+ }
135
+
136
+ // Validar permisos
137
+ if (plugin.permissions) {
138
+ if (!Array.isArray(plugin.permissions)) {
139
+ throw new Error('[WuPlugin] Invalid plugin: permissions must be an array');
140
+ }
141
+
142
+ for (const perm of plugin.permissions) {
143
+ if (!this.availablePermissions.includes(perm)) {
144
+ throw new Error(`[WuPlugin] Invalid permission: ${perm}`);
145
+ }
146
+ }
147
+ }
32
148
 
33
- console.log('[WuPlugin] 🔌 Plugin system initialized');
149
+ return true;
34
150
  }
35
151
 
36
152
  /**
37
- * 📦 USE: Instalar plugin
153
+ * 📦 USE: Instalar plugin con sandboxing
38
154
  * @param {Object|Function} plugin - Plugin o factory function
39
155
  * @param {Object} options - Opciones del plugin
40
156
  */
41
157
  use(plugin, options = {}) {
42
158
  // Si es una función, ejecutarla para obtener el plugin
159
+ // Nota: factory functions NO reciben acceso al core
43
160
  if (typeof plugin === 'function') {
44
- plugin = plugin(this.core, options);
161
+ plugin = plugin(options);
45
162
  }
46
163
 
47
164
  // Validar plugin
48
- if (!plugin.name) {
49
- throw new Error('[WuPlugin] Plugin must have a name property');
50
- }
165
+ this._validatePlugin(plugin);
51
166
 
52
167
  // Verificar si ya está instalado
53
168
  if (this.plugins.has(plugin.name)) {
@@ -55,15 +170,28 @@ export class WuPluginSystem {
55
170
  return;
56
171
  }
57
172
 
58
- // Ejecutar install del plugin
173
+ // Determinar permisos (por defecto: solo eventos)
174
+ const permissions = plugin.permissions || ['events'];
175
+
176
+ // 🔐 Crear API sandboxeada
177
+ const sandboxedApi = this._createSandboxedApi(permissions);
178
+
179
+ // Ejecutar install del plugin con API sandboxeada
59
180
  if (plugin.install) {
60
- plugin.install(this.core, options);
181
+ try {
182
+ plugin.install(sandboxedApi, options);
183
+ } catch (error) {
184
+ console.error(`[WuPlugin] Error installing "${plugin.name}":`, error);
185
+ throw error;
186
+ }
61
187
  }
62
188
 
63
- // Registrar hooks del plugin
189
+ // Registrar hooks del plugin con protección
64
190
  this.availableHooks.forEach(hookName => {
65
191
  if (typeof plugin[hookName] === 'function') {
66
- this.registerHook(hookName, plugin[hookName].bind(plugin));
192
+ // Wrap el hook con timeout y try-catch
193
+ const wrappedHook = this._wrapHook(plugin[hookName].bind(plugin), plugin.name, hookName);
194
+ this.registerHook(hookName, wrappedHook);
67
195
  }
68
196
  });
69
197
 
@@ -71,31 +199,52 @@ export class WuPluginSystem {
71
199
  this.plugins.set(plugin.name, {
72
200
  plugin,
73
201
  options,
202
+ permissions,
203
+ sandboxedApi,
74
204
  installedAt: Date.now()
75
205
  });
76
206
 
77
- console.log(`[WuPlugin] ✅ Plugin "${plugin.name}" installed`);
207
+ console.log(`[WuPlugin] ✅ Plugin "${plugin.name}" installed (permissions: ${permissions.join(', ')})`);
208
+ }
209
+
210
+ /**
211
+ * 🔐 WRAP HOOK: Envolver hook con timeout y error handling
212
+ */
213
+ _wrapHook(hookFn, pluginName, hookName) {
214
+ return async (context) => {
215
+ const timeoutPromise = new Promise((_, reject) => {
216
+ setTimeout(() => {
217
+ reject(new Error(`Plugin "${pluginName}" hook "${hookName}" timed out after ${this.hookTimeout}ms`));
218
+ }, this.hookTimeout);
219
+ });
220
+
221
+ try {
222
+ // Race entre el hook y el timeout
223
+ return await Promise.race([
224
+ hookFn(context),
225
+ timeoutPromise
226
+ ]);
227
+ } catch (error) {
228
+ console.error(`[WuPlugin] Error in ${pluginName}.${hookName}:`, error);
229
+ // No propagar error para no romper otros plugins
230
+ return undefined;
231
+ }
232
+ };
78
233
  }
79
234
 
80
235
  /**
81
- * 🪝 REGISTER HOOK: Registrar callback en un hook
82
- * @param {string} hookName - Nombre del hook
83
- * @param {Function} callback - Función callback
236
+ * 🪝 REGISTER HOOK
84
237
  */
85
238
  registerHook(hookName, callback) {
86
239
  if (!this.hooks.has(hookName)) {
87
240
  console.warn(`[WuPlugin] Unknown hook: ${hookName}`);
88
241
  return;
89
242
  }
90
-
91
243
  this.hooks.get(hookName).push(callback);
92
244
  }
93
245
 
94
246
  /**
95
- * 🎯 CALL HOOK: Ejecutar todos los callbacks de un hook
96
- * @param {string} hookName - Nombre del hook
97
- * @param {*} context - Contexto a pasar a los callbacks
98
- * @returns {Promise<boolean>} false si algún callback cancela la operación
247
+ * 🎯 CALL HOOK
99
248
  */
100
249
  async callHook(hookName, context) {
101
250
  const callbacks = this.hooks.get(hookName) || [];
@@ -103,17 +252,11 @@ export class WuPluginSystem {
103
252
  for (const callback of callbacks) {
104
253
  try {
105
254
  const result = await callback(context);
106
-
107
- // Si retorna false, cancelar operación
108
255
  if (result === false) {
109
- console.log(`[WuPlugin] Hook ${hookName} cancelled by plugin`);
110
256
  return false;
111
257
  }
112
258
  } catch (error) {
113
259
  console.error(`[WuPlugin] Error in hook ${hookName}:`, error);
114
-
115
- // Llamar hook de error
116
- await this.callHook('onError', { hookName, error, context });
117
260
  }
118
261
  }
119
262
 
@@ -121,61 +264,46 @@ export class WuPluginSystem {
121
264
  }
122
265
 
123
266
  /**
124
- * 🗑️ UNINSTALL: Desinstalar plugin
125
- * @param {string} pluginName - Nombre del plugin
267
+ * 🗑️ UNINSTALL
126
268
  */
127
269
  uninstall(pluginName) {
128
270
  const pluginData = this.plugins.get(pluginName);
129
-
130
271
  if (!pluginData) {
131
272
  console.warn(`[WuPlugin] Plugin "${pluginName}" not found`);
132
273
  return;
133
274
  }
134
275
 
135
- const { plugin } = pluginData;
276
+ const { plugin, sandboxedApi } = pluginData;
136
277
 
137
- // Ejecutar uninstall si existe
138
278
  if (plugin.uninstall) {
139
- plugin.uninstall(this.core);
140
- }
141
-
142
- // Remover hooks del plugin
143
- this.availableHooks.forEach(hookName => {
144
- if (typeof plugin[hookName] === 'function') {
145
- const callbacks = this.hooks.get(hookName);
146
- const index = callbacks.indexOf(plugin[hookName]);
147
- if (index > -1) {
148
- callbacks.splice(index, 1);
149
- }
279
+ try {
280
+ plugin.uninstall(sandboxedApi);
281
+ } catch (error) {
282
+ console.error(`[WuPlugin] Error uninstalling "${pluginName}":`, error);
150
283
  }
151
- });
284
+ }
152
285
 
153
- // Remover plugin
154
286
  this.plugins.delete(pluginName);
155
-
156
287
  console.log(`[WuPlugin] ✅ Plugin "${pluginName}" uninstalled`);
157
288
  }
158
289
 
159
290
  /**
160
- * 📋 GET PLUGIN: Obtener plugin instalado
161
- * @param {string} pluginName - Nombre del plugin
162
- * @returns {Object}
291
+ * 📋 GET PLUGIN
163
292
  */
164
293
  getPlugin(pluginName) {
165
294
  return this.plugins.get(pluginName)?.plugin;
166
295
  }
167
296
 
168
297
  /**
169
- * 📊 GET STATS: Estadísticas de plugins
170
- * @returns {Object}
298
+ * 📊 GET STATS
171
299
  */
172
300
  getStats() {
173
301
  return {
174
302
  totalPlugins: this.plugins.size,
175
303
  plugins: Array.from(this.plugins.entries()).map(([name, data]) => ({
176
304
  name,
177
- installedAt: data.installedAt,
178
- hasUninstall: typeof data.plugin.uninstall === 'function'
305
+ permissions: data.permissions,
306
+ installedAt: data.installedAt
179
307
  })),
180
308
  hooks: Array.from(this.hooks.entries()).map(([name, callbacks]) => ({
181
309
  name,
@@ -185,23 +313,25 @@ export class WuPluginSystem {
185
313
  }
186
314
 
187
315
  /**
188
- * 🧹 CLEANUP: Limpiar todos los plugins
316
+ * 🧹 CLEANUP
189
317
  */
190
318
  cleanup() {
191
319
  for (const [name] of this.plugins) {
192
320
  this.uninstall(name);
193
321
  }
194
-
195
- console.log('[WuPlugin] 🧹 All plugins cleaned up');
196
322
  }
197
323
  }
198
324
 
199
325
  /**
200
- * 📦 PLUGIN HELPER: Helper para crear plugins fácilmente
326
+ * 📦 PLUGIN HELPER: Helper para crear plugins
327
+ * @param {Object} config - Configuración del plugin
328
+ * @param {string} config.name - Nombre del plugin
329
+ * @param {Array} config.permissions - Permisos requeridos
201
330
  */
202
331
  export const createPlugin = (config) => {
203
332
  return {
204
333
  name: config.name,
334
+ permissions: config.permissions || ['events'],
205
335
  install: config.install,
206
336
  uninstall: config.uninstall,
207
337
  beforeMount: config.beforeMount,
package/src/index.js CHANGED
@@ -103,7 +103,7 @@ if (typeof window !== 'undefined') {
103
103
 
104
104
  // Configurar propiedades si no existen
105
105
  if (!wu.version) {
106
- wu.version = '1.0.5';
106
+ wu.version = '1.0.6';
107
107
  console.log('🚀 Wu Framework loaded - Universal Microfrontends ready');
108
108
  wu.info = {
109
109
  name: 'Wu Framework',