@vibecheckai/cli 3.5.1 → 3.5.2

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 (272) hide show
  1. package/bin/registry.js +406 -154
  2. package/bin/runners/context/analyzer.js +52 -1
  3. package/bin/runners/context/generators/mcp.js +15 -13
  4. package/bin/runners/context/git-context.js +3 -1
  5. package/bin/runners/context/proof-context.js +248 -1
  6. package/bin/runners/context/team-conventions.js +33 -7
  7. package/bin/runners/lib/agent-firewall/ai/false-positive-analyzer.js +474 -0
  8. package/bin/runners/lib/agent-firewall/change-packet/builder.js +488 -0
  9. package/bin/runners/lib/agent-firewall/change-packet/schema.json +228 -0
  10. package/bin/runners/lib/agent-firewall/change-packet/store.js +200 -0
  11. package/bin/runners/lib/agent-firewall/claims/claim-types.js +21 -0
  12. package/bin/runners/lib/agent-firewall/claims/extractor.js +303 -0
  13. package/bin/runners/lib/agent-firewall/claims/patterns.js +24 -0
  14. package/bin/runners/lib/agent-firewall/critic/index.js +151 -0
  15. package/bin/runners/lib/agent-firewall/critic/judge.js +432 -0
  16. package/bin/runners/lib/agent-firewall/critic/prompts.js +305 -0
  17. package/bin/runners/lib/agent-firewall/evidence/auth-evidence.js +88 -0
  18. package/bin/runners/lib/agent-firewall/evidence/contract-evidence.js +75 -0
  19. package/bin/runners/lib/agent-firewall/evidence/env-evidence.js +127 -0
  20. package/bin/runners/lib/agent-firewall/evidence/resolver.js +102 -0
  21. package/bin/runners/lib/agent-firewall/evidence/route-evidence.js +213 -0
  22. package/bin/runners/lib/agent-firewall/evidence/side-effect-evidence.js +145 -0
  23. package/bin/runners/lib/agent-firewall/fs-hook/daemon.js +19 -0
  24. package/bin/runners/lib/agent-firewall/fs-hook/installer.js +87 -0
  25. package/bin/runners/lib/agent-firewall/fs-hook/watcher.js +184 -0
  26. package/bin/runners/lib/agent-firewall/git-hook/pre-commit.js +163 -0
  27. package/bin/runners/lib/agent-firewall/ide-extension/cursor.js +107 -0
  28. package/bin/runners/lib/agent-firewall/ide-extension/vscode.js +68 -0
  29. package/bin/runners/lib/agent-firewall/ide-extension/windsurf.js +66 -0
  30. package/bin/runners/lib/agent-firewall/interceptor/base.js +304 -0
  31. package/bin/runners/lib/agent-firewall/interceptor/cursor.js +35 -0
  32. package/bin/runners/lib/agent-firewall/interceptor/vscode.js +35 -0
  33. package/bin/runners/lib/agent-firewall/interceptor/windsurf.js +34 -0
  34. package/bin/runners/lib/agent-firewall/lawbook/distributor.js +465 -0
  35. package/bin/runners/lib/agent-firewall/lawbook/evaluator.js +604 -0
  36. package/bin/runners/lib/agent-firewall/lawbook/index.js +304 -0
  37. package/bin/runners/lib/agent-firewall/lawbook/registry.js +514 -0
  38. package/bin/runners/lib/agent-firewall/lawbook/schema.js +420 -0
  39. package/bin/runners/lib/agent-firewall/logger.js +141 -0
  40. package/bin/runners/lib/agent-firewall/policy/default-policy.json +90 -0
  41. package/bin/runners/lib/agent-firewall/policy/engine.js +103 -0
  42. package/bin/runners/lib/agent-firewall/policy/loader.js +451 -0
  43. package/bin/runners/lib/agent-firewall/policy/rules/auth-drift.js +50 -0
  44. package/bin/runners/lib/agent-firewall/policy/rules/contract-drift.js +50 -0
  45. package/bin/runners/lib/agent-firewall/policy/rules/fake-success.js +86 -0
  46. package/bin/runners/lib/agent-firewall/policy/rules/ghost-env.js +162 -0
  47. package/bin/runners/lib/agent-firewall/policy/rules/ghost-route.js +189 -0
  48. package/bin/runners/lib/agent-firewall/policy/rules/scope.js +93 -0
  49. package/bin/runners/lib/agent-firewall/policy/rules/unsafe-side-effect.js +57 -0
  50. package/bin/runners/lib/agent-firewall/policy/schema.json +183 -0
  51. package/bin/runners/lib/agent-firewall/policy/verdict.js +54 -0
  52. package/bin/runners/lib/agent-firewall/proposal/extractor.js +394 -0
  53. package/bin/runners/lib/agent-firewall/proposal/index.js +212 -0
  54. package/bin/runners/lib/agent-firewall/proposal/schema.js +251 -0
  55. package/bin/runners/lib/agent-firewall/proposal/validator.js +386 -0
  56. package/bin/runners/lib/agent-firewall/reality/index.js +332 -0
  57. package/bin/runners/lib/agent-firewall/reality/state.js +625 -0
  58. package/bin/runners/lib/agent-firewall/reality/watcher.js +322 -0
  59. package/bin/runners/lib/agent-firewall/risk/index.js +173 -0
  60. package/bin/runners/lib/agent-firewall/risk/scorer.js +328 -0
  61. package/bin/runners/lib/agent-firewall/risk/thresholds.js +321 -0
  62. package/bin/runners/lib/agent-firewall/risk/vectors.js +421 -0
  63. package/bin/runners/lib/agent-firewall/simulator/diff-simulator.js +472 -0
  64. package/bin/runners/lib/agent-firewall/simulator/import-resolver.js +346 -0
  65. package/bin/runners/lib/agent-firewall/simulator/index.js +181 -0
  66. package/bin/runners/lib/agent-firewall/simulator/route-validator.js +380 -0
  67. package/bin/runners/lib/agent-firewall/time-machine/incident-correlator.js +661 -0
  68. package/bin/runners/lib/agent-firewall/time-machine/index.js +267 -0
  69. package/bin/runners/lib/agent-firewall/time-machine/replay-engine.js +436 -0
  70. package/bin/runners/lib/agent-firewall/time-machine/state-reconstructor.js +490 -0
  71. package/bin/runners/lib/agent-firewall/time-machine/timeline-builder.js +530 -0
  72. package/bin/runners/lib/agent-firewall/truthpack/index.js +67 -0
  73. package/bin/runners/lib/agent-firewall/truthpack/loader.js +137 -0
  74. package/bin/runners/lib/agent-firewall/unblock/planner.js +337 -0
  75. package/bin/runners/lib/agent-firewall/utils/ignore-checker.js +118 -0
  76. package/bin/runners/lib/analysis-core.js +220 -182
  77. package/bin/runners/lib/analyzers.js +2145 -224
  78. package/bin/runners/lib/api-client.js +269 -0
  79. package/bin/runners/lib/authority-badge.js +425 -0
  80. package/bin/runners/lib/cli-output.js +242 -210
  81. package/bin/runners/lib/default-config.js +127 -0
  82. package/bin/runners/lib/detectors-v2.js +547 -785
  83. package/bin/runners/lib/doctor/modules/security.js +3 -1
  84. package/bin/runners/lib/engine/ast-cache.js +210 -0
  85. package/bin/runners/lib/engine/auth-extractor.js +211 -0
  86. package/bin/runners/lib/engine/billing-extractor.js +112 -0
  87. package/bin/runners/lib/engine/enforcement-extractor.js +100 -0
  88. package/bin/runners/lib/engine/env-extractor.js +207 -0
  89. package/bin/runners/lib/engine/express-extractor.js +208 -0
  90. package/bin/runners/lib/engine/extractors.js +849 -0
  91. package/bin/runners/lib/engine/index.js +207 -0
  92. package/bin/runners/lib/engine/repo-index.js +514 -0
  93. package/bin/runners/lib/engine/types.js +124 -0
  94. package/bin/runners/lib/engines/accessibility-engine.js +190 -0
  95. package/bin/runners/lib/engines/api-consistency-engine.js +162 -0
  96. package/bin/runners/lib/engines/ast-cache.js +99 -0
  97. package/bin/runners/lib/engines/code-quality-engine.js +255 -0
  98. package/bin/runners/lib/engines/console-logs-engine.js +115 -0
  99. package/bin/runners/lib/engines/cross-file-analysis-engine.js +268 -0
  100. package/bin/runners/lib/engines/dead-code-engine.js +198 -0
  101. package/bin/runners/lib/engines/deprecated-api-engine.js +226 -0
  102. package/bin/runners/lib/engines/empty-catch-engine.js +150 -0
  103. package/bin/runners/lib/engines/file-filter.js +131 -0
  104. package/bin/runners/lib/engines/hardcoded-secrets-engine.js +251 -0
  105. package/bin/runners/lib/engines/mock-data-engine.js +272 -0
  106. package/bin/runners/lib/engines/parallel-processor.js +71 -0
  107. package/bin/runners/lib/engines/performance-issues-engine.js +265 -0
  108. package/bin/runners/lib/engines/security-vulnerabilities-engine.js +243 -0
  109. package/bin/runners/lib/engines/todo-fixme-engine.js +115 -0
  110. package/bin/runners/lib/engines/type-aware-engine.js +152 -0
  111. package/bin/runners/lib/engines/unsafe-regex-engine.js +225 -0
  112. package/bin/runners/lib/engines/vibecheck-engines/README.md +53 -0
  113. package/bin/runners/lib/engines/vibecheck-engines/index.js +15 -0
  114. package/bin/runners/lib/engines/vibecheck-engines/lib/ast-cache.js +164 -0
  115. package/bin/runners/lib/engines/vibecheck-engines/lib/code-quality-engine.js +291 -0
  116. package/bin/runners/lib/engines/vibecheck-engines/lib/console-logs-engine.js +83 -0
  117. package/bin/runners/lib/engines/vibecheck-engines/lib/dead-code-engine.js +198 -0
  118. package/bin/runners/lib/engines/vibecheck-engines/lib/deprecated-api-engine.js +275 -0
  119. package/bin/runners/lib/engines/vibecheck-engines/lib/empty-catch-engine.js +167 -0
  120. package/bin/runners/lib/engines/vibecheck-engines/lib/file-filter.js +217 -0
  121. package/bin/runners/lib/engines/vibecheck-engines/lib/hardcoded-secrets-engine.js +139 -0
  122. package/bin/runners/lib/engines/vibecheck-engines/lib/mock-data-engine.js +140 -0
  123. package/bin/runners/lib/engines/vibecheck-engines/lib/parallel-processor.js +164 -0
  124. package/bin/runners/lib/engines/vibecheck-engines/lib/performance-issues-engine.js +234 -0
  125. package/bin/runners/lib/engines/vibecheck-engines/lib/type-aware-engine.js +217 -0
  126. package/bin/runners/lib/engines/vibecheck-engines/lib/unsafe-regex-engine.js +78 -0
  127. package/bin/runners/lib/engines/vibecheck-engines/package.json +13 -0
  128. package/bin/runners/lib/entitlements-v2.js +152 -446
  129. package/bin/runners/lib/error-handler.js +60 -12
  130. package/bin/runners/lib/error-messages.js +289 -0
  131. package/bin/runners/lib/evidence-pack.js +7 -1
  132. package/bin/runners/lib/exit-codes.js +275 -0
  133. package/bin/runners/lib/finding-id.js +69 -0
  134. package/bin/runners/lib/finding-sorter.js +89 -0
  135. package/bin/runners/lib/fingerprint.js +377 -0
  136. package/bin/runners/lib/global-flags.js +37 -0
  137. package/bin/runners/lib/help-formatter.js +413 -0
  138. package/bin/runners/lib/logger.js +38 -0
  139. package/bin/runners/lib/next-action.js +560 -0
  140. package/bin/runners/lib/prerequisites.js +149 -0
  141. package/bin/runners/lib/route-detection.js +137 -68
  142. package/bin/runners/lib/route-truth.js +1167 -322
  143. package/bin/runners/lib/scan-output.js +504 -463
  144. package/bin/runners/lib/scan-runner.js +135 -0
  145. package/bin/runners/lib/schemas/ajv-validator.js +464 -0
  146. package/bin/runners/lib/schemas/error-envelope.schema.json +105 -0
  147. package/bin/runners/lib/schemas/finding-v3.schema.json +151 -0
  148. package/bin/runners/lib/schemas/report-artifact.schema.json +120 -0
  149. package/bin/runners/lib/schemas/run-request.schema.json +108 -0
  150. package/bin/runners/lib/schemas/validator.js +27 -0
  151. package/bin/runners/lib/schemas/verdict.schema.json +140 -0
  152. package/bin/runners/lib/ship-output-enterprise.js +239 -0
  153. package/bin/runners/lib/ship-output.js +328 -31
  154. package/bin/runners/lib/terminal-ui.js +234 -731
  155. package/bin/runners/lib/truth.js +1332 -308
  156. package/bin/runners/lib/unified-cli-output.js +604 -0
  157. package/bin/runners/lib/unified-output.js +163 -155
  158. package/bin/runners/lib/upsell.js +104 -204
  159. package/bin/runners/runAgent.d.ts +5 -0
  160. package/bin/runners/runAgent.js +161 -0
  161. package/bin/runners/runAllowlist.js +166 -101
  162. package/bin/runners/runApprove.js +1200 -0
  163. package/bin/runners/runAuth.js +373 -95
  164. package/bin/runners/runCheckpoint.js +59 -21
  165. package/bin/runners/runClassify.js +926 -0
  166. package/bin/runners/runContext.d.ts +4 -0
  167. package/bin/runners/runContext.js +136 -24
  168. package/bin/runners/runDoctor.js +115 -67
  169. package/bin/runners/runEvidencePack.js +239 -96
  170. package/bin/runners/runFirewall.d.ts +5 -0
  171. package/bin/runners/runFirewall.js +134 -0
  172. package/bin/runners/runFirewallHook.d.ts +5 -0
  173. package/bin/runners/runFirewallHook.js +56 -0
  174. package/bin/runners/runFix.js +6 -5
  175. package/bin/runners/runGuard.js +212 -118
  176. package/bin/runners/runInit.js +66 -21
  177. package/bin/runners/runLabs.js +204 -121
  178. package/bin/runners/runMcp.js +131 -60
  179. package/bin/runners/runPolish.d.ts +4 -0
  180. package/bin/runners/runPolish.js +43 -20
  181. package/bin/runners/runProof.zip +0 -0
  182. package/bin/runners/runProve.js +15 -5
  183. package/bin/runners/runQuickstart.js +531 -0
  184. package/bin/runners/runReality.js +14 -0
  185. package/bin/runners/runReport.js +36 -4
  186. package/bin/runners/runScan.js +689 -91
  187. package/bin/runners/runShip.js +96 -40
  188. package/bin/runners/runTruth.d.ts +5 -0
  189. package/bin/runners/runTruth.js +101 -0
  190. package/bin/runners/runValidate.js +21 -4
  191. package/bin/runners/runWatch.js +118 -54
  192. package/bin/scan.js +6 -1
  193. package/bin/vibecheck.js +297 -52
  194. package/mcp-server/HARDENING_SUMMARY.md +299 -0
  195. package/mcp-server/agent-firewall-interceptor.js +500 -0
  196. package/mcp-server/authority-tools.js +569 -0
  197. package/mcp-server/conductor/conflict-resolver.js +588 -0
  198. package/mcp-server/conductor/execution-planner.js +544 -0
  199. package/mcp-server/conductor/index.js +377 -0
  200. package/mcp-server/conductor/lock-manager.js +615 -0
  201. package/mcp-server/conductor/request-queue.js +550 -0
  202. package/mcp-server/conductor/session-manager.js +500 -0
  203. package/mcp-server/conductor/tools.js +510 -0
  204. package/mcp-server/deprecation-middleware.js +282 -0
  205. package/mcp-server/handlers/index.ts +15 -0
  206. package/mcp-server/handlers/tool-handler.ts +474 -591
  207. package/mcp-server/index.js +1748 -1099
  208. package/mcp-server/lib/api-client.cjs +13 -0
  209. package/mcp-server/lib/cache-wrapper.cjs +383 -0
  210. package/mcp-server/lib/error-envelope.js +138 -0
  211. package/mcp-server/lib/executor.ts +428 -721
  212. package/mcp-server/lib/index.ts +19 -0
  213. package/mcp-server/lib/logger.cjs +30 -0
  214. package/mcp-server/lib/rate-limiter.js +166 -0
  215. package/mcp-server/lib/sandbox.test.ts +519 -0
  216. package/mcp-server/lib/sandbox.ts +342 -284
  217. package/mcp-server/lib/types.ts +267 -0
  218. package/mcp-server/logger.js +173 -0
  219. package/mcp-server/package.json +11 -27
  220. package/mcp-server/premium-tools.js +2 -2
  221. package/mcp-server/registry/tool-registry.js +794 -0
  222. package/mcp-server/registry/tools.json +507 -378
  223. package/mcp-server/registry.test.ts +334 -0
  224. package/mcp-server/tests/tier-gating.test.js +297 -0
  225. package/mcp-server/tier-auth.js +492 -347
  226. package/mcp-server/tools-v3.js +950 -0
  227. package/mcp-server/truth-context.js +131 -90
  228. package/mcp-server/truth-firewall-tools.js +1612 -1001
  229. package/mcp-server/tsconfig.json +8 -5
  230. package/mcp-server/vibecheck-2.0-tools.js +14 -1
  231. package/mcp-server/vibecheck-mcp-server-3.2.0.tgz +0 -0
  232. package/mcp-server/vibecheck-tools.js +2 -2
  233. package/package.json +4 -3
  234. package/bin/runners/runInstall.js +0 -281
  235. package/mcp-server/ARCHITECTURE.md +0 -339
  236. package/mcp-server/__tests__/cache.test.ts +0 -313
  237. package/mcp-server/__tests__/executor.test.ts +0 -239
  238. package/mcp-server/__tests__/fixtures/exclusion-test/.cache/webpack/cache.pack +0 -1
  239. package/mcp-server/__tests__/fixtures/exclusion-test/.next/server/chunk.js +0 -3
  240. package/mcp-server/__tests__/fixtures/exclusion-test/.turbo/cache.json +0 -3
  241. package/mcp-server/__tests__/fixtures/exclusion-test/.venv/lib/env.py +0 -3
  242. package/mcp-server/__tests__/fixtures/exclusion-test/dist/bundle.js +0 -3
  243. package/mcp-server/__tests__/fixtures/exclusion-test/package.json +0 -5
  244. package/mcp-server/__tests__/fixtures/exclusion-test/src/app.ts +0 -5
  245. package/mcp-server/__tests__/fixtures/exclusion-test/venv/lib/config.py +0 -4
  246. package/mcp-server/__tests__/ids.test.ts +0 -345
  247. package/mcp-server/__tests__/integration/tools.test.ts +0 -410
  248. package/mcp-server/__tests__/registry.test.ts +0 -365
  249. package/mcp-server/__tests__/sandbox.test.ts +0 -323
  250. package/mcp-server/__tests__/schemas.test.ts +0 -372
  251. package/mcp-server/benchmarks/run-benchmarks.ts +0 -304
  252. package/mcp-server/examples/doctor.request.json +0 -14
  253. package/mcp-server/examples/doctor.response.json +0 -53
  254. package/mcp-server/examples/error.response.json +0 -15
  255. package/mcp-server/examples/scan.request.json +0 -14
  256. package/mcp-server/examples/scan.response.json +0 -108
  257. package/mcp-server/index-v3.ts +0 -293
  258. package/mcp-server/index.old.js +0 -4137
  259. package/mcp-server/lib/cache.ts +0 -341
  260. package/mcp-server/lib/errors.ts +0 -346
  261. package/mcp-server/lib/ids.ts +0 -238
  262. package/mcp-server/lib/logger.ts +0 -368
  263. package/mcp-server/lib/metrics.ts +0 -365
  264. package/mcp-server/lib/validator.ts +0 -229
  265. package/mcp-server/package-lock.json +0 -165
  266. package/mcp-server/schemas/error-envelope.schema.json +0 -125
  267. package/mcp-server/schemas/finding.schema.json +0 -167
  268. package/mcp-server/schemas/report-artifact.schema.json +0 -88
  269. package/mcp-server/schemas/run-request.schema.json +0 -75
  270. package/mcp-server/schemas/verdict.schema.json +0 -168
  271. package/mcp-server/tier-auth.d.ts +0 -71
  272. package/mcp-server/vitest.config.ts +0 -16
