@sparkleideas/security 3.0.0-alpha.10

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 (34) hide show
  1. package/README.md +234 -0
  2. package/__tests__/acceptance/security-compliance.test.ts +674 -0
  3. package/__tests__/credential-generator.test.ts +310 -0
  4. package/__tests__/fixtures/configurations.ts +419 -0
  5. package/__tests__/fixtures/index.ts +21 -0
  6. package/__tests__/helpers/create-mock.ts +469 -0
  7. package/__tests__/helpers/index.ts +32 -0
  8. package/__tests__/input-validator.test.ts +381 -0
  9. package/__tests__/integration/security-flow.test.ts +606 -0
  10. package/__tests__/password-hasher.test.ts +239 -0
  11. package/__tests__/path-validator.test.ts +302 -0
  12. package/__tests__/safe-executor.test.ts +292 -0
  13. package/__tests__/token-generator.test.ts +371 -0
  14. package/__tests__/unit/credential-generator.test.ts +182 -0
  15. package/__tests__/unit/password-hasher.test.ts +359 -0
  16. package/__tests__/unit/path-validator.test.ts +509 -0
  17. package/__tests__/unit/safe-executor.test.ts +667 -0
  18. package/__tests__/unit/token-generator.test.ts +310 -0
  19. package/package.json +28 -0
  20. package/src/CVE-REMEDIATION.ts +251 -0
  21. package/src/application/index.ts +10 -0
  22. package/src/application/services/security-application-service.ts +193 -0
  23. package/src/credential-generator.ts +368 -0
  24. package/src/domain/entities/security-context.ts +173 -0
  25. package/src/domain/index.ts +17 -0
  26. package/src/domain/services/security-domain-service.ts +296 -0
  27. package/src/index.ts +271 -0
  28. package/src/input-validator.ts +466 -0
  29. package/src/password-hasher.ts +270 -0
  30. package/src/path-validator.ts +525 -0
  31. package/src/safe-executor.ts +525 -0
  32. package/src/token-generator.ts +463 -0
  33. package/tmp.json +0 -0
  34. package/tsconfig.json +9 -0
