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.
- package/dist/audit/emitter.d.ts +97 -0
- package/dist/audit/emitter.d.ts.map +1 -0
- package/dist/audit/emitter.js +197 -0
- package/dist/audit/events.d.ts +304 -0
- package/dist/audit/events.d.ts.map +1 -0
- package/dist/audit/events.js +267 -0
- package/dist/audit/index.d.ts +11 -0
- package/dist/audit/index.d.ts.map +1 -0
- package/dist/audit/index.js +51 -0
- package/dist/audit/storage.d.ts +93 -0
- package/dist/audit/storage.d.ts.map +1 -0
- package/dist/audit/storage.js +337 -0
- package/dist/automation/__tests__/compliance-scheduler.test.d.ts +2 -0
- package/dist/automation/__tests__/compliance-scheduler.test.d.ts.map +1 -0
- package/dist/automation/__tests__/compliance-scheduler.test.js +140 -0
- package/dist/automation/audit-logger.d.ts +129 -0
- package/dist/automation/audit-logger.d.ts.map +1 -0
- package/dist/automation/audit-logger.js +473 -0
- package/dist/automation/compliance-scheduler-fixed.d.ts +1 -0
- package/dist/automation/compliance-scheduler-fixed.d.ts.map +1 -0
- package/dist/automation/compliance-scheduler-fixed.js +1 -0
- package/dist/automation/compliance-scheduler.d.ts +83 -0
- package/dist/automation/compliance-scheduler.d.ts.map +1 -0
- package/dist/automation/compliance-scheduler.js +414 -0
- package/dist/automation/dashboard.d.ts +194 -0
- package/dist/automation/dashboard.d.ts.map +1 -0
- package/dist/automation/dashboard.js +768 -0
- package/dist/automation/email-service.d.ts +69 -0
- package/dist/automation/email-service.d.ts.map +1 -0
- package/dist/automation/email-service.js +218 -0
- package/dist/automation/evidence-collector.d.ts +140 -0
- package/dist/automation/evidence-collector.d.ts.map +1 -0
- package/dist/automation/evidence-collector.js +682 -0
- package/dist/automation/index.d.ts +8 -0
- package/dist/automation/index.d.ts.map +1 -0
- package/dist/automation/index.js +24 -0
- package/dist/automation/pdf-exporter.d.ts +90 -0
- package/dist/automation/pdf-exporter.d.ts.map +1 -0
- package/dist/automation/pdf-exporter.js +381 -0
- package/dist/automation/reporting-engine.d.ts +116 -0
- package/dist/automation/reporting-engine.d.ts.map +1 -0
- package/dist/automation/reporting-engine.js +329 -0
- package/dist/container/index.d.ts +4 -0
- package/dist/container/index.d.ts.map +1 -0
- package/dist/container/index.js +19 -0
- package/dist/container/kubernetes.d.ts +94 -0
- package/dist/container/kubernetes.d.ts.map +1 -0
- package/dist/container/kubernetes.js +268 -0
- package/dist/container/rules.d.ts +27 -0
- package/dist/container/rules.d.ts.map +1 -0
- package/dist/container/rules.js +216 -0
- package/dist/container/scanner.d.ts +50 -0
- package/dist/container/scanner.d.ts.map +1 -0
- package/dist/container/scanner.js +143 -0
- package/dist/frameworks/engine.d.ts +108 -0
- package/dist/frameworks/engine.d.ts.map +1 -0
- package/dist/frameworks/engine.js +206 -0
- package/dist/frameworks/gdpr.d.ts +6 -0
- package/dist/frameworks/gdpr.d.ts.map +1 -0
- package/dist/frameworks/gdpr.js +198 -0
- package/dist/frameworks/hipaa.d.ts +6 -0
- package/dist/frameworks/hipaa.d.ts.map +1 -0
- package/dist/frameworks/hipaa.js +183 -0
- package/dist/frameworks/index.d.ts +8 -0
- package/dist/frameworks/index.d.ts.map +1 -0
- package/dist/frameworks/index.js +30 -0
- package/dist/frameworks/iso27001.d.ts +63 -0
- package/dist/frameworks/iso27001.d.ts.map +1 -0
- package/dist/frameworks/iso27001.js +331 -0
- package/dist/frameworks/nist.d.ts +62 -0
- package/dist/frameworks/nist.d.ts.map +1 -0
- package/dist/frameworks/nist.js +424 -0
- package/dist/frameworks/pci.d.ts +6 -0
- package/dist/frameworks/pci.d.ts.map +1 -0
- package/dist/frameworks/pci.js +201 -0
- package/dist/frameworks/soc2.d.ts +7 -0
- package/dist/frameworks/soc2.d.ts.map +1 -0
- package/dist/frameworks/soc2.js +248 -0
- package/dist/iac/drift-detector.d.ts +64 -0
- package/dist/iac/drift-detector.d.ts.map +1 -0
- package/dist/iac/drift-detector.js +134 -0
- package/dist/iac/index.d.ts +4 -0
- package/dist/iac/index.d.ts.map +1 -0
- package/dist/iac/index.js +19 -0
- package/dist/iac/rules.d.ts +17 -0
- package/dist/iac/rules.d.ts.map +1 -0
- package/dist/iac/rules.js +385 -0
- package/dist/iac/scanner.d.ts +104 -0
- package/dist/iac/scanner.d.ts.map +1 -0
- package/dist/iac/scanner.js +343 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +28 -0
- package/dist/pii/data-flow.d.ts +58 -0
- package/dist/pii/data-flow.d.ts.map +1 -0
- package/dist/pii/data-flow.js +154 -0
- package/dist/pii/detector.d.ts +60 -0
- package/dist/pii/detector.d.ts.map +1 -0
- package/dist/pii/detector.js +267 -0
- package/dist/pii/index.d.ts +4 -0
- package/dist/pii/index.d.ts.map +1 -0
- package/dist/pii/index.js +19 -0
- package/dist/pii/patterns.d.ts +36 -0
- package/dist/pii/patterns.d.ts.map +1 -0
- package/dist/pii/patterns.js +108 -0
- package/dist/policy/index.d.ts +5 -0
- package/dist/policy/index.d.ts.map +1 -0
- package/dist/policy/index.js +20 -0
- package/dist/policy/opa-engine.d.ts +121 -0
- package/dist/policy/opa-engine.d.ts.map +1 -0
- package/dist/policy/opa-engine.js +423 -0
- package/package.json +31 -0
- package/src/audit/emitter.ts +383 -0
- package/src/audit/events.ts +351 -0
- package/src/audit/index.ts +35 -0
- package/src/audit/storage.ts +394 -0
- package/src/automation/__tests__/compliance-scheduler.test.ts +183 -0
- package/src/automation/audit-logger.ts +629 -0
- package/src/automation/compliance-scheduler-fixed.ts +0 -0
- package/src/automation/compliance-scheduler.ts +516 -0
- package/src/automation/dashboard.ts +947 -0
- package/src/automation/email-service.ts +230 -0
- package/src/automation/evidence-collector.ts +866 -0
- package/src/automation/index.ts +8 -0
- package/src/automation/pdf-exporter.ts +434 -0
- package/src/automation/reporting-engine.ts +462 -0
- package/src/container/index.ts +3 -0
- package/src/container/kubernetes.ts +379 -0
- package/src/container/rules.ts +244 -0
- package/src/container/scanner.ts +202 -0
- package/src/frameworks/engine.ts +298 -0
- package/src/frameworks/gdpr.ts +204 -0
- package/src/frameworks/hipaa.ts +209 -0
- package/src/frameworks/index.ts +23 -0
- package/src/frameworks/iso27001.ts +398 -0
- package/src/frameworks/nist.ts +518 -0
- package/src/frameworks/pci.ts +226 -0
- package/src/frameworks/soc2.ts +281 -0
- package/src/iac/drift-detector.ts +197 -0
- package/src/iac/index.ts +3 -0
- package/src/iac/rules.ts +420 -0
- package/src/iac/scanner.ts +445 -0
- package/src/index.ts +17 -0
- package/src/pii/data-flow.ts +216 -0
- package/src/pii/detector.ts +327 -0
- package/src/pii/index.ts +3 -0
- package/src/pii/patterns.ts +128 -0
- package/src/policy/index.ts +5 -0
- 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();
|