mcp-wordpress 2.2.0 → 2.3.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/security/AISecurityScanner.d.ts +175 -0
- package/dist/security/AISecurityScanner.d.ts.map +1 -0
- package/dist/security/AISecurityScanner.js +645 -0
- package/dist/security/AISecurityScanner.js.map +1 -0
- package/dist/security/AutomatedRemediation.d.ts +145 -0
- package/dist/security/AutomatedRemediation.d.ts.map +1 -0
- package/dist/security/AutomatedRemediation.js +535 -0
- package/dist/security/AutomatedRemediation.js.map +1 -0
- package/dist/security/SecurityCIPipeline.d.ts +213 -0
- package/dist/security/SecurityCIPipeline.d.ts.map +1 -0
- package/dist/security/SecurityCIPipeline.js +684 -0
- package/dist/security/SecurityCIPipeline.js.map +1 -0
- package/dist/security/SecurityConfigManager.d.ts +294 -0
- package/dist/security/SecurityConfigManager.d.ts.map +1 -0
- package/dist/security/SecurityConfigManager.js +553 -0
- package/dist/security/SecurityConfigManager.js.map +1 -0
- package/dist/security/SecurityMonitoring.d.ts +245 -0
- package/dist/security/SecurityMonitoring.d.ts.map +1 -0
- package/dist/security/SecurityMonitoring.js +596 -0
- package/dist/security/SecurityMonitoring.js.map +1 -0
- package/dist/security/SecurityReviewer.d.ts +168 -0
- package/dist/security/SecurityReviewer.d.ts.map +1 -0
- package/dist/security/SecurityReviewer.js +683 -0
- package/dist/security/SecurityReviewer.js.map +1 -0
- package/dist/security/index.d.ts +182 -0
- package/dist/security/index.d.ts.map +1 -0
- package/dist/security/index.js +189 -0
- package/dist/security/index.js.map +1 -0
- package/package.json +8 -3
- package/src/security/AISecurityScanner.ts +780 -0
- package/src/security/AutomatedRemediation.ts +665 -0
- package/src/security/SecurityCIPipeline.ts +969 -0
- package/src/security/SecurityConfigManager.ts +829 -0
- package/src/security/SecurityMonitoring.ts +841 -0
- package/src/security/SecurityReviewer.ts +855 -0
- package/src/security/index.ts +249 -0
|
@@ -0,0 +1,665 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Automated Security Remediation System
|
|
3
|
+
* Provides intelligent automated fixes for detected vulnerabilities
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import * as fs from "fs/promises";
|
|
7
|
+
import * as path from "path";
|
|
8
|
+
import { SecurityVulnerability, SecurityScanResult } from "./AISecurityScanner";
|
|
9
|
+
import { SecurityUtils } from "./SecurityConfig";
|
|
10
|
+
import { SecurityValidationError } from "./InputValidator";
|
|
11
|
+
|
|
12
|
+
interface RemediationAction {
|
|
13
|
+
id: string;
|
|
14
|
+
type: "replace" | "insert" | "delete" | "config" | "file";
|
|
15
|
+
target: {
|
|
16
|
+
file?: string | undefined;
|
|
17
|
+
line?: number | undefined;
|
|
18
|
+
pattern?: RegExp | undefined;
|
|
19
|
+
value?: string | undefined;
|
|
20
|
+
};
|
|
21
|
+
replacement: {
|
|
22
|
+
content?: string;
|
|
23
|
+
config?: Record<string, any>;
|
|
24
|
+
action?: string;
|
|
25
|
+
};
|
|
26
|
+
backup: {
|
|
27
|
+
enabled: boolean;
|
|
28
|
+
path?: string;
|
|
29
|
+
};
|
|
30
|
+
validation: {
|
|
31
|
+
test?: string;
|
|
32
|
+
expected?: any;
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface RemediationResult {
|
|
37
|
+
vulnerabilityId: string;
|
|
38
|
+
success: boolean;
|
|
39
|
+
action: string;
|
|
40
|
+
details: string;
|
|
41
|
+
timestamp: Date;
|
|
42
|
+
backupPath?: string | undefined;
|
|
43
|
+
validationResult?: boolean | undefined;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
interface RemediationPlan {
|
|
47
|
+
planId: string;
|
|
48
|
+
vulnerabilities: SecurityVulnerability[];
|
|
49
|
+
actions: RemediationAction[];
|
|
50
|
+
estimatedDuration: number;
|
|
51
|
+
riskLevel: "low" | "medium" | "high";
|
|
52
|
+
requiresApproval: boolean;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Automated remediation patterns for common vulnerabilities
|
|
57
|
+
*/
|
|
58
|
+
const REMEDIATION_PATTERNS = {
|
|
59
|
+
sqlInjection: {
|
|
60
|
+
pattern: /(SELECT|INSERT|UPDATE|DELETE).*?[\'\"].*?[\'\"].*?(WHERE|FROM|INTO)/gi,
|
|
61
|
+
replacement: "// TODO: Replace with parameterized query",
|
|
62
|
+
confidence: 0.7,
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
xssSimple: {
|
|
66
|
+
pattern: /innerHTML\s*=\s*[^;]+;?/gi,
|
|
67
|
+
replacement: "textContent = $1; // XSS remediation",
|
|
68
|
+
confidence: 0.8,
|
|
69
|
+
},
|
|
70
|
+
|
|
71
|
+
pathTraversal: {
|
|
72
|
+
pattern: /\.\.[\/\\]/g,
|
|
73
|
+
replacement: "",
|
|
74
|
+
confidence: 0.9,
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
credentialExposure: {
|
|
78
|
+
pattern: /(password|secret|key|token)\s*[:=]\s*['"][^'"]+['"]/gi,
|
|
79
|
+
replacement: "$1 = process.env.$1?.toUpperCase() || '[REQUIRED]'",
|
|
80
|
+
confidence: 0.85,
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
httpToHttps: {
|
|
84
|
+
pattern: /http:\/\//gi,
|
|
85
|
+
replacement: "https://",
|
|
86
|
+
confidence: 0.95,
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
insecureConfig: {
|
|
90
|
+
pattern: /(ssl|secure|verify)\s*[:=]\s*false/gi,
|
|
91
|
+
replacement: "$1: true",
|
|
92
|
+
confidence: 0.9,
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Automated Security Remediation Engine
|
|
98
|
+
*/
|
|
99
|
+
export class AutomatedRemediation {
|
|
100
|
+
private remediationHistory: RemediationResult[] = [];
|
|
101
|
+
private backupDirectory = "security-backups";
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Create remediation plan for scan results
|
|
105
|
+
*/
|
|
106
|
+
async createRemediationPlan(scanResult: SecurityScanResult): Promise<RemediationPlan> {
|
|
107
|
+
const planId = SecurityUtils.generateSecureToken(16);
|
|
108
|
+
const remediableVulns = scanResult.vulnerabilities.filter((v) => v.remediation.automated);
|
|
109
|
+
|
|
110
|
+
console.log(`[Remediation] Creating plan for ${remediableVulns.length} remediable vulnerabilities`);
|
|
111
|
+
|
|
112
|
+
const actions: RemediationAction[] = [];
|
|
113
|
+
let estimatedDuration = 0;
|
|
114
|
+
let maxRiskLevel: "low" | "medium" | "high" = "low";
|
|
115
|
+
|
|
116
|
+
for (const vulnerability of remediableVulns) {
|
|
117
|
+
const action = await this.createRemediationAction(vulnerability);
|
|
118
|
+
if (action) {
|
|
119
|
+
actions.push(action);
|
|
120
|
+
estimatedDuration += 30; // 30 seconds per action estimate
|
|
121
|
+
|
|
122
|
+
// Update risk level
|
|
123
|
+
if (vulnerability.severity === "critical" || vulnerability.severity === "high") {
|
|
124
|
+
maxRiskLevel = "high";
|
|
125
|
+
} else if (vulnerability.severity === "medium" && maxRiskLevel === "low") {
|
|
126
|
+
maxRiskLevel = "medium";
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const requiresApproval = maxRiskLevel === "high" || actions.length > 10;
|
|
132
|
+
|
|
133
|
+
return {
|
|
134
|
+
planId,
|
|
135
|
+
vulnerabilities: remediableVulns,
|
|
136
|
+
actions,
|
|
137
|
+
estimatedDuration,
|
|
138
|
+
riskLevel: maxRiskLevel,
|
|
139
|
+
requiresApproval,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Create remediation action for a specific vulnerability
|
|
145
|
+
*/
|
|
146
|
+
private async createRemediationAction(vulnerability: SecurityVulnerability): Promise<RemediationAction | null> {
|
|
147
|
+
const actionId = SecurityUtils.generateSecureToken(12);
|
|
148
|
+
|
|
149
|
+
switch (vulnerability.type) {
|
|
150
|
+
case "SQL Injection":
|
|
151
|
+
return this.createSQLInjectionRemediation(actionId, vulnerability);
|
|
152
|
+
|
|
153
|
+
case "Cross-Site Scripting (XSS)":
|
|
154
|
+
return this.createXSSRemediation(actionId, vulnerability);
|
|
155
|
+
|
|
156
|
+
case "Path Traversal":
|
|
157
|
+
return this.createPathTraversalRemediation(actionId, vulnerability);
|
|
158
|
+
|
|
159
|
+
case "Credential Exposure":
|
|
160
|
+
return this.createCredentialExposureRemediation(actionId, vulnerability);
|
|
161
|
+
|
|
162
|
+
case "Insecure Configuration":
|
|
163
|
+
return this.createConfigRemediation(actionId, vulnerability);
|
|
164
|
+
|
|
165
|
+
case "Information Disclosure":
|
|
166
|
+
return this.createInfoDisclosureRemediation(actionId, vulnerability);
|
|
167
|
+
|
|
168
|
+
default:
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Create SQL injection remediation
|
|
175
|
+
*/
|
|
176
|
+
private createSQLInjectionRemediation(actionId: string, vulnerability: SecurityVulnerability): RemediationAction {
|
|
177
|
+
return {
|
|
178
|
+
id: actionId,
|
|
179
|
+
type: "replace",
|
|
180
|
+
target: {
|
|
181
|
+
file: vulnerability.location.file,
|
|
182
|
+
line: vulnerability.location.line,
|
|
183
|
+
pattern: REMEDIATION_PATTERNS.sqlInjection.pattern,
|
|
184
|
+
},
|
|
185
|
+
replacement: {
|
|
186
|
+
content:
|
|
187
|
+
"// SECURITY FIX: Replace with parameterized query to prevent SQL injection\n" +
|
|
188
|
+
"// Example: db.query('SELECT * FROM users WHERE id = ?', [userId])",
|
|
189
|
+
},
|
|
190
|
+
backup: {
|
|
191
|
+
enabled: true,
|
|
192
|
+
},
|
|
193
|
+
validation: {
|
|
194
|
+
test: 'grep -n "SELECT.*WHERE" $file | wc -l',
|
|
195
|
+
expected: 0,
|
|
196
|
+
},
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Create XSS remediation
|
|
202
|
+
*/
|
|
203
|
+
private createXSSRemediation(actionId: string, vulnerability: SecurityVulnerability): RemediationAction {
|
|
204
|
+
return {
|
|
205
|
+
id: actionId,
|
|
206
|
+
type: "replace",
|
|
207
|
+
target: {
|
|
208
|
+
file: vulnerability.location.file,
|
|
209
|
+
line: vulnerability.location.line,
|
|
210
|
+
pattern: /innerHTML\s*=\s*([^;]+);?/gi,
|
|
211
|
+
},
|
|
212
|
+
replacement: {
|
|
213
|
+
content: "textContent = $1; // XSS remediation: use textContent instead of innerHTML",
|
|
214
|
+
},
|
|
215
|
+
backup: {
|
|
216
|
+
enabled: true,
|
|
217
|
+
},
|
|
218
|
+
validation: {
|
|
219
|
+
test: 'grep -n "innerHTML" $file | wc -l',
|
|
220
|
+
expected: 0,
|
|
221
|
+
},
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Create path traversal remediation
|
|
227
|
+
*/
|
|
228
|
+
private createPathTraversalRemediation(actionId: string, vulnerability: SecurityVulnerability): RemediationAction {
|
|
229
|
+
return {
|
|
230
|
+
id: actionId,
|
|
231
|
+
type: "replace",
|
|
232
|
+
target: {
|
|
233
|
+
file: vulnerability.location.file,
|
|
234
|
+
line: vulnerability.location.line,
|
|
235
|
+
pattern: /\.\.[\/\\]/g,
|
|
236
|
+
},
|
|
237
|
+
replacement: {
|
|
238
|
+
content: "", // Remove path traversal sequences
|
|
239
|
+
},
|
|
240
|
+
backup: {
|
|
241
|
+
enabled: true,
|
|
242
|
+
},
|
|
243
|
+
validation: {
|
|
244
|
+
test: 'grep -n "\\.\\./" $file | wc -l',
|
|
245
|
+
expected: 0,
|
|
246
|
+
},
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Create credential exposure remediation
|
|
252
|
+
*/
|
|
253
|
+
private createCredentialExposureRemediation(
|
|
254
|
+
actionId: string,
|
|
255
|
+
vulnerability: SecurityVulnerability,
|
|
256
|
+
): RemediationAction {
|
|
257
|
+
return {
|
|
258
|
+
id: actionId,
|
|
259
|
+
type: "replace",
|
|
260
|
+
target: {
|
|
261
|
+
file: vulnerability.location.file,
|
|
262
|
+
line: vulnerability.location.line,
|
|
263
|
+
pattern: /(password|secret|key|token)\s*[:=]\s*['"][^'"]+['"]/gi,
|
|
264
|
+
},
|
|
265
|
+
replacement: {
|
|
266
|
+
content:
|
|
267
|
+
"// SECURITY FIX: Credential moved to environment variable\n" +
|
|
268
|
+
"// Add to .env: $1=your_actual_value\n" +
|
|
269
|
+
"$1: process.env.$1 || (() => { throw new Error('$1 environment variable required'); })()",
|
|
270
|
+
},
|
|
271
|
+
backup: {
|
|
272
|
+
enabled: true,
|
|
273
|
+
},
|
|
274
|
+
validation: {
|
|
275
|
+
test: 'grep -n "password.*=.*[\'\\"]" $file | wc -l',
|
|
276
|
+
expected: 0,
|
|
277
|
+
},
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Create configuration remediation
|
|
283
|
+
*/
|
|
284
|
+
private createConfigRemediation(actionId: string, vulnerability: SecurityVulnerability): RemediationAction {
|
|
285
|
+
return {
|
|
286
|
+
id: actionId,
|
|
287
|
+
type: "replace",
|
|
288
|
+
target: {
|
|
289
|
+
file: vulnerability.location.file,
|
|
290
|
+
line: vulnerability.location.line,
|
|
291
|
+
pattern: /(ssl|secure|verify)\s*[:=]\s*false/gi,
|
|
292
|
+
},
|
|
293
|
+
replacement: {
|
|
294
|
+
content: "$1: true // Security fix: enabled secure configuration",
|
|
295
|
+
},
|
|
296
|
+
backup: {
|
|
297
|
+
enabled: true,
|
|
298
|
+
},
|
|
299
|
+
validation: {
|
|
300
|
+
test: 'grep -n "ssl.*false" $file | wc -l',
|
|
301
|
+
expected: 0,
|
|
302
|
+
},
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Create information disclosure remediation
|
|
308
|
+
*/
|
|
309
|
+
private createInfoDisclosureRemediation(actionId: string, vulnerability: SecurityVulnerability): RemediationAction {
|
|
310
|
+
return {
|
|
311
|
+
id: actionId,
|
|
312
|
+
type: "replace",
|
|
313
|
+
target: {
|
|
314
|
+
file: vulnerability.location.file,
|
|
315
|
+
line: vulnerability.location.line,
|
|
316
|
+
pattern: /(debug|trace|error)\s*[:=]\s*true/gi,
|
|
317
|
+
},
|
|
318
|
+
replacement: {
|
|
319
|
+
content: "$1: process.env.NODE_ENV !== 'production' // Security fix: disable in production",
|
|
320
|
+
},
|
|
321
|
+
backup: {
|
|
322
|
+
enabled: true,
|
|
323
|
+
},
|
|
324
|
+
validation: {
|
|
325
|
+
test: 'grep -n "debug.*true" $file | wc -l',
|
|
326
|
+
expected: 0,
|
|
327
|
+
},
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Execute remediation plan
|
|
333
|
+
*/
|
|
334
|
+
async executeRemediationPlan(
|
|
335
|
+
plan: RemediationPlan,
|
|
336
|
+
options: {
|
|
337
|
+
dryRun?: boolean;
|
|
338
|
+
requireConfirmation?: boolean;
|
|
339
|
+
} = {},
|
|
340
|
+
): Promise<RemediationResult[]> {
|
|
341
|
+
console.log(`[Remediation] Executing plan ${plan.planId} with ${plan.actions.length} actions`);
|
|
342
|
+
|
|
343
|
+
if (options.dryRun) {
|
|
344
|
+
console.log("[Remediation] DRY RUN MODE - No changes will be made");
|
|
345
|
+
return this.simulateRemediationActions(plan.actions);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
const results: RemediationResult[] = [];
|
|
349
|
+
|
|
350
|
+
// Ensure backup directory exists
|
|
351
|
+
await this.ensureBackupDirectory();
|
|
352
|
+
|
|
353
|
+
for (const action of plan.actions) {
|
|
354
|
+
try {
|
|
355
|
+
console.log(`[Remediation] Executing action ${action.id} of type ${action.type}`);
|
|
356
|
+
|
|
357
|
+
const result = await this.executeRemediationAction(action);
|
|
358
|
+
results.push(result);
|
|
359
|
+
|
|
360
|
+
if (!result.success) {
|
|
361
|
+
console.error(`[Remediation] Action ${action.id} failed: ${result.details}`);
|
|
362
|
+
}
|
|
363
|
+
} catch (error) {
|
|
364
|
+
console.error(`[Remediation] Action ${action.id} threw error:`, error);
|
|
365
|
+
results.push({
|
|
366
|
+
vulnerabilityId: action.id,
|
|
367
|
+
success: false,
|
|
368
|
+
action: action.type,
|
|
369
|
+
details: `Error: ${error instanceof Error ? error.message : String(error)}`,
|
|
370
|
+
timestamp: new Date(),
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
this.remediationHistory.push(...results);
|
|
376
|
+
|
|
377
|
+
const successCount = results.filter((r) => r.success).length;
|
|
378
|
+
console.log(`[Remediation] Plan completed: ${successCount}/${results.length} actions successful`);
|
|
379
|
+
|
|
380
|
+
return results;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Execute a single remediation action
|
|
385
|
+
*/
|
|
386
|
+
private async executeRemediationAction(action: RemediationAction): Promise<RemediationResult> {
|
|
387
|
+
const startTime = Date.now();
|
|
388
|
+
|
|
389
|
+
try {
|
|
390
|
+
let backupPath: string | undefined;
|
|
391
|
+
|
|
392
|
+
// Create backup if enabled
|
|
393
|
+
if (action.backup.enabled && action.target.file) {
|
|
394
|
+
backupPath = await this.createBackup(action.target.file);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// Execute the action based on type
|
|
398
|
+
switch (action.type) {
|
|
399
|
+
case "replace":
|
|
400
|
+
await this.executeReplaceAction(action);
|
|
401
|
+
break;
|
|
402
|
+
|
|
403
|
+
case "insert":
|
|
404
|
+
await this.executeInsertAction(action);
|
|
405
|
+
break;
|
|
406
|
+
|
|
407
|
+
case "delete":
|
|
408
|
+
await this.executeDeleteAction(action);
|
|
409
|
+
break;
|
|
410
|
+
|
|
411
|
+
case "config":
|
|
412
|
+
await this.executeConfigAction(action);
|
|
413
|
+
break;
|
|
414
|
+
|
|
415
|
+
case "file":
|
|
416
|
+
await this.executeFileAction(action);
|
|
417
|
+
break;
|
|
418
|
+
|
|
419
|
+
default:
|
|
420
|
+
throw new Error(`Unknown action type: ${action.type}`);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// Validate the fix if validation is provided
|
|
424
|
+
let validationResult: boolean | undefined;
|
|
425
|
+
if (action.validation && action.target.file) {
|
|
426
|
+
validationResult = await this.validateRemediation(action);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
return {
|
|
430
|
+
vulnerabilityId: action.id,
|
|
431
|
+
success: true,
|
|
432
|
+
action: action.type,
|
|
433
|
+
details: `Remediation completed successfully in ${Date.now() - startTime}ms`,
|
|
434
|
+
timestamp: new Date(),
|
|
435
|
+
backupPath,
|
|
436
|
+
validationResult,
|
|
437
|
+
};
|
|
438
|
+
} catch (error) {
|
|
439
|
+
return {
|
|
440
|
+
vulnerabilityId: action.id,
|
|
441
|
+
success: false,
|
|
442
|
+
action: action.type,
|
|
443
|
+
details: `Remediation failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
444
|
+
timestamp: new Date(),
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* Execute replace action
|
|
451
|
+
*/
|
|
452
|
+
private async executeReplaceAction(action: RemediationAction): Promise<void> {
|
|
453
|
+
if (!action.target.file || !action.target.pattern || !action.replacement.content) {
|
|
454
|
+
throw new Error("Replace action missing required fields");
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
const content = await fs.readFile(action.target.file, "utf-8");
|
|
458
|
+
const updatedContent = content.replace(action.target.pattern, action.replacement.content);
|
|
459
|
+
|
|
460
|
+
if (content === updatedContent) {
|
|
461
|
+
throw new Error("No changes made - pattern not found");
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
await fs.writeFile(action.target.file, updatedContent, "utf-8");
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* Execute insert action
|
|
469
|
+
*/
|
|
470
|
+
private async executeInsertAction(action: RemediationAction): Promise<void> {
|
|
471
|
+
if (!action.target.file || !action.replacement.content) {
|
|
472
|
+
throw new Error("Insert action missing required fields");
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
const content = await fs.readFile(action.target.file, "utf-8");
|
|
476
|
+
const lines = content.split("\n");
|
|
477
|
+
|
|
478
|
+
const insertIndex = action.target.line ? Math.max(0, action.target.line - 1) : lines.length;
|
|
479
|
+
lines.splice(insertIndex, 0, action.replacement.content);
|
|
480
|
+
|
|
481
|
+
await fs.writeFile(action.target.file, lines.join("\n"), "utf-8");
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* Execute delete action
|
|
486
|
+
*/
|
|
487
|
+
private async executeDeleteAction(action: RemediationAction): Promise<void> {
|
|
488
|
+
if (!action.target.file) {
|
|
489
|
+
throw new Error("Delete action missing file");
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
if (action.target.line) {
|
|
493
|
+
// Delete specific line
|
|
494
|
+
const content = await fs.readFile(action.target.file, "utf-8");
|
|
495
|
+
const lines = content.split("\n");
|
|
496
|
+
|
|
497
|
+
if (action.target.line > 0 && action.target.line <= lines.length) {
|
|
498
|
+
lines.splice(action.target.line - 1, 1);
|
|
499
|
+
await fs.writeFile(action.target.file, lines.join("\n"), "utf-8");
|
|
500
|
+
}
|
|
501
|
+
} else if (action.target.pattern) {
|
|
502
|
+
// Delete pattern matches
|
|
503
|
+
const content = await fs.readFile(action.target.file, "utf-8");
|
|
504
|
+
const updatedContent = content.replace(action.target.pattern, "");
|
|
505
|
+
await fs.writeFile(action.target.file, updatedContent, "utf-8");
|
|
506
|
+
} else {
|
|
507
|
+
throw new Error("Delete action missing target specification");
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/**
|
|
512
|
+
* Execute config action
|
|
513
|
+
*/
|
|
514
|
+
private async executeConfigAction(action: RemediationAction): Promise<void> {
|
|
515
|
+
if (!action.target.file || !action.replacement.config) {
|
|
516
|
+
throw new Error("Config action missing required fields");
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
const content = await fs.readFile(action.target.file, "utf-8");
|
|
520
|
+
const config = JSON.parse(content);
|
|
521
|
+
|
|
522
|
+
// Merge configuration changes
|
|
523
|
+
Object.assign(config, action.replacement.config);
|
|
524
|
+
|
|
525
|
+
await fs.writeFile(action.target.file, JSON.stringify(config, null, 2), "utf-8");
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
/**
|
|
529
|
+
* Execute file action
|
|
530
|
+
*/
|
|
531
|
+
private async executeFileAction(action: RemediationAction): Promise<void> {
|
|
532
|
+
if (!action.replacement.action) {
|
|
533
|
+
throw new Error("File action missing action specification");
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
switch (action.replacement.action) {
|
|
537
|
+
case "delete":
|
|
538
|
+
if (action.target.file) {
|
|
539
|
+
await fs.unlink(action.target.file);
|
|
540
|
+
}
|
|
541
|
+
break;
|
|
542
|
+
|
|
543
|
+
case "create":
|
|
544
|
+
if (action.target.file && action.replacement.content) {
|
|
545
|
+
await fs.writeFile(action.target.file, action.replacement.content, "utf-8");
|
|
546
|
+
}
|
|
547
|
+
break;
|
|
548
|
+
|
|
549
|
+
default:
|
|
550
|
+
throw new Error(`Unknown file action: ${action.replacement.action}`);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
/**
|
|
555
|
+
* Create backup of file
|
|
556
|
+
*/
|
|
557
|
+
private async createBackup(filePath: string): Promise<string> {
|
|
558
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
559
|
+
const backupPath = path.join(this.backupDirectory, `${path.basename(filePath)}.${timestamp}.backup`);
|
|
560
|
+
|
|
561
|
+
const content = await fs.readFile(filePath, "utf-8");
|
|
562
|
+
await fs.writeFile(backupPath, content, "utf-8");
|
|
563
|
+
|
|
564
|
+
console.log(`[Remediation] Created backup: ${backupPath}`);
|
|
565
|
+
return backupPath;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
/**
|
|
569
|
+
* Ensure backup directory exists
|
|
570
|
+
*/
|
|
571
|
+
private async ensureBackupDirectory(): Promise<void> {
|
|
572
|
+
try {
|
|
573
|
+
await fs.access(this.backupDirectory);
|
|
574
|
+
} catch {
|
|
575
|
+
await fs.mkdir(this.backupDirectory, { recursive: true });
|
|
576
|
+
console.log(`[Remediation] Created backup directory: ${this.backupDirectory}`);
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
/**
|
|
581
|
+
* Validate remediation
|
|
582
|
+
*/
|
|
583
|
+
private async validateRemediation(action: RemediationAction): Promise<boolean> {
|
|
584
|
+
if (!action.validation?.test || !action.target.file) {
|
|
585
|
+
return true; // No validation specified
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
try {
|
|
589
|
+
// This is a simplified validation - in practice you'd run the test command
|
|
590
|
+
const content = await fs.readFile(action.target.file, "utf-8");
|
|
591
|
+
|
|
592
|
+
// Simple pattern-based validation
|
|
593
|
+
if (action.target.pattern) {
|
|
594
|
+
const matches = content.match(action.target.pattern);
|
|
595
|
+
return (matches?.length || 0) === (action.validation.expected || 0);
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
return true;
|
|
599
|
+
} catch (error) {
|
|
600
|
+
console.warn(`[Remediation] Validation failed for action ${action.id}:`, error);
|
|
601
|
+
return false;
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
/**
|
|
606
|
+
* Simulate remediation actions for dry run
|
|
607
|
+
*/
|
|
608
|
+
private async simulateRemediationActions(actions: RemediationAction[]): Promise<RemediationResult[]> {
|
|
609
|
+
const results: RemediationResult[] = [];
|
|
610
|
+
|
|
611
|
+
for (const action of actions) {
|
|
612
|
+
results.push({
|
|
613
|
+
vulnerabilityId: action.id,
|
|
614
|
+
success: true,
|
|
615
|
+
action: `${action.type} (simulated)`,
|
|
616
|
+
details: `Would ${action.type} in ${action.target.file || "configuration"}`,
|
|
617
|
+
timestamp: new Date(),
|
|
618
|
+
});
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
return results;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
/**
|
|
625
|
+
* Get remediation history
|
|
626
|
+
*/
|
|
627
|
+
getRemediationHistory(): RemediationResult[] {
|
|
628
|
+
return [...this.remediationHistory];
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
/**
|
|
632
|
+
* Rollback remediation using backup
|
|
633
|
+
*/
|
|
634
|
+
async rollbackRemediation(backupPath: string, targetFile: string): Promise<void> {
|
|
635
|
+
try {
|
|
636
|
+
const backupContent = await fs.readFile(backupPath, "utf-8");
|
|
637
|
+
await fs.writeFile(targetFile, backupContent, "utf-8");
|
|
638
|
+
console.log(`[Remediation] Rolled back ${targetFile} from ${backupPath}`);
|
|
639
|
+
} catch (error) {
|
|
640
|
+
throw new SecurityValidationError(`Rollback failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
/**
|
|
645
|
+
* Clean up old backups
|
|
646
|
+
*/
|
|
647
|
+
async cleanupBackups(maxAge: number = 7 * 24 * 60 * 60 * 1000): Promise<void> {
|
|
648
|
+
try {
|
|
649
|
+
const files = await fs.readdir(this.backupDirectory);
|
|
650
|
+
const now = Date.now();
|
|
651
|
+
|
|
652
|
+
for (const file of files) {
|
|
653
|
+
const filePath = path.join(this.backupDirectory, file);
|
|
654
|
+
const stats = await fs.stat(filePath);
|
|
655
|
+
|
|
656
|
+
if (now - stats.mtime.getTime() > maxAge) {
|
|
657
|
+
await fs.unlink(filePath);
|
|
658
|
+
console.log(`[Remediation] Cleaned up old backup: ${file}`);
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
} catch (error) {
|
|
662
|
+
console.warn("[Remediation] Backup cleanup failed:", error);
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
}
|