vaspera 2.13.0 → 2.15.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 (300) hide show
  1. package/CHANGELOG.md +78 -0
  2. package/README.md +15 -2
  3. package/dist/__tests__/antagonist-integration.test.d.ts +6 -0
  4. package/dist/__tests__/antagonist-integration.test.d.ts.map +1 -0
  5. package/dist/__tests__/antagonist-integration.test.js +239 -0
  6. package/dist/__tests__/antagonist-integration.test.js.map +1 -0
  7. package/dist/__tests__/certification/agent-certificate-e2e.test.d.ts +2 -0
  8. package/dist/__tests__/certification/agent-certificate-e2e.test.d.ts.map +1 -0
  9. package/dist/__tests__/certification/agent-certificate-e2e.test.js +90 -0
  10. package/dist/__tests__/certification/agent-certificate-e2e.test.js.map +1 -0
  11. package/dist/__tests__/certification/agent-certificate-map.test.d.ts +2 -0
  12. package/dist/__tests__/certification/agent-certificate-map.test.d.ts.map +1 -0
  13. package/dist/__tests__/certification/agent-certificate-map.test.js +107 -0
  14. package/dist/__tests__/certification/agent-certificate-map.test.js.map +1 -0
  15. package/dist/__tests__/certification/agent-certificate.test.d.ts +2 -0
  16. package/dist/__tests__/certification/agent-certificate.test.d.ts.map +1 -0
  17. package/dist/__tests__/certification/agent-certificate.test.js +78 -0
  18. package/dist/__tests__/certification/agent-certificate.test.js.map +1 -0
  19. package/dist/__tests__/certification/verify-endpoint.test.d.ts +2 -0
  20. package/dist/__tests__/certification/verify-endpoint.test.d.ts.map +1 -0
  21. package/dist/__tests__/certification/verify-endpoint.test.js +81 -0
  22. package/dist/__tests__/certification/verify-endpoint.test.js.map +1 -0
  23. package/dist/__tests__/compliance/ai-frameworks.test.d.ts +2 -0
  24. package/dist/__tests__/compliance/ai-frameworks.test.d.ts.map +1 -0
  25. package/dist/__tests__/compliance/ai-frameworks.test.js +87 -0
  26. package/dist/__tests__/compliance/ai-frameworks.test.js.map +1 -0
  27. package/dist/__tests__/eval/llm-analyzer.test.d.ts +2 -0
  28. package/dist/__tests__/eval/llm-analyzer.test.d.ts.map +1 -0
  29. package/dist/__tests__/eval/llm-analyzer.test.js +93 -0
  30. package/dist/__tests__/eval/llm-analyzer.test.js.map +1 -0
  31. package/dist/__tests__/eval/redteam-harness.test.d.ts +2 -0
  32. package/dist/__tests__/eval/redteam-harness.test.d.ts.map +1 -0
  33. package/dist/__tests__/eval/redteam-harness.test.js +136 -0
  34. package/dist/__tests__/eval/redteam-harness.test.js.map +1 -0
  35. package/dist/__tests__/evidence/evidence.test.d.ts +2 -0
  36. package/dist/__tests__/evidence/evidence.test.d.ts.map +1 -0
  37. package/dist/__tests__/evidence/evidence.test.js +240 -0
  38. package/dist/__tests__/evidence/evidence.test.js.map +1 -0
  39. package/dist/__tests__/history/decisions.test.d.ts +2 -0
  40. package/dist/__tests__/history/decisions.test.d.ts.map +1 -0
  41. package/dist/__tests__/history/decisions.test.js +54 -0
  42. package/dist/__tests__/history/decisions.test.js.map +1 -0
  43. package/dist/__tests__/http-auth.test.d.ts +2 -0
  44. package/dist/__tests__/http-auth.test.d.ts.map +1 -0
  45. package/dist/__tests__/http-auth.test.js +55 -0
  46. package/dist/__tests__/http-auth.test.js.map +1 -0
  47. package/dist/__tests__/http-policy.test.d.ts +2 -0
  48. package/dist/__tests__/http-policy.test.d.ts.map +1 -0
  49. package/dist/__tests__/http-policy.test.js +69 -0
  50. package/dist/__tests__/http-policy.test.js.map +1 -0
  51. package/dist/__tests__/http-server-transport.test.d.ts +2 -0
  52. package/dist/__tests__/http-server-transport.test.d.ts.map +1 -0
  53. package/dist/__tests__/http-server-transport.test.js +132 -0
  54. package/dist/__tests__/http-server-transport.test.js.map +1 -0
  55. package/dist/__tests__/integration/destructive-guards.test.d.ts +2 -0
  56. package/dist/__tests__/integration/destructive-guards.test.d.ts.map +1 -0
  57. package/dist/__tests__/integration/destructive-guards.test.js +49 -0
  58. package/dist/__tests__/integration/destructive-guards.test.js.map +1 -0
  59. package/dist/__tests__/logger-redaction.test.d.ts +2 -0
  60. package/dist/__tests__/logger-redaction.test.d.ts.map +1 -0
  61. package/dist/__tests__/logger-redaction.test.js +74 -0
  62. package/dist/__tests__/logger-redaction.test.js.map +1 -0
  63. package/dist/__tests__/manifest-schema.test.d.ts +2 -0
  64. package/dist/__tests__/manifest-schema.test.d.ts.map +1 -0
  65. package/dist/__tests__/manifest-schema.test.js +43 -0
  66. package/dist/__tests__/manifest-schema.test.js.map +1 -0
  67. package/dist/__tests__/scanners/builtin-rules.test.d.ts +2 -0
  68. package/dist/__tests__/scanners/builtin-rules.test.d.ts.map +1 -0
  69. package/dist/__tests__/scanners/builtin-rules.test.js +51 -0
  70. package/dist/__tests__/scanners/builtin-rules.test.js.map +1 -0
  71. package/dist/__tests__/scanners/runtime/golden-path-runner.test.js +13 -1
  72. package/dist/__tests__/scanners/runtime/golden-path-runner.test.js.map +1 -1
  73. package/dist/__tests__/tool-guard.test.d.ts +2 -0
  74. package/dist/__tests__/tool-guard.test.d.ts.map +1 -0
  75. package/dist/__tests__/tool-guard.test.js +97 -0
  76. package/dist/__tests__/tool-guard.test.js.map +1 -0
  77. package/dist/__tests__/util/contained-file.test.d.ts +2 -0
  78. package/dist/__tests__/util/contained-file.test.d.ts.map +1 -0
  79. package/dist/__tests__/util/contained-file.test.js +78 -0
  80. package/dist/__tests__/util/contained-file.test.js.map +1 -0
  81. package/dist/__tests__/util/subprocess.test.d.ts +2 -0
  82. package/dist/__tests__/util/subprocess.test.d.ts.map +1 -0
  83. package/dist/__tests__/util/subprocess.test.js +48 -0
  84. package/dist/__tests__/util/subprocess.test.js.map +1 -0
  85. package/dist/action/diff-mode.d.ts.map +1 -1
  86. package/dist/action/diff-mode.js +31 -12
  87. package/dist/action/diff-mode.js.map +1 -1
  88. package/dist/agents/antagonist/challenger.d.ts +46 -0
  89. package/dist/agents/antagonist/challenger.d.ts.map +1 -0
  90. package/dist/agents/antagonist/challenger.js +257 -0
  91. package/dist/agents/antagonist/challenger.js.map +1 -0
  92. package/dist/agents/antagonist/index.d.ts +31 -0
  93. package/dist/agents/antagonist/index.d.ts.map +1 -0
  94. package/dist/agents/antagonist/index.js +175 -0
  95. package/dist/agents/antagonist/index.js.map +1 -0
  96. package/dist/agents/antagonist/prioritizer.d.ts +27 -0
  97. package/dist/agents/antagonist/prioritizer.d.ts.map +1 -0
  98. package/dist/agents/antagonist/prioritizer.js +181 -0
  99. package/dist/agents/antagonist/prioritizer.js.map +1 -0
  100. package/dist/agents/antagonist/prompts.d.ts +12 -0
  101. package/dist/agents/antagonist/prompts.d.ts.map +1 -0
  102. package/dist/agents/antagonist/prompts.js +155 -0
  103. package/dist/agents/antagonist/prompts.js.map +1 -0
  104. package/dist/agents/antagonist/synthesizer.d.ts +34 -0
  105. package/dist/agents/antagonist/synthesizer.d.ts.map +1 -0
  106. package/dist/agents/antagonist/synthesizer.js +451 -0
  107. package/dist/agents/antagonist/synthesizer.js.map +1 -0
  108. package/dist/agents/antagonist/types.d.ts +145 -0
  109. package/dist/agents/antagonist/types.d.ts.map +1 -0
  110. package/dist/agents/antagonist/types.js +63 -0
  111. package/dist/agents/antagonist/types.js.map +1 -0
  112. package/dist/agents/index.d.ts +1 -0
  113. package/dist/agents/index.d.ts.map +1 -1
  114. package/dist/agents/index.js +2 -0
  115. package/dist/agents/index.js.map +1 -1
  116. package/dist/certification/agent-certificate-map.d.ts +51 -0
  117. package/dist/certification/agent-certificate-map.d.ts.map +1 -0
  118. package/dist/certification/agent-certificate-map.js +265 -0
  119. package/dist/certification/agent-certificate-map.js.map +1 -0
  120. package/dist/certification/agent-certificate-sample.d.ts +25 -0
  121. package/dist/certification/agent-certificate-sample.d.ts.map +1 -0
  122. package/dist/certification/agent-certificate-sample.js +207 -0
  123. package/dist/certification/agent-certificate-sample.js.map +1 -0
  124. package/dist/certification/agent-certificate.d.ts +1981 -0
  125. package/dist/certification/agent-certificate.d.ts.map +1 -0
  126. package/dist/certification/agent-certificate.js +309 -0
  127. package/dist/certification/agent-certificate.js.map +1 -0
  128. package/dist/certification/autofix.d.ts.map +1 -1
  129. package/dist/certification/autofix.js +5 -3
  130. package/dist/certification/autofix.js.map +1 -1
  131. package/dist/certification/consensus.test.js +2 -0
  132. package/dist/certification/consensus.test.js.map +1 -1
  133. package/dist/certification/store.d.ts.map +1 -1
  134. package/dist/certification/store.js +11 -3
  135. package/dist/certification/store.js.map +1 -1
  136. package/dist/certification/types.d.ts +1 -1
  137. package/dist/certification/types.d.ts.map +1 -1
  138. package/dist/certification/types.js +2 -0
  139. package/dist/certification/types.js.map +1 -1
  140. package/dist/certification/verify-endpoint.d.ts +48 -0
  141. package/dist/certification/verify-endpoint.d.ts.map +1 -0
  142. package/dist/certification/verify-endpoint.js +79 -0
  143. package/dist/certification/verify-endpoint.js.map +1 -0
  144. package/dist/compliance/index.d.ts +2 -0
  145. package/dist/compliance/index.d.ts.map +1 -1
  146. package/dist/compliance/index.js +4 -0
  147. package/dist/compliance/index.js.map +1 -1
  148. package/dist/compliance/iso42001.d.ts +21 -0
  149. package/dist/compliance/iso42001.d.ts.map +1 -0
  150. package/dist/compliance/iso42001.js +160 -0
  151. package/dist/compliance/iso42001.js.map +1 -0
  152. package/dist/compliance/mapper.d.ts.map +1 -1
  153. package/dist/compliance/mapper.js +12 -0
  154. package/dist/compliance/mapper.js.map +1 -1
  155. package/dist/compliance/nist-ai-rmf.d.ts +20 -0
  156. package/dist/compliance/nist-ai-rmf.d.ts.map +1 -0
  157. package/dist/compliance/nist-ai-rmf.js +140 -0
  158. package/dist/compliance/nist-ai-rmf.js.map +1 -0
  159. package/dist/config/flags.d.ts +4 -4
  160. package/dist/eval/fixtures.d.ts.map +1 -1
  161. package/dist/eval/fixtures.js +161 -119
  162. package/dist/eval/fixtures.js.map +1 -1
  163. package/dist/eval/fixtures.test.js +4 -2
  164. package/dist/eval/fixtures.test.js.map +1 -1
  165. package/dist/eval/llm-analyzer.d.ts +40 -0
  166. package/dist/eval/llm-analyzer.d.ts.map +1 -0
  167. package/dist/eval/llm-analyzer.js +154 -0
  168. package/dist/eval/llm-analyzer.js.map +1 -0
  169. package/dist/eval/redteam-harness.d.ts +95 -0
  170. package/dist/eval/redteam-harness.d.ts.map +1 -0
  171. package/dist/eval/redteam-harness.js +137 -0
  172. package/dist/eval/redteam-harness.js.map +1 -0
  173. package/dist/evidence/collector.d.ts.map +1 -1
  174. package/dist/evidence/collector.js +21 -1
  175. package/dist/evidence/collector.js.map +1 -1
  176. package/dist/evidence/store.d.ts.map +1 -1
  177. package/dist/evidence/store.js +29 -5
  178. package/dist/evidence/store.js.map +1 -1
  179. package/dist/evidence/types.d.ts +16 -9
  180. package/dist/evidence/types.d.ts.map +1 -1
  181. package/dist/history/decisions.d.ts +63 -0
  182. package/dist/history/decisions.d.ts.map +1 -0
  183. package/dist/history/decisions.js +60 -0
  184. package/dist/history/decisions.js.map +1 -0
  185. package/dist/history/index.d.ts +2 -0
  186. package/dist/history/index.d.ts.map +1 -1
  187. package/dist/history/index.js +2 -0
  188. package/dist/history/index.js.map +1 -1
  189. package/dist/history/types.d.ts +34 -5
  190. package/dist/history/types.d.ts.map +1 -1
  191. package/dist/history/types.js +2 -0
  192. package/dist/history/types.js.map +1 -1
  193. package/dist/http-auth.d.ts +22 -0
  194. package/dist/http-auth.d.ts.map +1 -0
  195. package/dist/http-auth.js +58 -0
  196. package/dist/http-auth.js.map +1 -0
  197. package/dist/http-policy.d.ts +30 -0
  198. package/dist/http-policy.d.ts.map +1 -0
  199. package/dist/http-policy.js +54 -0
  200. package/dist/http-policy.js.map +1 -0
  201. package/dist/http-server.js +195 -12
  202. package/dist/http-server.js.map +1 -1
  203. package/dist/index.d.ts.map +1 -1
  204. package/dist/index.js +411 -15
  205. package/dist/index.js.map +1 -1
  206. package/dist/logger.d.ts.map +1 -1
  207. package/dist/logger.js +56 -2
  208. package/dist/logger.js.map +1 -1
  209. package/dist/plugins/types.d.ts +2 -2
  210. package/dist/sbom/provenance.test.js +2 -2
  211. package/dist/sbom/provenance.test.js.map +1 -1
  212. package/dist/sbom/signing.d.ts.map +1 -1
  213. package/dist/sbom/signing.js +5 -3
  214. package/dist/sbom/signing.js.map +1 -1
  215. package/dist/scanners/agent/prompt-injection-fuzzer.d.ts.map +1 -1
  216. package/dist/scanners/agent/prompt-injection-fuzzer.js +26 -0
  217. package/dist/scanners/agent/prompt-injection-fuzzer.js.map +1 -1
  218. package/dist/scanners/agent/types.d.ts +10 -10
  219. package/dist/scanners/bandit.d.ts.map +1 -1
  220. package/dist/scanners/bandit.js +35 -29
  221. package/dist/scanners/bandit.js.map +1 -1
  222. package/dist/scanners/binary-analysis.d.ts.map +1 -1
  223. package/dist/scanners/binary-analysis.js +24 -49
  224. package/dist/scanners/binary-analysis.js.map +1 -1
  225. package/dist/scanners/brakeman.d.ts.map +1 -1
  226. package/dist/scanners/brakeman.js +19 -33
  227. package/dist/scanners/brakeman.js.map +1 -1
  228. package/dist/scanners/builtin-rules.d.ts +24 -0
  229. package/dist/scanners/builtin-rules.d.ts.map +1 -0
  230. package/dist/scanners/builtin-rules.js +175 -0
  231. package/dist/scanners/builtin-rules.js.map +1 -0
  232. package/dist/scanners/dast.d.ts.map +1 -1
  233. package/dist/scanners/dast.js +24 -34
  234. package/dist/scanners/dast.js.map +1 -1
  235. package/dist/scanners/deploy/types.d.ts +6 -6
  236. package/dist/scanners/eslint.d.ts.map +1 -1
  237. package/dist/scanners/eslint.js +15 -24
  238. package/dist/scanners/eslint.js.map +1 -1
  239. package/dist/scanners/gosec.d.ts.map +1 -1
  240. package/dist/scanners/gosec.js +14 -62
  241. package/dist/scanners/gosec.js.map +1 -1
  242. package/dist/scanners/index.d.ts.map +1 -1
  243. package/dist/scanners/index.js +38 -7
  244. package/dist/scanners/index.js.map +1 -1
  245. package/dist/scanners/memory-safety.d.ts.map +1 -1
  246. package/dist/scanners/memory-safety.js +27 -28
  247. package/dist/scanners/memory-safety.js.map +1 -1
  248. package/dist/scanners/openapi.d.ts.map +1 -1
  249. package/dist/scanners/openapi.js +14 -22
  250. package/dist/scanners/openapi.js.map +1 -1
  251. package/dist/scanners/race-condition.d.ts.map +1 -1
  252. package/dist/scanners/race-condition.js +17 -16
  253. package/dist/scanners/race-condition.js.map +1 -1
  254. package/dist/scanners/runtime/types.d.ts +4 -4
  255. package/dist/scanners/rust.d.ts.map +1 -1
  256. package/dist/scanners/rust.js +38 -37
  257. package/dist/scanners/rust.js.map +1 -1
  258. package/dist/scanners/scale/types.d.ts +16 -16
  259. package/dist/scanners/secrets.d.ts.map +1 -1
  260. package/dist/scanners/secrets.js +66 -78
  261. package/dist/scanners/secrets.js.map +1 -1
  262. package/dist/scanners/semgrep.d.ts +2 -0
  263. package/dist/scanners/semgrep.d.ts.map +1 -1
  264. package/dist/scanners/semgrep.js +12 -0
  265. package/dist/scanners/semgrep.js.map +1 -1
  266. package/dist/scanners/terraform.d.ts.map +1 -1
  267. package/dist/scanners/terraform.js +47 -40
  268. package/dist/scanners/terraform.js.map +1 -1
  269. package/dist/scanners/trivy.d.ts.map +1 -1
  270. package/dist/scanners/trivy.js +38 -30
  271. package/dist/scanners/trivy.js.map +1 -1
  272. package/dist/tool-guard.d.ts +40 -0
  273. package/dist/tool-guard.d.ts.map +1 -0
  274. package/dist/tool-guard.js +55 -0
  275. package/dist/tool-guard.js.map +1 -0
  276. package/dist/util/index.d.ts +2 -1
  277. package/dist/util/index.d.ts.map +1 -1
  278. package/dist/util/index.js +2 -1
  279. package/dist/util/index.js.map +1 -1
  280. package/dist/util/paths.d.ts +20 -3
  281. package/dist/util/paths.d.ts.map +1 -1
  282. package/dist/util/paths.js +84 -4
  283. package/dist/util/paths.js.map +1 -1
  284. package/dist/util/subprocess.d.ts +51 -0
  285. package/dist/util/subprocess.d.ts.map +1 -0
  286. package/dist/util/subprocess.js +77 -0
  287. package/dist/util/subprocess.js.map +1 -0
  288. package/package.json +12 -2
  289. package/dist/eval/fixtures/healthcare/audit-gaps.d.ts +0 -28
  290. package/dist/eval/fixtures/healthcare/audit-gaps.d.ts.map +0 -1
  291. package/dist/eval/fixtures/healthcare/audit-gaps.js +0 -90
  292. package/dist/eval/fixtures/healthcare/audit-gaps.js.map +0 -1
  293. package/dist/eval/fixtures/healthcare/consent-bypass.d.ts +0 -31
  294. package/dist/eval/fixtures/healthcare/consent-bypass.d.ts.map +0 -1
  295. package/dist/eval/fixtures/healthcare/consent-bypass.js +0 -61
  296. package/dist/eval/fixtures/healthcare/consent-bypass.js.map +0 -1
  297. package/dist/eval/fixtures/healthcare/phi-in-logs.d.ts +0 -24
  298. package/dist/eval/fixtures/healthcare/phi-in-logs.d.ts.map +0 -1
  299. package/dist/eval/fixtures/healthcare/phi-in-logs.js +0 -41
  300. package/dist/eval/fixtures/healthcare/phi-in-logs.js.map +0 -1
