guardrail-compliance 1.0.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 (149) hide show
  1. package/dist/audit/emitter.d.ts +97 -0
  2. package/dist/audit/emitter.d.ts.map +1 -0
  3. package/dist/audit/emitter.js +197 -0
  4. package/dist/audit/events.d.ts +304 -0
  5. package/dist/audit/events.d.ts.map +1 -0
  6. package/dist/audit/events.js +267 -0
  7. package/dist/audit/index.d.ts +11 -0
  8. package/dist/audit/index.d.ts.map +1 -0
  9. package/dist/audit/index.js +51 -0
  10. package/dist/audit/storage.d.ts +93 -0
  11. package/dist/audit/storage.d.ts.map +1 -0
  12. package/dist/audit/storage.js +337 -0
  13. package/dist/automation/__tests__/compliance-scheduler.test.d.ts +2 -0
  14. package/dist/automation/__tests__/compliance-scheduler.test.d.ts.map +1 -0
  15. package/dist/automation/__tests__/compliance-scheduler.test.js +140 -0
  16. package/dist/automation/audit-logger.d.ts +129 -0
  17. package/dist/automation/audit-logger.d.ts.map +1 -0
  18. package/dist/automation/audit-logger.js +473 -0
  19. package/dist/automation/compliance-scheduler-fixed.d.ts +1 -0
  20. package/dist/automation/compliance-scheduler-fixed.d.ts.map +1 -0
  21. package/dist/automation/compliance-scheduler-fixed.js +1 -0
  22. package/dist/automation/compliance-scheduler.d.ts +83 -0
  23. package/dist/automation/compliance-scheduler.d.ts.map +1 -0
  24. package/dist/automation/compliance-scheduler.js +414 -0
  25. package/dist/automation/dashboard.d.ts +194 -0
  26. package/dist/automation/dashboard.d.ts.map +1 -0
  27. package/dist/automation/dashboard.js +768 -0
  28. package/dist/automation/email-service.d.ts +69 -0
  29. package/dist/automation/email-service.d.ts.map +1 -0
  30. package/dist/automation/email-service.js +218 -0
  31. package/dist/automation/evidence-collector.d.ts +140 -0
  32. package/dist/automation/evidence-collector.d.ts.map +1 -0
  33. package/dist/automation/evidence-collector.js +682 -0
  34. package/dist/automation/index.d.ts +8 -0
  35. package/dist/automation/index.d.ts.map +1 -0
  36. package/dist/automation/index.js +24 -0
  37. package/dist/automation/pdf-exporter.d.ts +90 -0
  38. package/dist/automation/pdf-exporter.d.ts.map +1 -0
  39. package/dist/automation/pdf-exporter.js +381 -0
  40. package/dist/automation/reporting-engine.d.ts +116 -0
  41. package/dist/automation/reporting-engine.d.ts.map +1 -0
  42. package/dist/automation/reporting-engine.js +329 -0
  43. package/dist/container/index.d.ts +4 -0
  44. package/dist/container/index.d.ts.map +1 -0
  45. package/dist/container/index.js +19 -0
  46. package/dist/container/kubernetes.d.ts +94 -0
  47. package/dist/container/kubernetes.d.ts.map +1 -0
  48. package/dist/container/kubernetes.js +268 -0
  49. package/dist/container/rules.d.ts +27 -0
  50. package/dist/container/rules.d.ts.map +1 -0
  51. package/dist/container/rules.js +216 -0
  52. package/dist/container/scanner.d.ts +50 -0
  53. package/dist/container/scanner.d.ts.map +1 -0
  54. package/dist/container/scanner.js +143 -0
  55. package/dist/frameworks/engine.d.ts +108 -0
  56. package/dist/frameworks/engine.d.ts.map +1 -0
  57. package/dist/frameworks/engine.js +206 -0
  58. package/dist/frameworks/gdpr.d.ts +6 -0
  59. package/dist/frameworks/gdpr.d.ts.map +1 -0
  60. package/dist/frameworks/gdpr.js +198 -0
  61. package/dist/frameworks/hipaa.d.ts +6 -0
  62. package/dist/frameworks/hipaa.d.ts.map +1 -0
  63. package/dist/frameworks/hipaa.js +183 -0
  64. package/dist/frameworks/index.d.ts +8 -0
  65. package/dist/frameworks/index.d.ts.map +1 -0
  66. package/dist/frameworks/index.js +30 -0
  67. package/dist/frameworks/iso27001.d.ts +63 -0
  68. package/dist/frameworks/iso27001.d.ts.map +1 -0
  69. package/dist/frameworks/iso27001.js +331 -0
  70. package/dist/frameworks/nist.d.ts +62 -0
  71. package/dist/frameworks/nist.d.ts.map +1 -0
  72. package/dist/frameworks/nist.js +424 -0
  73. package/dist/frameworks/pci.d.ts +6 -0
  74. package/dist/frameworks/pci.d.ts.map +1 -0
  75. package/dist/frameworks/pci.js +201 -0
  76. package/dist/frameworks/soc2.d.ts +7 -0
  77. package/dist/frameworks/soc2.d.ts.map +1 -0
  78. package/dist/frameworks/soc2.js +248 -0
  79. package/dist/iac/drift-detector.d.ts +64 -0
  80. package/dist/iac/drift-detector.d.ts.map +1 -0
  81. package/dist/iac/drift-detector.js +134 -0
  82. package/dist/iac/index.d.ts +4 -0
  83. package/dist/iac/index.d.ts.map +1 -0
  84. package/dist/iac/index.js +19 -0
  85. package/dist/iac/rules.d.ts +17 -0
  86. package/dist/iac/rules.d.ts.map +1 -0
  87. package/dist/iac/rules.js +385 -0
  88. package/dist/iac/scanner.d.ts +104 -0
  89. package/dist/iac/scanner.d.ts.map +1 -0
  90. package/dist/iac/scanner.js +343 -0
  91. package/dist/index.d.ts +7 -0
  92. package/dist/index.d.ts.map +1 -0
  93. package/dist/index.js +28 -0
  94. package/dist/pii/data-flow.d.ts +58 -0
  95. package/dist/pii/data-flow.d.ts.map +1 -0
  96. package/dist/pii/data-flow.js +154 -0
  97. package/dist/pii/detector.d.ts +60 -0
  98. package/dist/pii/detector.d.ts.map +1 -0
  99. package/dist/pii/detector.js +267 -0
  100. package/dist/pii/index.d.ts +4 -0
  101. package/dist/pii/index.d.ts.map +1 -0
  102. package/dist/pii/index.js +19 -0
  103. package/dist/pii/patterns.d.ts +36 -0
  104. package/dist/pii/patterns.d.ts.map +1 -0
  105. package/dist/pii/patterns.js +108 -0
  106. package/dist/policy/index.d.ts +5 -0
  107. package/dist/policy/index.d.ts.map +1 -0
  108. package/dist/policy/index.js +20 -0
  109. package/dist/policy/opa-engine.d.ts +121 -0
  110. package/dist/policy/opa-engine.d.ts.map +1 -0
  111. package/dist/policy/opa-engine.js +423 -0
  112. package/package.json +31 -0
  113. package/src/audit/emitter.ts +383 -0
  114. package/src/audit/events.ts +351 -0
  115. package/src/audit/index.ts +35 -0
  116. package/src/audit/storage.ts +394 -0
  117. package/src/automation/__tests__/compliance-scheduler.test.ts +183 -0
  118. package/src/automation/audit-logger.ts +629 -0
  119. package/src/automation/compliance-scheduler-fixed.ts +0 -0
  120. package/src/automation/compliance-scheduler.ts +516 -0
  121. package/src/automation/dashboard.ts +947 -0
  122. package/src/automation/email-service.ts +230 -0
  123. package/src/automation/evidence-collector.ts +866 -0
  124. package/src/automation/index.ts +8 -0
  125. package/src/automation/pdf-exporter.ts +434 -0
  126. package/src/automation/reporting-engine.ts +462 -0
  127. package/src/container/index.ts +3 -0
  128. package/src/container/kubernetes.ts +379 -0
  129. package/src/container/rules.ts +244 -0
  130. package/src/container/scanner.ts +202 -0
  131. package/src/frameworks/engine.ts +298 -0
  132. package/src/frameworks/gdpr.ts +204 -0
  133. package/src/frameworks/hipaa.ts +209 -0
  134. package/src/frameworks/index.ts +23 -0
  135. package/src/frameworks/iso27001.ts +398 -0
  136. package/src/frameworks/nist.ts +518 -0
  137. package/src/frameworks/pci.ts +226 -0
  138. package/src/frameworks/soc2.ts +281 -0
  139. package/src/iac/drift-detector.ts +197 -0
  140. package/src/iac/index.ts +3 -0
  141. package/src/iac/rules.ts +420 -0
  142. package/src/iac/scanner.ts +445 -0
  143. package/src/index.ts +17 -0
  144. package/src/pii/data-flow.ts +216 -0
  145. package/src/pii/detector.ts +327 -0
  146. package/src/pii/index.ts +3 -0
  147. package/src/pii/patterns.ts +128 -0
  148. package/src/policy/index.ts +5 -0
  149. package/src/policy/opa-engine.ts +504 -0