@@ -0,0 +1,500 @@
1
+ /**
2
+ * Agent Firewall Interceptor - MCP Tool
3
+ *
4
+ * Intercepts file write/patch tool calls from AI agents.
5
+ * Validates changes against truthpack and policy before allowing writes.
6
+ *
7
+ * Codename: Sentinel
8
+ *
9
+ * Tier-based enforcement:
10
+ * - FREE: Observe mode (logs violations but allows writes)
11
+ * - STARTER: Advisory mode (warns but allows with confirmation)
12
+ * - PRO: Enforce mode (blocks violations)
13
+ * - ENTERPRISE: Enforce mode + audit trail
14
+ *
15
+ * SECURITY: Tier is NEVER trusted from client - always derived from validated API key
16
+ */
17
+
18
+ import path from "path";
19
+ import fs from "fs";
20
+ import { createRequire } from "module";
21
+
22
+ // Import tier auth for secure tier validation
23
+ import { getMcpToolAccess } from "./tier-auth.js";
24
+
25
+ const require = createRequire(import.meta.url);
26
+
27
+ // Import core firewall modules
28
+ const { interceptFileWrite, interceptMultiFileWrite } = require("../bin/runners/lib/agent-firewall/interceptor/base");
29
+ const { loadPolicy } = require("../bin/runners/lib/agent-firewall/policy/loader");
30
+
31
+ // Import new Sentinel modules
32
+ let reality, risk, simulator, proposal, critic, proofBuilder;
33
+ try {
34
+ reality = require("../bin/runners/lib/agent-firewall/reality");
35
+ risk = require("../bin/runners/lib/agent-firewall/risk");
36
+ simulator = require("../bin/runners/lib/agent-firewall/simulator");
37
+ proposal = require("../bin/runners/lib/agent-firewall/proposal");
38
+ critic = require("../bin/runners/lib/agent-firewall/critic");
39
+ proofBuilder = require("../bin/runners/lib/agent-firewall/change-packet/builder");
40
+ } catch (err) {
41
+ console.warn(`[Agent Firewall] Some Sentinel modules not available: ${err.message}`);
42
+ }
43
+
44
+ // Tier-based policy mode mapping
45
+ const TIER_POLICY_MODES = {
46
+ FREE: "observe", // Log only, never block
47
+ STARTER: "advisory", // Warn, allow with confirmation
48
+ PRO: "enforce", // Block violations
49
+ ENTERPRISE: "enforce", // Block + full audit
50
+ };
51
+
52
+ /**
53
+ * Get effective policy mode based on tier
54
+ * @param {string} tier - User's subscription tier
55
+ * @param {object} policy - Policy configuration
56
+ * @returns {string} Effective mode
57
+ */
58
+ function getEffectivePolicyMode(tier, policy) {
59
+ // If policy explicitly sets mode, respect it for PRO+
60
+ if (policy.mode && (tier === "PRO" || tier === "ENTERPRISE")) {
61
+ return policy.mode;
62
+ }
63
+
64
+ // Otherwise use tier-based defaults
65
+ return TIER_POLICY_MODES[tier] || "observe";
66
+ }
67
+
68
+ /**
69
+ * MCP Tool Definition
70
+ */
71
+ const AGENT_FIREWALL_TOOL = {
72
+ name: "vibecheck_agent_firewall_intercept",
73
+ description: `🛡️ Agent Firewall (Sentinel) - Intercepts AI code changes and validates against repo truth.
74
+
75
+ This tool MUST be called before any file write/patch operations.
76
+ It validates changes against truthpack, policy rules, and reality state.
77
+
78
+ Features:
79
+ - Reality state validation (routes, env vars, services)
80
+ - Risk scoring (surface area, blast radius, irreversibility)
81
+ - Diff simulation (broken imports, orphaned files)
82
+ - Assumption verification
83
+ - Proof artifact generation
84
+
85
+ Tier modes:
86
+ - FREE: Observe (logs only)
87
+ - STARTER: Advisory (warns)
88
+ - PRO/ENTERPRISE: Enforce (blocks)
89
+
90
+ Returns: { allowed, verdict, riskScore, violations, unblockPlan, proofId }`,
91
+ inputSchema: {
92
+ type: "object",
93
+ required: ["agentId", "filePath", "content"],
94
+ properties: {
95
+ agentId: {
96
+ type: "string",
97
+ description: "Agent identifier (e.g., 'cursor', 'windsurf', 'copilot')"
98
+ },
99
+ filePath: {
100
+ type: "string",
101
+ description: "File path relative to project root"
102
+ },
103
+ content: {
104
+ type: "string",
105
+ description: "New file content"
106
+ },
107
+ oldContent: {
108
+ type: "string",
109
+ description: "Old file content (optional, for diff generation)"
110
+ },
111
+ intent: {
112
+ type: "string",
113
+ description: "Agent's stated intent for this change"
114
+ },
115
+ summary: {
116
+ type: "string",
117
+ description: "Human-readable summary of the change"
118
+ },
119
+ assumptions: {
120
+ type: "array",
121
+ description: "Declared assumptions (auto-extracted if not provided)",
122
+ items: {
123
+ type: "object",
124
+ properties: {
125
+ type: { type: "string", enum: ["env", "route", "service", "file"] },
126
+ key: { type: "string" },
127
+ reason: { type: "string" }
128
+ }
129
+ }
130
+ },
131
+ confidence: {
132
+ type: "number",
133
+ description: "Confidence level (0-1) that the change is correct",
134
+ minimum: 0,
135
+ maximum: 1
136
+ },
137
+ projectRoot: {
138
+ type: "string",
139
+ default: ".",
140
+ description: "Project root directory"
141
+ },
142
+ apiKey: {
143
+ type: "string",
144
+ description: "API key for authentication (tier is derived from this, never client-provided)"
145
+ }
146
+ // NOTE: 'tier' parameter removed for security - tier is now derived from validated apiKey
147
+ // Accepting tier from client allowed privilege escalation attacks
148
+ }
149
+ }
150
+ };
151
+
152
+ /**
153
+ * Handle MCP tool call
154
+ *
155
+ * SECURITY: Tier is NEVER accepted from client args - always derived from validated API key.
156
+ * Previous implementation accepted args.tier which allowed attackers to escalate privileges
157
+ * by simply passing tier: "ENTERPRISE".
158
+ *
159
+ * @param {string} name - Tool name (unused, for consistency)
160
+ * @param {object} args - Tool arguments
161
+ * @returns {object} MCP tool response
162
+ */
163
+ async function handleAgentFirewallIntercept(name, args) {
164
+ const projectRoot = path.resolve(args.projectRoot || ".");
165
+ const agentId = args.agentId || "unknown";
166
+ const filePath = args.filePath;
167
+ const content = args.content;
168
+ const oldContent = args.oldContent || null;
169
+ const intent = args.intent || "No intent provided";
170
+
171
+ // SECURITY FIX: Derive tier from validated API key, NEVER trust client-provided tier
172
+ let tier = "FREE"; // Default to most restrictive (observe mode)
173
+ try {
174
+ const access = await getMcpToolAccess("vibecheck_agent_firewall_intercept", args.apiKey);
175
+ if (access.tier) {
176
+ tier = access.tier.toUpperCase();
177
+ }
178
+ // Note: Even if access check fails, we continue with FREE tier (observe mode)
179
+ // This allows the tool to still provide value while logging violations
180
+ } catch (err) {
181
+ console.warn(`[Agent Firewall] Tier validation failed, defaulting to FREE: ${err.message}`);
182
+ }
183
+
184
+ // Validate file path is within project root
185
+ const fileAbs = path.resolve(projectRoot, filePath);
186
+ if (!fileAbs.startsWith(projectRoot + path.sep) && fileAbs !== projectRoot) {
187
+ return {
188
+ content: [{
189
+ type: "text",
190
+ text: `❌ BLOCKED: File path outside project root: ${filePath}`
191
+ }],
192
+ isError: true
193
+ };
194
+ }
195
+
196
+ try {
197
+ // Load policy and determine effective mode based on tier
198
+ const policy = loadPolicy(projectRoot);
199
+ const effectiveMode = getEffectivePolicyMode(tier, policy);
200
+
201
+ // Read old content if not provided
202
+ // SECURITY FIX: Compute content hash to detect concurrent modifications
203
+ // Between reading and validation, another agent could modify the file.
204
+ // We provide the hash so callers can verify before actual write.
205
+ let actualOldContent = oldContent;
206
+ let oldContentHash = null;
207
+ const crypto = require('crypto');
208
+
209
+ if (!actualOldContent && fs.existsSync(fileAbs)) {
210
+ actualOldContent = fs.readFileSync(fileAbs, "utf8");
211
+ }
212
+
213
+ // Compute hash of the content we're validating against
214
+ if (actualOldContent) {
215
+ oldContentHash = crypto.createHash('sha256').update(actualOldContent).digest('hex');
216
+ }
217
+
218
+ // Build structured proposal from args
219
+ let structuredProposal = null;
220
+ if (proposal) {
221
+ structuredProposal = proposal.proposal.create(
222
+ proposal.proposal.normalizeIntent(intent),
223
+ [{ type: actualOldContent ? "modify" : "create", path: filePath, content }]
224
+ );
225
+ structuredProposal.summary = args.summary || intent;
226
+ structuredProposal.assumptions = args.assumptions || [];
227
+ structuredProposal.confidence = args.confidence ?? 0.5;
228
+
229
+ // Auto-extract assumptions if not provided
230
+ if (structuredProposal.assumptions.length === 0) {
231
+ const extracted = proposal.extractFromOperations(structuredProposal.operations);
232
+ structuredProposal.assumptions = extracted;
233
+ }
234
+
235
+ // Validate proposal
236
+ const validationResult = proposal.proposal.validate(structuredProposal);
237
+ if (!validationResult.valid && effectiveMode === "enforce") {
238
+ return {
239
+ content: [{
240
+ type: "text",
241
+ text: `❌ INVALID PROPOSAL: ${validationResult.errors.map(e => e.message).join(", ")}`
242
+ }],
243
+ isError: true
244
+ };
245
+ }
246
+ }
247
+
248
+ // Get reality state
249
+ let realityState = null;
250
+ if (reality) {
251
+ try {
252
+ realityState = reality.reality.getState(projectRoot);
253
+ } catch (err) {
254
+ console.warn(`[Agent Firewall] Reality state unavailable: ${err.message}`);
255
+ }
256
+ }
257
+
258
+ // Calculate risk score
259
+ let riskScore = null;
260
+ if (risk && structuredProposal) {
261
+ try {
262
+ riskScore = risk.calculateRiskScore({
263
+ files: [{ path: filePath }],
264
+ operations: structuredProposal.operations,
265
+ claims: [],
266
+ evidence: [],
267
+ intent,
268
+ assumptions: structuredProposal.assumptions,
269
+ proposalConfidence: structuredProposal.confidence,
270
+ policy,
271
+ });
272
+ } catch (err) {
273
+ console.warn(`[Agent Firewall] Risk scoring failed: ${err.message}`);
274
+ }
275
+ }
276
+
277
+ // Run diff simulation
278
+ let simulationResult = null;
279
+ if (simulator && content) {
280
+ try {
281
+ simulationResult = simulator.quickSimulate(projectRoot, filePath, content, actualOldContent);
282
+ } catch (err) {
283
+ console.warn(`[Agent Firewall] Simulation failed: ${err.message}`);
284
+ }
285
+ }
286
+
287
+ // Intercept the write with core firewall
288
+ const result = await interceptFileWrite({
289
+ projectRoot,
290
+ agentId,
291
+ intent,
292
+ filePath,
293
+ content,
294
+ oldContent: actualOldContent
295
+ });
296
+
297
+ // Get critic verdict (rule-based, LLM optional)
298
+ let criticVerdict = null;
299
+ if (critic) {
300
+ try {
301
+ criticVerdict = await critic.critic.evaluate({
302
+ proposal: structuredProposal,
303
+ validationResults: result,
304
+ riskScore,
305
+ simulationResult,
306
+ realityState,
307
+ });
308
+ } catch (err) {
309
+ console.warn(`[Agent Firewall] Critic evaluation failed: ${err.message}`);
310
+ }
311
+ }
312
+
313
+ // Build enhanced proof artifact
314
+ let proofId = result.packetId;
315
+ if (proofBuilder && riskScore) {
316
+ try {
317
+ const proofArtifact = proofBuilder.buildProofArtifact({
318
+ changeId: `c-${result.packetId}`,
319
+ decision: result.verdict,
320
+ rulesTriggered: (result.violations || []).map(v => v.rule),
321
+ assumptionsFailed: (result.violations || []).filter(v => v.type === "assumption").map(v => v.key),
322
+ riskScore,
323
+ simulationResult,
324
+ criticVerdict,
325
+ });
326
+ proofId = proofArtifact.changeId;
327
+ } catch (err) {
328
+ console.warn(`[Agent Firewall] Proof artifact generation failed: ${err.message}`);
329
+ }
330
+ }
331
+
332
+ // Format response based on mode
333
+ const formatResponse = (isBlocked) => {
334
+ let message = "";
335
+ const icon = isBlocked ? "❌" : (effectiveMode === "observe" ? "📊" : "✅");
336
+ const modeLabel = effectiveMode.toUpperCase();
337
+
338
+ message += `${icon} ${modeLabel} MODE: ${result.verdict}\n\n`;
339
+
340
+ // Risk score
341
+ if (riskScore) {
342
+ message += `Risk: ${riskScore.total} (${riskScore.level})\n`;
343
+ if (riskScore.reasons.length > 0) {
344
+ message += `Factors: ${riskScore.reasons.slice(0, 3).join(", ")}\n`;
345
+ }
346
+ message += "\n";
347
+ }
348
+
349
+ // Simulation result
350
+ if (simulationResult) {
351
+ message += `Simulation: ${simulationResult.passed ? "✅ Passed" : "❌ Failed"}\n`;
352
+ if (!simulationResult.passed && simulationResult.errors.length > 0) {
353
+ message += `Errors: ${simulationResult.errors.slice(0, 2).map(e => e.message).join("; ")}\n`;
354
+ }
355
+ message += "\n";
356
+ }
357
+
358
+ // Violations
359
+ if (result.violations && result.violations.length > 0) {
360
+ message += "Violations:\n";
361
+ for (const violation of result.violations.slice(0, 5)) {
362
+ message += ` - ${violation.rule || violation.type}: ${violation.message}\n`;
363
+ }
364
+ message += "\n";
365
+ }
366
+
367
+ // Critic verdict (if blocked)
368
+ if (criticVerdict && criticVerdict.verdict === "BLOCK") {
369
+ message += `Critic: ${criticVerdict.verdict} (${(criticVerdict.confidence * 100).toFixed(0)}% confidence)\n`;
370
+ if (criticVerdict.reasoning.length > 0) {
371
+ message += `Reasoning: ${criticVerdict.reasoning[0]}\n`;
372
+ }
373
+ message += "\n";
374
+ }
375
+
376
+ // Unblock plan
377
+ if (isBlocked && result.unblockPlan && result.unblockPlan.steps?.length > 0) {
378
+ message += "To unblock:\n";
379
+ for (const step of result.unblockPlan.steps.slice(0, 3)) {
380
+ message += ` ${step.action === "create" ? "➕ Create" : "✏️ Modify"} ${step.file || "file"}: ${step.description}\n`;
381
+ }
382
+ message += "\n";
383
+ }
384
+
385
+ message += `Proof ID: ${proofId}\n`;
386
+
387
+ // SECURITY: Include content hash for race condition protection
388
+ // Callers MUST verify this hash matches current file content before writing
389
+ // to prevent TOCTOU (time-of-check-time-of-use) vulnerabilities
390
+ if (oldContentHash) {
391
+ message += `\n⚠️ IMPORTANT: Before writing, verify file hash matches:\n`;
392
+ message += `Content Hash: ${oldContentHash}\n`;
393
+ message += `(Re-read file and compare SHA-256 hash to detect concurrent modifications)`;
394
+ } else {
395
+ message += `\nNote: New file (no existing content to verify)`;
396
+ }
397
+
398
+ return message;
399
+ };
400
+
401
+ // Determine final decision based on mode
402
+ if (effectiveMode === "observe") {
403
+ // Observe mode - always allow, log everything
404
+ return {
405
+ content: [{
406
+ type: "text",
407
+ text: formatResponse(false)
408
+ }]
409
+ };
410
+ }
411
+
412
+ if (effectiveMode === "advisory") {
413
+ // Advisory mode - warn but allow (for STARTER tier)
414
+ const shouldWarn = !result.allowed || (riskScore && riskScore.total > 50);
415
+ return {
416
+ content: [{
417
+ type: "text",
418
+ text: formatResponse(false) + (shouldWarn ? "\n\n⚠️ Consider reviewing before proceeding." : "")
419
+ }]
420
+ };
421
+ }
422
+
423
+ // Enforce mode - block if not allowed
424
+ // More intelligent blocking logic to reduce false positives:
425
+ // 1. Base interception result must say not allowed, OR
426
+ // 2. Simulation failed with actual errors (not just warnings), OR
427
+ // 3. Critic says BLOCK with very high confidence (90%+), OR
428
+ // 4. Risk score decision is BLOCK (but respect lowered thresholds)
429
+
430
+ // Count the number of blocking signals
431
+ let blockingSignals = 0;
432
+ const blockReasons = [];
433
+
434
+ if (!result.allowed) {
435
+ blockingSignals++;
436
+ blockReasons.push("policy_violation");
437
+ }
438
+
439
+ // Only count simulation failure if it has actual errors (not just warnings)
440
+ if (simulationResult && !simulationResult.passed) {
441
+ const hasActualErrors = simulationResult.errors?.some(e =>
442
+ e.severity === 'error' || e.type === 'broken_import' || e.type === 'syntax_error'
443
+ );
444
+ if (hasActualErrors) {
445
+ blockingSignals++;
446
+ blockReasons.push("simulation_failed");
447
+ }
448
+ }
449
+
450
+ // Critic verdict - require very high confidence (90%+) to block
451
+ if (criticVerdict && criticVerdict.verdict === "BLOCK" && criticVerdict.confidence >= 0.9) {
452
+ blockingSignals++;
453
+ blockReasons.push("critic_blocked");
454
+ }
455
+
456
+ // Risk score decision
457
+ if (riskScore && riskScore.decision?.decision === "BLOCK") {
458
+ blockingSignals++;
459
+ blockReasons.push("risk_threshold_exceeded");
460
+ }
461
+
462
+ // Only block if we have at least 2 independent blocking signals
463
+ // This prevents a single false positive from blocking legitimate changes
464
+ // Exception: if risk score is CRITICAL (total >= 120), block with 1 signal
465
+ const isCriticalRisk = riskScore && riskScore.total >= 120;
466
+ const shouldBlock = (blockingSignals >= 2) || (isCriticalRisk && blockingSignals >= 1);
467
+
468
+ if (shouldBlock) {
469
+ return {
470
+ content: [{
471
+ type: "text",
472
+ text: formatResponse(true) + `\n\nBlocking reasons: ${blockReasons.join(", ")}`
473
+ }],
474
+ isError: true
475
+ };
476
+ }
477
+
478
+ // Allowed
479
+ return {
480
+ content: [{
481
+ type: "text",
482
+ text: formatResponse(false)
483
+ }]
484
+ };
485
+
486
+ } catch (error) {
487
+ return {
488
+ content: [{
489
+ type: "text",
490
+ text: `❌ Error intercepting write: ${error.message}\n\n${error.stack}`
491
+ }],
492
+ isError: true
493
+ };
494
+ }
495
+ }
496
+
497
+ export {
498
+ AGENT_FIREWALL_TOOL,
499
+ handleAgentFirewallIntercept
500
+ };