package/dist/index.js CHANGED
@@ -3,7 +3,10 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
3
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
4
  import { z } from "zod";
5
5
  import { readdir, readFile, writeFile, mkdir, stat, access } from "fs/promises";
6
- import { join, basename } from "path";
6
+ import { readFileSync } from "fs";
7
+ import { join, basename, dirname } from "path";
8
+ import { fileURLToPath } from "url";
9
+ import { randomUUID } from "crypto";
7
10
  import { COMMANDS, listCommands, getCommand } from "./commands/index.js";
8
11
  import { logger, createChildLogger } from "./logger.js";
9
12
  import { generateCertificationId, initializeCertification, getCertification, startAgent, getAgentFindings, submitFinding, completeAgent, addCrossVerification, addRedTeamChallenge, saveConsensus, finalizeCertification, getLatestCertification, isCertificationValid, calculateConsensus, canFinalize, writeCertificationArtifacts, updateCertificationMetadata,
@@ -43,6 +46,7 @@ runSingleFrameworkAssessment, runComplianceAssessment, generateComplianceSummary
43
46
  import { collectEvidence, storeEvidenceBundle, formatEvidenceBundleAsMarkdown, } from "./evidence/index.js";
44
47
  import { verifyHistoryIntegrity, formatVerificationResultAsMarkdown, } from "./history/verify.js";
45
48
  import { exportAuditTrail, } from "./history/store.js";
49
+ import { recordDecision, getDecisionProvenance } from "./history/decisions.js";
46
50
  // SIEM Integration
47
51
  import { createSIEMClient, getSIEMRegistry, createFindingEvent, } from "./integrations/siem/index.js";
48
52
  // SBOM, Provenance, and Sigstore Signing (uses @sigstore/sign for real signing)
@@ -52,7 +56,11 @@ import { getTracker, formatCost, formatTokens, estimateCost, getSupportedModels,
52
56
  // Multi-model consensus
53
57
  import { getRunner, DEFAULT_MODELS, formatProvider, } from "./multimodel/index.js";
54
58
  // Path validation utilities
55
- import { validateProjectPath, PathValidationError } from "./util/paths.js";
59
+ import { validateProjectPath, PathValidationError, resolveContainedFile, resolveContainedWritePath, } from "./util/paths.js";
60
+ import { parseJson, tryParseJson } from "./util/json.js";
61
+ import { applyProjectPathGuard } from "./tool-guard.js";
62
+ import { finalizeCertificate, verifyCertificate, CertificateError, } from "./certification/agent-certificate.js";
63
+ import { certificationToCertificateBody, baselineCertificateBody, } from "./certification/agent-certificate-map.js";
56
64
  // Telemetry and scan registry
57
65
  import { trackCertificationStarted, trackCertificationCompleted, trackScannerRun, } from "./telemetry/usage.js";
58
66
  import { getRegistry } from "./telemetry/registry.js";
@@ -159,10 +167,18 @@ function parseAuditCounts(content) {
159
167
  // ---------------------------------------------------------------------------
160
168
  // Server
161
169
  // ---------------------------------------------------------------------------
170
+ const PKG_PATH = join(dirname(fileURLToPath(import.meta.url)), "..", "package.json");
171
+ const PKG_VERSION = parseJson(readFileSync(PKG_PATH, "utf-8"), "package.json").version;
162
172
  const server = new McpServer({
163
173
  name: "vaspera-hardening-mcp-server",
164
- version: "2.0.0",
174
+ version: PKG_VERSION,
165
175
  });
176
+ // Every tool with a project_path input gets validateProjectPath() by
177
+ // construction (CONSTITUTION rule 3). Set VASPERA_PATH_BOUNDARY to also
178
+ // confine all scans to a workspace root.
179
+ applyProjectPathGuard(server, process.env.VASPERA_PATH_BOUNDARY
180
+ ? { basePath: process.env.VASPERA_PATH_BOUNDARY }
181
+ : {});
166
182
  // ---------------------------------------------------------------------------
167
183
  // Tool: List Projects
168
184
  // ---------------------------------------------------------------------------
@@ -1587,6 +1603,170 @@ server.registerTool("redteam_challenge", {
1587
1603
  };
1588
1604
  });
1589
1605
  // ---------------------------------------------------------------------------
1606
+ // Tool: Antagonist Synthesize
1607
+ // ---------------------------------------------------------------------------
1608
+ server.registerTool("antagonist_synthesize", {
1609
+ title: "Synthesize Attack Narratives",
1610
+ description: `Run antagonist analysis after all agents complete. Synthesizes findings into attack narratives and challenges assumptions. Two modes: "synthesis" (attack narratives), "challenger" (internal critic), or "both".`,
1611
+ inputSchema: {
1612
+ project_path: z.string().describe("Absolute path to the project root"),
1613
+ certification_id: z.string().describe("Certification ID"),
1614
+ mode: z.enum(["synthesis", "challenger", "both"]).optional().default("both").describe("Analysis mode"),
1615
+ include_prioritization: z.boolean().optional().default(true).describe("Include remediation prioritization"),
1616
+ use_llm: z.boolean().optional().default(true).describe("Use LLM for enhanced analysis"),
1617
+ },
1618
+ annotations: {
1619
+ readOnlyHint: false,
1620
+ destructiveHint: false,
1621
+ idempotentHint: true,
1622
+ openWorldHint: false,
1623
+ },
1624
+ }, async ({ project_path, certification_id, mode = "both", include_prioritization = true, use_llm = true }) => {
1625
+ const { runAntagonistAnalysis } = await import("./agents/antagonist/index.js");
1626
+ const { analyzeExploitChains } = await import("./agents/exploit-chain.js");
1627
+ const certification = await getCertification(project_path, certification_id);
1628
+ if (!certification) {
1629
+ return errorResponse(`Certification ${certification_id} not found`);
1630
+ }
1631
+ const ready = await allAgentsCompleted(project_path, certification_id);
1632
+ if (!ready) {
1633
+ return errorResponse("Not all agents have completed. Run antagonist after all agents finish.");
1634
+ }
1635
+ const allFindings = [];
1636
+ const agentSummaries = {};
1637
+ for (const [agentName, agentData] of Object.entries(certification.agents)) {
1638
+ if (agentData) {
1639
+ allFindings.push(...agentData.findings);
1640
+ agentSummaries[agentName] = {
1641
+ completed: agentData.status === "completed",
1642
+ findingCount: agentData.findings.length,
1643
+ };
1644
+ }
1645
+ }
1646
+ const chainResult = await analyzeExploitChains(allFindings);
1647
+ const result = await runAntagonistAnalysis({
1648
+ projectPath: project_path,
1649
+ certificationId: certification_id,
1650
+ findings: allFindings,
1651
+ exploitChains: chainResult.chains,
1652
+ exfilPaths: [],
1653
+ agentSummaries: agentSummaries,
1654
+ }, {
1655
+ mode,
1656
+ includePrioritization: include_prioritization,
1657
+ useLlm: use_llm,
1658
+ });
1659
+ return jsonResponse({
1660
+ success: result.success,
1661
+ analysisId: result.analysisId,
1662
+ narrativesFound: result.attackNarratives.length,
1663
+ challengesFound: result.challengerAssessments.length,
1664
+ coverageScore: result.gapAnalysis.coverageScore,
1665
+ summary: result.summary,
1666
+ attackNarratives: result.attackNarratives.slice(0, 5).map((n) => ({
1667
+ name: n.name,
1668
+ likelihood: n.likelihood,
1669
+ difficulty: n.difficulty,
1670
+ impact: n.impact,
1671
+ findingCount: n.findingIds.length,
1672
+ narrative: n.narrative.slice(0, 300),
1673
+ })),
1674
+ challenges: result.challengerAssessments.slice(0, 5).map((c) => ({
1675
+ type: c.type,
1676
+ targetAgent: c.targetAgent,
1677
+ challenge: c.challenge,
1678
+ severity: c.severity,
1679
+ })),
1680
+ prioritization: result.prioritization.slice(0, 5).map((p) => ({
1681
+ order: p.order,
1682
+ findingId: p.findingId,
1683
+ reason: p.reason,
1684
+ effort: p.effort,
1685
+ impact: p.impact,
1686
+ })),
1687
+ gapAnalysis: {
1688
+ coverageScore: result.gapAnalysis.coverageScore,
1689
+ untestedVectors: result.gapAnalysis.untestedAttackVectors.slice(0, 5),
1690
+ recommendations: result.gapAnalysis.recommendations.slice(0, 3),
1691
+ },
1692
+ duration: result.duration,
1693
+ tokensUsed: result.tokensUsed,
1694
+ });
1695
+ });
1696
+ // ---------------------------------------------------------------------------
1697
+ // Tool: Antagonist Challenge
1698
+ // ---------------------------------------------------------------------------
1699
+ server.registerTool("antagonist_challenge", {
1700
+ title: "Challenge Finding",
1701
+ description: `Challenge a specific finding from another agent. Used to flag potential false positives, missing context, or wrong severity.`,
1702
+ inputSchema: {
1703
+ project_path: z.string().describe("Absolute path to the project root"),
1704
+ certification_id: z.string().describe("Certification ID"),
1705
+ target_finding_id: z.string().describe("Finding ID to challenge"),
1706
+ challenge_type: z.enum(["false_positive", "missing_context", "wrong_severity", "wrong_assumption"]).describe("Type of challenge"),
1707
+ evidence: z.string().describe("Evidence supporting the challenge"),
1708
+ },
1709
+ annotations: {
1710
+ readOnlyHint: false,
1711
+ destructiveHint: false,
1712
+ idempotentHint: false,
1713
+ openWorldHint: false,
1714
+ },
1715
+ }, async ({ project_path, certification_id, target_finding_id, challenge_type, evidence }) => {
1716
+ const certification = await getCertification(project_path, certification_id);
1717
+ if (!certification) {
1718
+ return errorResponse(`Certification ${certification_id} not found`);
1719
+ }
1720
+ let targetFinding = null;
1721
+ let targetAgent = null;
1722
+ for (const [agentName, agentData] of Object.entries(certification.agents)) {
1723
+ if (agentData) {
1724
+ const found = agentData.findings.find((f) => f.id === target_finding_id);
1725
+ if (found) {
1726
+ targetFinding = found;
1727
+ targetAgent = agentName;
1728
+ break;
1729
+ }
1730
+ }
1731
+ }
1732
+ if (!targetFinding || !targetAgent) {
1733
+ return errorResponse(`Finding ${target_finding_id} not found`);
1734
+ }
1735
+ const challengeTypeMap = {
1736
+ false_positive: "false_positive_likely",
1737
+ missing_context: "insufficient_evidence",
1738
+ wrong_severity: "wrong_severity",
1739
+ wrong_assumption: "wrong_assumption",
1740
+ };
1741
+ const assessment = {
1742
+ id: `chal-manual-${Date.now().toString(36)}`,
1743
+ type: challengeTypeMap[challenge_type] || challenge_type,
1744
+ targetAgent,
1745
+ targetFindingId: target_finding_id,
1746
+ challenge: `Manual challenge: ${challenge_type}`,
1747
+ evidence,
1748
+ suggestedAction: challenge_type === "false_positive"
1749
+ ? "Review and potentially dismiss this finding"
1750
+ : challenge_type === "wrong_severity"
1751
+ ? "Re-evaluate severity level"
1752
+ : "Provide additional context or evidence",
1753
+ severity: targetFinding.severity,
1754
+ confidence: 90,
1755
+ };
1756
+ await addCrossVerification(project_path, certification_id, {
1757
+ finding_id: target_finding_id,
1758
+ verifying_agent: "antagonist",
1759
+ verdict: "disputed",
1760
+ evidence: `[${challenge_type}] ${evidence}`,
1761
+ });
1762
+ return jsonResponse({
1763
+ success: true,
1764
+ challenge: assessment,
1765
+ findingDisputed: target_finding_id,
1766
+ message: `Finding ${target_finding_id} has been challenged and marked as disputed.`,
1767
+ });
1768
+ });
1769
+ // ---------------------------------------------------------------------------
1590
1770
  // Tool: Calculate Consensus
1591
1771
  // ---------------------------------------------------------------------------
1592
1772
  server.registerTool("certification_consensus", {
@@ -1745,6 +1925,181 @@ server.registerTool("certification_finalize", {
1745
1925
  };
1746
1926
  });
1747
1927
  // ---------------------------------------------------------------------------
1928
+ // Tool: Generate Agent Certificate
1929
+ // ---------------------------------------------------------------------------
1930
+ server.registerTool("agent_certificate_generate", {
1931
+ title: "Generate Agent Certificate",
1932
+ description: `Produce a versioned, tamper-evident agent certificate (v1) across six dimensions (security, scalability, quality, explainability, compliance, AI-BOM). If certification_id is provided, the real certification run is mapped into the certificate; dimensions not covered are marked not_assessed rather than fabricated. The certificate carries a sha256 content digest and is Sigstore-signed when an OIDC identity is available (CI), otherwise unsigned-but-digested.`,
1933
+ inputSchema: {
1934
+ project_path: z.string().describe("Absolute path to the project root"),
1935
+ certification_id: z
1936
+ .string()
1937
+ .optional()
1938
+ .describe("Map an existing certification run into the certificate"),
1939
+ subject_name: z.string().optional().describe("Override the certified subject name"),
1940
+ subject_kind: z
1941
+ .enum(["agent", "mcp-server", "codebase"])
1942
+ .optional()
1943
+ .describe("Kind of subject being certified (default: codebase)"),
1944
+ compliance_frameworks: z
1945
+ .array(z.enum([
1946
+ "ISO-42001",
1947
+ "NIST-AI-RMF",
1948
+ "OWASP-LLM",
1949
+ "SOC2",
1950
+ "ISO27001",
1951
+ "HIPAA",
1952
+ "GDPR",
1953
+ "PCI-DSS",
1954
+ "NIST-800-53",
1955
+ ]))
1956
+ .optional()
1957
+ .describe("Frameworks to evaluate for the compliance dimension (requires certification_id)"),
1958
+ validity_days: z
1959
+ .number()
1960
+ .int()
1961
+ .positive()
1962
+ .max(3650)
1963
+ .optional()
1964
+ .describe("Certificate validity window in days (default 90)"),
1965
+ sign: z
1966
+ .boolean()
1967
+ .optional()
1968
+ .describe("Attempt Sigstore signing (default true; falls back to unsigned)"),
1969
+ },
1970
+ annotations: {
1971
+ readOnlyHint: true,
1972
+ destructiveHint: false,
1973
+ idempotentHint: false,
1974
+ openWorldHint: false,
1975
+ },
1976
+ }, async ({ project_path, certification_id, subject_name, subject_kind, compliance_frameworks, validity_days, sign, }) => {
1977
+ const now = new Date();
1978
+ const expires = new Date(now.getTime() + (validity_days ?? 90) * 86400000);
1979
+ const certificateId = `vac_${randomUUID()}`;
1980
+ let body;
1981
+ if (certification_id) {
1982
+ const cert = await getCertification(project_path, certification_id);
1983
+ if (!cert) {
1984
+ return errorResponse(`Certification ${certification_id} not found`);
1985
+ }
1986
+ // Anchor the certificate to the tamper-evident decision chain.
1987
+ const provenance = await getDecisionProvenance(project_path);
1988
+ body = certificationToCertificateBody(cert, {
1989
+ toolVersion: PKG_VERSION,
1990
+ issuedAt: now.toISOString(),
1991
+ expiresAt: expires.toISOString(),
1992
+ certificateId,
1993
+ complianceFrameworks: compliance_frameworks,
1994
+ provenance,
1995
+ });
1996
+ if (subject_name)
1997
+ body.subject.name = subject_name;
1998
+ if (subject_kind)
1999
+ body.subject.kind = subject_kind;
2000
+ }
2001
+ else {
2002
+ // No certification supplied — baseline skeleton with all dimensions
2003
+ // not_assessed (honest, never fabricated).
2004
+ body = baselineCertificateBody({
2005
+ toolVersion: PKG_VERSION,
2006
+ issuedAt: now.toISOString(),
2007
+ expiresAt: expires.toISOString(),
2008
+ certificateId,
2009
+ subjectName: subject_name ?? basename(project_path),
2010
+ subjectKind: subject_kind ?? "codebase",
2011
+ identifier: project_path,
2012
+ });
2013
+ }
2014
+ try {
2015
+ const certificate = await finalizeCertificate(body, { sign: sign ?? true });
2016
+ return jsonResponse({ certificate });
2017
+ }
2018
+ catch (error) {
2019
+ if (error instanceof CertificateError) {
2020
+ return errorResponse(`Certificate generation failed: ${error.message}`);
2021
+ }
2022
+ throw error;
2023
+ }
2024
+ });
2025
+ // ---------------------------------------------------------------------------
2026
+ // Tool: Verify Agent Certificate
2027
+ // ---------------------------------------------------------------------------
2028
+ server.registerTool("agent_certificate_verify", {
2029
+ title: "Verify Agent Certificate",
2030
+ description: `Independently verify an agent certificate without trusting the issuer: re-validate the schema, recompute the content digest from the canonical body, and check the Sigstore signature if present. Returns which checks passed and any errors.`,
2031
+ inputSchema: {
2032
+ certificate_json: z
2033
+ .string()
2034
+ .describe("The agent certificate as a JSON string"),
2035
+ },
2036
+ annotations: {
2037
+ readOnlyHint: true,
2038
+ destructiveHint: false,
2039
+ idempotentHint: true,
2040
+ openWorldHint: false,
2041
+ },
2042
+ }, async ({ certificate_json }) => {
2043
+ const parsed = tryParseJson(certificate_json, "certificate_json");
2044
+ if (parsed === undefined) {
2045
+ return errorResponse("certificate_json is not valid JSON");
2046
+ }
2047
+ const result = await verifyCertificate(parsed);
2048
+ return jsonResponse({ ...result });
2049
+ });
2050
+ // ---------------------------------------------------------------------------
2051
+ // Tool: Record AI Decision (provenance)
2052
+ // ---------------------------------------------------------------------------
2053
+ server.registerTool("decision_record", {
2054
+ title: "Record AI Decision (Provenance)",
2055
+ description: `Append a tamper-evident record of an AI decision to the project's hash-chained audit trail, for explainability and traceability. Raw input/prompt/output are stored as sha256 digests (+ optional summary), so the chain proves what was decided without retaining secrets. Verify the chain with verify_audit_trail.`,
2056
+ inputSchema: {
2057
+ project_path: z.string().describe("Absolute path to the project root"),
2058
+ decision_type: z
2059
+ .string()
2060
+ .describe("Kind of decision (tool_call, classification, generation, refusal, …)"),
2061
+ model: z.string().describe("Model that produced the decision"),
2062
+ model_version: z.string().optional(),
2063
+ input: z.string().describe("Input/context that led to the decision (digested, not stored raw)"),
2064
+ prompt: z.string().optional().describe("Prompt, if applicable (digested)"),
2065
+ output: z.string().describe("The output/decision (digested)"),
2066
+ tools_invoked: z.array(z.string()).optional(),
2067
+ summary: z.string().optional().describe("Short human-readable summary"),
2068
+ rationale: z.string().optional().describe("Rationale / explanation"),
2069
+ confidence: z.number().min(0).max(100).optional(),
2070
+ certification_id: z.string().optional(),
2071
+ sign: z.boolean().optional().describe("Sigstore-sign the entry (requires OIDC)"),
2072
+ },
2073
+ annotations: {
2074
+ readOnlyHint: false,
2075
+ destructiveHint: false,
2076
+ idempotentHint: false,
2077
+ openWorldHint: false,
2078
+ },
2079
+ }, async ({ project_path, decision_type, model, model_version, input, prompt, output, tools_invoked, summary, rationale, confidence, certification_id, sign, }) => {
2080
+ const entry = await recordDecision(project_path, {
2081
+ decisionType: decision_type,
2082
+ model,
2083
+ modelVersion: model_version,
2084
+ input,
2085
+ prompt,
2086
+ output,
2087
+ toolsInvoked: tools_invoked,
2088
+ summary,
2089
+ rationale,
2090
+ confidence,
2091
+ certificationId: certification_id,
2092
+ }, { sign: sign ?? false });
2093
+ return jsonResponse({
2094
+ recorded: true,
2095
+ id: entry.id,
2096
+ hash: entry.integrity?.hash,
2097
+ previousHash: entry.integrity?.previousHash,
2098
+ inputDigest: entry.inputDigest,
2099
+ outputDigest: entry.outputDigest,
2100
+ });
2101
+ });
2102
+ // ---------------------------------------------------------------------------
1748
2103
  // Tool: Certification Dashboard
1749
2104
  // ---------------------------------------------------------------------------
1750
2105
  server.registerTool("certification_dashboard", {
@@ -1928,7 +2283,7 @@ server.registerTool("autofix_apply", {
1928
2283
  },
1929
2284
  annotations: {
1930
2285
  readOnlyHint: false,
1931
- destructiveHint: false,
2286
+ destructiveHint: true,
1932
2287
  idempotentHint: false,
1933
2288
  openWorldHint: false,
1934
2289
  },
@@ -1982,7 +2337,7 @@ server.registerTool("autofix_batch", {
1982
2337
  },
1983
2338
  annotations: {
1984
2339
  readOnlyHint: false,
1985
- destructiveHint: false,
2340
+ destructiveHint: true,
1986
2341
  idempotentHint: false,
1987
2342
  openWorldHint: false,
1988
2343
  },
@@ -2556,7 +2911,9 @@ server.registerTool("rules_check_file", {
2556
2911
  message: "No custom rules configured. Use rules_generate_config to create a sample.",
2557
2912
  });
2558
2913
  }
2559
- const fullPath = join(project_path, file_path);
2914
+ // file_path is untrusted — contain it within the (already validated)
2915
+ // project tree before reading.
2916
+ const fullPath = await resolveContainedFile(project_path, file_path);
2560
2917
  const content = await readFile(fullPath, "utf-8");
2561
2918
  const matches = await checkFileAgainstRules(file_path, content, rules);
2562
2919
  const findings = matchesToFindings(matches);
@@ -3509,9 +3866,10 @@ server.registerTool("sbom_generate", {
3509
3866
  includeDevDependencies: include_dev,
3510
3867
  includeVulnerabilities: include_vulnerabilities,
3511
3868
  }, findings);
3512
- // Write to file if requested
3869
+ // Write to file if requested. output_file is untrusted — contain
3870
+ // it within the project tree (no absolute-path escape hatch).
3513
3871
  if (output_file) {
3514
- const filePath = output_file.startsWith("/") ? output_file : join(project_path, output_file);
3872
+ const filePath = await resolveContainedWritePath(project_path, output_file);
3515
3873
  const content = output_format === "summary"
3516
3874
  ? generateSBOMSummary(sbom)
3517
3875
  : JSON.stringify(sbom, null, 2);
@@ -4307,10 +4665,15 @@ server.registerTool("consensus_models", {
4307
4665
  // ---------------------------------------------------------------------------
4308
4666
  server.registerTool("consensus_clear", {
4309
4667
  title: "Clear Recorded Results",
4310
- description: `Clear previously recorded findings for a certification. Use before re-recording results for fresh consensus calculation.`,
4668
+ description: `Clear previously recorded findings for a certification. Use before re-recording results for fresh consensus calculation. Fail-closed: pass confirm: true to actually clear; otherwise returns a no-op preview.`,
4311
4669
  inputSchema: {
4312
4670
  certification_id: z.string().describe("Certification ID"),
4313
4671
  agent: z.enum(["security", "reliability", "typesafety", "performance", "quality", "redteam"]).optional().describe("Specific agent to clear, or all if not specified"),
4672
+ confirm: z
4673
+ .boolean()
4674
+ .optional()
4675
+ .default(false)
4676
+ .describe("Must be true to actually clear recorded results; otherwise a no-op preview is returned"),
4314
4677
  },
4315
4678
  annotations: {
4316
4679
  readOnlyHint: false,
@@ -4318,8 +4681,15 @@ server.registerTool("consensus_clear", {
4318
4681
  idempotentHint: true,
4319
4682
  openWorldHint: false,
4320
4683
  },
4321
- }, async ({ certification_id, agent }) => {
4684
+ }, async ({ certification_id, agent, confirm }) => {
4322
4685
  try {
4686
+ if (!confirm) {
4687
+ return jsonResponse({
4688
+ preview: true,
4689
+ wouldClear: { certificationId: certification_id, agent: agent || "all" },
4690
+ message: `No-op preview: would clear recorded results for ${certification_id} (${agent || "all"} agents). Re-run with confirm: true to apply.`,
4691
+ });
4692
+ }
4323
4693
  const runner = getRunner();
4324
4694
  runner.clearResults(certification_id, agent);
4325
4695
  return jsonResponse({
@@ -5922,10 +6292,15 @@ server.registerTool("deploy_vercel_list", {
5922
6292
  }
5923
6293
  });
5924
6294
  server.registerTool("deploy_vercel_promote", {
5925
- description: "Promote a Vercel preview deployment to production.",
6295
+ description: "Promote a Vercel preview deployment to production. Fail-closed: pass confirm: true to actually promote; otherwise returns a no-op preview.",
5926
6296
  inputSchema: {
5927
6297
  project_id: z.string().describe("Vercel project ID"),
5928
6298
  deployment_id: z.string().describe("Deployment ID to promote"),
6299
+ confirm: z
6300
+ .boolean()
6301
+ .optional()
6302
+ .default(false)
6303
+ .describe("Must be true to actually promote to production; otherwise a no-op preview is returned"),
5929
6304
  },
5930
6305
  annotations: {
5931
6306
  readOnlyHint: false,
@@ -5933,8 +6308,15 @@ server.registerTool("deploy_vercel_promote", {
5933
6308
  idempotentHint: false,
5934
6309
  openWorldHint: true,
5935
6310
  },
5936
- }, async ({ project_id, deployment_id }) => {
6311
+ }, async ({ project_id, deployment_id, confirm }) => {
5937
6312
  try {
6313
+ if (!confirm) {
6314
+ return jsonResponse({
6315
+ preview: true,
6316
+ wouldPromote: { project_id, deployment_id },
6317
+ message: `No-op preview: would promote deployment ${deployment_id} to production. Re-run with confirm: true to apply.`,
6318
+ });
6319
+ }
5938
6320
  if (!isVercelAvailable()) {
5939
6321
  return errorResponse("VERCEL_TOKEN not configured");
5940
6322
  }
@@ -5952,10 +6334,15 @@ server.registerTool("deploy_vercel_promote", {
5952
6334
  }
5953
6335
  });
5954
6336
  server.registerTool("deploy_vercel_rollback", {
5955
- description: "Rollback to a previous Vercel deployment.",
6337
+ description: "Rollback to a previous Vercel deployment. Fail-closed: pass confirm: true to actually roll back; otherwise returns a no-op preview.",
5956
6338
  inputSchema: {
5957
6339
  project_id: z.string().describe("Vercel project ID"),
5958
6340
  deployment_id: z.string().describe("Deployment ID to rollback to"),
6341
+ confirm: z
6342
+ .boolean()
6343
+ .optional()
6344
+ .default(false)
6345
+ .describe("Must be true to actually roll back production; otherwise a no-op preview is returned"),
5959
6346
  },
5960
6347
  annotations: {
5961
6348
  readOnlyHint: false,
@@ -5963,8 +6350,15 @@ server.registerTool("deploy_vercel_rollback", {
5963
6350
  idempotentHint: false,
5964
6351
  openWorldHint: true,
5965
6352
  },
5966
- }, async ({ project_id, deployment_id }) => {
6353
+ }, async ({ project_id, deployment_id, confirm }) => {
5967
6354
  try {
6355
+ if (!confirm) {
6356
+ return jsonResponse({
6357
+ preview: true,
6358
+ wouldRollbackTo: { project_id, deployment_id },
6359
+ message: `No-op preview: would roll back ${project_id} to deployment ${deployment_id}. Re-run with confirm: true to apply.`,
6360
+ });
6361
+ }
5968
6362
  if (!isVercelAvailable()) {
5969
6363
  return errorResponse("VERCEL_TOKEN not configured");
5970
6364
  }
@@ -6057,7 +6451,9 @@ Use this for fast feedback on individual files.`,
6057
6451
  },
6058
6452
  }, async ({ project_path, file_path }) => {
6059
6453
  try {
6060
- const fullPath = file_path.startsWith("/") ? file_path : join(project_path, file_path);
6454
+ // file_path is untrusted contain it within the (already
6455
+ // validated) project tree (no absolute-path escape hatch).
6456
+ const fullPath = await resolveContainedFile(project_path, file_path);
6061
6457
  const result = await quickAICheck(fullPath, project_path);
6062
6458
  return jsonResponse({
6063
6459
  file: file_path,