vaspera 2.11.0 → 2.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (149) hide show
  1. package/dist/__tests__/audit-trail.test.d.ts +7 -0
  2. package/dist/__tests__/audit-trail.test.d.ts.map +1 -0
  3. package/dist/__tests__/audit-trail.test.js +336 -0
  4. package/dist/__tests__/audit-trail.test.js.map +1 -0
  5. package/dist/__tests__/property-test-helpers.d.ts +1 -1
  6. package/dist/action/pr-comment.test.js +1 -0
  7. package/dist/action/pr-comment.test.js.map +1 -1
  8. package/dist/action/sarif-upload.test.js +1 -0
  9. package/dist/action/sarif-upload.test.js.map +1 -1
  10. package/dist/autofix/ast/__tests__/typescript.test.d.ts +5 -0
  11. package/dist/autofix/ast/__tests__/typescript.test.d.ts.map +1 -0
  12. package/dist/autofix/ast/__tests__/typescript.test.js +210 -0
  13. package/dist/autofix/ast/__tests__/typescript.test.js.map +1 -0
  14. package/dist/autofix/ast/index.d.ts +11 -0
  15. package/dist/autofix/ast/index.d.ts.map +1 -0
  16. package/dist/autofix/ast/index.js +11 -0
  17. package/dist/autofix/ast/index.js.map +1 -0
  18. package/dist/autofix/ast/types.d.ts +77 -0
  19. package/dist/autofix/ast/types.d.ts.map +1 -0
  20. package/dist/autofix/ast/types.js +9 -0
  21. package/dist/autofix/ast/types.js.map +1 -0
  22. package/dist/autofix/ast/typescript.d.ts +17 -0
  23. package/dist/autofix/ast/typescript.d.ts.map +1 -0
  24. package/dist/autofix/ast/typescript.js +427 -0
  25. package/dist/autofix/ast/typescript.js.map +1 -0
  26. package/dist/autofix/constitution.schema.d.ts +21 -21
  27. package/dist/autofix/index.d.ts +1 -0
  28. package/dist/autofix/index.d.ts.map +1 -1
  29. package/dist/autofix/index.js +2 -0
  30. package/dist/autofix/index.js.map +1 -1
  31. package/dist/config/flags.d.ts +6 -6
  32. package/dist/history/store.d.ts +55 -1
  33. package/dist/history/store.d.ts.map +1 -1
  34. package/dist/history/store.js +152 -4
  35. package/dist/history/store.js.map +1 -1
  36. package/dist/history/types.d.ts +9 -5
  37. package/dist/history/types.d.ts.map +1 -1
  38. package/dist/history/verify.d.ts.map +1 -1
  39. package/dist/history/verify.js +5 -3
  40. package/dist/history/verify.js.map +1 -1
  41. package/dist/index.d.ts.map +1 -1
  42. package/dist/index.js +627 -0
  43. package/dist/index.js.map +1 -1
  44. package/dist/integrations/siem/datadog.d.ts +44 -0
  45. package/dist/integrations/siem/datadog.d.ts.map +1 -0
  46. package/dist/integrations/siem/datadog.js +211 -0
  47. package/dist/integrations/siem/datadog.js.map +1 -0
  48. package/dist/integrations/siem/format.d.ts +59 -0
  49. package/dist/integrations/siem/format.d.ts.map +1 -0
  50. package/dist/integrations/siem/format.js +360 -0
  51. package/dist/integrations/siem/format.js.map +1 -0
  52. package/dist/integrations/siem/index.d.ts +56 -0
  53. package/dist/integrations/siem/index.d.ts.map +1 -0
  54. package/dist/integrations/siem/index.js +117 -0
  55. package/dist/integrations/siem/index.js.map +1 -0
  56. package/dist/integrations/siem/sentinel.d.ts +53 -0
  57. package/dist/integrations/siem/sentinel.d.ts.map +1 -0
  58. package/dist/integrations/siem/sentinel.js +231 -0
  59. package/dist/integrations/siem/sentinel.js.map +1 -0
  60. package/dist/integrations/siem/splunk.d.ts +46 -0
  61. package/dist/integrations/siem/splunk.d.ts.map +1 -0
  62. package/dist/integrations/siem/splunk.js +210 -0
  63. package/dist/integrations/siem/splunk.js.map +1 -0
  64. package/dist/integrations/siem/types.d.ts +210 -0
  65. package/dist/integrations/siem/types.d.ts.map +1 -0
  66. package/dist/integrations/siem/types.js +9 -0
  67. package/dist/integrations/siem/types.js.map +1 -0
  68. package/dist/persistence/__tests__/persistence.test.d.ts +5 -0
  69. package/dist/persistence/__tests__/persistence.test.d.ts.map +1 -0
  70. package/dist/persistence/__tests__/persistence.test.js +369 -0
  71. package/dist/persistence/__tests__/persistence.test.js.map +1 -0
  72. package/dist/persistence/db.d.ts +15 -0
  73. package/dist/persistence/db.d.ts.map +1 -0
  74. package/dist/persistence/db.js +79 -0
  75. package/dist/persistence/db.js.map +1 -0
  76. package/dist/persistence/index.d.ts +66 -0
  77. package/dist/persistence/index.d.ts.map +1 -0
  78. package/dist/persistence/index.js +143 -0
  79. package/dist/persistence/index.js.map +1 -0
  80. package/dist/persistence/migrations/index.d.ts +10 -0
  81. package/dist/persistence/migrations/index.d.ts.map +1 -0
  82. package/dist/persistence/migrations/index.js +125 -0
  83. package/dist/persistence/migrations/index.js.map +1 -0
  84. package/dist/persistence/repositories/findings.d.ts +41 -0
  85. package/dist/persistence/repositories/findings.d.ts.map +1 -0
  86. package/dist/persistence/repositories/findings.js +238 -0
  87. package/dist/persistence/repositories/findings.js.map +1 -0
  88. package/dist/persistence/repositories/projects.d.ts +22 -0
  89. package/dist/persistence/repositories/projects.d.ts.map +1 -0
  90. package/dist/persistence/repositories/projects.js +71 -0
  91. package/dist/persistence/repositories/projects.js.map +1 -0
  92. package/dist/persistence/repositories/scans.d.ts +30 -0
  93. package/dist/persistence/repositories/scans.d.ts.map +1 -0
  94. package/dist/persistence/repositories/scans.js +107 -0
  95. package/dist/persistence/repositories/scans.js.map +1 -0
  96. package/dist/persistence/repositories/trends.d.ts +42 -0
  97. package/dist/persistence/repositories/trends.d.ts.map +1 -0
  98. package/dist/persistence/repositories/trends.js +178 -0
  99. package/dist/persistence/repositories/trends.js.map +1 -0
  100. package/dist/persistence/types.d.ts +105 -0
  101. package/dist/persistence/types.d.ts.map +1 -0
  102. package/dist/persistence/types.js +13 -0
  103. package/dist/persistence/types.js.map +1 -0
  104. package/dist/plugins/types.d.ts +2 -2
  105. package/dist/scanners/ai-code/types.d.ts +12 -12
  106. package/dist/scanners/cache.d.ts.map +1 -1
  107. package/dist/scanners/cache.js +1 -0
  108. package/dist/scanners/cache.js.map +1 -1
  109. package/dist/scanners/deploy/types.d.ts +13 -13
  110. package/dist/scanners/detection/__tests__/detection.test.d.ts +5 -0
  111. package/dist/scanners/detection/__tests__/detection.test.d.ts.map +1 -0
  112. package/dist/scanners/detection/__tests__/detection.test.js +265 -0
  113. package/dist/scanners/detection/__tests__/detection.test.js.map +1 -0
  114. package/dist/scanners/detection/engines/ast-query.d.ts +23 -0
  115. package/dist/scanners/detection/engines/ast-query.d.ts.map +1 -0
  116. package/dist/scanners/detection/engines/ast-query.js +232 -0
  117. package/dist/scanners/detection/engines/ast-query.js.map +1 -0
  118. package/dist/scanners/detection/engines/data-flow.d.ts +12 -0
  119. package/dist/scanners/detection/engines/data-flow.d.ts.map +1 -0
  120. package/dist/scanners/detection/engines/data-flow.js +269 -0
  121. package/dist/scanners/detection/engines/data-flow.js.map +1 -0
  122. package/dist/scanners/detection/index.d.ts +29 -0
  123. package/dist/scanners/detection/index.d.ts.map +1 -0
  124. package/dist/scanners/detection/index.js +140 -0
  125. package/dist/scanners/detection/index.js.map +1 -0
  126. package/dist/scanners/detection/rules/builtin.d.ts +14 -0
  127. package/dist/scanners/detection/rules/builtin.d.ts.map +1 -0
  128. package/dist/scanners/detection/rules/builtin.js +307 -0
  129. package/dist/scanners/detection/rules/builtin.js.map +1 -0
  130. package/dist/scanners/detection/rules/loader.d.ts +19 -0
  131. package/dist/scanners/detection/rules/loader.d.ts.map +1 -0
  132. package/dist/scanners/detection/rules/loader.js +111 -0
  133. package/dist/scanners/detection/rules/loader.js.map +1 -0
  134. package/dist/scanners/detection/types.d.ts +171 -0
  135. package/dist/scanners/detection/types.d.ts.map +1 -0
  136. package/dist/scanners/detection/types.js +36 -0
  137. package/dist/scanners/detection/types.js.map +1 -0
  138. package/dist/scanners/index.d.ts +9 -1
  139. package/dist/scanners/index.d.ts.map +1 -1
  140. package/dist/scanners/index.js +64 -0
  141. package/dist/scanners/index.js.map +1 -1
  142. package/dist/scanners/index.test.js +6 -6
  143. package/dist/scanners/index.test.js.map +1 -1
  144. package/dist/scanners/scale/types.d.ts +3 -3
  145. package/dist/scanners/types.d.ts +1 -1
  146. package/dist/scanners/types.d.ts.map +1 -1
  147. package/dist/scanners/types.js +1 -0
  148. package/dist/scanners/types.js.map +1 -1
  149. package/package.json +5 -1
