@greenarmor/ges-mcp-server 0.5.5 → 0.6.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 (3) hide show
  1. package/bundle/server.js +3084 -113
  2. package/dist/server.js +1619 -51
  3. package/package.json +9 -6
package/dist/server.js CHANGED
@@ -1,22 +1,35 @@
1
1
  #!/usr/bin/env node
2
2
  import * as readline from "node:readline";
3
- import { getAllPacks, getPacksForProjectType } from "@greenarmor/ges-policy-engine";
3
+ import * as fs from "node:fs";
4
+ import * as path from "node:path";
5
+ import { getAllPacks, getPacksForProjectType, getPack } from "@greenarmor/ges-policy-engine";
4
6
  import { generateScoreFile, formatScoreOutput } from "@greenarmor/ges-scoring-engine";
7
+ import { runAudit, deduplicateFindings } from "@greenarmor/ges-audit-engine";
5
8
  import { GESF_VERSION } from "@greenarmor/ges-core";
6
9
  const TOOLS = [
7
10
  {
8
11
  name: "check_compliance",
9
- description: "Check GDPR compliance status for a project",
12
+ description: "Check GDPR compliance status for a project. Returns compliance scores per framework (GDPR, OWASP, CIS, NIST) with grades and control breakdown.",
10
13
  inputSchema: {
11
14
  type: "object",
12
15
  properties: {
13
- project_type: { type: "string", description: "Project type" },
16
+ project_type: { type: "string", description: "Project type (saas, ai-application, mcp-server, blockchain, wallet, government-system, healthcare-system, event-platform, photo-storage-platform, vulnerability-scanner, generic-web-application, api-backend, mobile-application)" },
17
+ },
18
+ },
19
+ },
20
+ {
21
+ name: "check_project_status",
22
+ description: "Read the actual project's .ges/ directory to get real-time compliance status, scores, config, and audit results. Use this when the project has already been initialized with 'ges init'.",
23
+ inputSchema: {
24
+ type: "object",
25
+ properties: {
26
+ project_path: { type: "string", description: "Absolute path to the project root. Defaults to current working directory." },
14
27
  },
15
28
  },
16
29
  },
17
30
  {
18
31
  name: "list_missing_controls",
19
- description: "Show missing compliance controls",
32
+ description: "Show missing or failed compliance controls for a given framework. Returns control ID, severity, name, and implementation guidance.",
20
33
  inputSchema: {
21
34
  type: "object",
22
35
  properties: {
@@ -26,11 +39,72 @@ const TOOLS = [
26
39
  },
27
40
  framework: {
28
41
  type: "string",
29
- description: "Framework name (GDPR, OWASP, etc.)",
42
+ description: "Framework name (GDPR, OWASP, CIS, NIST)",
30
43
  },
31
44
  },
32
45
  },
33
46
  },
47
+ {
48
+ name: "list_framework_controls",
49
+ description: "List all controls for a given framework with their status, severity, category, and implementation guidance. Useful for understanding the full control landscape.",
50
+ inputSchema: {
51
+ type: "object",
52
+ properties: {
53
+ framework: {
54
+ type: "string",
55
+ description: "Framework name (GDPR, OWASP, CIS, NIST, AI, blockchain, government)",
56
+ },
57
+ status_filter: {
58
+ type: "string",
59
+ description: "Filter by status (pass, fail, warning, not-implemented, not-applicable). Omit to show all.",
60
+ },
61
+ },
62
+ },
63
+ },
64
+ {
65
+ name: "run_audit",
66
+ description: "Run a full source code security audit on the project. Scans for secrets, weak cryptography, injection vulnerabilities, auth issues, config problems, and database anti-patterns. Returns findings with severity, file location, evidence, and fix guidance.",
67
+ inputSchema: {
68
+ type: "object",
69
+ properties: {
70
+ project_path: { type: "string", description: "Absolute path to the project root to audit." },
71
+ },
72
+ },
73
+ },
74
+ {
75
+ name: "generate_compliance_report",
76
+ description: "Generate a full compliance report with executive summary, findings, framework scores, risk assessment, security controls, and actionable recommendations. The primary report tool for compliance status.",
77
+ inputSchema: {
78
+ type: "object",
79
+ properties: {
80
+ project_type: { type: "string", description: "Project type" },
81
+ project_name: { type: "string", description: "Project name" },
82
+ frameworks: { type: "string", description: "Comma-separated framework names (GDPR,OWASP,CIS,NIST)" },
83
+ },
84
+ },
85
+ },
86
+ {
87
+ name: "generate_audit_report",
88
+ description: "Generate a report from actual source code audit findings. Combines audit results with compliance scoring and detailed recommendations for each finding. Requires a project path.",
89
+ inputSchema: {
90
+ type: "object",
91
+ properties: {
92
+ project_path: { type: "string", description: "Absolute path to the project root to audit and report on." },
93
+ project_name: { type: "string", description: "Project name for the report title." },
94
+ },
95
+ },
96
+ },
97
+ {
98
+ name: "fix_recommendation",
99
+ description: "Get detailed step-by-step remediation guidance for a specific control or finding. Provides implementation steps, code examples, and verification steps. Use this to fix issues one by one.",
100
+ inputSchema: {
101
+ type: "object",
102
+ properties: {
103
+ control_id: { type: "string", description: "Control ID to get fix guidance for (e.g. GDPR-ART32-001, OWASP-AUTH-001)" },
104
+ finding_title: { type: "string", description: "Title of a specific audit finding to get fix guidance for." },
105
+ },
106
+ },
107
+ },
34
108
  {
35
109
  name: "generate_retention_policy",
36
110
  description: "Generate a data retention policy template",
@@ -71,10 +145,1017 @@ const TOOLS = [
71
145
  },
72
146
  },
73
147
  },
148
+ {
149
+ name: "generate_data_inventory",
150
+ description: "Generate a data inventory document listing data categories, classifications, retention periods, and legal basis. Required for GDPR Article 30 compliance.",
151
+ inputSchema: {
152
+ type: "object",
153
+ properties: {
154
+ project_name: { type: "string", description: "Project name" },
155
+ project_type: { type: "string", description: "Project type" },
156
+ },
157
+ },
158
+ },
159
+ {
160
+ name: "generate_processing_records",
161
+ description: "Generate Article 30 Records of Processing Activities (ROPA). Documents all processing activities, purposes, data categories, recipients, and retention periods.",
162
+ inputSchema: {
163
+ type: "object",
164
+ properties: {
165
+ project_name: { type: "string", description: "Project name" },
166
+ controller_name: { type: "string", description: "Data controller organization name" },
167
+ },
168
+ },
169
+ },
170
+ {
171
+ name: "auto_fix",
172
+ description: "Run an audit and automatically fix all fixable security/compliance issues in the project source code. Creates files, modifies source, generates security scaffolding. Returns a detailed report of what was fixed and what requires manual review.",
173
+ inputSchema: {
174
+ type: "object",
175
+ properties: {
176
+ project_path: { type: "string", description: "Absolute path to the project root." },
177
+ dry_run: { type: "boolean", description: "If true, show what would be fixed without making changes. Default: false." },
178
+ rule_ids: { type: "string", description: "Comma-separated rule IDs to fix (e.g. 'CONFIG-001,AUTH-002'). Omit to fix all auto-fixable issues." },
179
+ },
180
+ },
181
+ },
182
+ {
183
+ name: "apply_control_override",
184
+ description: "Mark a compliance control as not-applicable, pass, or another status in the project's .ges/control-overrides.json. Use this when a control doesn't apply to the project or has been verified manually.",
185
+ inputSchema: {
186
+ type: "object",
187
+ properties: {
188
+ project_path: { type: "string", description: "Absolute path to the project root." },
189
+ control_id: { type: "string", description: "Control ID to override (e.g. GDPR-ART32-004)" },
190
+ status: { type: "string", description: "New status: 'not-applicable' or 'pass'" },
191
+ reason: { type: "string", description: "Reason for the override" },
192
+ },
193
+ },
194
+ },
195
+ {
196
+ name: "implement_control",
197
+ description: "Generate and write actual implementation files for a compliance control into the target project. Creates source files, configuration, and middleware. Returns what was created and next steps.",
198
+ inputSchema: {
199
+ type: "object",
200
+ properties: {
201
+ project_path: { type: "string", description: "Absolute path to the project root." },
202
+ control_id: { type: "string", description: "Control ID to implement (e.g. GDPR-ART32-002, GDPR-ART32-006, AUTH-002)" },
203
+ },
204
+ },
205
+ },
74
206
  ];
75
207
  function send(message) {
76
208
  process.stdout.write(JSON.stringify(message) + "\n");
77
209
  }
