@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,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
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Test Helpers Index
3
+ *
4
+ * Re-exports all testing utilities for the security module.
5
+ *
6
+ * @module v3/security/__tests__/helpers
7
+ */
8
+
9
+ export {
10
+ createMock,
11
+ createPartialMock,
12
+ createSpy,
13
+ createMockPasswordHasher,
14
+ createMockCredentialGenerator,
15
+ createMockPathValidator,
16
+ createMockSafeExecutor,
17
+ createMockTokenGenerator,
18
+ createMockInputValidator,
19
+ resetMock,
20
+ clearMock,
21
+ restoreMock,
22
+ expectCalledWith,
23
+ expectCalledTimes,
24
+ type MockedInterface,
25
+ type DeepMockedInterface,
26
+ type MockPasswordHasher,
27
+ type MockCredentialGenerator,
28
+ type MockPathValidator,
29
+ type MockSafeExecutor,
30
+ type MockTokenGenerator,
31
+ type MockInputValidator,
32
+ } from './create-mock.js';