package/dist/index.js CHANGED
@@ -17,6 +17,10 @@ generateSummary, filterFindings, getActionItems,
17
17
  exportToSarif, exportForGitHub,
18
18
  // Rules
19
19
  loadCustomRules, checkFileAgainstRules, matchesToFindings, listRuleTemplates, generateSampleConfig, } from "./certification/index.js";
20
+ // Autofix PR generation
21
+ import { createAutofixPRs, isGhCliAvailable, isGhAuthenticated, } from "./autofix/index.js";
22
+ // Persistence layer
23
+ import { getProjectTrends, markFindingsFixed, getOpenFindings, } from "./persistence/index.js";
20
24
  // Deterministic scanners
21
25
  import { runAllScanners, checkScannersAvailable, scannerFindingsToCertificationFindings, generateScannerSummary, getScannerInstallCommands,
22
26
  // Mythos-class scanners
@@ -38,6 +42,9 @@ runSingleFrameworkAssessment, runComplianceAssessment, generateComplianceSummary
38
42
  // Evidence collection and audit trail verification
39
43
  import { collectEvidence, storeEvidenceBundle, formatEvidenceBundleAsMarkdown, } from "./evidence/index.js";
40
44
  import { verifyHistoryIntegrity, formatVerificationResultAsMarkdown, } from "./history/verify.js";
45
+ import { exportAuditTrail, } from "./history/store.js";
46
+ // SIEM Integration
47
+ import { createSIEMClient, getSIEMRegistry, createFindingEvent, } from "./integrations/siem/index.js";
41
48
  // SBOM, Provenance, and Sigstore Signing (uses @sigstore/sign for real signing)
42
49
  import { generateSBOM, generateSBOMSummary, generateProvenance, generateProvenanceSummary, verifyProvenance, signContent, isSigningAvailable, generateSigningSummary, detectCIEnvironment, } from "./sbom/index.js";
43
50
  // Cost tracking
@@ -1954,6 +1961,307 @@ server.registerTool("autofix_batch", {
1954
1961
  });
1955
1962
  });