@@ -0,0 +1,866 @@
1
+ import { prisma } from '@guardrail/database';
2
+ import { readFileSync, existsSync, readdirSync, statSync } from 'fs';
3
+ import { join, extname, basename } from 'path';
4
+ import { createHash } from 'crypto';
5
+ import { ComplianceAssessmentResult } from '../frameworks/engine';
6
+
7
+ export interface EvidenceArtifact {
8
+ id: string;
9
+ controlId: string;
10
+ type: 'document' | 'configuration' | 'screenshot' | 'log' | 'code' | 'test' | 'metadata';
11
+ name: string;
12
+ path?: string;
13
+ content?: string;
14
+ hash: string;
15
+ size: number;
16
+ timestamp: Date;
17
+ metadata: any;
18
+ }
19
+
20
+ interface EvidenceCollection {
21
+ id: string;
22
+ projectId: string;
23
+ name: string;
24
+ description?: string;
25
+ frameworkId?: string;
26
+ assessmentId?: string;
27
+ artifacts: EvidenceArtifact[];
28
+ summary: {
29
+ totalArtifacts: number;
30
+ byType: Record<string, number>;
31
+ size: number;
32
+ hash: string;
33
+ };
34
+ timestamp: Date;
35
+ }
36
+
37
+ /**
38
+ * Evidence Collection Engine
39
+ *
40
+ * Automatically collects, preserves, and manages evidence
41
+ * for compliance assessments and audits
42
+ */
43
+ export class EvidenceCollector {
44
+ // Remove unused evidenceTypes as it's not currently used
45
+
46
+ /**
47
+ * Collect evidence for a compliance assessment
48
+ */
49
+ async collectForAssessment(
50
+ projectId: string,
51
+ frameworkId: string,
52
+ assessment: ComplianceAssessmentResult
53
+ ): Promise<EvidenceArtifact[]> {
54
+ const artifacts: EvidenceArtifact[] = [];
55
+
56
+ // Get project path
57
+ const project = await prisma.project.findUnique({
58
+ where: { id: projectId }
59
+ });
60
+
61
+ if (!project) {
62
+ throw new Error(`Project ${projectId} not found`);
63
+ }
64
+
65
+ // Collect evidence for each control
66
+ for (const control of assessment.controls) {
67
+ const controlArtifacts = await this.collectControlEvidence(
68
+ project.path || '',
69
+ control.controlId,
70
+ control
71
+ );
72
+ artifacts.push(...controlArtifacts);
73
+ }
74
+
75
+ // Store evidence collection
76
+ const collection: EvidenceCollection = {
77
+ id: `ev_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
78
+ projectId,
79
+ frameworkId,
80
+ assessmentId: assessment.projectId,
81
+ name: `Evidence Collection - ${new Date().toISOString()}`,
82
+ artifacts,
83
+ summary: {
84
+ totalArtifacts: artifacts.length,
85
+ byType: this.groupArtifactsByType(artifacts),
86
+ size: this.calculateTotalSize(artifacts),
87
+ hash: await this.calculateHash(artifacts)
88
+ },
89
+ timestamp: new Date()
90
+ };
91
+
92
+ try {
93
+ await this.storeEvidenceCollection(collection);
94
+ } catch (error) {
95
+ console.warn('Could not store evidence collection in database:', error);
96
+ }
97
+
98
+ return artifacts;
99
+ }
100
+
101
+ /**
102
+ * Collect evidence for a specific control
103
+ */
104
+ private async collectControlEvidence(
105
+ projectPath: string,
106
+ controlId: string,
107
+ control: any
108
+ ): Promise<EvidenceArtifact[]> {
109
+ const artifacts: EvidenceArtifact[] = [];
110
+
111
+ // Collect based on control category
112
+ switch (control.category) {
113
+ case 'data-protection':
114
+ artifacts.push(...await this.collectDataProtectionEvidence(projectPath, controlId));
115
+ break;
116
+ case 'security':
117
+ artifacts.push(...await this.collectSecurityEvidence(projectPath, controlId));
118
+ break;
119
+ case 'access-control':
120
+ artifacts.push(...await this.collectAccessControlEvidence(projectPath, controlId));
121
+ break;
122
+ case 'incident-response':
123
+ artifacts.push(...await this.collectIncidentResponseEvidence(projectPath, controlId));
124
+ break;
125
+ case 'logging':
126
+ artifacts.push(...await this.collectLoggingEvidence(projectPath, controlId));
127
+ break;
128
+ default:
129
+ artifacts.push(...await this.collectGeneralEvidence(projectPath, controlId));
130
+ }
131
+
132
+ // Add metadata evidence
133
+ artifacts.push(await this.createMetadataArtifact(controlId, control));
134
+
135
+ return artifacts;
136
+ }
137
+
138
+ /**
139
+ * Collect data protection evidence
140
+ */
141
+ private async collectDataProtectionEvidence(
142
+ projectPath: string,
143
+ controlId: string
144
+ ): Promise<EvidenceArtifact[]> {
145
+ const artifacts: EvidenceArtifact[] = [];
146
+
147
+ // Privacy policy documents
148
+ const privacyDocs = [
149
+ 'PRIVACY.md',
150
+ 'privacy-policy.md',
151
+ 'docs/privacy.md',
152
+ 'docs/privacy-policy.md',
153
+ 'GDPR.md',
154
+ 'CCPA.md'
155
+ ];
156
+
157
+ for (const doc of privacyDocs) {
158
+ const artifact = await this.collectDocument(projectPath, doc, controlId, 'document');
159
+ if (artifact) artifacts.push(artifact);
160
+ }
161
+
162
+ // Data encryption configuration
163
+ const encryptionConfigs = [
164
+ '.env.example',
165
+ 'config/encryption.json',
166
+ 'config/security.json',
167
+ 'docker-compose.yml',
168
+ 'kubernetes/secrets.yaml'
169
+ ];
170
+
171
+ for (const config of encryptionConfigs) {
172
+ const artifact = await this.collectConfiguration(projectPath, config, controlId);
173
+ if (artifact) artifacts.push(artifact);
174
+ }
175
+
176
+ // Database schema for PII fields
177
+ const schemaFiles = [
178
+ 'prisma/schema.prisma',
179
+ 'models/index.js',
180
+ 'migrations/',
181
+ 'src/database/schema.sql'
182
+ ];
183
+
184
+ for (const schema of schemaFiles) {
185
+ const artifact = await this.collectCodeArtifact(projectPath, schema, controlId);
186
+ if (artifact) artifacts.push(artifact);
187
+ }
188
+
189
+ return artifacts;
190
+ }
191
+
192
+ /**
193
+ * Collect security evidence
194
+ */
195
+ private async collectSecurityEvidence(
196
+ projectPath: string,
197
+ controlId: string
198
+ ): Promise<EvidenceArtifact[]> {
199
+ const artifacts: EvidenceArtifact[] = [];
200
+
201
+ // Security documentation
202
+ const securityDocs = [
203
+ 'SECURITY.md',
204
+ 'docs/security.md',
205
+ 'docs/security-policy.md',
206
+ 'vulnerability-reporting.md'
207
+ ];
208
+
209
+ for (const doc of securityDocs) {
210
+ const artifact = await this.collectDocument(projectPath, doc, controlId, 'document');
211
+ if (artifact) artifacts.push(artifact);
212
+ }
213
+
214
+ // Security configurations
215
+ const securityConfigs = [
216
+ '.env.example',
217
+ 'config/security.js',
218
+ 'config/auth.js',
219
+ 'helmet.js',
220
+ 'cors.js',
221
+ 'webpack.security.js'
222
+ ];
223
+
224
+ for (const config of securityConfigs) {
225
+ const artifact = await this.collectConfiguration(projectPath, config, controlId);
226
+ if (artifact) artifacts.push(artifact);
227
+ }
228
+
229
+ // Security test files
230
+ const securityTests = await this.findFilesByPattern(
231
+ projectPath,
232
+ '**/*.security.test.*',
233
+ controlId
234
+ );
235
+
236
+ artifacts.push(...securityTests);
237
+
238
+ // Dependency security files
239
+ const depFiles = [
240
+ 'package-lock.json',
241
+ 'yarn.lock',
242
+ 'requirements.txt',
243
+ 'Pipfile.lock',
244
+ 'go.sum',
245
+ 'Cargo.lock'
246
+ ];
247
+
248
+ for (const dep of depFiles) {
249
+ const artifact = await this.collectMetadataFile(projectPath, dep, controlId);
250
+ if (artifact) artifacts.push(artifact);
251
+ }
252
+
253
+ return artifacts;
254
+ }
255
+
256
+ /**
257
+ * Collect access control evidence
258
+ */
259
+ private async collectAccessControlEvidence(
260
+ projectPath: string,
261
+ controlId: string
262
+ ): Promise<EvidenceArtifact[]> {
263
+ const artifacts: EvidenceArtifact[] = [];
264
+
265
+ // Auth configuration
266
+ const authConfigs = [
267
+ 'config/auth.js',
268
+ 'config/passport.js',
269
+ 'config/jwt.js',
270
+ 'middleware/auth.js',
271
+ 'src/auth/index.js'
272
+ ];
273
+
274
+ for (const config of authConfigs) {
275
+ const artifact = await this.collectCodeArtifact(projectPath, config, controlId);
276
+ if (artifact) artifacts.push(artifact);
277
+ }
278
+
279
+ // RBAC definitions
280
+ const rbacFiles = await this.findFilesByPattern(
281
+ projectPath,
282
+ '**/*{role,permission,rbac}*',
283
+ controlId
284
+ );
285
+
286
+ artifacts.push(...rbacFiles);
287
+
288
+ // API route protections
289
+ const routeFiles = await this.findFilesByPattern(
290
+ projectPath,
291
+ '**/routes/**/*.js',
292
+ controlId
293
+ );
294
+
295
+ artifacts.push(...routeFiles.slice(0, 10)); // Limit to prevent too many files
296
+
297
+ return artifacts;
298
+ }
299
+
300
+ /**
301
+ * Collect incident response evidence
302
+ */
303
+ private async collectIncidentResponseEvidence(
304
+ projectPath: string,
305
+ controlId: string
306
+ ): Promise<EvidenceArtifact[]> {
307
+ const artifacts: EvidenceArtifact[] = [];
308
+
309
+ // Incident response documentation
310
+ const irDocs = [
311
+ 'docs/incident-response.md',
312
+ 'docs/irp.md',
313
+ 'docs/emergency-procedures.md',
314
+ 'INCIDENT-RESPONSE.md'
315
+ ];
316
+
317
+ for (const doc of irDocs) {
318
+ const artifact = await this.collectDocument(projectPath, doc, controlId, 'document');
319
+ if (artifact) artifacts.push(artifact);
320
+ }
321
+
322
+ // Monitoring configuration
323
+ const monitorConfigs = [
324
+ 'config/monitoring.js',
325
+ 'prometheus.yml',
326
+ 'grafana/dashboards/',
327
+ 'docker-compose.monitoring.yml'
328
+ ];
329
+
330
+ for (const config of monitorConfigs) {
331
+ const artifact = await this.collectConfiguration(projectPath, config, controlId);
332
+ if (artifact) artifacts.push(artifact);
333
+ }
334
+
335
+ return artifacts;
336
+ }
337
+
338
+ /**
339
+ * Collect logging evidence
340
+ */
341
+ private async collectLoggingEvidence(
342
+ projectPath: string,
343
+ controlId: string
344
+ ): Promise<EvidenceArtifact[]> {
345
+ const artifacts: EvidenceArtifact[] = [];
346
+
347
+ // Logging configuration
348
+ const logConfigs = [
349
+ 'config/logger.js',
350
+ 'config/winston.js',
351
+ 'config/log4js.js',
352
+ 'src/utils/logger.js'
353
+ ];
354
+
355
+ for (const config of logConfigs) {
356
+ const artifact = await this.collectCodeArtifact(projectPath, config, controlId);
357
+ if (artifact) artifacts.push(artifact);
358
+ }
359
+
360
+ // Sample log files (if they exist)
361
+ const logFiles = await this.findFilesByPattern(
362
+ projectPath,
363
+ '**/*.log',
364
+ controlId
365
+ );
366
+
367
+ artifacts.push(...logFiles.slice(0, 5)); // Limit to prevent too many files
368
+
369
+ return artifacts;
370
+ }
371
+
372
+ /**
373
+ * Collect general evidence
374
+ */
375
+ private async collectGeneralEvidence(
376
+ projectPath: string,
377
+ controlId: string
378
+ ): Promise<EvidenceArtifact[]> {
379
+ const artifacts: EvidenceArtifact[] = [];
380
+
381
+ // README and documentation
382
+ const docs = [
383
+ 'README.md',
384
+ 'CONTRIBUTING.md',
385
+ 'CHANGELOG.md',
386
+ 'docs/'
387
+ ];
388
+
389
+ for (const doc of docs) {
390
+ const artifact = await this.collectDocument(projectPath, doc, controlId, 'document');
391
+ if (artifact) artifacts.push(artifact);
392
+ }
393
+
394
+ return artifacts;
395
+ }
396
+
397
+ /**
398
+ * Collect a document artifact
399
+ */
400
+ private async collectDocument(
401
+ projectPath: string,
402
+ relativePath: string,
403
+ controlId: string,
404
+ type: EvidenceArtifact['type']
405
+ ): Promise<EvidenceArtifact | null> {
406
+ const fullPath = join(projectPath, relativePath);
407
+
408
+ if (!existsSync(fullPath)) {
409
+ return null;
410
+ }
411
+
412
+ try {
413
+ const content = readFileSync(fullPath, 'utf-8');
414
+ const hash = createHash('sha256').update(content).digest('hex');
415
+ const stats = statSync(fullPath);
416
+
417
+ // Redact sensitive information
418
+ const redactedContent = this.redactSensitiveData(content);
419
+
420
+ return {
421
+ id: `doc_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
422
+ controlId,
423
+ type,
424
+ name: basename(relativePath),
425
+ path: relativePath,
426
+ content: redactedContent,
427
+ hash,
428
+ size: stats.size,
429
+ timestamp: new Date(),
430
+ metadata: {
431
+ extension: extname(relativePath),
432
+ lastModified: stats.mtime
433
+ }
434
+ };
435
+ } catch (error) {
436
+ console.error(`Failed to collect document ${relativePath}:`, error);
437
+ return null;
438
+ }
439
+ }
440
+
441
+ /**
442
+ * Collect a configuration artifact
443
+ */
444
+ private async collectConfiguration(
445
+ projectPath: string,
446
+ relativePath: string,
447
+ controlId: string
448
+ ): Promise<EvidenceArtifact | null> {
449
+ const fullPath = join(projectPath, relativePath);
450
+
451
+ if (!existsSync(fullPath)) {
452
+ return null;
453
+ }
454
+
455
+ try {
456
+ let content = readFileSync(fullPath, 'utf-8');
457
+ const hash = createHash('sha256').update(content).digest('hex');
458
+ const stats = statSync(fullPath);
459
+
460
+ // Redact sensitive values
461
+ content = this.redactConfiguration(content);
462
+
463
+ return {
464
+ id: `cfg_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
465
+ controlId,
466
+ type: 'configuration',
467
+ name: basename(relativePath),
468
+ path: relativePath,
469
+ content,
470
+ hash,
471
+ size: stats.size,
472
+ timestamp: new Date(),
473
+ metadata: {
474
+ extension: extname(relativePath),
475
+ lastModified: stats.mtime
476
+ }
477
+ };
478
+ } catch (error) {
479
+ console.error(`Failed to collect configuration ${relativePath}:`, error);
480
+ return null;
481
+ }
482
+ }
483
+
484
+ /**
485
+ * Collect a code artifact
486
+ */
487
+ private async collectCodeArtifact(
488
+ projectPath: string,
489
+ relativePath: string,
490
+ controlId: string
491
+ ): Promise<EvidenceArtifact | null> {
492
+ const fullPath = join(projectPath, relativePath);
493
+
494
+ if (!existsSync(fullPath)) {
495
+ return null;
496
+ }
497
+
498
+ try {
499
+ const content = readFileSync(fullPath, 'utf-8');
500
+ const hash = createHash('sha256').update(content).digest('hex');
501
+ const stats = statSync(fullPath);
502
+
503
+ return {
504
+ id: `code_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
505
+ controlId,
506
+ type: 'code',
507
+ name: basename(relativePath),
508
+ path: relativePath,
509
+ content,
510
+ hash,
511
+ size: stats.size,
512
+ timestamp: new Date(),
513
+ metadata: {
514
+ extension: extname(relativePath),
515
+ language: this.detectLanguage(relativePath),
516
+ lastModified: stats.mtime
517
+ }
518
+ };
519
+ } catch (error) {
520
+ console.error(`Failed to collect code artifact ${relativePath}:`, error);
521
+ return null;
522
+ }
523
+ }
524
+
525
+ /**
526
+ * Collect metadata file
527
+ */
528
+ private async collectMetadataFile(
529
+ projectPath: string,
530
+ relativePath: string,
531
+ controlId: string
532
+ ): Promise<EvidenceArtifact | null> {
533
+ const fullPath = join(projectPath, relativePath);
534
+
535
+ if (!existsSync(fullPath)) {
536
+ return null;
537
+ }
538
+
539
+ try {
540
+ const content = readFileSync(fullPath, 'utf-8');
541
+ const hash = createHash('sha256').update(content).digest('hex');
542
+ const stats = statSync(fullPath);
543
+
544
+ return {
545
+ id: `meta_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
546
+ controlId,
547
+ type: 'metadata',
548
+ name: basename(relativePath),
549
+ path: relativePath,
550
+ content,
551
+ hash,
552
+ size: stats.size,
553
+ timestamp: new Date(),
554
+ metadata: {
555
+ extension: extname(relativePath),
556
+ lastModified: stats.mtime
557
+ }
558
+ };
559
+ } catch (error) {
560
+ console.error(`Failed to collect metadata ${relativePath}:`, error);
561
+ return null;
562
+ }
563
+ }
564
+
565
+ /**
566
+ * Find files by pattern
567
+ */
568
+ private async findFilesByPattern(
569
+ projectPath: string,
570
+ pattern: string,
571
+ controlId: string
572
+ ): Promise<EvidenceArtifact[]> {
573
+ const artifacts: EvidenceArtifact[] = [];
574
+
575
+ try {
576
+ // Simple glob implementation - in production use a proper glob library
577
+ const files = this.globFiles(projectPath, pattern);
578
+
579
+ for (const file of files.slice(0, 20)) { // Limit results
580
+ const artifact = await this.collectCodeArtifact(
581
+ projectPath,
582
+ file.replace(projectPath + '/', ''),
583
+ controlId
584
+ );
585
+ if (artifact) artifacts.push(artifact);
586
+ }
587
+ } catch (error) {
588
+ console.error(`Failed to find files with pattern ${pattern}:`, error);
589
+ }
590
+
591
+ return artifacts;
592
+ }
593
+
594
+ /**
595
+ * Simple glob implementation
596
+ */
597
+ private globFiles(basePath: string, pattern: string): string[] {
598
+ const files: string[] = [];
599
+ const parts = pattern.split('/');
600
+
601
+ const search = (dir: string, index: number) => {
602
+ if (index === parts.length) {
603
+ files.push(dir);
604
+ return;
605
+ }
606
+
607
+ const part = parts[index];
608
+
609
+ if (!existsSync(dir)) return;
610
+
611
+ const items = readdirSync(dir);
612
+
613
+ for (const item of items) {
614
+ const fullPath = join(dir, item);
615
+ const stats = statSync(fullPath);
616
+
617
+ if (stats.isDirectory() && index < parts.length - 1) {
618
+ search(fullPath, index + 1);
619
+ } else if (stats.isFile() && part && this.matchesPattern(item, part)) {
620
+ files.push(fullPath);
621
+ }
622
+ }
623
+ };
624
+
625
+ search(basePath, 0);
626
+ return files;
627
+ }
628
+
629
+ /**
630
+ * Check if filename matches pattern
631
+ */
632
+ private matchesPattern(filename: string, pattern: string): boolean {
633
+ if (pattern === '**') return true;
634
+ if (pattern.includes('*')) {
635
+ const regex = new RegExp(pattern.replace(/\*/g, '.*'));
636
+ return regex.test(filename);
637
+ }
638
+ return filename === pattern;
639
+ }
640
+
641
+ /**
642
+ * Create metadata artifact
643
+ */
644
+ private async createMetadataArtifact(
645
+ controlId: string,
646
+ control: any
647
+ ): Promise<EvidenceArtifact> {
648
+ const metadata = {
649
+ controlId,
650
+ requirements: control.requirements,
651
+ assessmentStatus: control.status,
652
+ assessmentScore: control.score,
653
+ findings: control.findings,
654
+ gaps: control.gaps
655
+ };
656
+
657
+ const content = JSON.stringify(metadata, null, 2);
658
+ const hash = createHash('sha256').update(content).digest('hex');
659
+
660
+ return {
661
+ id: `meta_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
662
+ controlId,
663
+ type: 'metadata',
664
+ name: `control-${controlId}-metadata.json`,
665
+ content,
666
+ hash,
667
+ size: content.length,
668
+ timestamp: new Date(),
669
+ metadata: {
670
+ format: 'json',
671
+ generated: true
672
+ }
673
+ };
674
+ }
675
+
676
+ /**
677
+ * Redact sensitive data from content
678
+ */
679
+ private redactSensitiveData(content: string): string {
680
+ // Redact common sensitive patterns
681
+ return content
682
+ .replace(/password["\s]*[:=]["\s]*[^"'\s]+/gi, 'password: "[REDACTED]"')
683
+ .replace(/secret["\s]*[:=]["\s]*[^"'\s]+/gi, 'secret: "[REDACTED]"')
684
+ .replace(/key["\s]*[:=]["\s]*[^"'\s]+/gi, 'key: "[REDACTED]"')
685
+ .replace(/token["\s]*[:=]["\s]*[^"'\s]+/gi, 'token: "[REDACTED]"')
686
+ .replace(/api[_-]?key["\s]*[:=]["\s]*[^"'\s]+/gi, 'api_key: "[REDACTED]"')
687
+ .replace(/[A-Za-z0-9]{32,}/g, '[REDACTED]'); // Redact long strings (potential keys)
688
+ }
689
+
690
+ /**
691
+ * Redact sensitive configuration values
692
+ */
693
+ private redactConfiguration(content: string): string {
694
+ return this.redactSensitiveData(content);
695
+ }
696
+
697
+ /**
698
+ * Detect programming language from file extension
699
+ */
700
+ private detectLanguage(filename: string): string {
701
+ const ext = extname(filename).toLowerCase();
702
+ const languageMap: Record<string, string> = {
703
+ '.js': 'javascript',
704
+ '.ts': 'typescript',
705
+ '.py': 'python',
706
+ '.java': 'java',
707
+ '.go': 'go',
708
+ '.rb': 'ruby',
709
+ '.php': 'php',
710
+ '.cs': 'csharp',
711
+ '.cpp': 'cpp',
712
+ '.c': 'c',
713
+ '.rs': 'rust',
714
+ '.swift': 'swift',
715
+ '.kt': 'kotlin',
716
+ '.scala': 'scala',
717
+ '.sh': 'shell',
718
+ '.sql': 'sql',
719
+ '.html': 'html',
720
+ '.css': 'css',
721
+ '.scss': 'scss',
722
+ '.sass': 'sass',
723
+ '.less': 'less',
724
+ '.xml': 'xml',
725
+ '.yaml': 'yaml',
726
+ '.yml': 'yaml',
727
+ '.json': 'json',
728
+ '.toml': 'toml',
729
+ '.ini': 'ini'
730
+ };
731
+
732
+ return languageMap[ext] || 'unknown';
733
+ }
734
+
735
+ /**
736
+ * Group artifacts by type
737
+ */
738
+ private groupArtifactsByType(artifacts: EvidenceArtifact[]): Record<string, number> {
739
+ const byType: Record<string, number> = {};
740
+
741
+ for (const artifact of artifacts) {
742
+ byType[artifact.type] = (byType[artifact.type] || 0) + 1;
743
+ }
744
+
745
+ return byType;
746
+ }
747
+
748
+ /**
749
+ * Calculate total size of artifacts
750
+ */
751
+ private calculateTotalSize(artifacts: EvidenceArtifact[]): number {
752
+ let totalSize = 0;
753
+
754
+ for (const artifact of artifacts) {
755
+ totalSize += artifact.size;
756
+ }
757
+
758
+ return totalSize;
759
+ }
760
+
761
+ /**
762
+ * Calculate hash of artifacts
763
+ */
764
+ private async calculateHash(artifacts: EvidenceArtifact[]): Promise<string> {
765
+ const hash = createHash('sha256');
766
+
767
+ for (const artifact of artifacts) {
768
+ hash.update(artifact.content || '');
769
+ }
770
+
771
+ return hash.digest('hex');
772
+ }
773
+
774
+ /**
775
+ * Store evidence collection in database
776
+ */
777
+ private async storeEvidenceCollection(collection: EvidenceCollection): Promise<void> {
778
+ // Store in database
779
+ try {
780
+ // @ts-ignore - evidenceCollection may not exist in schema yet
781
+ await prisma.evidenceCollection.create({
782
+ data: {
783
+ projectId: collection.projectId,
784
+ name: `Evidence for ${collection.frameworkId}`,
785
+ description: `Collected on ${new Date().toISOString()}`,
786
+ artifacts: collection.artifacts as any,
787
+ metadata: {
788
+ frameworkId: collection.frameworkId,
789
+ assessmentId: collection.assessmentId,
790
+ totalArtifacts: collection.summary.totalArtifacts
791
+ }
792
+ }
793
+ });
794
+ } catch (error) {
795
+ console.warn('Could not store evidence collection in database:', error);
796
+ }
797
+ }
798
+
799
+ /**
800
+ * Retrieve evidence collection
801
+ */
802
+ async getEvidenceCollection(collectionId: string): Promise<EvidenceCollection | null> {
803
+ try {
804
+ const collection = await prisma.evidenceCollection.findUnique({
805
+ where: { id: collectionId }
806
+ });
807
+
808
+ if (!collection) return null;
809
+
810
+ return {
811
+ id: collection.id,
812
+ projectId: collection.projectId,
813
+ name: collection.name,
814
+ description: collection.description || undefined,
815
+ frameworkId: (collection.metadata as any)?.frameworkId,
816
+ assessmentId: (collection.metadata as any)?.assessmentId,
817
+ artifacts: (collection.artifacts as any) || [],
818
+ summary: {
819
+ totalArtifacts: (collection.metadata as any)?.totalArtifacts || 0,
820
+ byType: (collection.metadata as any)?.byType || {},
821
+ size: (collection.metadata as any)?.size || 0,
822
+ hash: (collection.metadata as any)?.hash || ''
823
+ },
824
+ timestamp: collection.createdAt
825
+ };
826
+ } catch (error) {
827
+ console.warn('Could not retrieve evidence collection from database:', error);
828
+ return null;
829
+ }
830
+ }
831
+
832
+ /**
833
+ * List evidence collections for a project
834
+ */
835
+ async listEvidenceCollections(projectId: string): Promise<EvidenceCollection[]> {
836
+ try {
837
+ const collections = await prisma.evidenceCollection.findMany({
838
+ where: { projectId },
839
+ orderBy: { createdAt: 'desc' }
840
+ });
841
+
842
+ return collections.map((c: any) => ({
843
+ id: c.id,
844
+ projectId: c.projectId,
845
+ name: c.name,
846
+ description: c.description,
847
+ frameworkId: (c.metadata as any)?.frameworkId,
848
+ assessmentId: (c.metadata as any)?.assessmentId,
849
+ artifacts: c.artifacts as EvidenceArtifact[],
850
+ summary: {
851
+ totalArtifacts: (c.metadata as any)?.totalArtifacts || 0,
852
+ byType: (c.metadata as any)?.byType || {},
853
+ size: (c.metadata as any)?.size || 0,
854
+ hash: (c.metadata as any)?.hash || ''
855
+ },
856
+ timestamp: c.createdAt
857
+ }));
858
+ } catch (error) {
859
+ console.warn('Could not list evidence collections from database:', error);
860
+ return [];
861
+ }
862
+ }
863
+ }
864
+
865
+ // Export singleton instance
866
+ export const evidenceCollector = new EvidenceCollector();