@@ -0,0 +1,606 @@
1
+ /**
2
+ * V3 Claude-Flow Security Flow Integration Tests
3
+ *
4
+ * Integration tests for security module workflow
5
+ * Tests end-to-end security operations across components
6
+ */
7
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
8
+ import { createMock, type MockedInterface } from '../helpers/create-mock';
9
+ import { securityConfigs } from '../fixtures/configurations';
10
+
11
+ /**
12
+ * Security service integration interface
13
+ */
14
+ interface ISecurityService {
15
+ initialize(): Promise<void>;
16
+ validateAndHash(password: string): Promise<{ valid: boolean; hash?: string; errors: string[] }>;
17
+ validateAndExecute(command: string, args: string[]): Promise<ExecutionResult>;
18
+ validatePath(path: string): ValidationResult;
19
+ shutdown(): Promise<void>;
20
+ }
21
+
22
+ /**
23
+ * Crypto provider interface
24
+ */
25
+ interface ICryptoProvider {
26
+ argon2Hash(password: string, options: HashOptions): Promise<string>;
27
+ argon2Verify(hash: string, password: string): Promise<boolean>;
28
+ generateSalt(length: number): Promise<string>;
29
+ }
30
+
31
+ /**
32
+ * Command executor interface
33
+ */
34
+ interface ICommandExecutor {
35
+ spawn(command: string, args: string[], options: SpawnOptions): Promise<SpawnResult>;
36
+ }
37
+
38
+ /**
39
+ * Audit logger interface
40
+ */
41
+ interface IAuditLogger {
42
+ log(event: AuditEvent): Promise<void>;
43
+ getEvents(filter?: AuditFilter): Promise<AuditEvent[]>;
44
+ }
45
+
46
+ interface HashOptions {
47
+ memoryCost: number;
48
+ timeCost: number;
49
+ parallelism: number;
50
+ salt: string;
51
+ }
52
+
53
+ interface ExecutionResult {
54
+ success: boolean;
55
+ stdout: string;
56
+ stderr: string;
57
+ exitCode: number;
58
+ blocked: boolean;
59
+ reason?: string;
60
+ }
61
+
62
+ interface ValidationResult {
63
+ valid: boolean;
64
+ sanitized?: string;
65
+ errors: string[];
66
+ }
67
+
68
+ interface SpawnOptions {
69
+ timeout: number;
70
+ shell: boolean;
71
+ }
72
+
73
+ interface SpawnResult {
74
+ stdout: string;
75
+ stderr: string;
76
+ exitCode: number;
77
+ }
78
+
79
+ interface AuditEvent {
80
+ type: string;
81
+ action: string;
82
+ details: unknown;
83
+ timestamp: Date;
84
+ success: boolean;
85
+ }
86
+
87
+ interface AuditFilter {
88
+ type?: string;
89
+ startTime?: Date;
90
+ endTime?: Date;
91
+ }
92
+
93
+ /**
94
+ * Security service implementation for integration testing
95
+ */
96
+ class SecurityService implements ISecurityService {
97
+ private initialized = false;
98
+
99
+ constructor(
100
+ private readonly crypto: ICryptoProvider,
101
+ private readonly executor: ICommandExecutor,
102
+ private readonly auditLogger: IAuditLogger,
103
+ private readonly config: typeof securityConfigs.strict
104
+ ) {}
105
+
106
+ async initialize(): Promise<void> {
107
+ this.initialized = true;
108
+ await this.auditLogger.log({
109
+ type: 'security',
110
+ action: 'initialize',
111
+ details: { config: 'strict' },
112
+ timestamp: new Date(),
113
+ success: true,
114
+ });
115
+ }
116
+
117
+ async validateAndHash(password: string): Promise<{ valid: boolean; hash?: string; errors: string[] }> {
118
+ const errors: string[] = [];
119
+
120
+ // Validate password length
121
+ if (!password || password.length < 8) {
122
+ errors.push('Password must be at least 8 characters');
123
+ }
124
+
125
+ // Validate password complexity
126
+ if (!/[A-Z]/.test(password)) {
127
+ errors.push('Password must contain uppercase letter');
128
+ }
129
+ if (!/[a-z]/.test(password)) {
130
+ errors.push('Password must contain lowercase letter');
131
+ }
132
+ if (!/[0-9]/.test(password)) {
133
+ errors.push('Password must contain number');
134
+ }
135
+
136
+ if (errors.length > 0) {
137
+ await this.auditLogger.log({
138
+ type: 'security',
139
+ action: 'password_validation_failed',
140
+ details: { errors },
141
+ timestamp: new Date(),
142
+ success: false,
143
+ });
144
+ return { valid: false, errors };
145
+ }
146
+
147
+ const salt = await this.crypto.generateSalt(16);
148
+ const hash = await this.crypto.argon2Hash(password, {
149
+ memoryCost: this.config.hashing.memoryCost ?? 65536,
150
+ timeCost: this.config.hashing.timeCost ?? 3,
151
+ parallelism: this.config.hashing.parallelism ?? 4,
152
+ salt,
153
+ });
154
+
155
+ await this.auditLogger.log({
156
+ type: 'security',
157
+ action: 'password_hashed',
158
+ details: { algorithm: this.config.hashing.algorithm },
159
+ timestamp: new Date(),
160
+ success: true,
161
+ });
162
+
163
+ return { valid: true, hash, errors: [] };
164
+ }
165
+
166
+ async validateAndExecute(command: string, args: string[]): Promise<ExecutionResult> {
167
+ const baseCommand = command.split(' ')[0];
168
+
169
+ // Check blocked commands
170
+ if (this.config.execution.blockedCommands.includes(baseCommand)) {
171
+ await this.auditLogger.log({
172
+ type: 'security',
173
+ action: 'command_blocked',
174
+ details: { command: baseCommand },
175
+ timestamp: new Date(),
176
+ success: false,
177
+ });
178
+
179
+ return {
180
+ success: false,
181
+ stdout: '',
182
+ stderr: 'Command blocked by security policy',
183
+ exitCode: -1,
184
+ blocked: true,
185
+ reason: `Command "${baseCommand}" is blocked`,
186
+ };
187
+ }
188
+
189
+ // Check allowed commands
190
+ if (!this.config.execution.allowedCommands.includes(baseCommand)) {
191
+ await this.auditLogger.log({
192
+ type: 'security',
193
+ action: 'command_not_allowed',
194
+ details: { command: baseCommand },
195
+ timestamp: new Date(),
196
+ success: false,
197
+ });
198
+
199
+ return {
200
+ success: false,
201
+ stdout: '',
202
+ stderr: 'Command not in allowed list',
203
+ exitCode: -1,
204
+ blocked: true,
205
+ reason: `Command "${baseCommand}" is not allowed`,
206
+ };
207
+ }
208
+
209
+ // Sanitize arguments
210
+ const sanitizedArgs = args.map((arg) =>
211
+ arg.replace(/[;&|`$()]/g, '').replace(/\n/g, '')
212
+ );
213
+
214
+ // Execute command
215
+ const result = await this.executor.spawn(baseCommand, sanitizedArgs, {
216
+ timeout: this.config.execution.timeout,
217
+ shell: this.config.execution.shell,
218
+ });
219
+
220
+ await this.auditLogger.log({
221
+ type: 'security',
222
+ action: 'command_executed',
223
+ details: { command: baseCommand, exitCode: result.exitCode },
224
+ timestamp: new Date(),
225
+ success: result.exitCode === 0,
226
+ });
227
+
228
+ return {
229
+ success: result.exitCode === 0,
230
+ stdout: result.stdout,
231
+ stderr: result.stderr,
232
+ exitCode: result.exitCode,
233
+ blocked: false,
234
+ };
235
+ }
236
+
237
+ validatePath(path: string): ValidationResult {
238
+ const errors: string[] = [];
239
+
240
+ // Check for empty path
241
+ if (!path || path.length === 0) {
242
+ errors.push('Path cannot be empty');
243
+ return { valid: false, errors };
244
+ }
245
+
246
+ // Check for blocked patterns
247
+ for (const pattern of this.config.paths.blockedPatterns) {
248
+ if (path.includes(pattern)) {
249
+ errors.push(`Path contains blocked pattern: ${pattern}`);
250
+ }
251
+ }
252
+
253
+ // Check length
254
+ if (path.length > this.config.paths.maxPathLength) {
255
+ errors.push(`Path exceeds maximum length of ${this.config.paths.maxPathLength}`);
256
+ }
257
+
258
+ // Check null bytes
259
+ if (path.includes('\0')) {
260
+ errors.push('Path contains null byte');
261
+ }
262
+
263
+ if (errors.length > 0) {
264
+ return { valid: false, errors };
265
+ }
266
+
267
+ // Sanitize path
268
+ let sanitized = path;
269
+ for (const pattern of this.config.paths.blockedPatterns) {
270
+ sanitized = sanitized.split(pattern).join('');
271
+ }
272
+ sanitized = sanitized.replace(/\0/g, '');
273
+
274
+ return { valid: true, sanitized, errors: [] };
275
+ }
276
+
277
+ async shutdown(): Promise<void> {
278
+ await this.auditLogger.log({
279
+ type: 'security',
280
+ action: 'shutdown',
281
+ details: {},
282
+ timestamp: new Date(),
283
+ success: true,
284
+ });
285
+ this.initialized = false;
286
+ }
287
+ }
288
+
289
+ describe('Security Flow Integration', () => {
290
+ let mockCrypto: MockedInterface<ICryptoProvider>;
291
+ let mockExecutor: MockedInterface<ICommandExecutor>;
292
+ let mockAuditLogger: MockedInterface<IAuditLogger>;
293
+ let securityService: SecurityService;
294
+
295
+ beforeEach(() => {
296
+ mockCrypto = createMock<ICryptoProvider>();
297
+ mockExecutor = createMock<ICommandExecutor>();
298
+ mockAuditLogger = createMock<IAuditLogger>();
299
+
300
+ mockCrypto.generateSalt.mockResolvedValue('random-salt-16');
301
+ mockCrypto.argon2Hash.mockResolvedValue('$argon2id$v=19$...');
302
+ mockCrypto.argon2Verify.mockResolvedValue(true);
303
+
304
+ mockExecutor.spawn.mockResolvedValue({
305
+ stdout: 'success',
306
+ stderr: '',
307
+ exitCode: 0,
308
+ });
309
+
310
+ mockAuditLogger.log.mockResolvedValue(undefined);
311
+ mockAuditLogger.getEvents.mockResolvedValue([]);
312
+
313
+ securityService = new SecurityService(
314
+ mockCrypto,
315
+ mockExecutor,
316
+ mockAuditLogger,
317
+ securityConfigs.strict
318
+ );
319
+ });
320
+
321
+ describe('Password Security Flow', () => {
322
+ it('should validate and hash password end-to-end', async () => {
323
+ // Given
324
+ await securityService.initialize();
325
+ const password = 'SecurePass123!';
326
+
327
+ // When
328
+ const result = await securityService.validateAndHash(password);
329
+
330
+ // Then
331
+ expect(result.valid).toBe(true);
332
+ expect(result.hash).toBeDefined();
333
+ expect(result.errors).toHaveLength(0);
334
+ });
335
+
336
+ it('should reject invalid password with multiple errors', async () => {
337
+ // Given
338
+ await securityService.initialize();
339
+ const password = 'weak';
340
+
341
+ // When
342
+ const result = await securityService.validateAndHash(password);
343
+
344
+ // Then
345
+ expect(result.valid).toBe(false);
346
+ expect(result.errors).toContain('Password must be at least 8 characters');
347
+ });
348
+
349
+ it('should audit password operations', async () => {
350
+ // Given
351
+ await securityService.initialize();
352
+
353
+ // When
354
+ await securityService.validateAndHash('SecurePass123!');
355
+
356
+ // Then
357
+ expect(mockAuditLogger.log).toHaveBeenCalledWith(
358
+ expect.objectContaining({
359
+ type: 'security',
360
+ action: 'password_hashed',
361
+ })
362
+ );
363
+ });
364
+
365
+ it('should audit failed validation', async () => {
366
+ // Given
367
+ await securityService.initialize();
368
+
369
+ // When
370
+ await securityService.validateAndHash('weak');
371
+
372
+ // Then
373
+ expect(mockAuditLogger.log).toHaveBeenCalledWith(
374
+ expect.objectContaining({
375
+ type: 'security',
376
+ action: 'password_validation_failed',
377
+ success: false,
378
+ })
379
+ );
380
+ });
381
+ });
382
+
383
+ describe('Command Execution Security Flow', () => {
384
+ it('should execute allowed command successfully', async () => {
385
+ // Given
386
+ await securityService.initialize();
387
+
388
+ // When
389
+ const result = await securityService.validateAndExecute('npm', ['install']);
390
+
391
+ // Then
392
+ expect(result.success).toBe(true);
393
+ expect(result.blocked).toBe(false);
394
+ });
395
+
396
+ it('should block dangerous commands', async () => {
397
+ // Given
398
+ await securityService.initialize();
399
+
400
+ // When
401
+ const result = await securityService.validateAndExecute('rm', ['-rf', '/']);
402
+
403
+ // Then
404
+ expect(result.success).toBe(false);
405
+ expect(result.blocked).toBe(true);
406
+ expect(result.reason).toContain('rm');
407
+ });
408
+
409
+ it('should reject commands not in allowed list', async () => {
410
+ // Given
411
+ await securityService.initialize();
412
+
413
+ // When
414
+ const result = await securityService.validateAndExecute('wget', ['http://evil.com']);
415
+
416
+ // Then
417
+ expect(result.success).toBe(false);
418
+ expect(result.blocked).toBe(true);
419
+ });
420
+
421
+ it('should sanitize command arguments', async () => {
422
+ // Given
423
+ await securityService.initialize();
424
+
425
+ // When
426
+ await securityService.validateAndExecute('npm', ['install;rm -rf /']);
427
+
428
+ // Then
429
+ expect(mockExecutor.spawn).toHaveBeenCalledWith(
430
+ 'npm',
431
+ ['installrm -rf /'], // Semicolon removed
432
+ expect.any(Object)
433
+ );
434
+ });
435
+
436
+ it('should audit command execution', async () => {
437
+ // Given
438
+ await securityService.initialize();
439
+
440
+ // When
441
+ await securityService.validateAndExecute('npm', ['install']);
442
+
443
+ // Then
444
+ expect(mockAuditLogger.log).toHaveBeenCalledWith(
445
+ expect.objectContaining({
446
+ type: 'security',
447
+ action: 'command_executed',
448
+ })
449
+ );
450
+ });
451
+
452
+ it('should audit blocked commands', async () => {
453
+ // Given
454
+ await securityService.initialize();
455
+
456
+ // When
457
+ await securityService.validateAndExecute('rm', ['-rf']);
458
+
459
+ // Then
460
+ expect(mockAuditLogger.log).toHaveBeenCalledWith(
461
+ expect.objectContaining({
462
+ type: 'security',
463
+ action: 'command_blocked',
464
+ success: false,
465
+ })
466
+ );
467
+ });
468
+ });
469
+
470
+ describe('Path Validation Security Flow', () => {
471
+ it('should validate safe paths', async () => {
472
+ // Given
473
+ const path = './v3/src/security/index.ts';
474
+
475
+ // When
476
+ const result = securityService.validatePath(path);
477
+
478
+ // Then
479
+ expect(result.valid).toBe(true);
480
+ expect(result.errors).toHaveLength(0);
481
+ });
482
+
483
+ it('should block directory traversal', async () => {
484
+ // Given
485
+ const path = '../../../etc/passwd';
486
+
487
+ // When
488
+ const result = securityService.validatePath(path);
489
+
490
+ // Then
491
+ expect(result.valid).toBe(false);
492
+ expect(result.errors.some((e) => e.includes('../'))).toBe(true);
493
+ });
494
+
495
+ it('should block absolute system paths', async () => {
496
+ // Given
497
+ const path = '/etc/shadow';
498
+
499
+ // When
500
+ const result = securityService.validatePath(path);
501
+
502
+ // Then
503
+ expect(result.valid).toBe(false);
504
+ });
505
+
506
+ it('should block null byte injection', async () => {
507
+ // Given
508
+ const path = 'file.txt\0.exe';
509
+
510
+ // When
511
+ const result = securityService.validatePath(path);
512
+
513
+ // Then
514
+ expect(result.valid).toBe(false);
515
+ expect(result.errors.some((e) => e.includes('null byte'))).toBe(true);
516
+ });
517
+
518
+ it('should sanitize and return safe path', async () => {
519
+ // Given
520
+ const path = './safe/path/file.ts';
521
+
522
+ // When
523
+ const result = securityService.validatePath(path);
524
+
525
+ // Then
526
+ expect(result.valid).toBe(true);
527
+ expect(result.sanitized).toBe('./safe/path/file.ts');
528
+ });
529
+ });
530
+
531
+ describe('Security Service Lifecycle', () => {
532
+ it('should initialize and audit', async () => {
533
+ // When
534
+ await securityService.initialize();
535
+
536
+ // Then
537
+ expect(mockAuditLogger.log).toHaveBeenCalledWith(
538
+ expect.objectContaining({
539
+ type: 'security',
540
+ action: 'initialize',
541
+ })
542
+ );
543
+ });
544
+
545
+ it('should shutdown and audit', async () => {
546
+ // Given
547
+ await securityService.initialize();
548
+
549
+ // When
550
+ await securityService.shutdown();
551
+
552
+ // Then
553
+ expect(mockAuditLogger.log).toHaveBeenCalledWith(
554
+ expect.objectContaining({
555
+ type: 'security',
556
+ action: 'shutdown',
557
+ })
558
+ );
559
+ });
560
+ });
561
+
562
+ describe('CVE Prevention Integration', () => {
563
+ it('should prevent CVE-1 (directory traversal) end-to-end', async () => {
564
+ // Given
565
+ const attacks = [
566
+ '../../../etc/passwd',
567
+ '..\\..\\..\\Windows\\System32',
568
+ '....//....//etc/passwd',
569
+ ];
570
+
571
+ // When/Then
572
+ for (const attack of attacks) {
573
+ const result = securityService.validatePath(attack);
574
+ expect(result.valid).toBe(false);
575
+ }
576
+ });
577
+
578
+ it('should prevent CVE-2 (absolute path injection) end-to-end', async () => {
579
+ // Given
580
+ const attacks = ['/etc/passwd', '/var/log/auth.log', '/tmp/malicious'];
581
+
582
+ // When/Then
583
+ for (const attack of attacks) {
584
+ const result = securityService.validatePath(attack);
585
+ expect(result.valid).toBe(false);
586
+ }
587
+ });
588
+
589
+ it('should prevent CVE-3 (command injection) end-to-end', async () => {
590
+ // Given
591
+ await securityService.initialize();
592
+
593
+ // When
594
+ const result = await securityService.validateAndExecute('npm', [
595
+ 'install; rm -rf /',
596
+ ]);
597
+
598
+ // Then - command should execute but with sanitized args
599
+ expect(mockExecutor.spawn).toHaveBeenCalledWith(
600
+ 'npm',
601
+ ['install rm -rf /'], // Semicolon removed
602
+ expect.any(Object)
603
+ );
604
+ });
605
+ });
606
+ });