@claude-flow/security 3.0.0-alpha.1 → 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 (115) hide show
  1. package/README.md +292 -234
  2. package/__tests__/acceptance/security-compliance.test.ts +674 -674
  3. package/__tests__/authorization-propagator.test.ts +155 -0
  4. package/__tests__/credential-generator.test.ts +310 -310
  5. package/__tests__/fixtures/configurations.ts +425 -419
  6. package/__tests__/fixtures/index.ts +21 -21
  7. package/__tests__/helpers/create-mock.ts +469 -469
  8. package/__tests__/helpers/index.ts +32 -32
  9. package/__tests__/input-validator.test.ts +382 -381
  10. package/__tests__/integration/security-flow.test.ts +606 -606
  11. package/__tests__/password-hasher.test.ts +239 -239
  12. package/__tests__/path-validator.test.ts +302 -302
  13. package/__tests__/plugin-integrity-verifier.test.ts +199 -0
  14. package/__tests__/safe-executor.test.ts +292 -292
  15. package/__tests__/token-generator.test.ts +371 -371
  16. package/__tests__/tool-output-guardrail.test.ts +256 -0
  17. package/__tests__/unit/credential-generator.test.ts +182 -182
  18. package/__tests__/unit/password-hasher.test.ts +359 -359
  19. package/__tests__/unit/path-validator.test.ts +509 -509
  20. package/__tests__/unit/safe-executor.test.ts +667 -667
  21. package/__tests__/unit/token-generator.test.ts +310 -310
  22. package/dist/authorization/propagator.d.ts +148 -0
  23. package/dist/authorization/propagator.d.ts.map +1 -0
  24. package/dist/authorization/propagator.js +182 -0
  25. package/dist/authorization/propagator.js.map +1 -0
  26. package/dist/credential-generator.js +9 -9
  27. package/dist/domain/services/security-domain-service.js +1 -1
  28. package/dist/domain/services/security-domain-service.js.map +1 -1
  29. package/dist/index.d.ts +4 -1
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/index.js +11 -1
  32. package/dist/index.js.map +1 -1
  33. package/dist/password-hasher.d.ts.map +1 -1
  34. package/dist/password-hasher.js +8 -1
  35. package/dist/password-hasher.js.map +1 -1
  36. package/dist/plugins/integrity-verifier.d.ts +125 -0
  37. package/dist/plugins/integrity-verifier.d.ts.map +1 -0
  38. package/dist/plugins/integrity-verifier.js +186 -0
  39. package/dist/plugins/integrity-verifier.js.map +1 -0
  40. package/dist/safe-executor.d.ts +7 -0
  41. package/dist/safe-executor.d.ts.map +1 -1
  42. package/dist/safe-executor.js +21 -1
  43. package/dist/safe-executor.js.map +1 -1
  44. package/dist/tool-output-guardrail.d.ts +126 -0
  45. package/dist/tool-output-guardrail.d.ts.map +1 -0
  46. package/dist/tool-output-guardrail.js +257 -0
  47. package/dist/tool-output-guardrail.js.map +1 -0
  48. package/package.json +28 -28
  49. package/src/CVE-REMEDIATION.ts +251 -251
  50. package/src/application/index.ts +10 -10
  51. package/src/application/services/security-application-service.ts +193 -193
  52. package/src/authorization/propagator.ts +279 -0
  53. package/src/credential-generator.ts +368 -368
  54. package/src/domain/entities/security-context.ts +173 -173
  55. package/src/domain/index.ts +17 -17
  56. package/src/domain/services/security-domain-service.ts +296 -296
  57. package/src/index.ts +314 -271
  58. package/src/input-validator.ts +466 -466
  59. package/src/password-hasher.ts +277 -270
  60. package/src/path-validator.ts +525 -525
  61. package/src/plugins/integrity-verifier.ts +265 -0
  62. package/src/safe-executor.ts +546 -525
  63. package/src/token-generator.ts +463 -463
  64. package/src/tool-output-guardrail.ts +360 -0
  65. package/tmp.json +0 -0
  66. package/tsconfig.json +9 -9
  67. package/tsconfig.tsbuildinfo +1 -1
  68. package/vitest.config.ts +20 -0
  69. package/.agentic-flow/intelligence.json +0 -16
  70. package/__tests__/coverage/base.css +0 -224
  71. package/__tests__/coverage/block-navigation.js +0 -87
  72. package/__tests__/coverage/coverage-final.json +0 -11
  73. package/__tests__/coverage/favicon.png +0 -0
  74. package/__tests__/coverage/index.html +0 -161
  75. package/__tests__/coverage/lcov-report/base.css +0 -224
  76. package/__tests__/coverage/lcov-report/block-navigation.js +0 -87
  77. package/__tests__/coverage/lcov-report/favicon.png +0 -0
  78. package/__tests__/coverage/lcov-report/index.html +0 -161
  79. package/__tests__/coverage/lcov-report/prettify.css +0 -1
  80. package/__tests__/coverage/lcov-report/prettify.js +0 -2
  81. package/__tests__/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  82. package/__tests__/coverage/lcov-report/sorter.js +0 -210
  83. package/__tests__/coverage/lcov-report/src/CVE-REMEDIATION.ts.html +0 -838
  84. package/__tests__/coverage/lcov-report/src/application/services/index.html +0 -116
  85. package/__tests__/coverage/lcov-report/src/application/services/security-application-service.ts.html +0 -664
  86. package/__tests__/coverage/lcov-report/src/credential-generator.ts.html +0 -1189
  87. package/__tests__/coverage/lcov-report/src/domain/entities/index.html +0 -116
  88. package/__tests__/coverage/lcov-report/src/domain/entities/security-context.ts.html +0 -604
  89. package/__tests__/coverage/lcov-report/src/domain/services/index.html +0 -116
  90. package/__tests__/coverage/lcov-report/src/domain/services/security-domain-service.ts.html +0 -973
  91. package/__tests__/coverage/lcov-report/src/index.html +0 -206
  92. package/__tests__/coverage/lcov-report/src/input-validator.ts.html +0 -1483
  93. package/__tests__/coverage/lcov-report/src/password-hasher.ts.html +0 -895
  94. package/__tests__/coverage/lcov-report/src/path-validator.ts.html +0 -1660
  95. package/__tests__/coverage/lcov-report/src/safe-executor.ts.html +0 -1660
  96. package/__tests__/coverage/lcov-report/src/token-generator.ts.html +0 -1474
  97. package/__tests__/coverage/lcov.info +0 -3650
  98. package/__tests__/coverage/prettify.css +0 -1
  99. package/__tests__/coverage/prettify.js +0 -2
  100. package/__tests__/coverage/sort-arrow-sprite.png +0 -0
  101. package/__tests__/coverage/sorter.js +0 -210
  102. package/__tests__/coverage/src/CVE-REMEDIATION.ts.html +0 -838
  103. package/__tests__/coverage/src/application/services/index.html +0 -116
  104. package/__tests__/coverage/src/application/services/security-application-service.ts.html +0 -664
  105. package/__tests__/coverage/src/credential-generator.ts.html +0 -1189
  106. package/__tests__/coverage/src/domain/entities/index.html +0 -116
  107. package/__tests__/coverage/src/domain/entities/security-context.ts.html +0 -604
  108. package/__tests__/coverage/src/domain/services/index.html +0 -116
  109. package/__tests__/coverage/src/domain/services/security-domain-service.ts.html +0 -973
  110. package/__tests__/coverage/src/index.html +0 -206
  111. package/__tests__/coverage/src/input-validator.ts.html +0 -1483
  112. package/__tests__/coverage/src/password-hasher.ts.html +0 -895
  113. package/__tests__/coverage/src/path-validator.ts.html +0 -1660
  114. package/__tests__/coverage/src/safe-executor.ts.html +0 -1660
  115. package/__tests__/coverage/src/token-generator.ts.html +0 -1474