210
+ function resolveProjectPath(projectPath) {
211
+ return projectPath || process.cwd();
212
+ }
213
+ function readJsonFileSafe(filePath) {
214
+ try {
215
+ const content = fs.readFileSync(filePath, "utf-8");
216
+ return JSON.parse(content);
217
+ }
218
+ catch {
219
+ return null;
220
+ }
221
+ }
222
+ function loadProjectConfig(projectPath) {
223
+ const gesDir = path.join(projectPath, ".ges");
224
+ const config = readJsonFileSafe(path.join(gesDir, "config.json"));
225
+ const score = readJsonFileSafe(path.join(gesDir, "score.json"));
226
+ const overrides = readJsonFileSafe(path.join(gesDir, "control-overrides.json"));
227
+ return {
228
+ config,
229
+ score,
230
+ overrides: Array.isArray(overrides) ? overrides : [],
231
+ };
232
+ }
233
+ function applyControlOverrides(controls, overrides) {
234
+ if (overrides.length === 0)
235
+ return controls;
236
+ const overrideMap = new Map(overrides.map(o => [o.control_id, o]));
237
+ return controls.map(control => {
238
+ const override = overrideMap.get(control.id);
239
+ if (!override)
240
+ return control;
241
+ return {
242
+ ...control,
243
+ status: override.status,
244
+ checks: control.checks.map(check => ({ ...check, status: override.status })),
245
+ };
246
+ });
247
+ }
248
+ function updateControlsFromFindings(controls, findings) {
249
+ return controls.map(control => {
250
+ if (control.status === "pass" || control.status === "not-applicable")
251
+ return control;
252
+ const relevantFindings = findings.filter(f => f.controlIds && f.controlIds.includes(control.id));
253
+ if (relevantFindings.length === 0)
254
+ return control;
255
+ const hasCritical = relevantFindings.some(f => f.severity === "critical" || f.severity === "high");
256
+ return {
257
+ ...control,
258
+ status: hasCritical ? "fail" : "warning",
259
+ checks: control.checks.map(check => ({
260
+ ...check,
261
+ status: hasCritical ? "fail" : "warning",
262
+ })),
263
+ };
264
+ });
265
+ }
266
+ function getControlsForProject(projectType, frameworks) {
267
+ const projectPacks = getPacksForProjectType(projectType);
268
+ const packIds = new Set(projectPacks.map(p => p.id));
269
+ const fwLower = new Set(frameworks.map(f => f.toLowerCase()));
270
+ const allPacks = getAllPacks();
271
+ for (const p of allPacks) {
272
+ if (fwLower.has(p.id))
273
+ packIds.add(p.id);
274
+ }
275
+ return allPacks.filter(p => packIds.has(p.id)).flatMap(p => p.controls);
276
+ }
277
+ function generateFullComplianceReport(projectName, projectType, frameworks, findings, overrides) {
278
+ const controls = getControlsForProject(projectType, frameworks);
279
+ const overriddenControls = applyControlOverrides(controls, overrides || []);
280
+ const auditedControls = findings ? updateControlsFromFindings(overriddenControls, findings) : overriddenControls;
281
+ const score = generateScoreFile(auditedControls, frameworks, findings);
282
+ const sections = [];
283
+ sections.push(`# Compliance Report - ${projectName}`);
284
+ sections.push(`\nGenerated: ${new Date().toISOString()}`);
285
+ sections.push(`Project Type: ${projectType}`);
286
+ sections.push(`Frameworks: ${frameworks.join(", ")}\n`);
287
+ sections.push("## Executive Summary\n");
288
+ sections.push(`**Overall Score: ${score.overall}% (Grade: ${score.overall_grade})**\n`);
289
+ sections.push("| Framework | Score | Grade | Passed | Failed | Warnings | Critical Failures |");
290
+ sections.push("|-----------|-------|-------|--------|--------|----------|-------------------|");
291
+ for (const [fw, data] of Object.entries(score.frameworks)) {
292
+ sections.push(`| ${fw} | ${data.score}% | ${data.grade} | ${data.passed_controls} | ${data.failed_controls} | ${data.warning_controls} | ${data.critical_failures} |`);
293
+ }
294
+ if (findings && findings.length > 0) {
295
+ sections.push(`\n**Security Findings**: ${findings.length} total`);
296
+ const crit = findings.filter(f => f.severity === "critical").length;
297
+ const high = findings.filter(f => f.severity === "high").length;
298
+ sections.push(`- Critical: ${crit}, High: ${high}`);
299
+ }
300
+ if (score.audit_impact) {
301
+ const ai = score.audit_impact;
302
+ sections.push(`\n**Audit Impact**: -${ai.total_deduction}% deduction`);
303
+ }
304
+ if (findings && findings.length > 0) {
305
+ sections.push("\n## Security Findings\n");
306
+ const grouped = {};
307
+ for (const f of findings) {
308
+ if (!grouped[f.category])
309
+ grouped[f.category] = [];
310
+ grouped[f.category].push(f);
311
+ }
312
+ for (const [category, categoryFindings] of Object.entries(grouped)) {
313
+ sections.push(`### ${category.charAt(0).toUpperCase() + category.slice(1)}\n`);
314
+ sections.push("| Severity | Title | File | Fix |");
315
+ sections.push("|----------|-------|------|-----|");
316
+ for (const f of categoryFindings) {
317
+ const loc = f.file !== "project" ? `${f.file}${f.line ? `:${f.line}` : ""}` : "project-wide";
318
+ sections.push(`| ${f.severity} | ${f.title} | ${loc} | ${f.fix.slice(0, 80)} |`);
319
+ }
320
+ sections.push("");
321
+ }
322
+ }
323
+ sections.push("\n## Compliance Details\n");
324
+ for (const [fw, data] of Object.entries(score.frameworks)) {
325
+ sections.push(`### ${fw} - ${data.score}% (Grade: ${data.grade})\n`);
326
+ sections.push(`- Total Controls: ${data.total_controls}`);
327
+ sections.push(`- Passed: ${data.passed_controls}`);
328
+ sections.push(`- Failed: ${data.failed_controls}`);
329
+ sections.push(`- Warnings: ${data.warning_controls}`);
330
+ sections.push(`- Not Implemented: ${data.not_implemented}`);
331
+ sections.push(`- Critical Failures: ${data.critical_failures}`);
332
+ const sb = data.severity_breakdown;
333
+ sections.push("\n**Severity Breakdown:**");
334
+ sections.push("| Level | Total | Passed | Failed | Warning | Not Implemented |");
335
+ sections.push("|-------|-------|--------|--------|---------|-----------------|");
336
+ if (sb.critical.total > 0)
337
+ sections.push(`| Critical | ${sb.critical.total} | ${sb.critical.passed} | ${sb.critical.failed} | ${sb.critical.warning} | ${sb.critical.not_implemented} |`);
338
+ if (sb.high.total > 0)
339
+ sections.push(`| High | ${sb.high.total} | ${sb.high.passed} | ${sb.high.failed} | ${sb.high.warning} | ${sb.high.not_implemented} |`);
340
+ if (sb.medium.total > 0)
341
+ sections.push(`| Medium | ${sb.medium.total} | ${sb.medium.passed} | ${sb.medium.failed} | ${sb.medium.warning} | ${sb.medium.not_implemented} |`);
342
+ if (sb.low.total > 0)
343
+ sections.push(`| Low | ${sb.low.total} | ${sb.low.passed} | ${sb.low.failed} | ${sb.low.warning} | ${sb.low.not_implemented} |`);
344
+ sections.push("");
345
+ }
346
+ sections.push(generateRecommendations(auditedControls, findings));
347
+ return sections.join("\n");
348
+ }
349
+ function generateRecommendations(controls, findings) {
350
+ const lines = ["## Recommendations\n"];
351
+ const failedControls = controls.filter(c => c.status === "fail");
352
+ const criticalFails = failedControls.filter(c => c.severity === "critical");
353
+ const highFails = failedControls.filter(c => c.severity === "high");
354
+ const warningControls = controls.filter(c => c.status === "warning");
355
+ const notImplemented = controls.filter(c => c.status === "not-implemented");
356
+ if (criticalFails.length > 0) {
357
+ lines.push("### Critical Actions Required\n");
358
+ for (const c of criticalFails) {
359
+ lines.push(`**${c.id}** (${c.severity}): ${c.name}`);
360
+ lines.push(` Category: ${c.category}`);
361
+ lines.push(` Guidance: ${c.implementation_guidance}`);
362
+ lines.push(` Fix: Use \`fix_recommendation\` tool with control_id="${c.id}" for detailed steps.\n`);
363
+ }
364
+ }
365
+ if (highFails.length > 0) {
366
+ lines.push("### High Priority Actions\n");
367
+ for (const c of highFails) {
368
+ lines.push(`**${c.id}** (${c.severity}): ${c.name}`);
369
+ lines.push(` Category: ${c.category}`);
370
+ lines.push(` Guidance: ${c.implementation_guidance}\n`);
371
+ }
372
+ }
373
+ if (findings && findings.length > 0) {
374
+ const critFindings = findings.filter(f => f.severity === "critical");
375
+ const highFindings = findings.filter(f => f.severity === "high");
376
+ if (critFindings.length > 0) {
377
+ lines.push("### Immediate Security Fixes\n");
378
+ for (const f of critFindings) {
379
+ lines.push(`- **[${f.severity.toUpperCase()}] ${f.title}** (${f.file}${f.line ? `:${f.line}` : ""})`);
380
+ lines.push(` Evidence: ${f.evidence}`);
381
+ lines.push(` Fix: ${f.fix}`);
382
+ if (f.controlIds && f.controlIds.length > 0) {
383
+ lines.push(` Related controls: ${f.controlIds.join(", ")}`);
384
+ }
385
+ lines.push("");
386
+ }
387
+ }
388
+ if (highFindings.length > 0 && critFindings.length === 0) {
389
+ lines.push("### Security Fixes Needed\n");
390
+ for (const f of highFindings) {
391
+ lines.push(`- **[${f.severity.toUpperCase()}] ${f.title}** (${f.file}${f.line ? `:${f.line}` : ""})`);
392
+ lines.push(` Fix: ${f.fix}\n`);
393
+ }
394
+ }
395
+ }
396
+ if (warningControls.length > 0) {
397
+ lines.push("### Warnings to Address\n");
398
+ for (const c of warningControls.slice(0, 10)) {
399
+ lines.push(`- **${c.id}** (${c.severity}): ${c.name} — ${c.implementation_guidance.split(".")[0]}`);
400
+ }
401
+ if (warningControls.length > 10) {
402
+ lines.push(`- ... and ${warningControls.length - 10} more warnings`);
403
+ }
404
+ lines.push("");
405
+ }
406
+ if (notImplemented.length > 0) {
407
+ lines.push("### Not Yet Implemented\n");
408
+ lines.push(`${notImplemented.length} controls have not been implemented yet. Priority order:\n`);
409
+ const sorted = [...notImplemented].sort((a, b) => {
410
+ const sevOrder = { critical: 0, high: 1, medium: 2, low: 3 };
411
+ return (sevOrder[a.severity] ?? 4) - (sevOrder[b.severity] ?? 4);
412
+ });
413
+ for (const c of sorted.slice(0, 10)) {
414
+ lines.push(`- **${c.id}** (${c.severity}): ${c.name}`);
415
+ lines.push(` ${c.implementation_guidance.split(".")[0]}`);
416
+ }
417
+ if (notImplemented.length > 10) {
418
+ lines.push(`\n... and ${notImplemented.length - 10} more not-implemented controls.`);
419
+ }
420
+ lines.push("");
421
+ }
422
+ const totalIssues = failedControls.length + warningControls.length + notImplemented.length;
423
+ if (totalIssues === 0) {
424
+ lines.push("**All controls are passing.** No recommendations at this time. Continue monitoring with regular audits.");
425
+ }
426
+ else {
427
+ lines.push(`**Summary**: ${totalIssues} total issues (${criticalFails.length} critical, ${highFails.length} high, ${warningControls.length} warnings, ${notImplemented.length} not-implemented).`);
428
+ lines.push("\nUse the `fix_recommendation` tool with a specific control_id to get step-by-step implementation guidance for any issue.");
429
+ }
430
+ return lines.join("\n");
431
+ }
432
+ function generateFixGuidance(controlId, findingTitle) {
433
+ const allControls = getAllPacks().flatMap(p => p.controls);
434
+ const control = allControls.find(c => c.id === controlId);
435
+ const lines = [];
436
+ lines.push(`# Fix Guidance: ${controlId}\n`);
437
+ if (control) {
438
+ lines.push(`## Control: ${control.name}`);
439
+ lines.push(`**Framework**: ${control.framework}`);
440
+ lines.push(`**Category**: ${control.category}`);
441
+ lines.push(`**Severity**: ${control.severity}`);
442
+ lines.push(`**Current Status**: ${control.status}`);
443
+ lines.push(`**Article**: ${control.article || "N/A"}\n`);
444
+ lines.push(`### Description\n${control.description}\n`);
445
+ lines.push(`### Implementation Guidance\n${control.implementation_guidance}\n`);
446
+ lines.push("### Implementation Steps\n");
447
+ const steps = generateImplementationSteps(control);
448
+ for (let i = 0; i < steps.length; i++) {
449
+ lines.push(`${i + 1}. ${steps[i]}`);
450
+ }
451
+ lines.push("\n### Verification\n");
452
+ lines.push("After implementing the fix:");
453
+ lines.push("1. Run `ges audit` to verify the finding no longer appears");
454
+ lines.push("2. Run `ges score` to see the updated compliance score");
455
+ lines.push("3. If the control is not applicable to your project, add it to `.ges/control-overrides.json`:");
456
+ lines.push("```json");
457
+ lines.push('[\n {\n "control_id": "' + controlId + '",\n "status": "not-applicable",\n "reason": "Explain why this control does not apply"\n }\n]');
458
+ lines.push("```");
459
+ }
460
+ else {
461
+ lines.push(`Control **${controlId}** not found in any framework pack.`);
462
+ lines.push("\nAvailable control IDs:");
463
+ const grouped = {};
464
+ for (const c of allControls) {
465
+ if (!grouped[c.framework])
466
+ grouped[c.framework] = [];
467
+ grouped[c.framework].push(` ${c.id}: ${c.name} (${c.severity})`);
468
+ }
469
+ for (const [fw, ids] of Object.entries(grouped)) {
470
+ lines.push(`\n**${fw}:**`);
471
+ lines.push(ids.join("\n"));
472
+ }
473
+ }
474
+ if (findingTitle) {
475
+ lines.push(`\n### Finding: ${findingTitle}\n`);
476
+ lines.push("To fix this specific finding:");
477
+ lines.push("1. Locate the file mentioned in the finding");
478
+ lines.push("2. Apply the fix suggested in the finding details");
479
+ lines.push("3. Run `ges audit` to verify the fix");
480
+ }
481
+ return lines.join("\n");
482
+ }
483
+ function generateImplementationSteps(control) {
484
+ const steps = [];
485
+ const category = control.category;
486
+ const id = control.id;
487
+ if (category === "encryption") {
488
+ steps.push("Install an encryption library: `npm install crypto-js` or use Node.js built-in `crypto` module");
489
+ steps.push("Implement AES-256-GCM encryption for data at rest");
490
+ steps.push("Ensure TLS 1.2+ is configured for all data in transit");
491
+ steps.push("Add encryption key management (use environment variables or a vault service)");
492
+ steps.push("Verify encryption is applied to all sensitive data fields in your database schema");
493
+ }
494
+ else if (category === "authentication") {
495
+ steps.push("Implement Argon2id password hashing: `npm install argon2`");
496
+ steps.push("Add multi-factor authentication (MFA) support");
497
+ steps.push("Implement session expiration (recommended: 15-30 minutes of inactivity)");
498
+ steps.push("Add rate limiting to authentication endpoints: `npm install express-rate-limit`");
499
+ steps.push("Configure CORS to restrict origins (never use `*` in production)");
500
+ }
501
+ else if (category === "authorization") {
502
+ steps.push("Implement Role-Based Access Control (RBAC) with defined roles and permissions");
503
+ steps.push("Apply the principle of least privilege to all user roles");
504
+ steps.push("Configure deny-by-default access control policies");
505
+ steps.push("Add authorization middleware to all protected routes");
506
+ steps.push("Document the access control matrix in your compliance documentation");
507
+ }
508
+ else if (category === "audit") {
509
+ steps.push("Implement audit logging middleware that captures: userId, action, resource, timestamp, ipAddress");
510
+ steps.push("Store audit logs in a separate, append-only data store");
511
+ steps.push("Ensure logs are immutable (no update or delete operations)");
512
+ steps.push("Add logging for: authentication, authorization, data exports, role changes, admin actions");
513
+ steps.push("Configure log retention policy (minimum 1 year for compliance)");
514
+ }
515
+ else if (category === "secrets") {
516
+ steps.push("Audit all source files for hardcoded secrets: `ges scan` or `npx gitleaks detect`");
517
+ steps.push("Move all secrets to environment variables or a secrets manager (Vault, AWS KMS, etc.)");
518
+ steps.push("Add secrets to `.gitignore` (.env files, key files, certificate files)");
519
+ steps.push("Implement secret rotation policy (rotate every 90 days minimum)");
520
+ steps.push("Add pre-commit hooks to prevent secrets from being committed: `npx gitleaks protect --staged`");
521
+ }
522
+ else if (category === "security-testing") {
523
+ steps.push("Set up automated security scanning in CI/CD (Trivy, Semgrep, npm audit)");
524
+ steps.push("Add dependency scanning to detect vulnerable packages");
525
+ steps.push("Implement static application security testing (SAST)");
526
+ steps.push("Schedule regular penetration testing (quarterly recommended)");
527
+ steps.push("Create a security testing checklist and integrate into your development workflow");
528
+ }
529
+ else if (category === "privacy") {
530
+ steps.push("Implement data minimization - only collect data that is necessary");
531
+ steps.push("Add privacy-by-design principles to your development process");
532
+ steps.push("Implement data subject rights endpoints (access, rectification, erasure, portability)");
533
+ steps.push("Create and publish a privacy policy");
534
+ steps.push("Conduct a Privacy Impact Assessment (PIA) for high-risk processing");
535
+ }
536
+ else if (category === "data-protection") {
537
+ steps.push("Classify all data into categories: public, internal, confidential, restricted");
538
+ steps.push("Apply appropriate protection controls based on classification");
539
+ steps.push("Implement data retention policies with automated deletion");
540
+ steps.push("Add data access logging for all restricted and confidential data");
541
+ steps.push("Create a data inventory documenting all personal data processing activities");
542
+ }
543
+ else if (category === "access-control") {
544
+ steps.push("Review and document all user roles and their permissions");
545
+ steps.push("Implement the principle of least privilege");
546
+ steps.push("Add separation of duties for critical operations");
547
+ steps.push("Implement regular access reviews (quarterly recommended)");
548
+ steps.push("Automate provisioning and deprovisioning of access");
549
+ }
550
+ else if (category === "incident-response") {
551
+ steps.push("Create an incident response plan with defined severity levels and escalation paths");
552
+ steps.push("Define communication templates for GDPR breach notification (72-hour requirement)");
553
+ steps.push("Set up incident detection and alerting (monitoring, SIEM)");
554
+ steps.push("Conduct regular incident response tabletop exercises");
555
+ steps.push("Document lessons learned after each incident");
556
+ }
557
+ else if (category === "vulnerability-management") {
558
+ steps.push("Implement automated vulnerability scanning in CI/CD pipeline");
559
+ steps.push("Set up dependency scanning (npm audit, Dependabot, Snyk)");
560
+ steps.push("Define SLA for fixing vulnerabilities based on severity (critical: 24h, high: 7d)");
561
+ steps.push("Maintain a vulnerability register with tracking");
562
+ steps.push("Regularly review and update dependencies");
563
+ }
564
+ else if (category === "configuration") {
565
+ steps.push("Review and harden all service configurations");
566
+ steps.push("Implement security headers (helmet for Node.js: `npm install helmet`)");
567
+ steps.push("Configure proper CORS policies");
568
+ steps.push("Ensure containers do not run as root");
569
+ steps.push("Remove all default credentials and configurations");
570
+ }
571
+ else {
572
+ steps.push(`Review the control requirements: ${control.description}`);
573
+ steps.push(`Follow the implementation guidance: ${control.implementation_guidance}`);
574
+ steps.push("Implement the required controls based on your project's architecture");
575
+ steps.push("Test the implementation thoroughly");
576
+ steps.push("Document the implementation in your compliance documentation");
577
+ }
578
+ if (id.includes("AI") || id.includes("ai-")) {
579
+ steps.push("");
580
+ steps.push("**AI-Specific Considerations:**");
581
+ steps.push("- Implement prompt logging and monitoring");
582
+ steps.push("- Add PII detection for all inputs and outputs");
583
+ steps.push("- Rate limit AI API calls to prevent abuse");
584
+ steps.push("- Validate all AI outputs before presenting to users");
585
+ steps.push("- Classify data before sending to AI providers");
586
+ }
587
+ if (id.includes("BLOCK") || id.includes("blockchain")) {
588
+ steps.push("");
589
+ steps.push("**Blockchain-Specific Considerations:**");
590
+ steps.push("- Never store plaintext personal data on-chain");
591
+ steps.push("- Store only hashes, CIDs, or encrypted references on-chain");
592
+ steps.push("- Implement key rotation procedures");
593
+ steps.push("- Use cryptographic signatures for all on-chain transactions");
594
+ steps.push("- Maintain immutable audit trails off-chain");
595
+ }
596
+ return steps;
597
+ }
598
+ function createAutoFixPlan(root, findings, filterRuleIds) {
599
+ const actions = [];
600
+ const warnings = [];
601
+ const processedRules = new Set();
602
+ for (const f of findings) {
603
+ if (filterRuleIds && !filterRuleIds.has(f.ruleId))
604
+ continue;
605
+ const key = `${f.ruleId}:${f.file}`;
606
+ if (processedRules.has(key))
607
+ continue;
608
+ processedRules.add(key);
609
+ switch (f.ruleId) {
610
+ case "CONFIG-001":
611
+ actions.push(...buildHelmetFix(root));
612
+ break;
613
+ case "CONFIG-002":
614
+ actions.push(...buildCorsFix(root));
615
+ break;
616
+ case "CONFIG-004":
617
+ actions.push(...buildEnvGitignoreFix(root));
618
+ break;
619
+ case "CONFIG-005":
620
+ actions.push(...buildDockerNonRootFix(root));
621
+ break;
622
+ case "CONFIG-007":
623
+ actions.push(...buildTLSFix(root, f));
624
+ break;
625
+ case "CONFIG-008":
626
+ actions.push(...buildGitignoreCreateFix(root));
627
+ break;
628
+ case "CONFIG-009":
629
+ actions.push(...buildGitignoreEntryFix(root, f));
630
+ break;
631
+ case "CONFIG-010":
632
+ actions.push(...buildLoggingFix(root));
633
+ break;
634
+ case "SECRETS-001":
635
+ actions.push(...buildSecretsFix(root, f));
636
+ warnings.push(`[SECRETS-001] Secret in ${f.file}:${f.line}. Verify .env is in .gitignore and never committed.`);
637
+ break;
638
+ case "CRYPTO-001":
639
+ actions.push(...buildWeakHashFix(root, f));
640
+ warnings.push("[CRYPTO-001] For passwords, use Argon2id instead of SHA-256.");
641
+ break;
642
+ case "CRYPTO-003":
643
+ actions.push(...buildPasswordFix(root, f));
644
+ break;
645
+ case "AUTH-002":
646
+ actions.push(...buildRateLimitFix(root));
647
+ break;
648
+ case "AUTH-003":
649
+ actions.push(...buildSessionTimeoutFix(root));
650
+ break;
651
+ case "AUTH-004":
652
+ actions.push(...buildCORSWildcardFix(root));
653
+ break;
654
+ case "DB-001":
655
+ actions.push(...buildTimestampsFix(root, f));
656
+ break;
657
+ case "DB-002":
658
+ actions.push(...buildSoftDeleteFix(root, f));
659
+ break;
660
+ case "DB-003":
661
+ actions.push(...buildUserAuditFix(root, f));
662
+ break;
663
+ case "DB-004":
664
+ actions.push(...buildAuditModelFix(root));
665
+ break;
666
+ default:
667
+ warnings.push(`[${f.severity.toUpperCase()}] ${f.title} in ${f.file}${f.line ? `:${f.line}` : ""}: Manual fix required.`);
668
+ }
669
+ }
670
+ return { actions, warnings };
671
+ }
672
+ function applyAutoFixAction(root, action) {
673
+ const fullPath = path.join(root, action.filePath);
674
+ try {
675
+ switch (action.type) {
676
+ case "create": {
677
+ if (fs.existsSync(fullPath)) {
678
+ return { applied: false, action, error: "File already exists" };
679
+ }
680
+ const dir = path.dirname(fullPath);
681
+ if (!fs.existsSync(dir))
682
+ fs.mkdirSync(dir, { recursive: true });
683
+ fs.writeFileSync(fullPath, action.content || "", "utf-8");
684
+ return { applied: true, action };
685
+ }
686
+ case "modify": {
687
+ if (!fs.existsSync(fullPath)) {
688
+ return { applied: false, action, error: "File not found" };
689
+ }
690
+ const content = fs.readFileSync(fullPath, "utf-8");
691
+ if (action.search && !content.includes(action.search)) {
692
+ return { applied: false, action, error: "Search string not found" };
693
+ }
694
+ fs.writeFileSync(fullPath, content.replace(action.search || "", action.replace || ""), "utf-8");
695
+ return { applied: true, action };
696
+ }
697
+ case "append": {
698
+ const dir = path.dirname(fullPath);
699
+ if (!fs.existsSync(dir))
700
+ fs.mkdirSync(dir, { recursive: true });
701
+ fs.appendFileSync(fullPath, action.content || "", "utf-8");
702
+ return { applied: true, action };
703
+ }
704
+ case "npm-install": {
705
+ return { applied: true, action };
706
+ }
707
+ }
708
+ }
709
+ catch (err) {
710
+ return { applied: false, action, error: err instanceof Error ? err.message : String(err) };
711
+ }
712
+ }
713
+ function findMainAppFile(root) {
714
+ const candidates = ["src/index.ts", "src/index.js", "src/app.ts", "src/app.js", "src/server.ts", "src/server.js", "src/main.ts", "src/main.js", "index.ts", "index.js", "app.ts", "app.js"];
715
+ for (const c of candidates) {
716
+ if (fs.existsSync(path.join(root, c)))
717
+ return c;
718
+ }
719
+ return null;
720
+ }
721
+ function hasDep(root, dep) {
722
+ const pkg = readJsonFileSafe(path.join(root, "package.json"));
723
+ if (!pkg)
724
+ return false;
725
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
726
+ return dep in deps;
727
+ }
728
+ function readFileSafe(filePath) {
729
+ try {
730
+ return fs.readFileSync(filePath, "utf-8");
731
+ }
732
+ catch {
733
+ return null;
734
+ }
735
+ }
736
+ function buildHelmetFix(root) {
737
+ const appFile = findMainAppFile(root);
738
+ if (!appFile)
739
+ return [];
740
+ const actions = [
741
+ { type: "npm-install", filePath: "package.json", description: "Install helmet", ruleId: "CONFIG-001" },
742
+ ];
743
+ const content = readFileSafe(path.join(root, appFile));
744
+ if (content && content.includes("const app = express()")) {
745
+ actions.push({ type: "modify", filePath: appFile, search: "const app = express()", replace: "const app = express()\n\napp.use(helmet())", description: "Add helmet middleware", ruleId: "CONFIG-001" });
746
+ }
747
+ else {
748
+ actions.push({ type: "append", filePath: appFile, content: "\nimport helmet from 'helmet';\napp.use(helmet());\n", description: "Add helmet import and middleware", ruleId: "CONFIG-001" });
749
+ }
750
+ return actions;
751
+ }
752
+ function buildCorsFix(root) {
753
+ const appFile = findMainAppFile(root);
754
+ if (!appFile)
755
+ return [];
756
+ return [
757
+ { type: "npm-install", filePath: "package.json", description: "Install cors", ruleId: "CONFIG-002" },
758
+ { type: "append", filePath: appFile, content: "\nimport cors from 'cors';\napp.use(cors({ origin: process.env.ALLOWED_ORIGINS?.split(',') || ['http://localhost:3000'] }));\n", description: "Add CORS with configured origins", ruleId: "CONFIG-002" },
759
+ ];
760
+ }
761
+ function buildEnvGitignoreFix(root) {
762
+ const gi = fs.existsSync(path.join(root, ".gitignore")) ? ".gitignore" : null;
763
+ if (!gi)
764
+ return buildGitignoreCreateFix(root);
765
+ const content = readFileSafe(path.join(root, gi)) || "";
766
+ if (content.includes(".env"))
767
+ return [];
768
+ return [{ type: "append", filePath: ".gitignore", content: "\n.env\n.env.*\n!.env.example\n", description: "Add .env to .gitignore", ruleId: "CONFIG-004" }];
769
+ }
770
+ function buildDockerNonRootFix(root) {
771
+ if (!fs.existsSync(path.join(root, "Dockerfile")))
772
+ return [];
773
+ return [{ type: "append", filePath: "Dockerfile", content: "\nUSER node\n", description: "Add non-root USER to Dockerfile", ruleId: "CONFIG-005" }];
774
+ }
775
+ function buildTLSFix(root, f) {
776
+ return [{ type: "modify", filePath: f.file, search: "NODE_TLS_REJECT_UNAUTHORIZED=0", replace: "NODE_TLS_REJECT_UNAUTHORIZED=1", description: "Re-enable TLS verification", ruleId: "CONFIG-007" }];
777
+ }
778
+ function buildGitignoreCreateFix(root) {
779
+ return [{ type: "create", filePath: ".gitignore", content: "node_modules/\n.env\n.env.*\n!.env.example\ndist/\nbuild/\n*.key\n*.pem\ncoverage/\n.DS_Store\n", description: "Create .gitignore with security entries", ruleId: "CONFIG-008" }];
780
+ }
781
+ function buildGitignoreEntryFix(root, f) {
782
+ const entry = f.fix.replace("Add ", "").replace(" to .gitignore.", "");
783
+ if (!fs.existsSync(path.join(root, ".gitignore")))
784
+ return buildGitignoreCreateFix(root);
785
+ return [{ type: "append", filePath: ".gitignore", content: `\n${entry}\n`, description: `Add ${entry} to .gitignore`, ruleId: "CONFIG-009" }];
786
+ }
787
+ function buildLoggingFix(root) {
788
+ const appFile = findMainAppFile(root);
789
+ const hasSrc = fs.existsSync(path.join(root, "src"));
790
+ const loggerPath = hasSrc ? "src/lib/logger.ts" : "lib/logger.ts";
791
+ const actions = [
792
+ { type: "npm-install", filePath: "package.json", description: "Install pino logger", ruleId: "CONFIG-010" },
793
+ { type: "create", filePath: loggerPath, content: `import pino from 'pino';\n\nconst logger = pino({\n level: process.env.LOG_LEVEL || 'info',\n timestamp: pino.stdTimeFunctions.isoTime,\n});\n\ninterface AuditLogParams {\n userId: string;\n action: string;\n resource: string;\n ipAddress: string;\n metadata?: Record<string, unknown>;\n}\n\nexport function auditLog(params: AuditLogParams): void {\n logger.info({ ...params, timestamp: new Date().toISOString(), type: 'audit' });\n}\n\nexport default logger;\n`, description: "Create structured logger with audit logging", ruleId: "CONFIG-010" },
794
+ ];
795
+ if (appFile) {
796
+ actions.push({ type: "append", filePath: appFile, content: `\nimport logger from './${hasSrc ? "lib/logger" : (hasSrc ? "src/lib/logger" : "lib/logger")}';\n`, description: "Import logger", ruleId: "CONFIG-010" });
797
+ }
798
+ return actions;
799
+ }
800
+ function buildSecretsFix(root, f) {
801
+ const actions = [];
802
+ const content = readFileSafe(path.join(root, f.file));
803
+ if (!content)
804
+ return actions;
805
+ const lines = content.split("\n");
806
+ const idx = (f.line || 1) - 1;
807
+ if (idx >= lines.length)
808
+ return actions;
809
+ const line = lines[idx];
810
+ const match = line.match(/(\w+)\s*[:=]\s*['"]([^'"]+)['"]/);
811
+ if (match) {
812
+ const varName = match[1];
813
+ const value = match[2];
814
+ const envFile = fs.existsSync(path.join(root, ".env")) ? ".env" : ".env";
815
+ actions.push({ type: "append", filePath: envFile, content: `\n${varName}=${value}\n`, description: `Move ${varName} to .env`, ruleId: "SECRETS-001" });
816
+ actions.push({ type: "modify", filePath: f.file, search: line, replace: `${varName}: process.env.${varName}`, description: `Replace hardcoded ${varName}`, ruleId: "SECRETS-001" });
817
+ actions.push(...buildEnvGitignoreFix(root));
818
+ }
819
+ return actions;
820
+ }
821
+ function buildWeakHashFix(root, f) {
822
+ const content = readFileSafe(path.join(root, f.file));
823
+ if (!content)
824
+ return [];
825
+ const lines = content.split("\n");
826
+ const idx = (f.line || 1) - 1;
827
+ if (idx >= lines.length)
828
+ return [];
829
+ const line = lines[idx];
830
+ let replacement = line;
831
+ if (/createHash\s*\(\s*['"](?:md5|sha1)['"]\s*\)/.test(line)) {
832
+ replacement = line.replace(/createHash\s*\(\s*['"](?:md5|sha1)['"]\s*\)/, "createHash('sha256')");
833
+ }
834
+ else if (/hashlib\.md5\(/i.test(line)) {
835
+ replacement = line.replace(/hashlib\.md5\(/gi, "hashlib.sha256(");
836
+ }
837
+ else if (/hashlib\.sha1\(/i.test(line)) {
838
+ replacement = line.replace(/hashlib\.sha1\(/gi, "hashlib.sha256(");
839
+ }
840
+ if (replacement === line)
841
+ return [];
842
+ return [{ type: "modify", filePath: f.file, search: line, replace: replacement, description: "Replace weak hash with SHA-256", ruleId: "CRYPTO-001" }];
843
+ }
844
+ function buildPasswordFix(root, _f) {
845
+ const hasSrc = fs.existsSync(path.join(root, "src"));
846
+ const authPath = hasSrc ? "src/lib/auth.ts" : "lib/auth.ts";
847
+ const actions = [
848
+ { type: "npm-install", filePath: "package.json", description: "Install argon2", ruleId: "CRYPTO-003" },
849
+ ];
850
+ if (!fs.existsSync(path.join(root, authPath))) {
851
+ actions.push({ type: "create", filePath: authPath, content: `import argon2 from 'argon2';\n\nexport async function hashPassword(password: string): Promise<string> {\n return argon2.hash(password, { type: argon2.argon2id });\n}\n\nexport async function verifyPassword(hashedPassword: string, inputPassword: string): Promise<boolean> {\n return argon2.verify(hashedPassword, inputPassword);\n}\n`, description: "Create Argon2id password utility", ruleId: "CRYPTO-003" });
852
+ }
853
+ return actions;
854
+ }
855
+ function buildRateLimitFix(root) {
856
+ const appFile = findMainAppFile(root);
857
+ if (!appFile)
858
+ return [];
859
+ const isExpress = hasDep(root, "express");
860
+ const isFastify = hasDep(root, "fastify");
861
+ if (isExpress) {
862
+ return [
863
+ { type: "npm-install", filePath: "package.json", description: "Install express-rate-limit", ruleId: "AUTH-002" },
864
+ { type: "append", filePath: appFile, content: `\nimport rateLimit from 'express-rate-limit';\n\nconst limiter = rateLimit({\n windowMs: 15 * 60 * 1000,\n max: 100,\n standardHeaders: true,\n legacyHeaders: false,\n});\napp.use(limiter);\n`, description: "Add rate limiting (100 req/15min)", ruleId: "AUTH-002" },
865
+ ];
866
+ }
867
+ else if (isFastify) {
868
+ return [
869
+ { type: "npm-install", filePath: "package.json", description: "Install @fastify/rate-limit", ruleId: "AUTH-002" },
870
+ { type: "append", filePath: appFile, content: `\nimport rateLimit from '@fastify/rate-limit';\napp.register(rateLimit, { max: 100, timeWindow: '15 minutes' });\n`, description: "Add rate limiting to Fastify", ruleId: "AUTH-002" },
871
+ ];
872
+ }
873
+ return [];
874
+ }
875
+ function buildSessionTimeoutFix(root) {
876
+ const appFile = findMainAppFile(root);
877
+ if (!appFile)
878
+ return [];
879
+ const isExpress = hasDep(root, "express");
880
+ if (!isExpress)
881
+ return [{ type: "append", filePath: appFile, content: "\nconst SESSION_TIMEOUT_MS = 30 * 60 * 1000;\n", description: "Add session timeout constant", ruleId: "AUTH-003" }];
882
+ const content = readFileSafe(path.join(root, appFile)) || "";
883
+ if (content.includes("session("))
884
+ return [];
885
+ return [
886
+ { type: "npm-install", filePath: "package.json", description: "Install express-session", ruleId: "AUTH-003" },
887
+ { type: "append", filePath: appFile, content: `\nimport session from 'express-session';\n\napp.use(session({\n secret: process.env.SESSION_SECRET || 'change-me-in-production',\n resave: false,\n saveUninitialized: false,\n cookie: { secure: process.env.NODE_ENV === 'production', httpOnly: true, maxAge: 30 * 60 * 1000 },\n}));\n`, description: "Add session with 30-min timeout", ruleId: "AUTH-003" },
888
+ ];
889
+ }
890
+ function buildCORSWildcardFix(root) {
891
+ const appFile = findMainAppFile(root);
892
+ if (!appFile)
893
+ return [];
894
+ const content = readFileSafe(path.join(root, appFile)) || "";
895
+ const actions = [];
896
+ if (content.includes("origin: '*'")) {
897
+ actions.push({ type: "modify", filePath: appFile, search: "origin: '*'", replace: "origin: process.env.ALLOWED_ORIGINS?.split(',') || ['http://localhost:3000']", description: "Replace CORS wildcard", ruleId: "AUTH-004" });
898
+ }
899
+ if (content.includes('origin:"*"')) {
900
+ actions.push({ type: "modify", filePath: appFile, search: 'origin:"*"', replace: "origin: process.env.ALLOWED_ORIGINS?.split(',') || ['http://localhost:3000']", description: "Replace CORS wildcard", ruleId: "AUTH-004" });
901
+ }
902
+ return actions;
903
+ }
904
+ function buildTimestampsFix(root, f) {
905
+ if (!f.file.endsWith(".prisma"))
906
+ return [];
907
+ const content = readFileSafe(path.join(root, f.file));
908
+ if (!content)
909
+ return [];
910
+ const modelMatch = content.match(/model\s+\w+\s*\{[^}]*\}/g);
911
+ if (!modelMatch || modelMatch.length === 0)
912
+ return [];
913
+ const block = modelMatch[0];
914
+ const closingBrace = block.lastIndexOf("}");
915
+ if (closingBrace === -1)
916
+ return [];
917
+ const insertion = "\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt";
918
+ return [{ type: "modify", filePath: f.file, search: block, replace: block.slice(0, closingBrace) + insertion + block.slice(closingBrace), description: "Add createdAt/updatedAt to Prisma model", ruleId: "DB-001" }];
919
+ }
920
+ function buildSoftDeleteFix(root, f) {
921
+ if (!f.file.endsWith(".prisma"))
922
+ return [];
923
+ const content = readFileSafe(path.join(root, f.file));
924
+ if (!content)
925
+ return [];
926
+ const modelMatch = content.match(/model\s+\w+\s*\{[^}]*\}/g);
927
+ if (!modelMatch || modelMatch.length === 0)
928
+ return [];
929
+ const block = modelMatch[0];
930
+ const closingBrace = block.lastIndexOf("}");
931
+ if (closingBrace === -1)
932
+ return [];
933
+ return [{ type: "modify", filePath: f.file, search: block, replace: block.slice(0, closingBrace) + "\n deletedAt DateTime?" + block.slice(closingBrace), description: "Add deletedAt to Prisma model", ruleId: "DB-002" }];
934
+ }
935
+ function buildUserAuditFix(root, f) {
936
+ if (!f.file.endsWith(".prisma"))
937
+ return [];
938
+ const content = readFileSafe(path.join(root, f.file));
939
+ if (!content)
940
+ return [];
941
+ const modelMatch = content.match(/model\s+\w+\s*\{[^}]*\}/g);
942
+ if (!modelMatch || modelMatch.length === 0)
943
+ return [];
944
+ const block = modelMatch[0];
945
+ const closingBrace = block.lastIndexOf("}");
946
+ if (closingBrace === -1)
947
+ return [];
948
+ return [{ type: "modify", filePath: f.file, search: block, replace: block.slice(0, closingBrace) + "\n createdBy String?\n updatedBy String?" + block.slice(closingBrace), description: "Add createdBy/updatedBy columns", ruleId: "DB-003" }];
949
+ }
950
+ function buildAuditModelFix(root) {
951
+ const content = readFileSafe(path.join(root, "prisma/schema.prisma"));
952
+ if (!content)
953
+ return [];
954
+ return [{ type: "append", filePath: "prisma/schema.prisma", content: "\\nmodel Audit {\\n id Int @id @default(autoincrement())\\n userId String\\n action String\\n resource String\\n timestamp DateTime @default(now())\\n ipAddress String\\n metadata Json?\\n}\\n", description: "Add Audit model to Prisma schema", ruleId: "DB-004" }];
955
+ }
956
+ function getNpmInstallsFromActions(actions) {
957
+ const installs = new Set();
958
+ for (const a of actions) {
959
+ if (a.type !== "npm-install")
960
+ continue;
961
+ const map = {
962
+ "CONFIG-001": "helmet",
963
+ "CONFIG-002": "cors",
964
+ "CONFIG-010": "pino",
965
+ "CRYPTO-003": "argon2",
966
+ "AUTH-002": "express-rate-limit",
967
+ "AUTH-003": "express-session",
968
+ };
969
+ if (map[a.ruleId])
970
+ installs.add(map[a.ruleId]);
971
+ }
972
+ return [...installs];
973
+ }
974
+ function buildEncryptionAtRestImpl(root, hasSrc) {
975
+ const cryptoPath = hasSrc ? "src/lib/encryption.ts" : "lib/encryption.ts";
976
+ return [
977
+ { type: "npm-install", filePath: "package.json", description: "Node.js crypto is built-in", ruleId: "GDPR-ART32-002" },
978
+ { type: "create", filePath: cryptoPath, content: `import { createCipheriv, createDecipheriv, randomBytes, scryptSync } from 'node:crypto';\n\nconst ALGORITHM = 'aes-256-gcm';\nconst IV_LENGTH = 16;\nconst TAG_LENGTH = 16;\n\nfunction deriveKey(secret: string, salt: Buffer): Buffer {\n return scryptSync(secret, salt, 32);\n}\n\nexport function encrypt(plaintext: string, secret: string): string {\n const salt = randomBytes(16);\n const key = deriveKey(secret, salt);\n const iv = randomBytes(IV_LENGTH);\n const cipher = createCipheriv(ALGORITHM, key, iv);\n const encrypted = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);\n const tag = cipher.getAuthTag();\n return Buffer.concat([salt, iv, tag, encrypted]).toString('base64');\n}\n\nexport function decrypt(ciphertext: string, secret: string): string {\n const data = Buffer.from(ciphertext, 'base64');\n const salt = data.subarray(0, 16);\n const iv = data.subarray(16, 32);\n const tag = data.subarray(32, 48);\n const encrypted = data.subarray(48);\n const key = deriveKey(secret, salt);\n const decipher = createDecipheriv(ALGORITHM, key, iv);\n decipher.setAuthTag(tag);\n return decipher.update(encrypted) + decipher.final('utf8');\n}\n`, description: "Create AES-256-GCM encryption utility", ruleId: "GDPR-ART32-002" },
979
+ ];
980
+ }
981
+ function buildEncryptionInTransitImpl(root, _hasSrc) {
982
+ const appFile = findMainAppFile(root);
983
+ const actions = [];
984
+ if (appFile) {
985
+ actions.push({ type: "append", filePath: appFile, content: "\nif (process.env.NODE_ENV === 'production') {\n app.use((req, res, next) => {\n if (req.headers['x-forwarded-proto'] === 'http') {\n return res.redirect(301, `https://${req.headers.host}${req.url}`);\n }\n next();\n });\n}\n", description: "Add HTTPS redirect middleware", ruleId: "GDPR-ART32-003" });
986
+ }
987
+ return actions;
988
+ }
989
+ function buildUserIdentificationImpl(root, hasSrc) {
990
+ const authPath = hasSrc ? "src/lib/auth.ts" : "lib/auth.ts";
991
+ if (fs.existsSync(path.join(root, authPath)))
992
+ return [];
993
+ return [
994
+ { type: "npm-install", filePath: "package.json", description: "Install argon2 for password hashing", ruleId: "GDPR-ART32-004" },
995
+ { type: "create", filePath: authPath, content: `import argon2 from 'argon2';\n\nexport async function hashPassword(password: string): Promise<string> {\n return argon2.hash(password, { type: argon2.argon2id });\n}\n\nexport async function verifyPassword(hashedPassword: string, inputPassword: string): Promise<boolean> {\n return argon2.verify(hashedPassword, inputPassword);\n}\n`, description: "Create auth utility with Argon2id", ruleId: "GDPR-ART32-004" },
996
+ ];
997
+ }
998
+ function buildIntegrityControlsImpl(root, hasSrc) {
999
+ const integrityPath = hasSrc ? "src/lib/integrity.ts" : "lib/integrity.ts";
1000
+ return [
1001
+ { type: "create", filePath: integrityPath, content: `import { createHash } from 'node:crypto';\n\nexport function hashData(data: string): string {\n return createHash('sha256').update(data).digest('hex');\n}\n\nexport function verifyIntegrity(data: string, expectedHash: string): boolean {\n return hashData(data) === expectedHash;\n}\n\nexport function generateChecksum(content: string): string {\n return createHash('sha256').update(content).digest('base64');\n}\n`, description: "Create integrity verification utility", ruleId: "GDPR-ART32-007" },
1002
+ ];
1003
+ }
1004
+ function buildBackupPolicyImpl(root, _hasSrc) {
1005
+ return [
1006
+ { type: "create", filePath: "scripts/backup.sh", content: `#!/bin/bash\nset -euo pipefail\n\nBACKUP_DIR="\${BACKUP_DIR:-./backups}"\nTIMESTAMP=$(date +%Y%m%d_%H%M%S)\nBACKUP_FILE="$BACKUP_DIR/backup_$TIMESTAMP.tar.gz.gpg"\nENCRYPTION_KEY="'''\${BACKUP_ENCRYPTION_KEY:-change-me}'''"\n\nmkdir -p "$BACKUP_DIR"\n\necho "[$(date)] Starting backup..."\n\nif command -v pg_dump &> /dev/null; then\n pg_dump "$DATABASE_URL" | gpg --symmetric --cipher-algo AES256 --batch --passphrase "$ENCRYPTION_KEY" -o "$BACKUP_FILE"\n echo "[$(date)] Database backup completed: $BACKUP_FILE"\nfi\n\ntar czf - ./data 2>/dev/null | gpg --symmetric --cipher-algo AES256 --batch --passphrase "$ENCRYPTION_KEY" -o "$BACKUP_DIR/data_$TIMESTAMP.tar.gz.gpg" 2>/dev/null || true\n\necho "[$(date)] Backup completed."\n\nfind "$BACKUP_DIR" -name "*.gpg" -mtime +30 -delete\necho "[$(date)] Cleaned up backups older than 30 days."\n`, description: "Create encrypted backup script", ruleId: "GDPR-ART32-008" },
1007
+ ];
1008
+ }
1009
+ function buildSecurityTestingImpl(root) {
1010
+ const ghDir = path.join(root, ".github/workflows");
1011
+ return [
1012
+ { type: "create", filePath: ".github/workflows/security-scan.yml", content: `name: Security Scan\non:\n push:\n branches: [main, master]\n pull_request:\n branches: [main, master]\n schedule:\n - cron: '0 6 * * 1'\n\njobs:\n security:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@v4\n - uses: actions/setup-node@v4\n with:\n node-version: '22'\n - run: npm ci\n - name: npm audit\n run: npm audit --audit-level=high\n continue-on-error: true\n - name: Run GESF compliance check\n run: npx @greenarmor/ges audit --ci\n`, description: "Create security scanning GitHub Actions workflow", ruleId: "GDPR-ART32-009" },
1013
+ ];
1014
+ }
1015
+ function generateDataInventory(projectName, projectType) {
1016
+ const webCategories = [
1017
+ { category: "User Profiles", type: "Personal", classification: "Restricted", retention: "Account + 30 days", basis: "Contract (Art. 6(1)(b))" },
1018
+ { category: "Email Addresses", type: "Personal", classification: "Confidential", retention: "Account + 30 days", basis: "Contract (Art. 6(1)(b))" },
1019
+ { category: "Authentication Credentials", type: "Personal", classification: "Restricted", retention: "Session duration", basis: "Contract (Art. 6(1)(b))" },
1020
+ { category: "IP Addresses", type: "Personal", classification: "Internal", retention: "30 days", basis: "Legitimate interest (Art. 6(1)(f))" },
1021
+ { category: "Session Data", type: "Operational", classification: "Internal", retention: "Session duration", basis: "Contract (Art. 6(1)(b))" },
1022
+ { category: "Audit Logs", type: "Operational", classification: "Internal", retention: "1 year", basis: "Legal obligation (Art. 6(1)(c))" },
1023
+ ];
1024
+ const aiCategories = [
1025
+ { category: "AI Prompts", type: "Personal", classification: "Confidential", retention: "90 days", basis: "Legitimate interest (Art. 6(1)(f))" },
1026
+ { category: "AI Outputs", type: "Personal", classification: "Internal", retention: "30 days", basis: "Legitimate interest (Art. 6(1)(f))" },
1027
+ { category: "Training Data References", type: "Personal", classification: "Restricted", retention: "Duration of use", basis: "Consent (Art. 6(1)(a))" },
1028
+ ];
1029
+ const blockchainCategories = [
1030
+ { category: "Wallet Addresses", type: "Pseudonymous", classification: "Public", retention: "Indefinite (on-chain)", basis: "Contract (Art. 6(1)(b))" },
1031
+ { category: "Transaction History", type: "Pseudonymous", classification: "Public", retention: "Indefinite (on-chain)", basis: "Contract (Art. 6(1)(b))" },
1032
+ { category: "KYC Data", type: "Personal", classification: "Restricted", retention: "5 years", basis: "Legal obligation (Art. 6(1)(c))" },
1033
+ ];
1034
+ let categories = webCategories;
1035
+ if (projectType.includes("ai"))
1036
+ categories = [...webCategories, ...aiCategories];
1037
+ if (projectType.includes("blockchain") || projectType.includes("wallet"))
1038
+ categories = [...webCategories, ...blockchainCategories];
1039
+ if (projectType.includes("healthcare")) {
1040
+ categories = [...webCategories, { category: "Health Records", type: "Special Category", classification: "Restricted", retention: "10 years", basis: "Legal obligation (Art. 6(1)(c) + Art. 9)" }];
1041
+ }
1042
+ if (projectType.includes("photo")) {
1043
+ categories = [...webCategories, { category: "Photos/Images", type: "Personal", classification: "Restricted", retention: "Account + 30 days", basis: "Contract (Art. 6(1)(b))" }];
1044
+ }
1045
+ const lines = [
1046
+ `# Data Inventory - ${projectName}\n`,
1047
+ `Generated: ${new Date().toISOString()}\n`,
1048
+ `## Data Categories\n`,
1049
+ `| Category | Type | Classification | Retention | Legal Basis |`,
1050
+ `|----------|------|---------------|-----------|-------------|`,
1051
+ ];
1052
+ for (const cat of categories) {
1053
+ lines.push(`| ${cat.category} | ${cat.type} | ${cat.classification} | ${cat.retention} | ${cat.basis} |`);
1054
+ }
1055
+ lines.push("");
1056
+ lines.push("## Data Classification Rules\n");
1057
+ lines.push("| Classification | Encryption | Access Controls | Audit Logging |");
1058
+ lines.push("|---------------|-----------|-----------------|---------------|");
1059
+ lines.push("| Public | Not required | Not required | Not required |");
1060
+ lines.push("| Internal | Not required | Required | Recommended |");
1061
+ lines.push("| Confidential | Required | Required | Required |");
1062
+ lines.push("| Restricted | Required | Required + MFA | Required + Immutable |");
1063
+ lines.push("");
1064
+ lines.push("## Data Subject Rights Implementation\n");
1065
+ lines.push("- [ ] Right of access (Article 15) - API endpoint or process implemented");
1066
+ lines.push("- [ ] Right to rectification (Article 16) - Update process documented");
1067
+ lines.push("- [ ] Right to erasure (Article 17) - Deletion process with verification");
1068
+ lines.push("- [ ] Right to restriction (Article 18) - Mark-and-hold process");
1069
+ lines.push("- [ ] Right to data portability (Article 20) - Export in machine-readable format");
1070
+ lines.push("- [ ] Right to object (Article 21) - Opt-out mechanism");
1071
+ lines.push("");
1072
+ lines.push("## Third-Party Processors\n");
1073
+ lines.push("| Processor | Data Shared | Purpose | DPA Signed | Location |");
1074
+ lines.push("|-----------|------------|---------|------------|----------|");
1075
+ lines.push("| [Cloud Provider] | [Data categories] | [Purpose] | [Yes/No] | [Country] |");
1076
+ lines.push("");
1077
+ lines.push("## Cross-Border Transfers\n");
1078
+ lines.push("| Transfer From | Transfer To | Safeguard |");
1079
+ lines.push("|--------------|------------|-----------|");
1080
+ lines.push("| [EU] | [Country] | [SCCs / Adequacy Decision / BCRs] |");
1081
+ return lines.join("\n");
1082
+ }
1083
+ function generateProcessingRecords(projectName, controllerName) {
1084
+ const lines = [
1085
+ `# Records of Processing Activities (ROPA) - ${projectName}\n`,
1086
+ `**Controller**: ${controllerName}`,
1087
+ `**Date**: ${new Date().toISOString().split("T")[0]}`,
1088
+ `**Document Reference**: ROPA-${projectName.replace(/\s+/g, "-").toUpperCase()}-001\n`,
1089
+ `## Article 30(1) — Controller Records\n`,
1090
+ ];
1091
+ const activities = [
1092
+ {
1093
+ name: "User Account Management",
1094
+ purpose: "Provision and manage user accounts",
1095
+ categories: "Identity data, contact data, authentication data",
1096
+ recipients: "Internal systems, identity provider",
1097
+ transfers: "None (or specify if applicable)",
1098
+ retention: "Account lifetime + 30 days post-deletion",
1099
+ security: "Encryption at rest (AES-256-GCM), TLS 1.2+ in transit, MFA, RBAC",
1100
+ },
1101
+ {
1102
+ name: "Service Delivery",
1103
+ purpose: "Deliver core product/service functionality",
1104
+ categories: "Usage data, preferences, content data",
1105
+ recipients: "Internal systems, CDN provider",
1106
+ transfers: "None (or specify)",
1107
+ retention: "Account lifetime",
1108
+ security: "Encryption, access controls, audit logging",
1109
+ },
1110
+ {
1111
+ name: "Communication",
1112
+ purpose: "Service notifications, support, marketing (with consent)",
1113
+ categories: "Email addresses, communication preferences",
1114
+ recipients: "Email service provider",
1115
+ transfers: "None (or specify)",
1116
+ retention: "Until consent withdrawal or account closure",
1117
+ security: "Encryption, access controls",
1118
+ },
1119
+ {
1120
+ name: "Analytics and Monitoring",
1121
+ purpose: "Service improvement and security monitoring",
1122
+ categories: "Usage data, IP addresses, device information",
1123
+ recipients: "Analytics provider, monitoring systems",
1124
+ transfers: "None (or specify)",
1125
+ retention: "12 months for analytics, 1 year for security logs",
1126
+ security: "Pseudonymization, access controls, aggregated reporting",
1127
+ },
1128
+ {
1129
+ name: "Legal Compliance",
1130
+ purpose: "Meet regulatory and legal obligations",
1131
+ categories: "Identity data, transaction records, audit logs",
1132
+ recipients: "Legal authorities (upon request), auditors",
1133
+ transfers: "As required by law",
1134
+ retention: "Per legal requirements (typically 5-7 years)",
1135
+ security: "Encryption, access controls, immutable audit trail",
1136
+ },
1137
+ ];
1138
+ for (const activity of activities) {
1139
+ lines.push(`### ${activity.name}\n`);
1140
+ lines.push(`- **Purpose**: ${activity.purpose}`);
1141
+ lines.push(`- **Categories of Data Subjects**: Users, customers, employees`);
1142
+ lines.push(`- **Categories of Personal Data**: ${activity.categories}`);
1143
+ lines.push(`- **Categories of Recipients**: ${activity.recipients}`);
1144
+ lines.push(`- **International Transfers**: ${activity.transfers}`);
1145
+ lines.push(`- **Retention Period**: ${activity.retention}`);
1146
+ lines.push(`- **Technical and Organizational Measures**: ${activity.security}`);
1147
+ lines.push(`- **Legal Basis**: Contract (Art. 6(1)(b)), Legitimate Interest (Art. 6(1)(f))\n`);
1148
+ }
1149
+ lines.push("## Data Protection Officer\n");
1150
+ lines.push("- **Name**: [DPO Name or N/A if not required]");
1151
+ lines.push("- **Contact**: [DPO Contact Details]");
1152
+ lines.push("");
1153
+ lines.push("## Review History\n");
1154
+ lines.push("| Date | Reviewer | Changes |");
1155
+ lines.push("|------|----------|---------|");
1156
+ lines.push(`| ${new Date().toISOString().split("T")[0]} | Initial | Created ROPA |`);
1157
+ return lines.join("\n");
1158
+ }
78
1159
  export function handleRequest(request) {
79
1160
  const isNotification = request.id === undefined || request.id === null;
80
1161
  if (request.method === "initialize") {
@@ -117,53 +1198,540 @@ export function handleRequest(request) {
117
1198
  const toolName = request.params?.name || "";
118
1199
  const args = request.params?.arguments || {};
119
1200
  let resultText;
120
- switch (toolName) {
121
- case "check_compliance": {
122
- const projectType = (args.project_type || "saas");
123
- const packs = getPacksForProjectType(projectType);
124
- const controls = packs.flatMap((p) => p.controls);
125
- const score = generateScoreFile(controls, ["GDPR", "OWASP"]);
126
- resultText = formatScoreOutput(score);
127
- break;
128
- }
129
- case "list_missing_controls": {
130
- const framework = args.framework || "GDPR";
131
- const allControls = getAllPacks().flatMap((p) => p.controls);
132
- const missing = allControls.filter((c) => c.framework === framework && c.status !== "pass");
133
- resultText =
134
- missing.length > 0
135
- ? missing
136
- .map((c) => `- [${c.severity.toUpperCase()}] ${c.id}: ${c.name}`)
137
- .join("\n")
138
- : "All controls are passing.";
139
- break;
140
- }
141
- case "generate_retention_policy": {
142
- const name = args.project_name || "Project";
143
- resultText = `# Data Retention Policy - ${name}\n\n## Retention Periods\n\n| Category | Period | Justification |\n|----------|--------|---------------|\n| User data | Account + 30 days | Contract |\n| Audit logs | 1 year | Legal obligation |\n| Session data | Session duration | Operational |\n\nReview quarterly and update as needed.`;
144
- break;
145
- }
146
- case "generate_incident_response": {
147
- const name = args.project_name || "Project";
148
- resultText = `# Incident Response Plan - ${name}\n\n## Severity Levels\n- P1 (Critical): 15 min response\n- P2 (High): 1 hour response\n- P3 (Medium): 4 hour response\n\n## Process\n1. Detection → 2. Assessment → 3. Containment → 4. Eradication → 5. Recovery → 6. Post-Incident\n\n## GDPR: Notify supervisory authority within 72 hours.`;
149
- break;
150
- }
151
- case "generate_risk_assessment": {
152
- const name = args.project_name || "Project";
153
- resultText = `# Risk Assessment - ${name}\n\n| Risk | Likelihood | Impact | Mitigation |\n|------|-----------|--------|------------|\n| Data breach | Medium | Critical | Encryption, MFA, access controls |\n| Insider threat | Low | High | RBAC, audit logging |\n| Data loss | Low | Critical | Backups, DR plan |\n| Non-compliance | Medium | High | Regular audits |`;
154
- break;
155
- }
156
- case "generate_dpa": {
157
- const name = args.project_name || "Project";
158
- resultText = `# Data Processing Agreement - ${name}\n\n## Parties\n- Controller: [Company Name]\n- Processor: [Service Provider]\n\n## Subject Matter\nProcessing of personal data as described in the attached schedule.\n\n## Duration\nEffective until termination of services.\n\n## Obligations\n- Process data only on documented instructions\n- Ensure confidentiality\n- Implement appropriate security (Article 32)\n- Assist with data subject rights\n- Assist with breach notification\n- Delete/return data on termination`;
159
- break;
1201
+ try {
1202
+ switch (toolName) {
1203
+ case "check_compliance": {
1204
+ const projectType = (args.project_type || "saas");
1205
+ const packs = getPacksForProjectType(projectType);
1206
+ const controls = packs.flatMap((p) => p.controls);
1207
+ const frameworks = [...new Set(controls.map(c => c.framework))];
1208
+ const score = generateScoreFile(controls, frameworks);
1209
+ resultText = formatScoreOutput(score);
1210
+ break;
1211
+ }
1212
+ case "check_project_status": {
1213
+ const projectPath = resolveProjectPath(args.project_path);
1214
+ const { config, score, overrides } = loadProjectConfig(projectPath);
1215
+ if (!config) {
1216
+ resultText = `No GESF project found at ${projectPath}. Run 'ges init' first to initialize the project.`;
1217
+ break;
1218
+ }
1219
+ const lines = [];
1220
+ lines.push(`# Project Status: ${config.project_name || "Unknown"}\n`);
1221
+ lines.push(`**Path**: ${projectPath}`);
1222
+ lines.push(`**Type**: ${config.project_type || "Unknown"}`);
1223
+ lines.push(`**Initialized**: ${config.created_at || "Unknown"}`);
1224
+ lines.push(`**Frameworks**: ${Array.isArray(config.frameworks) ? config.frameworks.join(", ") : "Unknown"}`);
1225
+ if (overrides.length > 0) {
1226
+ const naCount = overrides.filter(o => o.status === "not-applicable").length;
1227
+ const passCount = overrides.filter(o => o.status === "pass").length;
1228
+ lines.push(`**Control Overrides**: ${overrides.length} (${naCount} not-applicable, ${passCount} pre-verified)`);
1229
+ }
1230
+ if (score) {
1231
+ lines.push(`\n## Compliance Score\n`);
1232
+ lines.push(`**Overall: ${score.overall}% (Grade: ${score.overall_grade})**\n`);
1233
+ lines.push("| Framework | Score | Grade | Passed | Failed | Warnings | Critical |");
1234
+ lines.push("|-----------|-------|-------|--------|--------|----------|----------|");
1235
+ for (const [fw, data] of Object.entries(score.frameworks)) {
1236
+ lines.push(`| ${fw} | ${data.score}% | ${data.grade} | ${data.passed_controls} | ${data.failed_controls} | ${data.warning_controls} | ${data.critical_failures} |`);
1237
+ }
1238
+ if (score.audit_impact) {
1239
+ const ai = score.audit_impact;
1240
+ lines.push(`\n**Audit Impact**: -${ai.total_deduction}% (${ai.critical_findings} critical, ${ai.high_findings} high, ${ai.medium_findings} medium, ${ai.low_findings} low findings)`);
1241
+ }
1242
+ lines.push(`\nLast evaluated: ${score.evaluated_at}`);
1243
+ }
1244
+ else {
1245
+ lines.push("\nNo compliance score found. Run 'ges audit' then 'ges score'.");
1246
+ }
1247
+ const controlsDir = path.join(projectPath, "controls");
1248
+ if (fs.existsSync(controlsDir)) {
1249
+ const controlFiles = fs.readdirSync(controlsDir).filter(f => f.endsWith(".json"));
1250
+ if (controlFiles.length > 0) {
1251
+ lines.push(`\n**Control Files**: ${controlFiles.join(", ")}`);
1252
+ }
1253
+ }
1254
+ resultText = lines.join("\n");
1255
+ break;
1256
+ }
1257
+ case "list_missing_controls": {
1258
+ const framework = args.framework || "GDPR";
1259
+ const projectType = args.project_type;
1260
+ let controls;
1261
+ if (projectType) {
1262
+ const packs = getPacksForProjectType(projectType);
1263
+ controls = packs.flatMap(p => p.controls);
1264
+ }
1265
+ else {
1266
+ controls = getAllPacks().flatMap(p => p.controls);
1267
+ }
1268
+ const missing = controls.filter((c) => c.framework.toLowerCase() === framework.toLowerCase() && c.status !== "pass");
1269
+ if (missing.length === 0) {
1270
+ resultText = `All ${framework} controls are passing. No missing controls found.`;
1271
+ }
1272
+ else {
1273
+ const lines = [`# Missing Controls - ${framework}\n`];
1274
+ const critical = missing.filter(c => c.severity === "critical");
1275
+ const high = missing.filter(c => c.severity === "high");
1276
+ const medium = missing.filter(c => c.severity === "medium");
1277
+ const low = missing.filter(c => c.severity === "low");
1278
+ lines.push(`**Total**: ${missing.length} (${critical.length} critical, ${high.length} high, ${medium.length} medium, ${low.length} low)\n`);
1279
+ for (const group of [
1280
+ { label: "Critical", items: critical },
1281
+ { label: "High", items: high },
1282
+ { label: "Medium", items: medium },
1283
+ { label: "Low", items: low },
1284
+ ]) {
1285
+ if (group.items.length > 0) {
1286
+ lines.push(`## ${group.label} Severity\n`);
1287
+ for (const c of group.items) {
1288
+ lines.push(`**${c.id}**: ${c.name}`);
1289
+ lines.push(` Status: ${c.status} | Category: ${c.category}`);
1290
+ lines.push(` ${c.implementation_guidance.split(".")[0]}\n`);
1291
+ }
1292
+ }
1293
+ }
1294
+ lines.push(`\nUse \`fix_recommendation\` with a control_id to get detailed implementation guidance.`);
1295
+ resultText = lines.join("\n");
1296
+ }
1297
+ break;
1298
+ }
1299
+ case "list_framework_controls": {
1300
+ const framework = args.framework || "GDPR";
1301
+ const statusFilter = args.status_filter;
1302
+ let allControls;
1303
+ const pack = getPack(framework.toLowerCase());
1304
+ if (pack) {
1305
+ allControls = pack.controls;
1306
+ }
1307
+ else {
1308
+ allControls = getAllPacks().flatMap(p => p.controls);
1309
+ }
1310
+ const filtered = framework.toLowerCase() !== "all"
1311
+ ? allControls.filter(c => c.framework.toLowerCase() === framework.toLowerCase())
1312
+ : allControls;
1313
+ const controls = statusFilter
1314
+ ? filtered.filter(c => c.status === statusFilter)
1315
+ : filtered;
1316
+ if (controls.length === 0) {
1317
+ resultText = statusFilter
1318
+ ? `No ${framework} controls with status '${statusFilter}' found.`
1319
+ : `No controls found for framework '${framework}'. Available: GDPR, OWASP, CIS, NIST, AI, blockchain, government.`;
1320
+ }
1321
+ else {
1322
+ const lines = [`# ${framework} Controls (${controls.length} total${statusFilter ? `, filtered by: ${statusFilter}` : ""})\n`];
1323
+ lines.push("| ID | Name | Severity | Category | Status |");
1324
+ lines.push("|----|------|----------|----------|--------|");
1325
+ for (const c of controls) {
1326
+ lines.push(`| ${c.id} | ${c.name} | ${c.severity} | ${c.category} | ${c.status} |`);
1327
+ }
1328
+ lines.push(`\n### Summary`);
1329
+ const byStatus = {};
1330
+ for (const c of controls) {
1331
+ byStatus[c.status] = (byStatus[c.status] || 0) + 1;
1332
+ }
1333
+ for (const [status, count] of Object.entries(byStatus)) {
1334
+ lines.push(`- ${status}: ${count}`);
1335
+ }
1336
+ resultText = lines.join("\n");
1337
+ }
1338
+ break;
1339
+ }
1340
+ case "run_audit": {
1341
+ const projectPath = resolveProjectPath(args.project_path);
1342
+ if (!fs.existsSync(projectPath)) {
1343
+ resultText = `Project path does not exist: ${projectPath}`;
1344
+ break;
1345
+ }
1346
+ if (!fs.existsSync(path.join(projectPath, ".ges"))) {
1347
+ resultText = `GESF not initialized at ${projectPath}. Run 'ges init' first.`;
1348
+ break;
1349
+ }
1350
+ const { findings: rawFindings, scannedFiles } = runAudit(projectPath);
1351
+ const findings = deduplicateFindings(rawFindings);
1352
+ const projectConfig = loadProjectConfig(projectPath);
1353
+ const config = projectConfig.config;
1354
+ const frameworks = (config?.frameworks || ["GDPR", "OWASP"]);
1355
+ const projectType = (config?.project_type || "generic-web-application");
1356
+ const controls = getControlsForProject(projectType, frameworks);
1357
+ const overriddenControls = applyControlOverrides(controls, projectConfig.overrides);
1358
+ const auditedControls = updateControlsFromFindings(overriddenControls, findings);
1359
+ const score = generateScoreFile(auditedControls, frameworks, findings);
1360
+ const critical = findings.filter(f => f.severity === "critical");
1361
+ const high = findings.filter(f => f.severity === "high");
1362
+ const medium = findings.filter(f => f.severity === "medium");
1363
+ const low = findings.filter(f => f.severity === "low");
1364
+ const lines = [];
1365
+ lines.push(`# Security Audit Report\n`);
1366
+ lines.push(`**Scanned**: ${scannedFiles} files`);
1367
+ lines.push(`**Findings**: ${findings.length} total (${critical.length} critical, ${high.length} high, ${medium.length} medium, ${low.length} low)\n`);
1368
+ if (findings.length > 0) {
1369
+ const grouped = {};
1370
+ for (const f of findings) {
1371
+ if (!grouped[f.category])
1372
+ grouped[f.category] = [];
1373
+ grouped[f.category].push(f);
1374
+ }
1375
+ const categoryOrder = ["secrets", "encryption", "authentication", "injection", "xss", "security", "database", "config", "infrastructure", "dependencies"];
1376
+ for (const cat of categoryOrder) {
1377
+ if (!grouped[cat])
1378
+ continue;
1379
+ lines.push(`## ${cat.charAt(0).toUpperCase() + cat.slice(1)}\n`);
1380
+ for (const f of grouped[cat]) {
1381
+ const loc = f.file !== "project" ? ` (${f.file}${f.line ? `:${f.line}` : ""})` : " (project-wide)";
1382
+ lines.push(`- **[${f.severity.toUpperCase()}] ${f.title}**${loc}`);
1383
+ lines.push(` Evidence: ${f.evidence.slice(0, 150)}`);
1384
+ lines.push(` Fix: ${f.fix}`);
1385
+ if (f.controlIds && f.controlIds.length > 0) {
1386
+ lines.push(` Controls: ${f.controlIds.join(", ")}`);
1387
+ }
1388
+ lines.push("");
1389
+ }
1390
+ }
1391
+ }
1392
+ else {
1393
+ lines.push("**No security findings detected.** All scanned files are clean.\n");
1394
+ }
1395
+ lines.push("## Compliance Score\n");
1396
+ lines.push(`**Overall: ${score.overall}% (Grade: ${score.overall_grade})**\n`);
1397
+ for (const [fw, data] of Object.entries(score.frameworks)) {
1398
+ lines.push(`- ${fw}: ${data.score}% (${data.grade}) — ${data.passed_controls}/${data.total_controls} controls passed, ${data.critical_failures} critical failures`);
1399
+ }
1400
+ if (projectConfig.overrides.length > 0) {
1401
+ lines.push(`\n*Note: ${projectConfig.overrides.length} control overrides applied.*`);
1402
+ }
1403
+ resultText = lines.join("\n");
1404
+ break;
1405
+ }
1406
+ case "generate_compliance_report": {
1407
+ const projectName = args.project_name || "Project";
1408
+ const projectType = (args.project_type || "saas");
1409
+ const frameworksStr = args.frameworks || "GDPR,OWASP";
1410
+ const frameworks = frameworksStr.split(",").map(f => f.trim());
1411
+ resultText = generateFullComplianceReport(projectName, projectType, frameworks);
1412
+ break;
1413
+ }
1414
+ case "generate_audit_report": {
1415
+ const projectPath = resolveProjectPath(args.project_path);
1416
+ if (!args.project_path && !fs.existsSync(path.join(projectPath, ".ges"))) {
1417
+ const projectName = args.project_name || "Project";
1418
+ const projectType = "generic-web-application";
1419
+ resultText = generateFullComplianceReport(projectName, projectType, ["GDPR", "OWASP"]);
1420
+ resultText += "\n\n**Note: No project path specified and no .ges/ directory found. Showing default compliance report. Provide project_path for actual audit results.**";
1421
+ break;
1422
+ }
1423
+ if (!fs.existsSync(projectPath)) {
1424
+ resultText = `Project path does not exist: ${projectPath}`;
1425
+ break;
1426
+ }
1427
+ const projectName = args.project_name || path.basename(projectPath);
1428
+ const projectConfig = loadProjectConfig(projectPath);
1429
+ const config = projectConfig.config;
1430
+ const projectType = (config?.project_type || "generic-web-application");
1431
+ const frameworks = (config?.frameworks || ["GDPR", "OWASP"]);
1432
+ const { findings: rawFindings, scannedFiles } = runAudit(projectPath);
1433
+ const findings = deduplicateFindings(rawFindings);
1434
+ resultText = generateFullComplianceReport(projectName, projectType, frameworks, findings, projectConfig.overrides);
1435
+ resultText += `\n\n**Audit Details**: Scanned ${scannedFiles} files. ${findings.length} findings detected.`;
1436
+ break;
1437
+ }
1438
+ case "fix_recommendation": {
1439
+ const controlId = args.control_id || "";
1440
+ const findingTitle = args.finding_title;
1441
+ if (!controlId && !findingTitle) {
1442
+ resultText = "Please provide either a control_id (e.g. 'GDPR-ART32-001') or a finding_title to get fix guidance.";
1443
+ break;
1444
+ }
1445
+ resultText = generateFixGuidance(controlId, findingTitle);
1446
+ break;
1447
+ }
1448
+ case "generate_retention_policy": {
1449
+ const name = args.project_name || "Project";
1450
+ resultText = `# Data Retention Policy - ${name}\n\n## 1. Purpose\n\nThis policy defines the retention periods for all data categories processed by ${name}.\n\n## 2. Retention Periods\n\n| Category | Period | Justification | Legal Basis |\n|----------|--------|---------------|-------------|\n| User account data | Account lifetime + 30 days | Contract fulfillment | Art. 6(1)(b) |\n| Email addresses | Account lifetime + 30 days | Communication | Art. 6(1)(b) |\n| Authentication data | Session duration | Security | Art. 6(1)(f) |\n| IP addresses | 30 days | Security monitoring | Art. 6(1)(f) |\n| Audit logs | 1 year | Legal obligation | Art. 6(1)(c) |\n| Session data | Session duration | Operational | Art. 6(1)(b) |\n| Marketing consent | Until withdrawal | Consent | Art. 6(1)(a) |\n| Support tickets | 2 years | Quality assurance | Art. 6(1)(f) |\n\n## 3. Deletion Process\n\n1. Automated deletion: Data past retention period is flagged for deletion\n2. Deletion verification: Monthly audit of deletion jobs\n3. Backup purge: Backups containing expired data are purged within 90 days\n4. Deletion log: All deletions are logged with timestamp and scope\n\n## 4. Exceptions\n\n- Data subject to legal hold: Retained until hold is lifted\n- Data required for ongoing legal proceedings: Retained until proceedings conclude\n- Anonymized data may be retained indefinitely for statistical purposes\n\n## 5. Data Subject Rights\n\n- Users can request early deletion via the data subject rights process\n- Right to erasure (Article 17) requests are processed within 30 days\n- Verification of identity is required before any deletion\n\n## 6. Review Schedule\n\nThis policy is reviewed quarterly and updated as needed.\n\nLast reviewed: ${new Date().toISOString().split("T")[0]}`;
1451
+ break;
1452
+ }
1453
+ case "generate_incident_response": {
1454
+ const name = args.project_name || "Project";
1455
+ resultText = `# Incident Response Plan - ${name}\n\n## 1. Severity Levels\n\n| Level | Response Time | Examples |\n|-------|--------------|----------|\n| P1 (Critical) | 15 minutes | Data breach, system compromise, ransomware |\n| P2 (High) | 1 hour | Unauthorized access, vulnerability exploitation |\n| P3 (Medium) | 4 hours | Suspicious activity, policy violation |\n| P4 (Low) | 24 hours | Minor misconfiguration, informational findings |\n\n## 2. Response Team\n\n| Role | Responsibility |\n|------|---------------|\n| Incident Commander | Overall coordination and decision making |\n| Security Lead | Technical investigation and containment |\n| Communications Lead | Internal and external notifications |\n| Legal Advisor | Regulatory and legal compliance |\n| DPO (if applicable) | GDPR compliance and data subject notification |\n\n## 3. Response Process\n\n### Phase 1: Detection & Identification\n- Alert triggered by monitoring, user report, or external notification\n- Initial assessment of scope and severity\n- Assign severity level (P1-P4)\n\n### Phase 2: Containment\n- Isolate affected systems\n- Preserve evidence for forensic analysis\n- Implement temporary controls\n\n### Phase 3: Eradication\n- Identify root cause\n- Remove threat from all systems\n- Patch vulnerabilities\n\n### Phase 4: Recovery\n- Restore systems from verified backups\n- Verify system integrity\n- Resume normal operations with enhanced monitoring\n\n### Phase 5: Post-Incident Review\n- Document timeline and actions taken\n- Identify lessons learned\n- Update security controls and processes\n- Update this plan if needed\n\n## 4. GDPR Breach Notification\n\n**72-hour rule**: If a breach is likely to result in a risk to data subjects:\n1. Notify supervisory authority within 72 hours (Article 33)\n2. If high risk: Notify affected data subjects without undue delay (Article 34)\n3. Document all actions in the breach register\n\n### Notification Template\n- Nature of the breach\n- Categories and approximate number of data subjects\n- Likely consequences\n- Measures taken or proposed\n\n## 5. Communication Templates\n\n### Internal Notification\nSubject: [P-level] Security Incident - [Brief Description]\n- What: [Description]\n- When: [Detection time]\n- Impact: [Known impact]\n- Actions: [Current containment measures]\n- Next update: [Time]\n\n### Regulatory Notification\nAddressed to: [Supervisory Authority]\n- DPO contact: [Name, email, phone]\n- Breach description: [Details]\n- Affected individuals: [Number and categories]\n- Measures taken: [Containment and remediation]\n\n## 6. Testing\n\n- Tabletop exercises: Quarterly\n- Full simulation: Annually\n- Plan review: After each incident and at least semi-annually\n\nLast reviewed: ${new Date().toISOString().split("T")[0]}`;
1456
+ break;
1457
+ }
1458
+ case "generate_risk_assessment": {
1459
+ const name = args.project_name || "Project";
1460
+ resultText = `# Risk Assessment - ${name}\n\n## 1. Methodology\n\nRisk assessment follows the GESF methodology based on ISO 27005 and NIST SP 800-30.\n\nRisk Score = Likelihood × Impact\n\n| Rating | Score |\n|--------|-------|\n| Critical | 5 |\n| High | 4 |\n| Medium | 3 |\n| Low | 2 |\n| Negligible | 1 |\n\n## 2. Risk Register\n\n| ID | Risk | Likelihood | Impact | Score | Mitigation | Residual |\n|----|------|-----------|--------|-------|------------|----------|\n| R001 | Data breach (external) | Medium (3) | Critical (5) | 15 | Encryption, MFA, WAF, pen testing | Medium |\n| R002 | Insider threat | Low (2) | High (4) | 8 | RBAC, audit logging, DLP | Low |\n| R003 | Data loss | Low (2) | Critical (5) | 10 | Backups, DR plan, replication | Low |\n| R004 | Ransomware | Low (2) | Critical (5) | 10 | Backups, EDR, email filtering | Low |\n| R005 | Supply chain attack | Medium (3) | High (4) | 12 | Dependency scanning, SBOM, vendor assessment | Medium |\n| R006 | Misconfiguration | Medium (3) | High (4) | 12 | IaC scanning, security review, hardening | Medium |\n| R007 | Credential compromise | Medium (3) | High (4) | 12 | MFA, password policy, monitoring | Low |\n| R008 | DDoS attack | Low (2) | Medium (3) | 6 | CDN, rate limiting, WAF | Low |\n| R009 | Non-compliance (GDPR) | Medium (3) | High (4) | 12 | Regular audits, compliance scanning | Low |\n| R010 | Third-party data breach | Medium (3) | High (4) | 12 | DPA requirements, vendor assessment | Medium |\n\n## 3. Risk Treatment Plan\n\n| ID | Treatment | Owner | Deadline | Status |\n|----|-----------|-------|----------|--------|\n| R001 | Implement WAF + annual pen testing | Security Lead | Quarterly | In progress |\n| R002 | Deploy DLP solution | Security Lead | Q2 | Planned |\n| R003 | Test DR plan monthly | Platform Lead | Monthly | In progress |\n| R005 | Automate dependency scanning | DevOps | Q1 | In progress |\n| R006 | Implement IaC security scanning | DevOps | Q2 | Planned |\n| R007 | Enforce MFA for all users | Security Lead | Q1 | Done |\n| R009 | Monthly compliance audits | Compliance Lead | Monthly | In progress |\n\n## 4. Acceptance Criteria\n\nRisks with residual score > 12 require executive sign-off.\nAll critical risks must have active mitigation plans.\n\n## 5. Review Schedule\n\n- Full assessment: Annually\n- Risk register review: Quarterly\n- After any significant change or incident\n\nLast reviewed: ${new Date().toISOString().split("T")[0]}`;
1461
+ break;
1462
+ }
1463
+ case "generate_dpa": {
1464
+ const name = args.project_name || "Project";
1465
+ resultText = `# Data Processing Agreement - ${name}\n\n## 1. Parties\n\n**Controller**: [Company Name]\nAddress: [Address]\nDPO: [Name, Email]\n\n**Processor**: [Service Provider Name]\nAddress: [Address]\nDPO: [Name, Email]\n\n## 2. Subject Matter and Duration\n\nThis Agreement governs the processing of personal data by the Processor on behalf of the Controller in connection with the provision of services for **${name}**.\n\n**Duration**: Effective from the date of signature until termination of the underlying service agreement.\n\n## 3. Details of Processing\n\n| Category | Type | Purpose |\n|----------|------|--------|\n| User data | Personal | Service delivery |\n| Authentication data | Personal | Access control |\n| Usage data | Operational | Analytics |\n| Communication data | Personal | Support |\n\n## 4. Obligations of the Processor\n\nThe Processor shall:\n\n1. Process data only on documented instructions from the Controller\n2. Ensure confidentiality of all persons authorized to process personal data\n3. Implement appropriate technical and organizational measures (Article 32)\n4. Not engage sub-processors without prior authorization\n5. Assist the Controller in responding to data subject rights requests\n6. Assist the Controller in ensuring compliance with Articles 32-36\n7. Delete or return all personal data upon termination\n8. Make available all information necessary to demonstrate compliance\n9. Allow and contribute to audits conducted by the Controller or mandated auditor\n\n## 5. Security Measures (Article 32)\n\n- Encryption of personal data at rest (AES-256-GCM)\n- Encryption of personal data in transit (TLS 1.2+)\n- Access controls with principle of least privilege\n- Regular security testing and vulnerability assessments\n- Incident response plan with 72-hour notification\n- Audit logging with immutable records\n- Regular backup and disaster recovery testing\n\n## 6. Sub-Processors\n\n| Sub-Processor | Purpose | Location |\n|-------------|---------|----------|\n| [Cloud Provider] | Hosting | [Country] |\n| [Email Provider] | Communications | [Country] |\n\nThe Controller authorizes the use of the above sub-processors. Any changes will be notified 30 days in advance.\n\n## 7. Data Breach Notification\n\nThe Processor shall notify the Controller within 24 hours of becoming aware of a personal data breach, providing:\n- Nature of the breach including categories and approximate numbers\n- Name and contact details of the DPO\n- Likely consequences of the breach\n- Measures taken or proposed to address the breach\n\n## 8. Data Subject Rights\n\nThe Processor shall assist the Controller in fulfilling its obligations to respond to data subject requests for:\n- Access (Article 15)\n- Rectification (Article 16)\n- Erasure (Article 17)\n- Restriction (Article 18)\n- Data portability (Article 20)\n- Objection (Article 21)\n\n## 9. International Transfers\n\nAny transfer of personal data outside the EEA shall be subject to:\n- Adequacy decision by the European Commission, OR\n- Standard Contractual Clauses (SCCs), OR\n- Binding Corporate Rules (BCRs)\n\n## 10. Termination\n\nUpon termination:\n1. Processor shall return all personal data to the Controller within 30 days\n2. If return is not possible, Processor shall delete all personal data\n3. Processor shall certify deletion in writing\n\n## 11. Liability and Indemnification\n\nEach party's liability shall be governed by the underlying service agreement and applicable GDPR provisions.\n\n## 12. Governing Law\n\nThis Agreement shall be governed by [Applicable Jurisdiction].\n\n---\n\n**Signed:**\n\nController: _________________________ Date: ____________\n\nProcessor: _________________________ Date: ____________`;
1466
+ break;
1467
+ }
1468
+ case "generate_data_inventory": {
1469
+ const projectName = args.project_name || "Project";
1470
+ const projectType = args.project_type || "saas";
1471
+ resultText = generateDataInventory(projectName, projectType);
1472
+ break;
1473
+ }
1474
+ case "generate_processing_records": {
1475
+ const projectName = args.project_name || "Project";
1476
+ const controllerName = args.controller_name || "[Organization Name]";
1477
+ resultText = generateProcessingRecords(projectName, controllerName);
1478
+ break;
1479
+ }
1480
+ case "auto_fix": {
1481
+ const projectPath = resolveProjectPath(args.project_path);
1482
+ const dryRun = args.dry_run === "true";
1483
+ const ruleFilter = args.rule_ids ? new Set(args.rule_ids.split(",").map(r => r.trim())) : undefined;
1484
+ if (!fs.existsSync(projectPath)) {
1485
+ resultText = `Project path does not exist: ${projectPath}`;
1486
+ break;
1487
+ }
1488
+ const { findings: rawFindings, scannedFiles } = runAudit(projectPath);
1489
+ const findings = deduplicateFindings(rawFindings);
1490
+ if (findings.length === 0) {
1491
+ resultText = `# Auto-Fix Report\n\n**Project**: ${projectPath}\n**Scanned**: ${scannedFiles} files\n\nNo issues found. Project is clean!`;
1492
+ break;
1493
+ }
1494
+ const { actions, warnings } = createAutoFixPlan(projectPath, findings, ruleFilter);
1495
+ if (actions.length === 0) {
1496
+ const lines = [
1497
+ `# Auto-Fix Report\n`,
1498
+ `**Project**: ${projectPath}`,
1499
+ `**Scanned**: ${scannedFiles} files`,
1500
+ `**Findings**: ${findings.length}\n`,
1501
+ `## No Auto-Fixable Issues\n`,
1502
+ `All ${findings.length} findings require manual review:\n`,
1503
+ ];
1504
+ for (const w of warnings)
1505
+ lines.push(`- ${w}`);
1506
+ for (const f of findings.slice(0, 10)) {
1507
+ lines.push(`- [${f.severity.toUpperCase()}] ${f.title} (${f.file}${f.line ? `:${f.line}` : ""})`);
1508
+ }
1509
+ resultText = lines.join("\n");
1510
+ break;
1511
+ }
1512
+ const npmInstalls = getNpmInstallsFromActions(actions);
1513
+ const lines = [
1514
+ `# Auto-Fix Report\n`,
1515
+ `**Project**: ${projectPath}`,
1516
+ `**Scanned**: ${scannedFiles} files`,
1517
+ `**Findings**: ${findings.length} total`,
1518
+ `**Auto-fixable**: ${actions.length} actions`,
1519
+ `**Manual review**: ${warnings.length} items`,
1520
+ dryRun ? `**Mode**: DRY RUN (no changes applied)\n` : "",
1521
+ ];
1522
+ if (dryRun) {
1523
+ lines.push("## Planned Actions (dry run)\n");
1524
+ }
1525
+ else {
1526
+ lines.push("## Applied Fixes\n");
1527
+ }
1528
+ let applied = 0;
1529
+ let failed = 0;
1530
+ for (const action of actions) {
1531
+ if (dryRun) {
1532
+ lines.push(`- [${action.type}] ${action.filePath}: ${action.description}`);
1533
+ applied++;
1534
+ }
1535
+ else {
1536
+ const result = applyAutoFixAction(projectPath, action);
1537
+ if (result.applied) {
1538
+ applied++;
1539
+ lines.push(`- ✓ [${action.type}] ${action.filePath}: ${action.description}`);
1540
+ }
1541
+ else {
1542
+ failed++;
1543
+ lines.push(`- ✗ [${action.type}] ${action.filePath}: ${action.description} — ${result.error}`);
1544
+ }
1545
+ }
1546
+ }
1547
+ lines.push(`\n## Summary\n`);
1548
+ lines.push(`- Actions applied: ${applied}${failed > 0 ? ` (${failed} failed)` : ""}`);
1549
+ if (npmInstalls.length > 0) {
1550
+ lines.push(`\n## npm Packages to Install\n`);
1551
+ lines.push("```bash");
1552
+ lines.push(`npm install ${npmInstalls.join(" ")}`);
1553
+ lines.push("```\n");
1554
+ lines.push("Or if using pnpm:");
1555
+ lines.push("```bash");
1556
+ lines.push(`pnpm add ${npmInstalls.join(" ")}`);
1557
+ lines.push("```");
1558
+ }
1559
+ if (warnings.length > 0) {
1560
+ lines.push(`\n## Manual Review Required\n`);
1561
+ for (const w of warnings)
1562
+ lines.push(`- ${w}`);
1563
+ }
1564
+ lines.push(`\n## Next Steps`);
1565
+ lines.push("1. Install the npm packages listed above");
1566
+ lines.push("2. Review all changes with `git diff`");
1567
+ lines.push("3. Run `ges audit` to verify fixes");
1568
+ lines.push("4. Address remaining manual review items");
1569
+ lines.push("5. Use `fix_recommendation` tool for detailed guidance on manual items");
1570
+ resultText = lines.join("\n");
1571
+ break;
1572
+ }
1573
+ case "apply_control_override": {
1574
+ const projectPath = resolveProjectPath(args.project_path);
1575
+ const controlId = args.control_id || "";
1576
+ const status = (args.status || "not-applicable");
1577
+ const reason = args.reason || "";
1578
+ if (!controlId) {
1579
+ resultText = "Error: control_id is required.";
1580
+ break;
1581
+ }
1582
+ if (!["not-applicable", "pass"].includes(status)) {
1583
+ resultText = `Error: status must be 'not-applicable' or 'pass'. Got: ${status}`;
1584
+ break;
1585
+ }
1586
+ if (!fs.existsSync(path.join(projectPath, ".ges"))) {
1587
+ resultText = `Error: No .ges/ directory at ${projectPath}. Run 'ges init' first.`;
1588
+ break;
1589
+ }
1590
+ const overridePath = path.join(projectPath, ".ges", "control-overrides.json");
1591
+ let overrides = [];
1592
+ if (fs.existsSync(overridePath)) {
1593
+ const parsed = readJsonFileSafe(overridePath);
1594
+ if (Array.isArray(parsed))
1595
+ overrides = parsed;
1596
+ }
1597
+ const existing = overrides.findIndex(o => o.control_id === controlId);
1598
+ if (existing >= 0) {
1599
+ overrides[existing] = { control_id: controlId, status, reason };
1600
+ }
1601
+ else {
1602
+ overrides.push({ control_id: controlId, status, reason });
1603
+ }
1604
+ const dir = path.dirname(overridePath);
1605
+ if (!fs.existsSync(dir))
1606
+ fs.mkdirSync(dir, { recursive: true });
1607
+ fs.writeFileSync(overridePath, JSON.stringify(overrides, null, 2), "utf-8");
1608
+ const lines = [
1609
+ `# Control Override Applied\n`,
1610
+ `**Control**: ${controlId}`,
1611
+ `**Status**: ${status}`,
1612
+ `**Reason**: ${reason || "(none provided)"}`,
1613
+ `**File**: ${overridePath}`,
1614
+ `**Total overrides**: ${overrides.length}\n`,
1615
+ `The override will take effect on the next \`ges audit\` or \`ges score\` run.`,
1616
+ `\nRun \`ges audit\` then \`ges score\` to see the updated compliance score.`,
1617
+ ];
1618
+ resultText = lines.join("\n");
1619
+ break;
1620
+ }
1621
+ case "implement_control": {
1622
+ const projectPath = resolveProjectPath(args.project_path);
1623
+ const controlId = args.control_id || "";
1624
+ if (!controlId) {
1625
+ resultText = "Error: control_id is required. Example: GDPR-ART32-002, GDPR-ART32-006, AUTH-002";
1626
+ break;
1627
+ }
1628
+ if (!fs.existsSync(projectPath)) {
1629
+ resultText = `Error: Project path does not exist: ${projectPath}`;
1630
+ break;
1631
+ }
1632
+ const hasSrc = fs.existsSync(path.join(projectPath, "src"));
1633
+ const appFile = findMainAppFile(projectPath);
1634
+ const lines = [`# Implement Control: ${controlId}\n`];
1635
+ const actions = [];
1636
+ const controlMap = {
1637
+ "GDPR-ART32-002": {
1638
+ name: "Encryption at Rest",
1639
+ actions: buildEncryptionAtRestImpl(projectPath, hasSrc),
1640
+ warnings: ["Configure encryption keys via environment variables or a vault service."],
1641
+ },
1642
+ "GDPR-ART32-003": {
1643
+ name: "Encryption in Transit",
1644
+ actions: buildEncryptionInTransitImpl(projectPath, hasSrc),
1645
+ warnings: ["Ensure your server/infrastructure is configured with TLS certificates."],
1646
+ },
1647
+ "GDPR-ART32-004": {
1648
+ name: "Unique User Identification",
1649
+ actions: buildUserIdentificationImpl(projectPath, hasSrc),
1650
+ warnings: ["Integrate the auth middleware into your routes."],
1651
+ },
1652
+ "GDPR-ART32-005": {
1653
+ name: "Automatic Session Timeout",
1654
+ actions: buildSessionTimeoutFix(projectPath),
1655
+ warnings: [],
1656
+ },
1657
+ "GDPR-ART32-006": {
1658
+ name: "Audit Logging",
1659
+ actions: buildLoggingFix(projectPath),
1660
+ warnings: ["Use auditLog() for all security-relevant actions."],
1661
+ },
1662
+ "GDPR-ART32-007": {
1663
+ name: "Integrity Controls",
1664
+ actions: buildIntegrityControlsImpl(projectPath, hasSrc),
1665
+ warnings: ["Apply integrity hashing to all critical data flows."],
1666
+ },
1667
+ "GDPR-ART32-008": {
1668
+ name: "Backup and Recovery",
1669
+ actions: buildBackupPolicyImpl(projectPath, hasSrc),
1670
+ warnings: ["Test your backup recovery process monthly."],
1671
+ },
1672
+ "GDPR-ART32-009": {
1673
+ name: "Regular Security Testing",
1674
+ actions: buildSecurityTestingImpl(projectPath),
1675
+ warnings: ["Schedule regular security scans in CI/CD."],
1676
+ },
1677
+ };
1678
+ const plan = controlMap[controlId];
1679
+ if (!plan) {
1680
+ resultText = `Control ${controlId} does not have an auto-implementation. Use \`fix_recommendation\` for manual guidance.\n\nAvailable auto-implementations: ${Object.keys(controlMap).join(", ")}`;
1681
+ break;
1682
+ }
1683
+ lines.push(`**Control**: ${plan.name}\n`);
1684
+ for (const action of plan.actions) {
1685
+ const result = applyAutoFixAction(projectPath, action);
1686
+ if (result.applied) {
1687
+ lines.push(`- ✓ [${action.type}] ${action.filePath}: ${action.description}`);
1688
+ }
1689
+ else if (result.error === "File already exists") {
1690
+ lines.push(`- → [${action.type}] ${action.filePath}: Already exists (skipped)`);
1691
+ }
1692
+ else {
1693
+ lines.push(`- ✗ [${action.type}] ${action.filePath}: ${result.error}`);
1694
+ }
1695
+ }
1696
+ const npmInstalls = getNpmInstallsFromActions(plan.actions);
1697
+ if (npmInstalls.length > 0) {
1698
+ lines.push(`\n## Install Dependencies\n`);
1699
+ lines.push("```bash");
1700
+ lines.push(`npm install ${npmInstalls.join(" ")}`);
1701
+ lines.push("```");
1702
+ }
1703
+ if (plan.warnings.length > 0) {
1704
+ lines.push(`\n## Notes`);
1705
+ for (const w of plan.warnings)
1706
+ lines.push(`- ${w}`);
1707
+ }
1708
+ lines.push(`\n## Next Steps`);
1709
+ lines.push("1. Install any npm packages listed above");
1710
+ lines.push("2. Import and integrate the generated files into your app");
1711
+ lines.push("3. Run `ges audit` to verify the control is now passing");
1712
+ lines.push(`4. Or use \`apply_control_override\` with control_id="${controlId}" if verified manually`);
1713
+ resultText = lines.join("\n");
1714
+ break;
1715
+ }
1716
+ default:
1717
+ return {
1718
+ jsonrpc: "2.0",
1719
+ id: request.id,
1720
+ error: { code: -32601, message: `Unknown tool: ${toolName}` },
1721
+ };
160
1722
  }
161
- default:
162
- return {
163
- jsonrpc: "2.0",
164
- id: request.id,
165
- error: { code: -32601, message: `Unknown tool: ${toolName}` },
166
- };
1723
+ }
1724
+ catch (err) {
1725
+ return {
1726
+ jsonrpc: "2.0",
1727
+ id: request.id,
1728
+ result: {
1729
+ content: [{
1730
+ type: "text",
1731
+ text: `Error executing tool '${toolName}': ${err instanceof Error ? err.message : String(err)}. Check your parameters and try again.`,
1732
+ }],
1733
+ },
1734
+ };
167
1735
  }
168
1736
  return {
169
1737
  jsonrpc: "2.0",