1956
1963
  // ---------------------------------------------------------------------------
1964
+ // Tool: Create Autofix Pull Requests
1965
+ // ---------------------------------------------------------------------------
1966
+ server.registerTool("autofix_create_prs", {
1967
+ title: "Create Autofix Pull Requests",
1968
+ description: `Create GitHub pull requests with automated security fixes. Groups findings by severity/file/pattern and generates separate PRs for each group. Requires gh CLI to be installed and authenticated.
1969
+
1970
+ **Prerequisites:**
1971
+ - gh CLI installed and authenticated
1972
+ - Git repository with a remote
1973
+ - Clean working tree (uncommitted changes will be stashed)
1974
+
1975
+ **Grouping strategies:**
1976
+ - severity: One PR per severity level (critical, high, medium, low)
1977
+ - file: One PR per affected file
1978
+ - pattern: One PR per fix pattern type
1979
+ - single: One PR per individual finding`,
1980
+ inputSchema: {
1981
+ project_path: z.string().describe("Absolute path to the project root"),
1982
+ certification_id: z.string().optional().describe("Certification ID to get findings from (if not providing findings directly)"),
1983
+ findings: z.array(z.object({
1984
+ id: z.string(),
1985
+ severity: z.enum(["critical", "high", "medium", "low", "info"]),
1986
+ category: z.string(),
1987
+ file: z.string().optional(),
1988
+ line: z.number().optional(),
1989
+ description: z.string(),
1990
+ evidence: z.string().optional(),
1991
+ confidence: z.number().optional(),
1992
+ })).optional().describe("Direct findings array (alternative to certification_id)"),
1993
+ group_by: z.enum(["severity", "file", "pattern", "single"]).default("severity").describe("How to group fixes into PRs"),
1994
+ dry_run: z.boolean().default(true).describe("Preview mode - show what PRs would be created without actually creating them"),
1995
+ include_unsafe: z.boolean().default(false).describe("Include high-risk fixes that may require manual review"),
1996
+ branch_prefix: z.string().default("vaspera/autofix").describe("Prefix for branch names"),
1997
+ base_branch: z.string().optional().describe("Base branch to create PRs against (defaults to main/master)"),
1998
+ },
1999
+ annotations: {
2000
+ readOnlyHint: false,
2001
+ destructiveHint: true,
2002
+ idempotentHint: false,
2003
+ openWorldHint: true,
2004
+ },
2005
+ }, async ({ project_path, certification_id, findings: directFindings, group_by, dry_run, include_unsafe, branch_prefix, base_branch }) => {
2006
+ // Check prerequisites
2007
+ if (!(await isGhCliAvailable())) {
2008
+ return errorResponse("gh CLI is not installed. Install from: https://cli.github.com/");
2009
+ }
2010
+ if (!(await isGhAuthenticated())) {
2011
+ return errorResponse("gh CLI is not authenticated. Run: gh auth login");
2012
+ }
2013
+ // Get findings from certification or direct input
2014
+ let findings = [];
2015
+ if (directFindings && directFindings.length > 0) {
2016
+ findings = directFindings;
2017
+ }
2018
+ else if (certification_id) {
2019
+ const certification = await getCertification(project_path, certification_id);
2020
+ if (!certification) {
2021
+ return errorResponse(`Certification ${certification_id} not found`);
2022
+ }
2023
+ // Collect all findings from all agents
2024
+ for (const agentData of Object.values(certification.agents)) {
2025
+ if (agentData) {
2026
+ findings.push(...agentData.findings);
2027
+ }
2028
+ }
2029
+ }
2030
+ else {
2031
+ return errorResponse("Either certification_id or findings must be provided");
2032
+ }
2033
+ if (findings.length === 0) {
2034
+ return jsonResponse({
2035
+ message: "No findings to fix",
2036
+ totalFindings: 0,
2037
+ prsCreated: [],
2038
+ unfixable: [],
2039
+ success: true,
2040
+ });
2041
+ }
2042
+ // Create PRs
2043
+ const result = await createAutofixPRs({
2044
+ projectPath: project_path,
2045
+ findings: findings,
2046
+ groupBy: group_by,
2047
+ dryRun: dry_run,
2048
+ includeUnsafe: include_unsafe,
2049
+ branchPrefix: branch_prefix,
2050
+ baseBranch: base_branch || "main",
2051
+ });
2052
+ // Format output
2053
+ const lines = [
2054
+ `# Autofix PR ${dry_run ? "Preview" : "Results"}`,
2055
+ "",
2056
+ `**Total Findings**: ${result.totalFindings}`,
2057
+ `**PRs ${dry_run ? "Would Create" : "Created"}**: ${result.prsCreated.length}`,
2058
+ `**Unfixable**: ${result.unfixable.length}`,
2059
+ "",
2060
+ ];
2061
+ if (result.prsCreated.length > 0) {
2062
+ lines.push("## Pull Requests");
2063
+ lines.push("");
2064
+ for (const pr of result.prsCreated) {
2065
+ const fixCount = pr.fixesApplied.length;
2066
+ const fileList = pr.filesModified.slice(0, 3).join(", ");
2067
+ const moreFiles = pr.filesModified.length > 3 ? ` +${pr.filesModified.length - 3} more` : "";
2068
+ if (dry_run) {
2069
+ lines.push(`- **${pr.branch}**: ${fixCount} fixes (${fileList}${moreFiles})`);
2070
+ }
2071
+ else {
2072
+ lines.push(`- [PR #${pr.prNumber}](${pr.prUrl}): ${fixCount} fixes`);
2073
+ }
2074
+ }
2075
+ lines.push("");
2076
+ }
2077
+ if (result.unfixable.length > 0) {
2078
+ lines.push("## Unfixable Findings");
2079
+ lines.push("");
2080
+ for (const uf of result.unfixable.slice(0, 10)) {
2081
+ lines.push(`- ${uf.findingId}: ${uf.reason}`);
2082
+ }
2083
+ if (result.unfixable.length > 10) {
2084
+ lines.push(`- ... and ${result.unfixable.length - 10} more`);
2085
+ }
2086
+ lines.push("");
2087
+ }
2088
+ if (result.summary && Object.keys(result.summary.byPattern).length > 0) {
2089
+ lines.push("## Summary by Pattern");
2090
+ lines.push("");
2091
+ for (const [pattern, count] of Object.entries(result.summary.byPattern)) {
2092
+ lines.push(`- ${pattern}: ${count} fixes`);
2093
+ }
2094
+ }
2095
+ return {
2096
+ content: [
2097
+ {
2098
+ type: "text",
2099
+ text: lines.join("\n"),
2100
+ },
2101
+ ],
2102
+ result,
2103
+ };
2104
+ });
2105
+ // ---------------------------------------------------------------------------
2106
+ // Tool: Persistence - Get Project Trends
2107
+ // ---------------------------------------------------------------------------
2108
+ server.registerTool("persistence_get_trends", {
2109
+ title: "Get Project Security Trends",
2110
+ description: `Get historical security trends for a project including new/fixed findings over time, MTTR, and fix velocity. Requires SQLite persistence to be enabled.`,
2111
+ inputSchema: {
2112
+ project_path: z.string().describe("Absolute path to the project root"),
2113
+ period: z.enum(["day", "week", "month"]).default("week").describe("Trend aggregation period"),
2114
+ lookback_periods: z.number().default(12).describe("Number of periods to look back"),
2115
+ },
2116
+ annotations: {
2117
+ readOnlyHint: true,
2118
+ destructiveHint: false,
2119
+ idempotentHint: true,
2120
+ openWorldHint: false,
2121
+ },
2122
+ }, async ({ project_path, period, lookback_periods }) => {
2123
+ try {
2124
+ const trends = await getProjectTrends(project_path, period, lookback_periods);
2125
+ const lines = [
2126
+ `# Security Trends for ${trends.project.name}`,
2127
+ "",
2128
+ `**Period**: ${period} (last ${lookback_periods} periods)`,
2129
+ `**Last Scan**: ${trends.project.lastScanAt || "Never"}`,
2130
+ "",
2131
+ "## Current Stats",
2132
+ "",
2133
+ `- **Open Findings**: ${trends.stats.openFindings}`,
2134
+ `- **Fixed Findings**: ${trends.stats.fixedFindings}`,
2135
+ `- **False Positives**: ${trends.stats.falsePositives}`,
2136
+ `- **Avg MTTR**: ${trends.stats.avgMttrHours ? `${trends.stats.avgMttrHours.toFixed(1)} hours` : "N/A"}`,
2137
+ `- **Fix Velocity**: ${trends.stats.fixVelocityPerWeek ? `${trends.stats.fixVelocityPerWeek.toFixed(1)}/week` : "N/A"}`,
2138
+ "",
2139
+ "## By Severity",
2140
+ "",
2141
+ ];
2142
+ for (const [sev, count] of Object.entries(trends.stats.bySeverity)) {
2143
+ if (count > 0) {
2144
+ lines.push(`- **${sev}**: ${count}`);
2145
+ }
2146
+ }
2147
+ lines.push("", "## Trend Data", "");
2148
+ lines.push("| Period | New | Fixed | Open |");
2149
+ lines.push("|--------|-----|-------|------|");
2150
+ for (const t of trends.trends.slice(-10)) {
2151
+ lines.push(`| ${t.period} | ${t.newFindings} | ${t.fixedFindings} | ${t.openFindings} |`);
2152
+ }
2153
+ return {
2154
+ content: [{ type: "text", text: lines.join("\n") }],
2155
+ trends,
2156
+ };
2157
+ }
2158
+ catch (error) {
2159
+ return errorResponse(`Failed to get trends: ${error instanceof Error ? error.message : "Unknown error"}`);
2160
+ }
2161
+ });
2162
+ // ---------------------------------------------------------------------------
2163
+ // Tool: Persistence - Get Open Findings
2164
+ // ---------------------------------------------------------------------------
2165
+ server.registerTool("persistence_get_findings", {
2166
+ title: "Get Persisted Findings",
2167
+ description: `Get open security findings from the persistence layer with filtering options.`,
2168
+ inputSchema: {
2169
+ project_path: z.string().describe("Absolute path to the project root"),
2170
+ severity: z.enum(["critical", "high", "medium", "low", "info"]).optional().describe("Filter by severity"),
2171
+ category: z.string().optional().describe("Filter by category"),
2172
+ file: z.string().optional().describe("Filter by file path (partial match)"),
2173
+ limit: z.number().default(50).describe("Maximum findings to return"),
2174
+ },
2175
+ annotations: {
2176
+ readOnlyHint: true,
2177
+ destructiveHint: false,
2178
+ idempotentHint: true,
2179
+ openWorldHint: false,
2180
+ },
2181
+ }, async ({ project_path, severity, category, file, limit }) => {
2182
+ try {
2183
+ const findings = await getOpenFindings(project_path, {
2184
+ severity: severity,
2185
+ category,
2186
+ file,
2187
+ limit,
2188
+ });
2189
+ if (findings.length === 0) {
2190
+ return {
2191
+ content: [{ type: "text", text: "No open findings found matching the criteria." }],
2192
+ findings: [],
2193
+ };
2194
+ }
2195
+ const lines = [
2196
+ `# Open Findings (${findings.length})`,
2197
+ "",
2198
+ ];
2199
+ const bySeverity = {};
2200
+ for (const f of findings) {
2201
+ if (!bySeverity[f.severity])
2202
+ bySeverity[f.severity] = [];
2203
+ bySeverity[f.severity].push(f);
2204
+ }
2205
+ for (const sev of ["critical", "high", "medium", "low", "info"]) {
2206
+ const sevFindings = bySeverity[sev];
2207
+ if (!sevFindings || sevFindings.length === 0)
2208
+ continue;
2209
+ lines.push(`## ${sev.toUpperCase()} (${sevFindings.length})`);
2210
+ lines.push("");
2211
+ for (const f of sevFindings.slice(0, 10)) {
2212
+ lines.push(`- **${f.file}:${f.line}** - ${f.description.slice(0, 80)}`);
2213
+ lines.push(` - Category: ${f.category}, Scanner: ${f.scannerSource}`);
2214
+ lines.push(` - First seen: ${f.firstSeenAt.split("T")[0]}`);
2215
+ }
2216
+ if (sevFindings.length > 10) {
2217
+ lines.push(`- ... and ${sevFindings.length - 10} more ${sev} findings`);
2218
+ }
2219
+ lines.push("");
2220
+ }
2221
+ return {
2222
+ content: [{ type: "text", text: lines.join("\n") }],
2223
+ findings,
2224
+ };
2225
+ }
2226
+ catch (error) {
2227
+ return errorResponse(`Failed to get findings: ${error instanceof Error ? error.message : "Unknown error"}`);
2228
+ }
2229
+ });
2230
+ // ---------------------------------------------------------------------------
2231
+ // Tool: Persistence - Mark Findings Fixed
2232
+ // ---------------------------------------------------------------------------
2233
+ server.registerTool("persistence_mark_fixed", {
2234
+ title: "Mark Findings as Fixed",
2235
+ description: `Mark one or more findings as fixed in the persistence layer. This updates the finding status and records fix history.`,
2236
+ inputSchema: {
2237
+ project_path: z.string().describe("Absolute path to the project root"),
2238
+ finding_ids: z.array(z.string()).describe("Finding IDs to mark as fixed"),
2239
+ fixed_by: z.string().optional().describe("Who fixed the findings"),
2240
+ },
2241
+ annotations: {
2242
+ readOnlyHint: false,
2243
+ destructiveHint: false,
2244
+ idempotentHint: true,
2245
+ openWorldHint: false,
2246
+ },
2247
+ }, async ({ project_path, finding_ids, fixed_by }) => {
2248
+ try {
2249
+ const count = await markFindingsFixed(project_path, finding_ids, fixed_by);
2250
+ return {
2251
+ content: [
2252
+ {
2253
+ type: "text",
2254
+ text: `Marked ${count} finding(s) as fixed.${fixed_by ? ` Fixed by: ${fixed_by}` : ""}`,
2255
+ },
2256
+ ],
2257
+ fixedCount: count,
2258
+ };
2259
+ }
2260
+ catch (error) {
2261
+ return errorResponse(`Failed to mark findings: ${error instanceof Error ? error.message : "Unknown error"}`);
2262
+ }
2263
+ });
2264
+ // ---------------------------------------------------------------------------
1957
2265
  // Tool: Certification Summary