@@ -1,469 +1,469 @@
1
- /**
2
- * Mock Factory Utilities for Security Module Testing
3
- *
4
- * Provides type-safe mock creation utilities for testing security components.
5
- * Uses vitest's mocking capabilities with full TypeScript support.
6
- *
7
- * @module v3/security/__tests__/helpers/create-mock
8
- */
9
-
10
- import { vi, type MockInstance } from 'vitest';
11
-
12
- /**
13
- * Type representing a mocked interface where all methods are vi.fn() mocks
14
- */
15
- export type MockedInterface<T> = {
16
- [K in keyof T]: T[K] extends (...args: infer A) => infer R
17
- ? MockInstance<(...args: A) => R>
18
- : T[K];
19
- };
20
-
21
- /**
22
- * Type for a deeply mocked interface (including nested objects)
23
- */
24
- export type DeepMockedInterface<T> = {
25
- [K in keyof T]: T[K] extends (...args: infer A) => infer R
26
- ? MockInstance<(...args: A) => R>
27
- : T[K] extends object
28
- ? DeepMockedInterface<T[K]>
29
- : T[K];
30
- };
31
-
32
- /**
33
- * Creates a type-safe mock object for an interface.
34
- * All methods are replaced with vi.fn() mocks.
35
- *
36
- * @example
37
- * ```typescript
38
- * interface IService {
39
- * getData(): Promise<string>;
40
- * process(input: number): boolean;
41
- * }
42
- *
43
- * const mock = createMock<IService>();
44
- * mock.getData.mockResolvedValue('test');
45
- * mock.process.mockReturnValue(true);
46
- * ```
47
- *
48
- * @returns A proxy-based mock object
49
- */
50
- export function createMock<T extends object>(): MockedInterface<T> {
51
- const cache = new Map<string | symbol, MockInstance>();
52
-
53
- return new Proxy({} as MockedInterface<T>, {
54
- get(target, prop) {
55
- if (!cache.has(prop)) {
56
- cache.set(prop, vi.fn());
57
- }
58
- return cache.get(prop);
59
- },
60
- });
61
- }
62
-
63
- /**
64
- * Creates a partial mock with some real implementations.
65
- *
66
- * @param overrides - Partial implementation to use
67
- * @returns A mock object with the provided overrides
68
- *
69
- * @example
70
- * ```typescript
71
- * const mock = createPartialMock<IService>({
72
- * getData: vi.fn().mockResolvedValue('real'),
73
- * });
74
- * ```
75
- */
76
- export function createPartialMock<T extends object>(
77
- overrides: Partial<MockedInterface<T>> = {}
78
- ): MockedInterface<T> {
79
- const baseMock = createMock<T>();
80
- return { ...baseMock, ...overrides };
81
- }
82
-
83
- /**
84
- * Creates a spy on an existing object's methods.
85
- *
86
- * @param obj - The object to spy on
87
- * @param methods - Array of method names to spy on
88
- * @returns The object with spied methods
89
- *
90
- * @example
91
- * ```typescript
92
- * const service = new RealService();
93
- * const spied = createSpy(service, ['getData', 'process']);
94
- * ```
95
- */
96
- export function createSpy<T extends object, K extends keyof T>(
97
- obj: T,
98
- methods: K[]
99
- ): T & { [P in K]: MockInstance } {
100
- const spiedObj = { ...obj } as T & { [P in K]: MockInstance };
101
-
102
- for (const method of methods) {
103
- if (typeof obj[method] === 'function') {
104
- (spiedObj as Record<K, MockInstance>)[method] = vi.fn(
105
- (obj[method] as Function).bind(obj)
106
- );
107
- }
108
- }
109
-
110
- return spiedObj;
111
- }
112
-
113
- /**
114
- * Mock factory for PasswordHasher
115
- */
116
- export interface MockPasswordHasher {
117
- hash: MockInstance<(password: string) => Promise<string>>;
118
- verify: MockInstance<(password: string, hash: string) => Promise<boolean>>;
119
- validate: MockInstance<(password: string) => { isValid: boolean; errors: string[] }>;
120
- needsRehash: MockInstance<(hash: string) => boolean>;
121
- getConfig: MockInstance<() => Record<string, unknown>>;
122
- }
123
-
124
- export function createMockPasswordHasher(
125
- overrides: Partial<MockPasswordHasher> = {}
126
- ): MockPasswordHasher {
127
- return {
128
- hash: vi.fn().mockResolvedValue('$2b$12$mockedHashValue'),
129
- verify: vi.fn().mockResolvedValue(true),
130
- validate: vi.fn().mockReturnValue({ isValid: true, errors: [] }),
131
- needsRehash: vi.fn().mockReturnValue(false),
132
- getConfig: vi.fn().mockReturnValue({ rounds: 12 }),
133
- ...overrides,
134
- };
135
- }
136
-
137
- /**
138
- * Mock factory for CredentialGenerator
139
- */
140
- export interface MockCredentialGenerator {
141
- generatePassword: MockInstance<(length?: number) => string>;
142
- generateApiKey: MockInstance<(prefix?: string) => { key: string; prefix: string; keyId: string; createdAt: Date }>;
143
- generateSecret: MockInstance<(length?: number) => string>;
144
- generateEncryptionKey: MockInstance<() => string>;
145
- generateInstallationCredentials: MockInstance<(expirationDays?: number) => {
146
- adminPassword: string;
147
- servicePassword: string;
148
- jwtSecret: string;
149
- sessionSecret: string;
150
- encryptionKey: string;
151
- generatedAt: Date;
152
- expiresAt?: Date;
153
- }>;
154
- generateSessionToken: MockInstance<() => string>;
155
- generateCsrfToken: MockInstance<() => string>;
156
- generateNonce: MockInstance<() => string>;
157
- }
158
-
159
- export function createMockCredentialGenerator(
160
- overrides: Partial<MockCredentialGenerator> = {}
161
- ): MockCredentialGenerator {
162
- const now = new Date();
163
- return {
164
- generatePassword: vi.fn().mockReturnValue('MockedSecureP@ssword123!'),
165
- generateApiKey: vi.fn().mockReturnValue({
166
- key: 'cf_mockedApiKey12345',
167
- prefix: 'cf_',
168
- keyId: '550e8400-e29b-41d4-a716-446655440000',
169
- createdAt: now,
170
- }),
171
- generateSecret: vi.fn().mockReturnValue('0'.repeat(64)),
172
- generateEncryptionKey: vi.fn().mockReturnValue('a'.repeat(64)),
173
- generateInstallationCredentials: vi.fn().mockReturnValue({
174
- adminPassword: 'MockedAdminP@ss123!',
175
- servicePassword: 'MockedServiceP@ss123!',
176
- jwtSecret: '0'.repeat(64),
177
- sessionSecret: '1'.repeat(64),
178
- encryptionKey: 'a'.repeat(64),
179
- generatedAt: now,
180
- }),
181
- generateSessionToken: vi.fn().mockReturnValue('mockedSessionToken'),
182
- generateCsrfToken: vi.fn().mockReturnValue('mockedCsrfToken'),
183
- generateNonce: vi.fn().mockReturnValue('0'.repeat(32)),
184
- ...overrides,
185
- };
186
- }
187
-
188
- /**
189
- * Mock factory for PathValidator
190
- */
191
- export interface MockPathValidator {
192
- validate: MockInstance<(path: string) => Promise<{
193
- isValid: boolean;
194
- resolvedPath: string;
195
- relativePath: string;
196
- matchedPrefix: string;
197
- errors: string[];
198
- }>>;
199
- validateSync: MockInstance<(path: string) => {
200
- isValid: boolean;
201
- resolvedPath: string;
202
- relativePath: string;
203
- matchedPrefix: string;
204
- errors: string[];
205
- }>;
206
- validateOrThrow: MockInstance<(path: string) => Promise<string>>;
207
- securePath: MockInstance<(prefix: string, ...segments: string[]) => Promise<string>>;
208
- isWithinAllowed: MockInstance<(path: string) => boolean>;
209
- getAllowedPrefixes: MockInstance<() => readonly string[]>;
210
- }
211
-
212
- export function createMockPathValidator(
213
- overrides: Partial<MockPathValidator> = {}
214
- ): MockPathValidator {
215
- return {
216
- validate: vi.fn().mockResolvedValue({
217
- isValid: true,
218
- resolvedPath: '/workspaces/project/src/file.ts',
219
- relativePath: 'src/file.ts',
220
- matchedPrefix: '/workspaces/project',
221
- errors: [],
222
- }),
223
- validateSync: vi.fn().mockReturnValue({
224
- isValid: true,
225
- resolvedPath: '/workspaces/project/src/file.ts',
226
- relativePath: 'src/file.ts',
227
- matchedPrefix: '/workspaces/project',
228
- errors: [],
229
- }),
230
- validateOrThrow: vi.fn().mockResolvedValue('/workspaces/project/src/file.ts'),
231
- securePath: vi.fn().mockResolvedValue('/workspaces/project/src/file.ts'),
232
- isWithinAllowed: vi.fn().mockReturnValue(true),
233
- getAllowedPrefixes: vi.fn().mockReturnValue(['/workspaces/project']),
234
- ...overrides,
235
- };
236
- }
237
-
238
- /**
239
- * Mock factory for SafeExecutor
240
- */
241
- export interface MockSafeExecutor {
242
- execute: MockInstance<(command: string, args?: string[]) => Promise<{
243
- stdout: string;
244
- stderr: string;
245
- exitCode: number;
246
- command: string;
247
- args: string[];
248
- duration: number;
249
- }>>;
250
- executeStreaming: MockInstance<(command: string, args?: string[]) => {
251
- process: unknown;
252
- stdout: unknown;
253
- stderr: unknown;
254
- promise: Promise<unknown>;
255
- }>;
256
- sanitizeArgument: MockInstance<(arg: string) => string>;
257
- isCommandAllowed: MockInstance<(command: string) => boolean>;
258
- allowCommand: MockInstance<(command: string) => void>;
259
- getAllowedCommands: MockInstance<() => readonly string[]>;
260
- }
261
-
262
- export function createMockSafeExecutor(
263
- overrides: Partial<MockSafeExecutor> = {}
264
- ): MockSafeExecutor {
265
- return {
266
- execute: vi.fn().mockResolvedValue({
267
- stdout: 'success',
268
- stderr: '',
269
- exitCode: 0,
270
- command: 'echo',
271
- args: ['hello'],
272
- duration: 10,
273
- }),
274
- executeStreaming: vi.fn().mockReturnValue({
275
- process: {},
276
- stdout: null,
277
- stderr: null,
278
- promise: Promise.resolve({
279
- stdout: 'success',
280
- stderr: '',
281
- exitCode: 0,
282
- command: 'echo',
283
- args: ['hello'],
284
- duration: 10,
285
- }),
286
- }),
287
- sanitizeArgument: vi.fn().mockImplementation((arg: string) => arg.replace(/[;&|]/g, '')),
288
- isCommandAllowed: vi.fn().mockReturnValue(true),
289
- allowCommand: vi.fn(),
290
- getAllowedCommands: vi.fn().mockReturnValue(['echo', 'git', 'npm', 'node']),
291
- ...overrides,
292
- };
293
- }
294
-
295
- /**
296
- * Mock factory for TokenGenerator
297
- */
298
- export interface MockTokenGenerator {
299
- generate: MockInstance<(length?: number) => string>;
300
- generateWithExpiration: MockInstance<(expirationSeconds?: number, metadata?: Record<string, unknown>) => {
301
- value: string;
302
- createdAt: Date;
303
- expiresAt: Date;
304
- metadata?: Record<string, unknown>;
305
- }>;
306
- generateSessionToken: MockInstance<() => { value: string; createdAt: Date; expiresAt: Date }>;
307
- generateCsrfToken: MockInstance<() => { value: string; createdAt: Date; expiresAt: Date }>;
308
- generateApiToken: MockInstance<(prefix?: string) => { value: string; createdAt: Date; expiresAt: Date }>;
309
- generateVerificationCode: MockInstance<(length?: number, expirationMinutes?: number, maxAttempts?: number) => {
310
- code: string;
311
- createdAt: Date;
312
- expiresAt: Date;
313
- attempts: number;
314
- maxAttempts: number;
315
- }>;
316
- generateSignedToken: MockInstance<(payload: Record<string, unknown>, expirationSeconds?: number) => {
317
- token: string;
318
- signature: string;
319
- combined: string;
320
- createdAt: Date;
321
- expiresAt: Date;
322
- }>;
323
- verifySignedToken: MockInstance<(combined: string) => Record<string, unknown> | null>;
324
- generateTokenPair: MockInstance<() => {
325
- accessToken: { value: string; createdAt: Date; expiresAt: Date };
326
- refreshToken: { value: string; createdAt: Date; expiresAt: Date };
327
- }>;
328
- isExpired: MockInstance<(token: { expiresAt: Date }) => boolean>;
329
- compare: MockInstance<(a: string, b: string) => boolean>;
330
- }
331
-
332
- export function createMockTokenGenerator(
333
- overrides: Partial<MockTokenGenerator> = {}
334
- ): MockTokenGenerator {
335
- const now = new Date();
336
- const expires = new Date(now.getTime() + 3600000);
337
-
338
- return {
339
- generate: vi.fn().mockReturnValue('mockedToken12345'),
340
- generateWithExpiration: vi.fn().mockReturnValue({
341
- value: 'mockedTokenWithExpiration',
342
- createdAt: now,
343
- expiresAt: expires,
344
- }),
345
- generateSessionToken: vi.fn().mockReturnValue({
346
- value: 'mockedSessionToken',
347
- createdAt: now,
348
- expiresAt: expires,
349
- }),
350
- generateCsrfToken: vi.fn().mockReturnValue({
351
- value: 'mockedCsrfToken',
352
- createdAt: now,
353
- expiresAt: expires,
354
- }),
355
- generateApiToken: vi.fn().mockReturnValue({
356
- value: 'cf_mockedApiToken',
357
- createdAt: now,
358
- expiresAt: expires,
359
- }),
360
- generateVerificationCode: vi.fn().mockReturnValue({
361
- code: '123456',
362
- createdAt: now,
363
- expiresAt: expires,
364
- attempts: 0,
365
- maxAttempts: 3,
366
- }),
367
- generateSignedToken: vi.fn().mockReturnValue({
368
- token: 'mockedToken',
369
- signature: 'mockedSignature',
370
- combined: 'mockedToken.mockedSignature',
371
- createdAt: now,
372
- expiresAt: expires,
373
- }),
374
- verifySignedToken: vi.fn().mockReturnValue({ userId: '123' }),
375
- generateTokenPair: vi.fn().mockReturnValue({
376
- accessToken: { value: 'accessToken', createdAt: now, expiresAt: expires },
377
- refreshToken: { value: 'refreshToken', createdAt: now, expiresAt: new Date(now.getTime() + 604800000) },
378
- }),
379
- isExpired: vi.fn().mockReturnValue(false),
380
- compare: vi.fn().mockImplementation((a: string, b: string) => a === b),
381
- ...overrides,
382
- };
383
- }
384
-
385
- /**
386
- * Mock factory for InputValidator
387
- */
388
- export interface MockInputValidator {
389
- validateEmail: MockInstance<(email: string) => string>;
390
- validatePassword: MockInstance<(password: string) => string>;
391
- validateIdentifier: MockInstance<(id: string) => string>;
392
- validatePath: MockInstance<(path: string) => string>;
393
- validateCommandArg: MockInstance<(arg: string) => string>;
394
- validateLoginRequest: MockInstance<(data: unknown) => { email: string; password: string; mfaCode?: string }>;
395
- validateCreateUser: MockInstance<(data: unknown) => { email: string; password: string; role: string }>;
396
- validateTaskInput: MockInstance<(data: unknown) => { taskId: string; content: string; agentType: string }>;
397
- }
398
-
399
- export function createMockInputValidator(
400
- overrides: Partial<MockInputValidator> = {}
401
- ): MockInputValidator {
402
- return {
403
- validateEmail: vi.fn().mockImplementation((email: string) => email.toLowerCase()),
404
- validatePassword: vi.fn().mockImplementation((password: string) => password),
405
- validateIdentifier: vi.fn().mockImplementation((id: string) => id),
406
- validatePath: vi.fn().mockImplementation((path: string) => path),
407
- validateCommandArg: vi.fn().mockImplementation((arg: string) => arg),
408
- validateLoginRequest: vi.fn().mockImplementation((data: unknown) => data as { email: string; password: string }),
409
- validateCreateUser: vi.fn().mockImplementation((data: unknown) => data as { email: string; password: string; role: string }),
410
- validateTaskInput: vi.fn().mockImplementation((data: unknown) => data as { taskId: string; content: string; agentType: string }),
411
- ...overrides,
412
- };
413
- }
414
-
415
- /**
416
- * Resets all mocks in a mock object
417
- */
418
- export function resetMock<T extends object>(mock: MockedInterface<T>): void {
419
- for (const key of Object.keys(mock)) {
420
- const value = (mock as Record<string, unknown>)[key];
421
- if (typeof value === 'function' && 'mockReset' in value) {
422
- (value as MockInstance).mockReset();
423
- }
424
- }
425
- }
426
-
427
- /**
428
- * Clears all mocks in a mock object (keeps implementation)
429
- */
430
- export function clearMock<T extends object>(mock: MockedInterface<T>): void {
431
- for (const key of Object.keys(mock)) {
432
- const value = (mock as Record<string, unknown>)[key];
433
- if (typeof value === 'function' && 'mockClear' in value) {
434
- (value as MockInstance).mockClear();
435
- }
436
- }
437
- }
438
-
439
- /**
440
- * Restores all mocks in a mock object
441
- */
442
- export function restoreMock<T extends object>(mock: MockedInterface<T>): void {
443
- for (const key of Object.keys(mock)) {
444
- const value = (mock as Record<string, unknown>)[key];
445
- if (typeof value === 'function' && 'mockRestore' in value) {
446
- (value as MockInstance).mockRestore();
447
- }
448
- }
449
- }
450
-
451
- /**
452
- * Helper to verify mock was called with specific arguments
453
- */
454
- export function expectCalledWith<T extends MockInstance>(
455
- mock: T,
456
- ...args: Parameters<T extends MockInstance<infer F> ? F : never>
457
- ): void {
458
- expect(mock).toHaveBeenCalledWith(...args);
459
- }
460
-
461
- /**
462
- * Helper to verify mock was called exactly N times
463
- */
464
- export function expectCalledTimes<T extends MockInstance>(
465
- mock: T,
466
- times: number
467
- ): void {
468
- expect(mock).toHaveBeenCalledTimes(times);
469
- }
1
+ /**
2
+ * Mock Factory Utilities for Security Module Testing
3
+ *
4
+ * Provides type-safe mock creation utilities for testing security components.
5
+ * Uses vitest's mocking capabilities with full TypeScript support.
6
+ *
7
+ * @module v3/security/__tests__/helpers/create-mock
8
+ */
9
+
10
+ import { vi, type MockInstance } from 'vitest';
11
+
12
+ /**
13
+ * Type representing a mocked interface where all methods are vi.fn() mocks
14
+ */
15
+ export type MockedInterface<T> = {
16
+ [K in keyof T]: T[K] extends (...args: infer A) => infer R
17
+ ? MockInstance<(...args: A) => R>
18
+ : T[K];
19
+ };
20
+
21
+ /**
22
+ * Type for a deeply mocked interface (including nested objects)
23
+ */
24
+ export type DeepMockedInterface<T> = {
25
+ [K in keyof T]: T[K] extends (...args: infer A) => infer R
26
+ ? MockInstance<(...args: A) => R>
27
+ : T[K] extends object
28
+ ? DeepMockedInterface<T[K]>
29
+ : T[K];
30
+ };
31
+
32
+ /**
33
+ * Creates a type-safe mock object for an interface.
34
+ * All methods are replaced with vi.fn() mocks.
35
+ *
36
+ * @example
37
+ * ```typescript
38
+ * interface IService {
39
+ * getData(): Promise<string>;
40
+ * process(input: number): boolean;
41
+ * }
42
+ *
43
+ * const mock = createMock<IService>();
44
+ * mock.getData.mockResolvedValue('test');
45
+ * mock.process.mockReturnValue(true);
46
+ * ```
47
+ *
48
+ * @returns A proxy-based mock object
49
+ */
50
+ export function createMock<T extends object>(): MockedInterface<T> {
51
+ const cache = new Map<string | symbol, MockInstance>();
52
+
53
+ return new Proxy({} as MockedInterface<T>, {
54
+ get(target, prop) {
55
+ if (!cache.has(prop)) {
56
+ cache.set(prop, vi.fn());
57
+ }
58
+ return cache.get(prop);
59
+ },
60
+ });
61
+ }
62
+
63
+ /**
64
+ * Creates a partial mock with some real implementations.
65
+ *
66
+ * @param overrides - Partial implementation to use
67
+ * @returns A mock object with the provided overrides
68
+ *
69
+ * @example
70
+ * ```typescript
71
+ * const mock = createPartialMock<IService>({
72
+ * getData: vi.fn().mockResolvedValue('real'),
73
+ * });
74
+ * ```
75
+ */
76
+ export function createPartialMock<T extends object>(
77
+ overrides: Partial<MockedInterface<T>> = {}
78
+ ): MockedInterface<T> {
79
+ const baseMock = createMock<T>();
80
+ return { ...baseMock, ...overrides };
81
+ }
82
+
83
+ /**
84
+ * Creates a spy on an existing object's methods.
85
+ *
86
+ * @param obj - The object to spy on
87
+ * @param methods - Array of method names to spy on
88
+ * @returns The object with spied methods
89
+ *
90
+ * @example
91
+ * ```typescript
92
+ * const service = new RealService();
93
+ * const spied = createSpy(service, ['getData', 'process']);
94
+ * ```
95
+ */
96
+ export function createSpy<T extends object, K extends keyof T>(
97
+ obj: T,
98
+ methods: K[]
99
+ ): T & { [P in K]: MockInstance } {
100
+ const spiedObj = { ...obj } as T & { [P in K]: MockInstance };
101
+
102
+ for (const method of methods) {
103
+ if (typeof obj[method] === 'function') {
104
+ (spiedObj as Record<K, MockInstance>)[method] = vi.fn(
105
+ (obj[method] as Function).bind(obj)
106
+ );
107
+ }
108
+ }
109
+
110
+ return spiedObj;
111
+ }
112
+
113
+ /**
114
+ * Mock factory for PasswordHasher
115
+ */
116
+ export interface MockPasswordHasher {
117
+ hash: MockInstance<(password: string) => Promise<string>>;
118
+ verify: MockInstance<(password: string, hash: string) => Promise<boolean>>;
119
+ validate: MockInstance<(password: string) => { isValid: boolean; errors: string[] }>;
120
+ needsRehash: MockInstance<(hash: string) => boolean>;
121
+ getConfig: MockInstance<() => Record<string, unknown>>;
122
+ }
123
+
124
+ export function createMockPasswordHasher(
125
+ overrides: Partial<MockPasswordHasher> = {}
126
+ ): MockPasswordHasher {
127
+ return {
128
+ hash: vi.fn().mockResolvedValue('$2b$12$mockedHashValue'),
129
+ verify: vi.fn().mockResolvedValue(true),
130
+ validate: vi.fn().mockReturnValue({ isValid: true, errors: [] }),
131
+ needsRehash: vi.fn().mockReturnValue(false),
132
+ getConfig: vi.fn().mockReturnValue({ rounds: 12 }),
133
+ ...overrides,
134
+ };
135
+ }
136
+
137
+ /**
138
+ * Mock factory for CredentialGenerator
139
+ */
140
+ export interface MockCredentialGenerator {
141
+ generatePassword: MockInstance<(length?: number) => string>;
142
+ generateApiKey: MockInstance<(prefix?: string) => { key: string; prefix: string; keyId: string; createdAt: Date }>;
143
+ generateSecret: MockInstance<(length?: number) => string>;
144
+ generateEncryptionKey: MockInstance<() => string>;
145
+ generateInstallationCredentials: MockInstance<(expirationDays?: number) => {
146
+ adminPassword: string;
147
+ servicePassword: string;
148
+ jwtSecret: string;
149
+ sessionSecret: string;
150
+ encryptionKey: string;
151
+ generatedAt: Date;
152
+ expiresAt?: Date;
153
+ }>;
154
+ generateSessionToken: MockInstance<() => string>;
155
+ generateCsrfToken: MockInstance<() => string>;
156
+ generateNonce: MockInstance<() => string>;
157
+ }
158
+
159
+ export function createMockCredentialGenerator(
160
+ overrides: Partial<MockCredentialGenerator> = {}
161
+ ): MockCredentialGenerator {
162
+ const now = new Date();
163
+ return {
164
+ generatePassword: vi.fn().mockReturnValue('MockedSecureP@ssword123!'),
165
+ generateApiKey: vi.fn().mockReturnValue({
166
+ key: 'cf_mockedApiKey12345',
167
+ prefix: 'cf_',
168
+ keyId: '550e8400-e29b-41d4-a716-446655440000',
169
+ createdAt: now,
170
+ }),
171
+ generateSecret: vi.fn().mockReturnValue('0'.repeat(64)),
172
+ generateEncryptionKey: vi.fn().mockReturnValue('a'.repeat(64)),
173
+ generateInstallationCredentials: vi.fn().mockReturnValue({
174
+ adminPassword: 'MockedAdminP@ss123!',
175
+ servicePassword: 'MockedServiceP@ss123!',
176
+ jwtSecret: '0'.repeat(64),
177
+ sessionSecret: '1'.repeat(64),
178
+ encryptionKey: 'a'.repeat(64),
179
+ generatedAt: now,
180
+ }),
181
+ generateSessionToken: vi.fn().mockReturnValue('mockedSessionToken'),
182
+ generateCsrfToken: vi.fn().mockReturnValue('mockedCsrfToken'),
183
+ generateNonce: vi.fn().mockReturnValue('0'.repeat(32)),
184
+ ...overrides,
185
+ };
186
+ }
187
+
188
+ /**
189
+ * Mock factory for PathValidator
190
+ */
191
+ export interface MockPathValidator {
192
+ validate: MockInstance<(path: string) => Promise<{
193
+ isValid: boolean;
194
+ resolvedPath: string;
195
+ relativePath: string;
196
+ matchedPrefix: string;
197
+ errors: string[];
198
+ }>>;
199
+ validateSync: MockInstance<(path: string) => {
200
+ isValid: boolean;
201
+ resolvedPath: string;
202
+ relativePath: string;
203
+ matchedPrefix: string;
204
+ errors: string[];
205
+ }>;
206
+ validateOrThrow: MockInstance<(path: string) => Promise<string>>;
207
+ securePath: MockInstance<(prefix: string, ...segments: string[]) => Promise<string>>;
208
+ isWithinAllowed: MockInstance<(path: string) => boolean>;
209
+ getAllowedPrefixes: MockInstance<() => readonly string[]>;
210
+ }
211
+
212
+ export function createMockPathValidator(
213
+ overrides: Partial<MockPathValidator> = {}
214
+ ): MockPathValidator {
215
+ return {
216
+ validate: vi.fn().mockResolvedValue({
217
+ isValid: true,
218
+ resolvedPath: '/workspaces/project/src/file.ts',
219
+ relativePath: 'src/file.ts',
220
+ matchedPrefix: '/workspaces/project',
221
+ errors: [],
222
+ }),
223
+ validateSync: vi.fn().mockReturnValue({
224
+ isValid: true,
225
+ resolvedPath: '/workspaces/project/src/file.ts',
226
+ relativePath: 'src/file.ts',
227
+ matchedPrefix: '/workspaces/project',
228
+ errors: [],
229
+ }),
230
+ validateOrThrow: vi.fn().mockResolvedValue('/workspaces/project/src/file.ts'),
231
+ securePath: vi.fn().mockResolvedValue('/workspaces/project/src/file.ts'),
232
+ isWithinAllowed: vi.fn().mockReturnValue(true),
233
+ getAllowedPrefixes: vi.fn().mockReturnValue(['/workspaces/project']),
234
+ ...overrides,
235
+ };
236
+ }
237
+
238
+ /**
239
+ * Mock factory for SafeExecutor
240
+ */
241
+ export interface MockSafeExecutor {
242
+ execute: MockInstance<(command: string, args?: string[]) => Promise<{
243
+ stdout: string;
244
+ stderr: string;
245
+ exitCode: number;
246
+ command: string;
247
+ args: string[];
248
+ duration: number;
249
+ }>>;
250
+ executeStreaming: MockInstance<(command: string, args?: string[]) => {
251
+ process: unknown;
252
+ stdout: unknown;
253
+ stderr: unknown;
254
+ promise: Promise<unknown>;
255
+ }>;
256
+ sanitizeArgument: MockInstance<(arg: string) => string>;
257
+ isCommandAllowed: MockInstance<(command: string) => boolean>;
258
+ allowCommand: MockInstance<(command: string) => void>;
259
+ getAllowedCommands: MockInstance<() => readonly string[]>;
260
+ }
261
+
262
+ export function createMockSafeExecutor(
263
+ overrides: Partial<MockSafeExecutor> = {}
264
+ ): MockSafeExecutor {
265
+ return {
266
+ execute: vi.fn().mockResolvedValue({
267
+ stdout: 'success',
268
+ stderr: '',
269
+ exitCode: 0,
270
+ command: 'echo',
271
+ args: ['hello'],
272
+ duration: 10,
273
+ }),
274
+ executeStreaming: vi.fn().mockReturnValue({
275
+ process: {},
276
+ stdout: null,
277
+ stderr: null,
278
+ promise: Promise.resolve({
279
+ stdout: 'success',
280
+ stderr: '',
281
+ exitCode: 0,
282
+ command: 'echo',
283
+ args: ['hello'],
284
+ duration: 10,
285
+ }),
286
+ }),
287
+ sanitizeArgument: vi.fn().mockImplementation((arg: string) => arg.replace(/[;&|]/g, '')),
288
+ isCommandAllowed: vi.fn().mockReturnValue(true),
289
+ allowCommand: vi.fn(),
290
+ getAllowedCommands: vi.fn().mockReturnValue(['echo', 'git', 'npm', 'node']),
291
+ ...overrides,
292
+ };
293
+ }
294
+
295
+ /**
296
+ * Mock factory for TokenGenerator
297
+ */
298
+ export interface MockTokenGenerator {
299
+ generate: MockInstance<(length?: number) => string>;
300
+ generateWithExpiration: MockInstance<(expirationSeconds?: number, metadata?: Record<string, unknown>) => {
301
+ value: string;
302
+ createdAt: Date;
303
+ expiresAt: Date;
304
+ metadata?: Record<string, unknown>;
305
+ }>;
306
+ generateSessionToken: MockInstance<() => { value: string; createdAt: Date; expiresAt: Date }>;
307
+ generateCsrfToken: MockInstance<() => { value: string; createdAt: Date; expiresAt: Date }>;
308
+ generateApiToken: MockInstance<(prefix?: string) => { value: string; createdAt: Date; expiresAt: Date }>;
309
+ generateVerificationCode: MockInstance<(length?: number, expirationMinutes?: number, maxAttempts?: number) => {
310
+ code: string;
311
+ createdAt: Date;
312
+ expiresAt: Date;
313
+ attempts: number;
314
+ maxAttempts: number;
315
+ }>;
316
+ generateSignedToken: MockInstance<(payload: Record<string, unknown>, expirationSeconds?: number) => {
317
+ token: string;
318
+ signature: string;
319
+ combined: string;
320
+ createdAt: Date;
321
+ expiresAt: Date;
322
+ }>;
323
+ verifySignedToken: MockInstance<(combined: string) => Record<string, unknown> | null>;
324
+ generateTokenPair: MockInstance<() => {
325
+ accessToken: { value: string; createdAt: Date; expiresAt: Date };
326
+ refreshToken: { value: string; createdAt: Date; expiresAt: Date };
327
+ }>;
328
+ isExpired: MockInstance<(token: { expiresAt: Date }) => boolean>;
329
+ compare: MockInstance<(a: string, b: string) => boolean>;
330
+ }
331
+
332
+ export function createMockTokenGenerator(
333
+ overrides: Partial<MockTokenGenerator> = {}
334
+ ): MockTokenGenerator {
335
+ const now = new Date();
336
+ const expires = new Date(now.getTime() + 3600000);
337
+
338
+ return {
339
+ generate: vi.fn().mockReturnValue('mockedToken12345'),
340
+ generateWithExpiration: vi.fn().mockReturnValue({
341
+ value: 'mockedTokenWithExpiration',
342
+ createdAt: now,
343
+ expiresAt: expires,
344
+ }),
345
+ generateSessionToken: vi.fn().mockReturnValue({
346
+ value: 'mockedSessionToken',
347
+ createdAt: now,
348
+ expiresAt: expires,
349
+ }),
350
+ generateCsrfToken: vi.fn().mockReturnValue({
351
+ value: 'mockedCsrfToken',
352
+ createdAt: now,
353
+ expiresAt: expires,
354
+ }),
355
+ generateApiToken: vi.fn().mockReturnValue({
356
+ value: 'cf_mockedApiToken',
357
+ createdAt: now,
358
+ expiresAt: expires,
359
+ }),
360
+ generateVerificationCode: vi.fn().mockReturnValue({
361
+ code: '123456',
362
+ createdAt: now,
363
+ expiresAt: expires,
364
+ attempts: 0,
365
+ maxAttempts: 3,
366
+ }),
367
+ generateSignedToken: vi.fn().mockReturnValue({
368
+ token: 'mockedToken',
369
+ signature: 'mockedSignature',
370
+ combined: 'mockedToken.mockedSignature',
371
+ createdAt: now,
372
+ expiresAt: expires,
373
+ }),
374
+ verifySignedToken: vi.fn().mockReturnValue({ userId: '123' }),
375
+ generateTokenPair: vi.fn().mockReturnValue({
376
+ accessToken: { value: 'accessToken', createdAt: now, expiresAt: expires },
377
+ refreshToken: { value: 'refreshToken', createdAt: now, expiresAt: new Date(now.getTime() + 604800000) },
378
+ }),
379
+ isExpired: vi.fn().mockReturnValue(false),
380
+ compare: vi.fn().mockImplementation((a: string, b: string) => a === b),
381
+ ...overrides,
382
+ };
383
+ }
384
+
385
+ /**
386
+ * Mock factory for InputValidator
387
+ */
388
+ export interface MockInputValidator {
389
+ validateEmail: MockInstance<(email: string) => string>;
390
+ validatePassword: MockInstance<(password: string) => string>;
391
+ validateIdentifier: MockInstance<(id: string) => string>;
392
+ validatePath: MockInstance<(path: string) => string>;
393
+ validateCommandArg: MockInstance<(arg: string) => string>;
394
+ validateLoginRequest: MockInstance<(data: unknown) => { email: string; password: string; mfaCode?: string }>;
395
+ validateCreateUser: MockInstance<(data: unknown) => { email: string; password: string; role: string }>;
396
+ validateTaskInput: MockInstance<(data: unknown) => { taskId: string; content: string; agentType: string }>;
397
+ }
398
+
399
+ export function createMockInputValidator(
400
+ overrides: Partial<MockInputValidator> = {}
401
+ ): MockInputValidator {
402
+ return {
403
+ validateEmail: vi.fn().mockImplementation((email: string) => email.toLowerCase()),
404
+ validatePassword: vi.fn().mockImplementation((password: string) => password),
405
+ validateIdentifier: vi.fn().mockImplementation((id: string) => id),
406
+ validatePath: vi.fn().mockImplementation((path: string) => path),
407
+ validateCommandArg: vi.fn().mockImplementation((arg: string) => arg),
408
+ validateLoginRequest: vi.fn().mockImplementation((data: unknown) => data as { email: string; password: string }),
409
+ validateCreateUser: vi.fn().mockImplementation((data: unknown) => data as { email: string; password: string; role: string }),
410
+ validateTaskInput: vi.fn().mockImplementation((data: unknown) => data as { taskId: string; content: string; agentType: string }),
411
+ ...overrides,
412
+ };
413
+ }
414
+
415
+ /**
416
+ * Resets all mocks in a mock object
417
+ */
418
+ export function resetMock<T extends object>(mock: MockedInterface<T>): void {
419
+ for (const key of Object.keys(mock)) {
420
+ const value = (mock as Record<string, unknown>)[key];
421
+ if (typeof value === 'function' && 'mockReset' in value) {
422
+ (value as MockInstance).mockReset();
423
+ }
424
+ }
425
+ }
426
+
427
+ /**
428
+ * Clears all mocks in a mock object (keeps implementation)
429
+ */
430
+ export function clearMock<T extends object>(mock: MockedInterface<T>): void {
431
+ for (const key of Object.keys(mock)) {
432
+ const value = (mock as Record<string, unknown>)[key];
433
+ if (typeof value === 'function' && 'mockClear' in value) {
434
+ (value as MockInstance).mockClear();
435
+ }
436
+ }
437
+ }
438
+
439
+ /**
440
+ * Restores all mocks in a mock object
441
+ */
442
+ export function restoreMock<T extends object>(mock: MockedInterface<T>): void {
443
+ for (const key of Object.keys(mock)) {
444
+ const value = (mock as Record<string, unknown>)[key];
445
+ if (typeof value === 'function' && 'mockRestore' in value) {
446
+ (value as MockInstance).mockRestore();
447
+ }
448
+ }
449
+ }
450
+
451
+ /**
452
+ * Helper to verify mock was called with specific arguments
453
+ */
454
+ export function expectCalledWith<T extends MockInstance>(
455
+ mock: T,
456
+ ...args: Parameters<T extends MockInstance<infer F> ? F : never>
457
+ ): void {
458
+ expect(mock).toHaveBeenCalledWith(...args);
459
+ }
460
+
461
+ /**
462
+ * Helper to verify mock was called exactly N times
463
+ */
464
+ export function expectCalledTimes<T extends MockInstance>(
465
+ mock: T,
466
+ times: number
467
+ ): void {
468
+ expect(mock).toHaveBeenCalledTimes(times);
469
+ }