elsabro 2.3.0 → 3.7.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.
Files changed (67) hide show
  1. package/README.md +668 -20
  2. package/bin/install.js +0 -0
  3. package/flows/development-flow.json +452 -0
  4. package/flows/quick-flow.json +118 -0
  5. package/package.json +3 -2
  6. package/references/SYSTEM_INDEX.md +379 -5
  7. package/references/agent-marketplace.md +2274 -0
  8. package/references/agent-protocol.md +1126 -0
  9. package/references/ai-code-suggestions.md +2413 -0
  10. package/references/checkpointing.md +595 -0
  11. package/references/collaboration-patterns.md +851 -0
  12. package/references/collaborative-sessions.md +1081 -0
  13. package/references/configuration-management.md +1810 -0
  14. package/references/cost-tracking.md +1095 -0
  15. package/references/enterprise-sso.md +2001 -0
  16. package/references/error-contracts-v2.md +968 -0
  17. package/references/event-driven.md +1031 -0
  18. package/references/flow-orchestration.md +940 -0
  19. package/references/flow-visualization.md +1557 -0
  20. package/references/ide-integrations.md +3513 -0
  21. package/references/interrupt-system.md +681 -0
  22. package/references/kubernetes-deployment.md +3099 -0
  23. package/references/memory-system.md +683 -0
  24. package/references/mobile-companion.md +3236 -0
  25. package/references/multi-llm-providers.md +2494 -0
  26. package/references/multi-project-memory.md +1182 -0
  27. package/references/observability.md +793 -0
  28. package/references/output-schemas.md +858 -0
  29. package/references/performance-profiler.md +955 -0
  30. package/references/plugin-system.md +1526 -0
  31. package/references/prompt-management.md +292 -0
  32. package/references/sandbox-execution.md +303 -0
  33. package/references/security-system.md +1253 -0
  34. package/references/streaming.md +696 -0
  35. package/references/testing-framework.md +1151 -0
  36. package/references/time-travel.md +802 -0
  37. package/references/tool-registry.md +886 -0
  38. package/references/voice-commands.md +3296 -0
  39. package/templates/agent-marketplace-config.json +220 -0
  40. package/templates/agent-protocol-config.json +136 -0
  41. package/templates/ai-suggestions-config.json +100 -0
  42. package/templates/checkpoint-state.json +61 -0
  43. package/templates/collaboration-config.json +157 -0
  44. package/templates/collaborative-sessions-config.json +153 -0
  45. package/templates/configuration-config.json +245 -0
  46. package/templates/cost-tracking-config.json +148 -0
  47. package/templates/enterprise-sso-config.json +438 -0
  48. package/templates/events-config.json +148 -0
  49. package/templates/flow-visualization-config.json +196 -0
  50. package/templates/ide-integrations-config.json +442 -0
  51. package/templates/kubernetes-config.json +764 -0
  52. package/templates/memory-state.json +84 -0
  53. package/templates/mobile-companion-config.json +600 -0
  54. package/templates/multi-llm-config.json +544 -0
  55. package/templates/multi-project-memory-config.json +145 -0
  56. package/templates/observability-config.json +109 -0
  57. package/templates/performance-profiler-config.json +125 -0
  58. package/templates/plugin-config.json +170 -0
  59. package/templates/prompt-management-config.json +86 -0
  60. package/templates/sandbox-config.json +185 -0
  61. package/templates/schemas-config.json +65 -0
  62. package/templates/security-config.json +120 -0
  63. package/templates/streaming-config.json +72 -0
  64. package/templates/testing-config.json +81 -0
  65. package/templates/timetravel-config.json +62 -0
  66. package/templates/tool-registry-config.json +109 -0
  67. package/templates/voice-commands-config.json +658 -0