1958
2266
  // ---------------------------------------------------------------------------
1959
2267
  server.registerTool("certification_summary", {
@@ -2664,6 +2972,325 @@ server.registerTool("verify_audit_trail", {
2664
2972
  }
2665
2973
  });
2666
2974
  // ---------------------------------------------------------------------------
2975
+ // Tool: Audit Export
2976
+ // ---------------------------------------------------------------------------
2977
+ server.registerTool("audit_export", {
2978
+ title: "Export Audit Trail",
2979
+ description: `Export the tamper-evident audit trail for compliance review. Creates a portable, verifiable export suitable for external auditors and compliance teams. Supports JSON, JSONL, and CSV formats.`,
2980
+ inputSchema: {
2981
+ project_path: z.string().describe("Absolute path to the project root"),
2982
+ format: z.enum(["json", "jsonl", "csv"]).optional().describe("Export format. Default: json"),
2983
+ start_date: z.string().optional().describe("Start date filter (ISO 8601)"),
2984
+ end_date: z.string().optional().describe("End date filter (ISO 8601)"),
2985
+ types: z.array(z.enum([
2986
+ "certification_started",
2987
+ "certification_completed",
2988
+ "scan_completed",
2989
+ "finding_submitted",
2990
+ "finding_fixed",
2991
+ "compliance_report",
2992
+ "model_run",
2993
+ ])).optional().describe("Entry types to include. Default: all"),
2994
+ include_integrity: z.boolean().optional().describe("Include integrity proofs. Default: true"),
2995
+ output_path: z.string().optional().describe("Output file path. If omitted, returns content directly"),
2996
+ output_format: z.enum(["markdown", "json"]).optional().describe("Response format. Default: json"),
2997
+ },
2998
+ annotations: {
2999
+ readOnlyHint: true,
3000
+ destructiveHint: false,
3001
+ idempotentHint: true,
3002
+ openWorldHint: false,
3003
+ },
3004
+ }, async ({ project_path, format, start_date, end_date, types, include_integrity, output_path, output_format }) => {
3005
+ try {
3006
+ const validatedPath = await validateProjectPath(project_path);
3007
+ const result = await exportAuditTrail(validatedPath, {
3008
+ format: format,
3009
+ startDate: start_date,
3010
+ endDate: end_date,
3011
+ types: types,
3012
+ includeIntegrity: include_integrity !== false,
3013
+ outputPath: output_path,
3014
+ });
3015
+ if (output_format === "markdown") {
3016
+ const lines = [
3017
+ "# Audit Trail Export",
3018
+ "",
3019
+ `**Project**: ${result.projectPath}`,
3020
+ `**Exported At**: ${result.exportedAt}`,
3021
+ `**Entry Count**: ${result.entryCount}`,
3022
+ "",
3023
+ "## Date Range",
3024
+ "",
3025
+ `| Field | Value |`,
3026
+ `|-------|-------|`,
3027
+ `| Start | ${result.dateRange.start} |`,
3028
+ `| End | ${result.dateRange.end} |`,
3029
+ "",
3030
+ "## Chain Integrity",
3031
+ "",
3032
+ `| Field | Value |`,
3033
+ `|-------|-------|`,
3034
+ `| Genesis Hash | \`${result.chainIntegrity.genesisHash?.slice(0, 16) || "N/A"}...\` |`,
3035
+ `| Head Hash | \`${result.chainIntegrity.headHash?.slice(0, 16) || "N/A"}...\` |`,
3036
+ `| Entries with Integrity | ${result.chainIntegrity.entriesWithIntegrity} |`,
3037
+ ];
3038
+ if (result.outputPath) {
3039
+ lines.push("", `**Output File**: ${result.outputPath}`);
3040
+ }
3041
+ return textResponse(lines.join("\n"));
3042
+ }
3043
+ return jsonResponse({
3044
+ exportedAt: result.exportedAt,
3045
+ projectPath: result.projectPath,
3046
+ entryCount: result.entryCount,
3047
+ dateRange: result.dateRange,
3048
+ chainIntegrity: result.chainIntegrity,
3049
+ outputPath: result.outputPath,
3050
+ content: result.content,
3051
+ });
3052
+ }
3053
+ catch (error) {
3054
+ if (error instanceof PathValidationError) {
3055
+ return errorResponse(error.message);
3056
+ }
3057
+ return errorResponse(`Audit export failed: ${error instanceof Error ? error.message : String(error)}`);
3058
+ }
3059
+ });
3060
+ // ---------------------------------------------------------------------------
3061
+ // Tool: SIEM Configure
3062
+ // ---------------------------------------------------------------------------
3063
+ server.registerTool("siem_configure", {
3064
+ title: "Configure SIEM Connection",
3065
+ description: `Configure a connection to a SIEM platform (Splunk, Microsoft Sentinel, or Datadog). The connection is stored in memory for the session and can be used to export findings and events.`,
3066
+ inputSchema: {
3067
+ name: z.string().describe("Unique name for this SIEM connection"),
3068
+ provider: z.enum(["splunk", "sentinel", "datadog"]).describe("SIEM provider type"),
3069
+ endpoint: z.string().optional().describe("SIEM endpoint URL (required for Splunk)"),
3070
+ token: z.string().describe("Authentication token/API key"),
3071
+ workspace_id: z.string().optional().describe("Log Analytics workspace ID (Sentinel only)"),
3072
+ index: z.string().optional().describe("Splunk index name"),
3073
+ source_type: z.string().optional().describe("Splunk source type"),
3074
+ site: z.string().optional().describe("Datadog site (e.g., datadoghq.com, datadoghq.eu)"),
3075
+ service: z.string().optional().describe("Service name for tagging"),
3076
+ env: z.string().optional().describe("Environment tag"),
3077
+ tags: z.array(z.string()).optional().describe("Additional tags (Datadog)"),
3078
+ },
3079
+ annotations: {
3080
+ readOnlyHint: false,
3081
+ destructiveHint: false,
3082
+ idempotentHint: true,
3083
+ openWorldHint: true,
3084
+ },
3085
+ }, async ({ name, provider, endpoint, token, workspace_id, index, source_type, site, service, env, tags }) => {
3086
+ try {
3087
+ const registry = getSIEMRegistry();
3088
+ let client;
3089
+ switch (provider) {
3090
+ case "splunk":
3091
+ if (!endpoint) {
3092
+ return errorResponse("Splunk requires an endpoint URL");
3093
+ }
3094
+ client = createSIEMClient({
3095
+ provider: "splunk",
3096
+ enabled: true,
3097
+ endpoint,
3098
+ token,
3099
+ options: { index, sourceType: source_type, source: service },
3100
+ });
3101
+ break;
3102
+ case "sentinel":
3103
+ if (!workspace_id) {
3104
+ return errorResponse("Sentinel requires a workspace_id");
3105
+ }
3106
+ client = createSIEMClient({
3107
+ provider: "sentinel",
3108
+ enabled: true,
3109
+ endpoint: `https://${workspace_id}.ods.opinsights.azure.com`,
3110
+ token,
3111
+ options: { workspaceId: workspace_id },
3112
+ });
3113
+ break;
3114
+ case "datadog":
3115
+ client = createSIEMClient({
3116
+ provider: "datadog",
3117
+ enabled: true,
3118
+ endpoint: "",
3119
+ token,
3120
+ options: { site, service, env, tags },
3121
+ });
3122
+ break;
3123
+ }
3124
+ registry.register(name, client);
3125
+ return jsonResponse({
3126
+ success: true,
3127
+ name,
3128
+ provider,
3129
+ message: `SIEM connection '${name}' configured successfully`,
3130
+ });
3131
+ }
3132
+ catch (error) {
3133
+ return errorResponse(`Failed to configure SIEM: ${error instanceof Error ? error.message : String(error)}`);
3134
+ }
3135
+ });
3136
+ // ---------------------------------------------------------------------------
3137
+ // Tool: SIEM Test
3138
+ // ---------------------------------------------------------------------------
3139
+ server.registerTool("siem_test", {
3140
+ title: "Test SIEM Connection",
3141
+ description: `Test connectivity to a configured SIEM platform. Sends a test event and reports latency and status.`,
3142
+ inputSchema: {
3143
+ name: z.string().describe("Name of the configured SIEM connection"),
3144
+ },
3145
+ annotations: {
3146
+ readOnlyHint: true,
3147
+ destructiveHint: false,
3148
+ idempotentHint: true,
3149
+ openWorldHint: true,
3150
+ },
3151
+ }, async ({ name }) => {
3152
+ try {
3153
+ const registry = getSIEMRegistry();
3154
+ const client = registry.get(name);
3155
+ if (!client) {
3156
+ return errorResponse(`SIEM connection '${name}' not found. Use siem_configure first.`);
3157
+ }
3158
+ const result = await client.testConnection();
3159
+ if (result.success) {
3160
+ return jsonResponse({
3161
+ success: true,
3162
+ provider: result.provider,
3163
+ endpoint: result.endpoint,
3164
+ latencyMs: result.latencyMs,
3165
+ message: `Connection to ${result.provider} successful`,
3166
+ details: result.details,
3167
+ });
3168
+ }
3169
+ return errorResponse(`Connection test failed: ${result.error}`);
3170
+ }
3171
+ catch (error) {
3172
+ return errorResponse(`Failed to test SIEM: ${error instanceof Error ? error.message : String(error)}`);
3173
+ }
3174
+ });
3175
+ // ---------------------------------------------------------------------------
3176
+ // Tool: SIEM Export Findings
3177
+ // ---------------------------------------------------------------------------
3178
+ server.registerTool("siem_export_findings", {
3179
+ title: "Export Findings to SIEM",
3180
+ description: `Export security findings to a configured SIEM platform. Sends finding events for SOC visibility and incident response integration.`,
3181
+ inputSchema: {
3182
+ name: z.string().describe("Name of the configured SIEM connection"),
3183
+ project_path: z.string().describe("Absolute path to the project"),
3184
+ certification_id: z.string().optional().describe("Certification ID to filter findings"),
3185
+ severity: z.array(z.enum(["critical", "high", "medium", "low", "info"])).optional().describe("Filter by severity"),
3186
+ event_type: z.enum(["finding.new", "finding.fixed", "scan.completed", "certification.completed"]).optional().describe("Event type to generate"),
3187
+ dry_run: z.boolean().optional().describe("Preview events without sending. Default: false"),
3188
+ },
3189
+ annotations: {
3190
+ readOnlyHint: false,
3191
+ destructiveHint: false,
3192
+ idempotentHint: false,
3193
+ openWorldHint: true,
3194
+ },
3195
+ }, async ({ name, project_path, certification_id, severity, event_type, dry_run }) => {
3196
+ try {
3197
+ const validatedPath = await validateProjectPath(project_path);
3198
+ const registry = getSIEMRegistry();
3199
+ const client = registry.get(name);
3200
+ if (!client) {
3201
+ return errorResponse(`SIEM connection '${name}' not found. Use siem_configure first.`);
3202
+ }
3203
+ // Get findings from certification
3204
+ if (!certification_id) {
3205
+ return errorResponse("certification_id is required to export findings");
3206
+ }
3207
+ const certification = await getCertification(validatedPath, certification_id);
3208
+ if (!certification) {
3209
+ return errorResponse("No certification data found. Run certification_scan first.");
3210
+ }
3211
+ // Extract findings from all agents
3212
+ let allFindings = [];
3213
+ for (const agentFindings of Object.values(certification.agents)) {
3214
+ if (agentFindings?.findings) {
3215
+ allFindings.push(...agentFindings.findings);
3216
+ }
3217
+ }
3218
+ // Filter by severity if provided
3219
+ if (severity && severity.length > 0) {
3220
+ allFindings = allFindings.filter((f) => severity.includes(f.severity));
3221
+ }
3222
+ // Generate events
3223
+ const events = allFindings.map((finding) => createFindingEvent(validatedPath, event_type || "finding.new", {
3224
+ findingId: finding.id,
3225
+ severity: finding.severity,
3226
+ category: finding.category,
3227
+ file: finding.file,
3228
+ line: finding.line,
3229
+ scanner: finding.scannerSource,
3230
+ ruleId: finding.ruleId,
3231
+ cweIds: finding.cweIds,
3232
+ description: finding.description,
3233
+ }, certification_id));
3234
+ if (dry_run) {
3235
+ return jsonResponse({
3236
+ dryRun: true,
3237
+ eventCount: events.length,
3238
+ events: events.slice(0, 10),
3239
+ message: `Would send ${events.length} events to ${client.provider}`,
3240
+ });
3241
+ }
3242
+ // Send events
3243
+ const result = await client.sendEvents(events);
3244
+ return jsonResponse({
3245
+ success: result.success,
3246
+ provider: client.provider,
3247
+ totalEvents: result.totalEvents,
3248
+ successCount: result.successCount,
3249
+ failureCount: result.failureCount,
3250
+ errors: result.errors,
3251
+ });
3252
+ }
3253
+ catch (error) {
3254
+ if (error instanceof PathValidationError) {
3255
+ return errorResponse(error.message);
3256
+ }
3257
+ return errorResponse(`Failed to export findings: ${error instanceof Error ? error.message : String(error)}`);
3258
+ }
3259
+ });
3260
+ // ---------------------------------------------------------------------------
3261
+ // Tool: SIEM List
3262
+ // ---------------------------------------------------------------------------
3263
+ server.registerTool("siem_list", {
3264
+ title: "List SIEM Connections",
3265
+ description: `List all configured SIEM connections in the current session.`,
3266
+ inputSchema: {},
3267
+ annotations: {
3268
+ readOnlyHint: true,
3269
+ destructiveHint: false,
3270
+ idempotentHint: true,
3271
+ openWorldHint: false,
3272
+ },
3273
+ }, async () => {
3274
+ try {
3275
+ const registry = getSIEMRegistry();
3276
+ const connections = registry.list();
3277
+ if (connections.length === 0) {
3278
+ return textResponse("No SIEM connections configured. Use siem_configure to add one.");
3279
+ }
3280
+ const lines = [
3281
+ "# Configured SIEM Connections",
3282
+ "",
3283
+ "| Name | Provider |",
3284
+ "|------|----------|",
3285
+ ...connections.map((c) => `| ${c.name} | ${c.provider} |`),
3286
+ ];
3287
+ return textResponse(lines.join("\n"));
3288
+ }
3289
+ catch (error) {
3290
+ return errorResponse(`Failed to list SIEM connections: ${error instanceof Error ? error.message : String(error)}`);
3291
+ }
3292
+ });
3293
+ // ---------------------------------------------------------------------------
2667
3294
  // Tool: Collect Evidence Bundle
2668
3295
  // ---------------------------------------------------------------------------
2669
3296
  server.registerTool("collect_evidence_bundle", {