@@ -0,0 +1,1253 @@
1
+ # Security & Access Control (v3.4)
2
+
3
+ Sistema de seguridad con RBAC, secrets management, audit logging y políticas de acceso.
4
+
5
+ ## Arquitectura
6
+
7
+ ```
8
+ ┌─────────────────────────────────────────────────────────────────────────┐
9
+ │ SECURITY SYSTEM │
10
+ ├─────────────────────────────────────────────────────────────────────────┤
11
+ │ │
12
+ │ ┌─────────────────────────────────────────────────────────────────┐ │
13
+ │ │ RBAC MANAGER │ │
14
+ │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
15
+ │ │ │ Roles │ │ Permissions │ │ Grants │ │ │
16
+ │ │ │ admin/user │ │ read/write │ │ role→perm │ │ │
17
+ │ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │
18
+ │ └─────────────────────────────────────────────────────────────────┘ │
19
+ │ │ │
20
+ │ ▼ │
21
+ │ ┌─────────────────────────────────────────────────────────────────┐ │
22
+ │ │ SECRETS VAULT │ │
23
+ │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
24
+ │ │ │ Encryption │ │ Storage │ │ Rotation │ │ │
25
+ │ │ │ AES-256 │ │ Secure │ │ Automatic │ │ │
26
+ │ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │
27
+ │ └─────────────────────────────────────────────────────────────────┘ │
28
+ │ │ │
29
+ │ ▼ │
30
+ │ ┌─────────────────────────────────────────────────────────────────┐ │
31
+ │ │ AUDIT LOGGER │ │
32
+ │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
33
+ │ │ │ Events │ │ Tracking │ │ Forensics │ │ │
34
+ │ │ │ Immutable │ │ Real-time │ │ Export │ │ │
35
+ │ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │
36
+ │ └─────────────────────────────────────────────────────────────────┘ │
37
+ │ │ │
38
+ │ ▼ │
39
+ │ ┌─────────────────────────────────────────────────────────────────┐ │
40
+ │ │ POLICY ENGINE │ │
41
+ │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
42
+ │ │ │ Rules │ │ Evaluation │ │ Enforcement │ │ │
43
+ │ │ │ Declarative│ │ Runtime │ │ Strict │ │ │
44
+ │ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │
45
+ │ └─────────────────────────────────────────────────────────────────┘ │
46
+ │ │
47
+ └─────────────────────────────────────────────────────────────────────────┘
48
+ ```
49
+
50
+ ---
51
+
52
+ ## RBACManager
53
+
54
+ ### API Principal
55
+
56
+ ```typescript
57
+ interface Role {
58
+ id: string;
59
+ name: string;
60
+ description: string;
61
+ permissions: string[];
62
+ inherits?: string[]; // Inherit from other roles
63
+ constraints?: RoleConstraint[];
64
+ }
65
+
66
+ interface Permission {
67
+ id: string;
68
+ name: string;
69
+ resource: string;
70
+ actions: string[]; // read, write, execute, delete
71
+ conditions?: PermissionCondition[];
72
+ }
73
+
74
+ interface RoleConstraint {
75
+ type: 'time' | 'location' | 'resource_limit' | 'custom';
76
+ value: unknown;
77
+ }
78
+
79
+ interface PermissionCondition {
80
+ field: string;
81
+ operator: 'eq' | 'neq' | 'in' | 'not_in' | 'matches';
82
+ value: unknown;
83
+ }
84
+
85
+ interface Principal {
86
+ id: string;
87
+ type: 'user' | 'agent' | 'service';
88
+ roles: string[];
89
+ attributes?: Record<string, unknown>;
90
+ }
91
+
92
+ class RBACManager {
93
+ private roles: Map<string, Role>;
94
+ private permissions: Map<string, Permission>;
95
+ private principals: Map<string, Principal>;
96
+ private grants: Map<string, Set<string>>; // principal -> roles
97
+
98
+ constructor(config: RBACConfig) {
99
+ this.roles = new Map();
100
+ this.permissions = new Map();
101
+ this.principals = new Map();
102
+ this.grants = new Map();
103
+ this.loadBuiltinRoles();
104
+ }
105
+
106
+ // Create role
107
+ createRole(role: Omit<Role, 'id'>): Role {
108
+ const newRole: Role = {
109
+ id: this.generateId('role'),
110
+ ...role
111
+ };
112
+
113
+ this.roles.set(newRole.id, newRole);
114
+
115
+ AuditLogger.log({
116
+ action: 'role.created',
117
+ resource: newRole.id,
118
+ details: { name: role.name }
119
+ });
120
+
121
+ return newRole;
122
+ }
123
+
124
+ // Create permission
125
+ createPermission(permission: Omit<Permission, 'id'>): Permission {
126
+ const newPerm: Permission = {
127
+ id: this.generateId('perm'),
128
+ ...permission
129
+ };
130
+
131
+ this.permissions.set(newPerm.id, newPerm);
132
+
133
+ return newPerm;
134
+ }
135
+
136
+ // Assign role to principal
137
+ assignRole(principalId: string, roleId: string): void {
138
+ const principal = this.principals.get(principalId);
139
+ if (!principal) throw new Error(`Principal not found: ${principalId}`);
140
+
141
+ const role = this.roles.get(roleId);
142
+ if (!role) throw new Error(`Role not found: ${roleId}`);
143
+
144
+ if (!this.grants.has(principalId)) {
145
+ this.grants.set(principalId, new Set());
146
+ }
147
+
148
+ this.grants.get(principalId)!.add(roleId);
149
+ principal.roles.push(roleId);
150
+
151
+ AuditLogger.log({
152
+ action: 'role.assigned',
153
+ principal: principalId,
154
+ resource: roleId
155
+ });
156
+ }
157
+
158
+ // Revoke role from principal
159
+ revokeRole(principalId: string, roleId: string): void {
160
+ const grants = this.grants.get(principalId);
161
+ if (grants) {
162
+ grants.delete(roleId);
163
+ }
164
+
165
+ const principal = this.principals.get(principalId);
166
+ if (principal) {
167
+ principal.roles = principal.roles.filter(r => r !== roleId);
168
+ }
169
+
170
+ AuditLogger.log({
171
+ action: 'role.revoked',
172
+ principal: principalId,
173
+ resource: roleId
174
+ });
175
+ }
176
+
177
+ // Check if principal has permission
178
+ hasPermission(
179
+ principalId: string,
180
+ resource: string,
181
+ action: string,
182
+ context?: PermissionContext
183
+ ): boolean {
184
+ const principal = this.principals.get(principalId);
185
+ if (!principal) return false;
186
+
187
+ // Get all effective permissions
188
+ const effectivePermissions = this.getEffectivePermissions(principal);
189
+
190
+ // Check each permission
191
+ for (const permId of effectivePermissions) {
192
+ const perm = this.permissions.get(permId);
193
+ if (!perm) continue;
194
+
195
+ // Check resource match
196
+ if (!this.matchesResource(perm.resource, resource)) continue;
197
+
198
+ // Check action
199
+ if (!perm.actions.includes(action) && !perm.actions.includes('*')) continue;
200
+
201
+ // Check conditions
202
+ if (perm.conditions && !this.evaluateConditions(perm.conditions, context)) continue;
203
+
204
+ return true;
205
+ }
206
+
207
+ return false;
208
+ }
209
+
210
+ // Enforce permission (throws if denied)
211
+ enforce(
212
+ principalId: string,
213
+ resource: string,
214
+ action: string,
215
+ context?: PermissionContext
216
+ ): void {
217
+ if (!this.hasPermission(principalId, resource, action, context)) {
218
+ AuditLogger.log({
219
+ action: 'permission.denied',
220
+ principal: principalId,
221
+ resource,
222
+ details: { action }
223
+ });
224
+
225
+ throw new PermissionDeniedError(
226
+ `Permission denied: ${principalId} cannot ${action} on ${resource}`
227
+ );
228
+ }
229
+
230
+ AuditLogger.log({
231
+ action: 'permission.granted',
232
+ principal: principalId,
233
+ resource,
234
+ details: { action }
235
+ });
236
+ }
237
+
238
+ // Get principal's effective permissions
239
+ getEffectivePermissions(principal: Principal): Set<string> {
240
+ const permissions = new Set<string>();
241
+
242
+ for (const roleId of principal.roles) {
243
+ const rolePerms = this.getRolePermissions(roleId);
244
+ rolePerms.forEach(p => permissions.add(p));
245
+ }
246
+
247
+ return permissions;
248
+ }
249
+
250
+ // Get role with inheritance
251
+ private getRolePermissions(roleId: string): string[] {
252
+ const role = this.roles.get(roleId);
253
+ if (!role) return [];
254
+
255
+ const permissions = [...role.permissions];
256
+
257
+ // Add inherited permissions
258
+ if (role.inherits) {
259
+ for (const inheritedRole of role.inherits) {
260
+ permissions.push(...this.getRolePermissions(inheritedRole));
261
+ }
262
+ }
263
+
264
+ return permissions;
265
+ }
266
+
267
+ private matchesResource(pattern: string, resource: string): boolean {
268
+ if (pattern === '*') return true;
269
+ if (pattern === resource) return true;
270
+
271
+ // Wildcard matching
272
+ const regex = new RegExp(
273
+ '^' + pattern.replace(/\*/g, '.*').replace(/\?/g, '.') + '$'
274
+ );
275
+ return regex.test(resource);
276
+ }
277
+
278
+ private evaluateConditions(
279
+ conditions: PermissionCondition[],
280
+ context?: PermissionContext
281
+ ): boolean {
282
+ if (!context) return true;
283
+
284
+ for (const condition of conditions) {
285
+ const value = context[condition.field];
286
+
287
+ switch (condition.operator) {
288
+ case 'eq':
289
+ if (value !== condition.value) return false;
290
+ break;
291
+ case 'neq':
292
+ if (value === condition.value) return false;
293
+ break;
294
+ case 'in':
295
+ if (!Array.isArray(condition.value) || !condition.value.includes(value))
296
+ return false;
297
+ break;
298
+ case 'not_in':
299
+ if (Array.isArray(condition.value) && condition.value.includes(value))
300
+ return false;
301
+ break;
302
+ case 'matches':
303
+ if (!new RegExp(condition.value as string).test(String(value)))
304
+ return false;
305
+ break;
306
+ }
307
+ }
308
+
309
+ return true;
310
+ }
311
+
312
+ private loadBuiltinRoles(): void {
313
+ // Admin role
314
+ this.createRole({
315
+ name: 'admin',
316
+ description: 'Full system access',
317
+ permissions: ['*']
318
+ });
319
+
320
+ // Agent roles
321
+ this.createRole({
322
+ name: 'agent:explore',
323
+ description: 'Read-only exploration',
324
+ permissions: ['file:read', 'search:*', 'web:fetch']
325
+ });
326
+
327
+ this.createRole({
328
+ name: 'agent:implement',
329
+ description: 'Read and write access',
330
+ permissions: ['file:read', 'file:write', 'file:create', 'bash:safe']
331
+ });
332
+
333
+ this.createRole({
334
+ name: 'agent:review',
335
+ description: 'Review access',
336
+ permissions: ['file:read', 'git:read']
337
+ });
338
+ }
339
+
340
+ private generateId(prefix: string): string {
341
+ return `${prefix}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
342
+ }
343
+ }
344
+ ```
345
+
346
+ ---
347
+
348
+ ## SecretsVault
349
+
350
+ ```typescript
351
+ interface Secret {
352
+ id: string;
353
+ name: string;
354
+ value: string; // Encrypted
355
+ type: 'api_key' | 'password' | 'token' | 'certificate' | 'custom';
356
+ metadata: {
357
+ created_at: string;
358
+ updated_at: string;
359
+ expires_at?: string;
360
+ rotation_policy?: RotationPolicy;
361
+ access_count: number;
362
+ last_accessed?: string;
363
+ };
364
+ tags?: string[];
365
+ }
366
+
367
+ interface RotationPolicy {
368
+ enabled: boolean;
369
+ interval_days: number;
370
+ notification_days: number;
371
+ auto_rotate?: boolean;
372
+ }
373
+
374
+ class SecretsVault {
375
+ private secrets: Map<string, Secret>;
376
+ private encryptionKey: Buffer;
377
+ private config: SecretsVaultConfig;
378
+
379
+ constructor(config: SecretsVaultConfig) {
380
+ this.config = config;
381
+ this.secrets = new Map();
382
+ this.encryptionKey = this.deriveKey(config.masterPassword);
383
+ this.loadSecrets();
384
+ }
385
+
386
+ // Store secret
387
+ async store(
388
+ name: string,
389
+ value: string,
390
+ options?: StoreOptions
391
+ ): Promise<Secret> {
392
+ const encrypted = this.encrypt(value);
393
+
394
+ const secret: Secret = {
395
+ id: this.generateId(),
396
+ name,
397
+ value: encrypted,
398
+ type: options?.type || 'custom',
399
+ metadata: {
400
+ created_at: new Date().toISOString(),
401
+ updated_at: new Date().toISOString(),
402
+ expires_at: options?.expires_at,
403
+ rotation_policy: options?.rotation_policy,
404
+ access_count: 0
405
+ },
406
+ tags: options?.tags
407
+ };
408
+
409
+ this.secrets.set(name, secret);
410
+ await this.persist();
411
+
412
+ AuditLogger.log({
413
+ action: 'secret.stored',
414
+ resource: name,
415
+ details: { type: secret.type }
416
+ });
417
+
418
+ return { ...secret, value: '[REDACTED]' };
419
+ }
420
+
421
+ // Retrieve secret
422
+ async get(name: string, principalId?: string): Promise<string> {
423
+ const secret = this.secrets.get(name);
424
+ if (!secret) throw new Error(`Secret not found: ${name}`);
425
+
426
+ // Check permissions
427
+ if (principalId) {
428
+ RBACManager.enforce(principalId, `secret:${name}`, 'read');
429
+ }
430
+
431
+ // Check expiration
432
+ if (secret.metadata.expires_at) {
433
+ if (new Date(secret.metadata.expires_at) < new Date()) {
434
+ throw new Error(`Secret expired: ${name}`);
435
+ }
436
+ }
437
+
438
+ // Update access metadata
439
+ secret.metadata.access_count++;
440
+ secret.metadata.last_accessed = new Date().toISOString();
441
+
442
+ AuditLogger.log({
443
+ action: 'secret.accessed',
444
+ resource: name,
445
+ principal: principalId
446
+ });
447
+
448
+ return this.decrypt(secret.value);
449
+ }
450
+
451
+ // Rotate secret
452
+ async rotate(name: string, newValue: string): Promise<void> {
453
+ const secret = this.secrets.get(name);
454
+ if (!secret) throw new Error(`Secret not found: ${name}`);
455
+
456
+ const oldValue = secret.value;
457
+ secret.value = this.encrypt(newValue);
458
+ secret.metadata.updated_at = new Date().toISOString();
459
+
460
+ await this.persist();
461
+
462
+ AuditLogger.log({
463
+ action: 'secret.rotated',
464
+ resource: name
465
+ });
466
+
467
+ // Emit rotation event
468
+ EventBus.publish('secret.rotated', {
469
+ name,
470
+ type: secret.type
471
+ });
472
+ }
473
+
474
+ // Delete secret
475
+ async delete(name: string): Promise<boolean> {
476
+ const deleted = this.secrets.delete(name);
477
+
478
+ if (deleted) {
479
+ await this.persist();
480
+
481
+ AuditLogger.log({
482
+ action: 'secret.deleted',
483
+ resource: name
484
+ });
485
+ }
486
+
487
+ return deleted;
488
+ }
489
+
490
+ // List secrets (metadata only)
491
+ list(): SecretMetadata[] {
492
+ return Array.from(this.secrets.values()).map(s => ({
493
+ id: s.id,
494
+ name: s.name,
495
+ type: s.type,
496
+ metadata: s.metadata,
497
+ tags: s.tags
498
+ }));
499
+ }
500
+
501
+ // Check for expiring secrets
502
+ getExpiring(days: number = 7): SecretMetadata[] {
503
+ const cutoff = new Date();
504
+ cutoff.setDate(cutoff.getDate() + days);
505
+
506
+ return this.list().filter(s => {
507
+ if (!s.metadata.expires_at) return false;
508
+ return new Date(s.metadata.expires_at) <= cutoff;
509
+ });
510
+ }
511
+
512
+ // Auto-rotate secrets based on policy
513
+ async autoRotate(): Promise<string[]> {
514
+ const rotated: string[] = [];
515
+
516
+ for (const [name, secret] of this.secrets) {
517
+ const policy = secret.metadata.rotation_policy;
518
+ if (!policy?.enabled || !policy.auto_rotate) continue;
519
+
520
+ const lastRotation = new Date(secret.metadata.updated_at);
521
+ const daysSinceRotation = Math.floor(
522
+ (Date.now() - lastRotation.getTime()) / (1000 * 60 * 60 * 24)
523
+ );
524
+
525
+ if (daysSinceRotation >= policy.interval_days) {
526
+ // Generate new value (for supported types)
527
+ const newValue = await this.generateNewValue(secret.type);
528
+ if (newValue) {
529
+ await this.rotate(name, newValue);
530
+ rotated.push(name);
531
+ }
532
+ }
533
+ }
534
+
535
+ return rotated;
536
+ }
537
+
538
+ // Encryption helpers
539
+ private encrypt(plaintext: string): string {
540
+ const iv = crypto.randomBytes(16);
541
+ const cipher = crypto.createCipheriv('aes-256-gcm', this.encryptionKey, iv);
542
+
543
+ let encrypted = cipher.update(plaintext, 'utf8', 'base64');
544
+ encrypted += cipher.final('base64');
545
+
546
+ const authTag = cipher.getAuthTag();
547
+
548
+ return JSON.stringify({
549
+ iv: iv.toString('base64'),
550
+ data: encrypted,
551
+ tag: authTag.toString('base64')
552
+ });
553
+ }
554
+
555
+ private decrypt(ciphertext: string): string {
556
+ const { iv, data, tag } = JSON.parse(ciphertext);
557
+
558
+ const decipher = crypto.createDecipheriv(
559
+ 'aes-256-gcm',
560
+ this.encryptionKey,
561
+ Buffer.from(iv, 'base64')
562
+ );
563
+
564
+ decipher.setAuthTag(Buffer.from(tag, 'base64'));
565
+
566
+ let decrypted = decipher.update(data, 'base64', 'utf8');
567
+ decrypted += decipher.final('utf8');
568
+
569
+ return decrypted;
570
+ }
571
+
572
+ private deriveKey(password: string): Buffer {
573
+ return crypto.scryptSync(password, this.config.salt || 'elsabro', 32);
574
+ }
575
+
576
+ private async persist(): Promise<void> {
577
+ const data = JSON.stringify(Array.from(this.secrets.entries()));
578
+ const encrypted = this.encrypt(data);
579
+ await fs.writeFile(this.config.vaultPath, encrypted);
580
+ }
581
+
582
+ private async loadSecrets(): Promise<void> {
583
+ try {
584
+ const encrypted = await fs.readFile(this.config.vaultPath, 'utf-8');
585
+ const data = this.decrypt(encrypted);
586
+ const entries = JSON.parse(data);
587
+ this.secrets = new Map(entries);
588
+ } catch {
589
+ // No vault file yet
590
+ }
591
+ }
592
+
593
+ private async generateNewValue(type: string): Promise<string | null> {
594
+ switch (type) {
595
+ case 'api_key':
596
+ return crypto.randomBytes(32).toString('hex');
597
+ case 'password':
598
+ return crypto.randomBytes(24).toString('base64');
599
+ case 'token':
600
+ return crypto.randomBytes(48).toString('base64url');
601
+ default:
602
+ return null; // Cannot auto-generate
603
+ }
604
+ }
605
+
606
+ private generateId(): string {
607
+ return `secret_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
608
+ }
609
+ }
610
+ ```
611
+
612
+ ---
613
+
614
+ ## AuditLogger
615
+
616
+ ```typescript
617
+ interface AuditEvent {
618
+ id: string;
619
+ timestamp: string;
620
+ action: string;
621
+ principal?: string;
622
+ resource?: string;
623
+ details?: Record<string, unknown>;
624
+ outcome: 'success' | 'failure' | 'denied';
625
+ ip_address?: string;
626
+ session_id?: string;
627
+ trace_id?: string;
628
+ }
629
+
630
+ interface AuditQuery {
631
+ action?: string;
632
+ principal?: string;
633
+ resource?: string;
634
+ outcome?: string;
635
+ from?: string;
636
+ to?: string;
637
+ limit?: number;
638
+ offset?: number;
639
+ }
640
+
641
+ class AuditLogger {
642
+ private static instance: AuditLogger;
643
+ private events: AuditEvent[] = [];
644
+ private config: AuditLoggerConfig;
645
+ private writeStream?: fs.WriteStream;
646
+
647
+ private constructor(config: AuditLoggerConfig) {
648
+ this.config = config;
649
+ this.initializeStorage();
650
+ }
651
+
652
+ static getInstance(config?: AuditLoggerConfig): AuditLogger {
653
+ if (!AuditLogger.instance && config) {
654
+ AuditLogger.instance = new AuditLogger(config);
655
+ }
656
+ return AuditLogger.instance;
657
+ }
658
+
659
+ // Log audit event
660
+ static log(event: Partial<AuditEvent>): void {
661
+ const instance = AuditLogger.getInstance();
662
+ instance.logEvent(event);
663
+ }
664
+
665
+ // Log with automatic success outcome
666
+ static success(action: string, details?: Partial<AuditEvent>): void {
667
+ AuditLogger.log({ action, outcome: 'success', ...details });
668
+ }
669
+
670
+ // Log with automatic failure outcome
671
+ static failure(action: string, details?: Partial<AuditEvent>): void {
672
+ AuditLogger.log({ action, outcome: 'failure', ...details });
673
+ }
674
+
675
+ // Log with automatic denied outcome
676
+ static denied(action: string, details?: Partial<AuditEvent>): void {
677
+ AuditLogger.log({ action, outcome: 'denied', ...details });
678
+ }
679
+
680
+ private logEvent(partial: Partial<AuditEvent>): void {
681
+ const event: AuditEvent = {
682
+ id: this.generateId(),
683
+ timestamp: new Date().toISOString(),
684
+ action: partial.action || 'unknown',
685
+ principal: partial.principal,
686
+ resource: partial.resource,
687
+ details: partial.details,
688
+ outcome: partial.outcome || 'success',
689
+ ip_address: partial.ip_address,
690
+ session_id: partial.session_id,
691
+ trace_id: partial.trace_id || TelemetryManager.getCurrentTraceId()
692
+ };
693
+
694
+ // Store in memory (with limit)
695
+ this.events.push(event);
696
+ if (this.events.length > this.config.maxMemoryEvents) {
697
+ this.events.shift();
698
+ }
699
+
700
+ // Write to file
701
+ if (this.writeStream) {
702
+ this.writeStream.write(JSON.stringify(event) + '\n');
703
+ }
704
+
705
+ // Emit event for real-time monitoring
706
+ EventBus.publish('audit.event', event);
707
+
708
+ // Check for alert conditions
709
+ this.checkAlerts(event);
710
+ }
711
+
712
+ // Query audit log
713
+ query(options: AuditQuery): AuditEvent[] {
714
+ let filtered = this.events;
715
+
716
+ if (options.action) {
717
+ filtered = filtered.filter(e =>
718
+ e.action.includes(options.action!)
719
+ );
720
+ }
721
+
722
+ if (options.principal) {
723
+ filtered = filtered.filter(e =>
724
+ e.principal === options.principal
725
+ );
726
+ }
727
+
728
+ if (options.resource) {
729
+ filtered = filtered.filter(e =>
730
+ e.resource?.includes(options.resource!)
731
+ );
732
+ }
733
+
734
+ if (options.outcome) {
735
+ filtered = filtered.filter(e =>
736
+ e.outcome === options.outcome
737
+ );
738
+ }
739
+
740
+ if (options.from) {
741
+ filtered = filtered.filter(e =>
742
+ e.timestamp >= options.from!
743
+ );
744
+ }
745
+
746
+ if (options.to) {
747
+ filtered = filtered.filter(e =>
748
+ e.timestamp <= options.to!
749
+ );
750
+ }
751
+
752
+ // Pagination
753
+ const offset = options.offset || 0;
754
+ const limit = options.limit || 100;
755
+
756
+ return filtered.slice(offset, offset + limit);
757
+ }
758
+
759
+ // Export audit log
760
+ async exportLogs(
761
+ format: 'json' | 'csv',
762
+ query?: AuditQuery
763
+ ): Promise<string> {
764
+ const events = query ? this.query(query) : this.events;
765
+
766
+ if (format === 'csv') {
767
+ const headers = [
768
+ 'id', 'timestamp', 'action', 'principal',
769
+ 'resource', 'outcome', 'trace_id'
770
+ ];
771
+ const rows = events.map(e => [
772
+ e.id, e.timestamp, e.action, e.principal || '',
773
+ e.resource || '', e.outcome, e.trace_id || ''
774
+ ]);
775
+ return [headers.join(','), ...rows.map(r => r.join(','))].join('\n');
776
+ }
777
+
778
+ return JSON.stringify(events, null, 2);
779
+ }
780
+
781
+ // Get statistics
782
+ getStats(timeRange?: { from: string; to: string }): AuditStats {
783
+ const events = timeRange
784
+ ? this.query({ from: timeRange.from, to: timeRange.to })
785
+ : this.events;
786
+
787
+ const byAction: Record<string, number> = {};
788
+ const byOutcome: Record<string, number> = {};
789
+ const byPrincipal: Record<string, number> = {};
790
+
791
+ for (const event of events) {
792
+ byAction[event.action] = (byAction[event.action] || 0) + 1;
793
+ byOutcome[event.outcome] = (byOutcome[event.outcome] || 0) + 1;
794
+ if (event.principal) {
795
+ byPrincipal[event.principal] = (byPrincipal[event.principal] || 0) + 1;
796
+ }
797
+ }
798
+
799
+ return {
800
+ total: events.length,
801
+ byAction,
802
+ byOutcome,
803
+ byPrincipal,
804
+ timeRange
805
+ };
806
+ }
807
+
808
+ private checkAlerts(event: AuditEvent): void {
809
+ // Multiple failed authentications
810
+ if (event.action === 'auth.failed') {
811
+ const recentFailures = this.events.filter(e =>
812
+ e.action === 'auth.failed' &&
813
+ e.principal === event.principal &&
814
+ new Date(e.timestamp) > new Date(Date.now() - 5 * 60 * 1000)
815
+ );
816
+
817
+ if (recentFailures.length >= 5) {
818
+ this.triggerAlert('multiple_auth_failures', event);
819
+ }
820
+ }
821
+
822
+ // Permission denied spike
823
+ if (event.outcome === 'denied') {
824
+ const recentDenials = this.events.filter(e =>
825
+ e.outcome === 'denied' &&
826
+ new Date(e.timestamp) > new Date(Date.now() - 60 * 1000)
827
+ );
828
+
829
+ if (recentDenials.length >= 10) {
830
+ this.triggerAlert('permission_denial_spike', event);
831
+ }
832
+ }
833
+
834
+ // Sensitive resource access
835
+ const sensitiveResources = ['secret:*', 'config:*', 'admin:*'];
836
+ if (event.resource && sensitiveResources.some(p =>
837
+ event.resource!.match(new RegExp(p.replace('*', '.*')))
838
+ )) {
839
+ this.triggerAlert('sensitive_access', event);
840
+ }
841
+ }
842
+
843
+ private triggerAlert(type: string, event: AuditEvent): void {
844
+ EventBus.publish('audit.alert', {
845
+ type,
846
+ event,
847
+ timestamp: new Date().toISOString()
848
+ });
849
+ }
850
+
851
+ private initializeStorage(): void {
852
+ if (this.config.logPath) {
853
+ this.writeStream = fs.createWriteStream(this.config.logPath, {
854
+ flags: 'a'
855
+ });
856
+ }
857
+ }
858
+
859
+ private generateId(): string {
860
+ return `audit_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
861
+ }
862
+ }
863
+ ```
864
+
865
+ ---
866
+
867
+ ## PolicyEngine
868
+
869
+ ```typescript
870
+ interface Policy {
871
+ id: string;
872
+ name: string;
873
+ description: string;
874
+ effect: 'allow' | 'deny';
875
+ principals: string[]; // Patterns
876
+ resources: string[]; // Patterns
877
+ actions: string[];
878
+ conditions?: PolicyCondition[];
879
+ priority: number;
880
+ }
881
+
882
+ interface PolicyCondition {
883
+ type: 'time' | 'ip' | 'rate_limit' | 'attribute' | 'custom';
884
+ config: Record<string, unknown>;
885
+ }
886
+
887
+ interface PolicyEvaluationResult {
888
+ allowed: boolean;
889
+ matchedPolicy?: Policy;
890
+ reason: string;
891
+ }
892
+
893
+ class PolicyEngine {
894
+ private policies: Policy[] = [];
895
+ private cache: Map<string, PolicyEvaluationResult>;
896
+ private rateLimits: Map<string, RateLimitState>;
897
+
898
+ constructor(config: PolicyEngineConfig) {
899
+ this.cache = new Map();
900
+ this.rateLimits = new Map();
901
+ this.loadPolicies(config.policiesPath);
902
+ }
903
+
904
+ // Add policy
905
+ addPolicy(policy: Omit<Policy, 'id'>): Policy {
906
+ const newPolicy: Policy = {
907
+ id: this.generateId(),
908
+ ...policy
909
+ };
910
+
911
+ this.policies.push(newPolicy);
912
+ this.policies.sort((a, b) => b.priority - a.priority);
913
+ this.cache.clear();
914
+
915
+ return newPolicy;
916
+ }
917
+
918
+ // Remove policy
919
+ removePolicy(policyId: string): boolean {
920
+ const index = this.policies.findIndex(p => p.id === policyId);
921
+ if (index === -1) return false;
922
+
923
+ this.policies.splice(index, 1);
924
+ this.cache.clear();
925
+
926
+ return true;
927
+ }
928
+
929
+ // Evaluate request against policies
930
+ evaluate(request: PolicyRequest): PolicyEvaluationResult {
931
+ // Check cache
932
+ const cacheKey = this.getCacheKey(request);
933
+ if (this.cache.has(cacheKey)) {
934
+ return this.cache.get(cacheKey)!;
935
+ }
936
+
937
+ // Find matching policies
938
+ const matchingPolicies = this.policies.filter(policy =>
939
+ this.matchesPolicy(policy, request)
940
+ );
941
+
942
+ if (matchingPolicies.length === 0) {
943
+ const result = {
944
+ allowed: false,
945
+ reason: 'No matching policy'
946
+ };
947
+ this.cache.set(cacheKey, result);
948
+ return result;
949
+ }
950
+
951
+ // Evaluate in priority order
952
+ for (const policy of matchingPolicies) {
953
+ // Check conditions
954
+ const conditionsMet = this.evaluateConditions(policy.conditions, request);
955
+
956
+ if (conditionsMet) {
957
+ const result: PolicyEvaluationResult = {
958
+ allowed: policy.effect === 'allow',
959
+ matchedPolicy: policy,
960
+ reason: policy.effect === 'allow'
961
+ ? `Allowed by policy: ${policy.name}`
962
+ : `Denied by policy: ${policy.name}`
963
+ };
964
+
965
+ this.cache.set(cacheKey, result);
966
+ return result;
967
+ }
968
+ }
969
+
970
+ const result = {
971
+ allowed: false,
972
+ reason: 'No policy conditions met'
973
+ };
974
+ this.cache.set(cacheKey, result);
975
+ return result;
976
+ }
977
+
978
+ // Enforce policy (throws if denied)
979
+ enforce(request: PolicyRequest): void {
980
+ const result = this.evaluate(request);
981
+
982
+ if (!result.allowed) {
983
+ AuditLogger.denied('policy.denied', {
984
+ principal: request.principal,
985
+ resource: request.resource,
986
+ details: {
987
+ action: request.action,
988
+ policy: result.matchedPolicy?.name,
989
+ reason: result.reason
990
+ }
991
+ });
992
+
993
+ throw new PolicyDeniedError(result.reason);
994
+ }
995
+
996
+ AuditLogger.success('policy.allowed', {
997
+ principal: request.principal,
998
+ resource: request.resource,
999
+ details: {
1000
+ action: request.action,
1001
+ policy: result.matchedPolicy?.name
1002
+ }
1003
+ });
1004
+ }
1005
+
1006
+ private matchesPolicy(policy: Policy, request: PolicyRequest): boolean {
1007
+ // Check principal
1008
+ const principalMatch = policy.principals.some(p =>
1009
+ this.matchPattern(p, request.principal)
1010
+ );
1011
+ if (!principalMatch) return false;
1012
+
1013
+ // Check resource
1014
+ const resourceMatch = policy.resources.some(r =>
1015
+ this.matchPattern(r, request.resource)
1016
+ );
1017
+ if (!resourceMatch) return false;
1018
+
1019
+ // Check action
1020
+ const actionMatch = policy.actions.some(a =>
1021
+ a === '*' || a === request.action
1022
+ );
1023
+ if (!actionMatch) return false;
1024
+
1025
+ return true;
1026
+ }
1027
+
1028
+ private evaluateConditions(
1029
+ conditions: PolicyCondition[] | undefined,
1030
+ request: PolicyRequest
1031
+ ): boolean {
1032
+ if (!conditions || conditions.length === 0) return true;
1033
+
1034
+ for (const condition of conditions) {
1035
+ switch (condition.type) {
1036
+ case 'time':
1037
+ if (!this.evaluateTimeCondition(condition.config)) return false;
1038
+ break;
1039
+
1040
+ case 'ip':
1041
+ if (!this.evaluateIPCondition(condition.config, request.ip)) return false;
1042
+ break;
1043
+
1044
+ case 'rate_limit':
1045
+ if (!this.evaluateRateLimit(condition.config, request)) return false;
1046
+ break;
1047
+
1048
+ case 'attribute':
1049
+ if (!this.evaluateAttribute(condition.config, request.attributes))
1050
+ return false;
1051
+ break;
1052
+
1053
+ case 'custom':
1054
+ if (!this.evaluateCustom(condition.config, request)) return false;
1055
+ break;
1056
+ }
1057
+ }
1058
+
1059
+ return true;
1060
+ }
1061
+
1062
+ private evaluateTimeCondition(config: Record<string, unknown>): boolean {
1063
+ const now = new Date();
1064
+ const hour = now.getHours();
1065
+ const day = now.getDay();
1066
+
1067
+ if (config.hours) {
1068
+ const { start, end } = config.hours as { start: number; end: number };
1069
+ if (hour < start || hour >= end) return false;
1070
+ }
1071
+
1072
+ if (config.days) {
1073
+ const days = config.days as number[];
1074
+ if (!days.includes(day)) return false;
1075
+ }
1076
+
1077
+ return true;
1078
+ }
1079
+
1080
+ private evaluateIPCondition(
1081
+ config: Record<string, unknown>,
1082
+ ip?: string
1083
+ ): boolean {
1084
+ if (!ip) return false;
1085
+
1086
+ const allowed = config.allowed as string[] | undefined;
1087
+ const blocked = config.blocked as string[] | undefined;
1088
+
1089
+ if (blocked && blocked.some(b => this.matchIP(b, ip))) return false;
1090
+ if (allowed && !allowed.some(a => this.matchIP(a, ip))) return false;
1091
+
1092
+ return true;
1093
+ }
1094
+
1095
+ private evaluateRateLimit(
1096
+ config: Record<string, unknown>,
1097
+ request: PolicyRequest
1098
+ ): boolean {
1099
+ const { requests, window_seconds } = config as {
1100
+ requests: number;
1101
+ window_seconds: number;
1102
+ };
1103
+
1104
+ const key = `${request.principal}:${request.resource}:${request.action}`;
1105
+ const state = this.rateLimits.get(key) || {
1106
+ count: 0,
1107
+ window_start: Date.now()
1108
+ };
1109
+
1110
+ // Reset window if expired
1111
+ if (Date.now() - state.window_start > window_seconds * 1000) {
1112
+ state.count = 0;
1113
+ state.window_start = Date.now();
1114
+ }
1115
+
1116
+ // Check limit
1117
+ if (state.count >= requests) {
1118
+ return false;
1119
+ }
1120
+
1121
+ // Increment
1122
+ state.count++;
1123
+ this.rateLimits.set(key, state);
1124
+
1125
+ return true;
1126
+ }
1127
+
1128
+ private evaluateAttribute(
1129
+ config: Record<string, unknown>,
1130
+ attributes?: Record<string, unknown>
1131
+ ): boolean {
1132
+ if (!attributes) return false;
1133
+
1134
+ for (const [key, expected] of Object.entries(config)) {
1135
+ if (attributes[key] !== expected) return false;
1136
+ }
1137
+
1138
+ return true;
1139
+ }
1140
+
1141
+ private evaluateCustom(
1142
+ config: Record<string, unknown>,
1143
+ request: PolicyRequest
1144
+ ): boolean {
1145
+ // Custom condition evaluation via config
1146
+ // Could call external service, run script, etc.
1147
+ return true;
1148
+ }
1149
+
1150
+ private matchPattern(pattern: string, value: string): boolean {
1151
+ if (pattern === '*') return true;
1152
+ const regex = new RegExp(
1153
+ '^' + pattern.replace(/\*/g, '.*').replace(/\?/g, '.') + '$'
1154
+ );
1155
+ return regex.test(value);
1156
+ }
1157
+
1158
+ private matchIP(pattern: string, ip: string): boolean {
1159
+ // Support CIDR notation
1160
+ if (pattern.includes('/')) {
1161
+ return this.matchCIDR(pattern, ip);
1162
+ }
1163
+ return this.matchPattern(pattern, ip);
1164
+ }
1165
+
1166
+ private matchCIDR(cidr: string, ip: string): boolean {
1167
+ // Simplified CIDR matching
1168
+ const [network, bits] = cidr.split('/');
1169
+ const mask = ~(Math.pow(2, 32 - parseInt(bits)) - 1);
1170
+
1171
+ const ipNum = this.ipToNum(ip);
1172
+ const networkNum = this.ipToNum(network);
1173
+
1174
+ return (ipNum & mask) === (networkNum & mask);
1175
+ }
1176
+
1177
+ private ipToNum(ip: string): number {
1178
+ return ip.split('.').reduce((acc, octet) =>
1179
+ (acc << 8) + parseInt(octet), 0
1180
+ );
1181
+ }
1182
+
1183
+ private getCacheKey(request: PolicyRequest): string {
1184
+ return `${request.principal}:${request.resource}:${request.action}`;
1185
+ }
1186
+
1187
+ private async loadPolicies(path?: string): Promise<void> {
1188
+ if (!path) return;
1189
+ try {
1190
+ const content = await fs.readFile(path, 'utf-8');
1191
+ const policies = JSON.parse(content);
1192
+ this.policies = policies;
1193
+ this.policies.sort((a: Policy, b: Policy) => b.priority - a.priority);
1194
+ } catch {
1195
+ // No policies file
1196
+ }
1197
+ }
1198
+
1199
+ private generateId(): string {
1200
+ return `policy_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
1201
+ }
1202
+ }
1203
+ ```
1204
+
1205
+ ---
1206
+
1207
+ ## Comandos
1208
+
1209
+ ```bash
1210
+ /elsabro:security roles # Listar roles
1211
+ /elsabro:security permissions # Ver permisos
1212
+ /elsabro:security audit # Ver audit log
1213
+ /elsabro:security secrets list # Listar secrets
1214
+ /elsabro:security policies # Ver políticas
1215
+ ```
1216
+
1217
+ ---
1218
+
1219
+ ## Configuración
1220
+
1221
+ ```json
1222
+ {
1223
+ "security": {
1224
+ "enabled": true,
1225
+ "rbac": {
1226
+ "enabled": true,
1227
+ "defaultRole": "agent:explore"
1228
+ },
1229
+ "secrets": {
1230
+ "vaultPath": ".elsabro/vault.enc",
1231
+ "autoRotate": true
1232
+ },
1233
+ "audit": {
1234
+ "enabled": true,
1235
+ "logPath": ".elsabro/audit.log"
1236
+ },
1237
+ "policies": {
1238
+ "enabled": true,
1239
+ "defaultEffect": "deny"
1240
+ }
1241
+ }
1242
+ }
1243
+ ```
1244
+
1245
+ ---
1246
+
1247
+ ## Changelog
1248
+
1249
+ - **v3.4.0**: Initial Security System
1250
+ - RBACManager with role inheritance
1251
+ - SecretsVault with encryption
1252
+ - AuditLogger with alerts
1253
+ - PolicyEngine with conditions