@vibecheckai/cli 3.4.0 → 3.5.1

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 (228) hide show
  1. package/bin/registry.js +154 -338
  2. package/bin/runners/context/generators/mcp.js +13 -15
  3. package/bin/runners/context/proof-context.js +1 -248
  4. package/bin/runners/lib/analysis-core.js +180 -198
  5. package/bin/runners/lib/analyzers.js +223 -1669
  6. package/bin/runners/lib/cli-output.js +210 -242
  7. package/bin/runners/lib/detectors-v2.js +785 -547
  8. package/bin/runners/lib/entitlements-v2.js +458 -96
  9. package/bin/runners/lib/error-handler.js +9 -16
  10. package/bin/runners/lib/global-flags.js +0 -37
  11. package/bin/runners/lib/route-truth.js +322 -1167
  12. package/bin/runners/lib/scan-output.js +469 -448
  13. package/bin/runners/lib/ship-output.js +27 -280
  14. package/bin/runners/lib/terminal-ui.js +733 -231
  15. package/bin/runners/lib/truth.js +321 -1004
  16. package/bin/runners/lib/unified-output.js +158 -162
  17. package/bin/runners/lib/upsell.js +204 -104
  18. package/bin/runners/runAllowlist.js +324 -0
  19. package/bin/runners/runAuth.js +95 -324
  20. package/bin/runners/runCheckpoint.js +21 -39
  21. package/bin/runners/runContext.js +24 -136
  22. package/bin/runners/runDoctor.js +67 -115
  23. package/bin/runners/runEvidencePack.js +219 -0
  24. package/bin/runners/runFix.js +5 -6
  25. package/bin/runners/runGuard.js +118 -212
  26. package/bin/runners/runInit.js +2 -14
  27. package/bin/runners/runInstall.js +281 -0
  28. package/bin/runners/runLabs.js +341 -0
  29. package/bin/runners/runMcp.js +52 -130
  30. package/bin/runners/runPolish.js +20 -43
  31. package/bin/runners/runProve.js +3 -13
  32. package/bin/runners/runReality.js +0 -14
  33. package/bin/runners/runReport.js +2 -3
  34. package/bin/runners/runScan.js +44 -511
  35. package/bin/runners/runShip.js +14 -28
  36. package/bin/runners/runValidate.js +2 -19
  37. package/bin/runners/runWatch.js +54 -118
  38. package/bin/vibecheck.js +41 -148
  39. package/mcp-server/ARCHITECTURE.md +339 -0
  40. package/mcp-server/__tests__/cache.test.ts +313 -0
  41. package/mcp-server/__tests__/executor.test.ts +239 -0
  42. package/mcp-server/__tests__/fixtures/exclusion-test/.cache/webpack/cache.pack +1 -0
  43. package/mcp-server/__tests__/fixtures/exclusion-test/.next/server/chunk.js +3 -0
  44. package/mcp-server/__tests__/fixtures/exclusion-test/.turbo/cache.json +3 -0
  45. package/mcp-server/__tests__/fixtures/exclusion-test/.venv/lib/env.py +3 -0
  46. package/mcp-server/__tests__/fixtures/exclusion-test/dist/bundle.js +3 -0
  47. package/mcp-server/__tests__/fixtures/exclusion-test/package.json +5 -0
  48. package/mcp-server/__tests__/fixtures/exclusion-test/src/app.ts +5 -0
  49. package/mcp-server/__tests__/fixtures/exclusion-test/venv/lib/config.py +4 -0
  50. package/mcp-server/__tests__/ids.test.ts +345 -0
  51. package/mcp-server/__tests__/integration/tools.test.ts +410 -0
  52. package/mcp-server/__tests__/registry.test.ts +365 -0
  53. package/mcp-server/__tests__/sandbox.test.ts +323 -0
  54. package/mcp-server/__tests__/schemas.test.ts +372 -0
  55. package/mcp-server/benchmarks/run-benchmarks.ts +304 -0
  56. package/mcp-server/examples/doctor.request.json +14 -0
  57. package/mcp-server/examples/doctor.response.json +53 -0
  58. package/mcp-server/examples/error.response.json +15 -0
  59. package/mcp-server/examples/scan.request.json +14 -0
  60. package/mcp-server/examples/scan.response.json +108 -0
  61. package/mcp-server/handlers/tool-handler.ts +671 -0
  62. package/mcp-server/index-v3.ts +293 -0
  63. package/mcp-server/index.js +1072 -1573
  64. package/mcp-server/index.old.js +4137 -0
  65. package/mcp-server/lib/cache.ts +341 -0
  66. package/mcp-server/lib/errors.ts +346 -0
  67. package/mcp-server/lib/executor.ts +792 -0
  68. package/mcp-server/lib/ids.ts +238 -0
  69. package/mcp-server/lib/logger.ts +368 -0
  70. package/mcp-server/lib/metrics.ts +365 -0
  71. package/mcp-server/lib/sandbox.ts +337 -0
  72. package/mcp-server/lib/validator.ts +229 -0
  73. package/mcp-server/package-lock.json +165 -0
  74. package/mcp-server/package.json +32 -7
  75. package/mcp-server/premium-tools.js +2 -2
  76. package/mcp-server/registry/tools.json +476 -0
  77. package/mcp-server/schemas/error-envelope.schema.json +125 -0
  78. package/mcp-server/schemas/finding.schema.json +167 -0
  79. package/mcp-server/schemas/report-artifact.schema.json +88 -0
  80. package/mcp-server/schemas/run-request.schema.json +75 -0
  81. package/mcp-server/schemas/verdict.schema.json +168 -0
  82. package/mcp-server/tier-auth.d.ts +71 -0
  83. package/mcp-server/tier-auth.js +371 -183
  84. package/mcp-server/truth-context.js +90 -131
  85. package/mcp-server/truth-firewall-tools.js +1000 -1611
  86. package/mcp-server/tsconfig.json +34 -0
  87. package/mcp-server/vibecheck-tools.js +2 -2
  88. package/mcp-server/vitest.config.ts +16 -0
  89. package/package.json +3 -4
  90. package/bin/runners/lib/agent-firewall/ai/false-positive-analyzer.js +0 -474
  91. package/bin/runners/lib/agent-firewall/change-packet/builder.js +0 -488
  92. package/bin/runners/lib/agent-firewall/change-packet/schema.json +0 -228
  93. package/bin/runners/lib/agent-firewall/change-packet/store.js +0 -200
  94. package/bin/runners/lib/agent-firewall/claims/claim-types.js +0 -21
  95. package/bin/runners/lib/agent-firewall/claims/extractor.js +0 -303
  96. package/bin/runners/lib/agent-firewall/claims/patterns.js +0 -24
  97. package/bin/runners/lib/agent-firewall/critic/index.js +0 -151
  98. package/bin/runners/lib/agent-firewall/critic/judge.js +0 -432
  99. package/bin/runners/lib/agent-firewall/critic/prompts.js +0 -305
  100. package/bin/runners/lib/agent-firewall/evidence/auth-evidence.js +0 -88
  101. package/bin/runners/lib/agent-firewall/evidence/contract-evidence.js +0 -75
  102. package/bin/runners/lib/agent-firewall/evidence/env-evidence.js +0 -127
  103. package/bin/runners/lib/agent-firewall/evidence/resolver.js +0 -102
  104. package/bin/runners/lib/agent-firewall/evidence/route-evidence.js +0 -213
  105. package/bin/runners/lib/agent-firewall/evidence/side-effect-evidence.js +0 -145
  106. package/bin/runners/lib/agent-firewall/fs-hook/daemon.js +0 -19
  107. package/bin/runners/lib/agent-firewall/fs-hook/installer.js +0 -87
  108. package/bin/runners/lib/agent-firewall/fs-hook/watcher.js +0 -184
  109. package/bin/runners/lib/agent-firewall/git-hook/pre-commit.js +0 -163
  110. package/bin/runners/lib/agent-firewall/ide-extension/cursor.js +0 -107
  111. package/bin/runners/lib/agent-firewall/ide-extension/vscode.js +0 -68
  112. package/bin/runners/lib/agent-firewall/ide-extension/windsurf.js +0 -66
  113. package/bin/runners/lib/agent-firewall/interceptor/base.js +0 -304
  114. package/bin/runners/lib/agent-firewall/interceptor/cursor.js +0 -35
  115. package/bin/runners/lib/agent-firewall/interceptor/vscode.js +0 -35
  116. package/bin/runners/lib/agent-firewall/interceptor/windsurf.js +0 -34
  117. package/bin/runners/lib/agent-firewall/lawbook/distributor.js +0 -465
  118. package/bin/runners/lib/agent-firewall/lawbook/evaluator.js +0 -604
  119. package/bin/runners/lib/agent-firewall/lawbook/index.js +0 -304
  120. package/bin/runners/lib/agent-firewall/lawbook/registry.js +0 -514
  121. package/bin/runners/lib/agent-firewall/lawbook/schema.js +0 -420
  122. package/bin/runners/lib/agent-firewall/logger.js +0 -141
  123. package/bin/runners/lib/agent-firewall/policy/default-policy.json +0 -90
  124. package/bin/runners/lib/agent-firewall/policy/engine.js +0 -103
  125. package/bin/runners/lib/agent-firewall/policy/loader.js +0 -451
  126. package/bin/runners/lib/agent-firewall/policy/rules/auth-drift.js +0 -50
  127. package/bin/runners/lib/agent-firewall/policy/rules/contract-drift.js +0 -50
  128. package/bin/runners/lib/agent-firewall/policy/rules/fake-success.js +0 -86
  129. package/bin/runners/lib/agent-firewall/policy/rules/ghost-env.js +0 -162
  130. package/bin/runners/lib/agent-firewall/policy/rules/ghost-route.js +0 -189
  131. package/bin/runners/lib/agent-firewall/policy/rules/scope.js +0 -93
  132. package/bin/runners/lib/agent-firewall/policy/rules/unsafe-side-effect.js +0 -57
  133. package/bin/runners/lib/agent-firewall/policy/schema.json +0 -183
  134. package/bin/runners/lib/agent-firewall/policy/verdict.js +0 -54
  135. package/bin/runners/lib/agent-firewall/proposal/extractor.js +0 -394
  136. package/bin/runners/lib/agent-firewall/proposal/index.js +0 -212
  137. package/bin/runners/lib/agent-firewall/proposal/schema.js +0 -251
  138. package/bin/runners/lib/agent-firewall/proposal/validator.js +0 -386
  139. package/bin/runners/lib/agent-firewall/reality/index.js +0 -332
  140. package/bin/runners/lib/agent-firewall/reality/state.js +0 -625
  141. package/bin/runners/lib/agent-firewall/reality/watcher.js +0 -322
  142. package/bin/runners/lib/agent-firewall/risk/index.js +0 -173
  143. package/bin/runners/lib/agent-firewall/risk/scorer.js +0 -328
  144. package/bin/runners/lib/agent-firewall/risk/thresholds.js +0 -321
  145. package/bin/runners/lib/agent-firewall/risk/vectors.js +0 -421
  146. package/bin/runners/lib/agent-firewall/simulator/diff-simulator.js +0 -472
  147. package/bin/runners/lib/agent-firewall/simulator/import-resolver.js +0 -346
  148. package/bin/runners/lib/agent-firewall/simulator/index.js +0 -181
  149. package/bin/runners/lib/agent-firewall/simulator/route-validator.js +0 -380
  150. package/bin/runners/lib/agent-firewall/time-machine/incident-correlator.js +0 -661
  151. package/bin/runners/lib/agent-firewall/time-machine/index.js +0 -267
  152. package/bin/runners/lib/agent-firewall/time-machine/replay-engine.js +0 -436
  153. package/bin/runners/lib/agent-firewall/time-machine/state-reconstructor.js +0 -490
  154. package/bin/runners/lib/agent-firewall/time-machine/timeline-builder.js +0 -530
  155. package/bin/runners/lib/agent-firewall/truthpack/index.js +0 -67
  156. package/bin/runners/lib/agent-firewall/truthpack/loader.js +0 -137
  157. package/bin/runners/lib/agent-firewall/unblock/planner.js +0 -337
  158. package/bin/runners/lib/agent-firewall/utils/ignore-checker.js +0 -118
  159. package/bin/runners/lib/api-client.js +0 -269
  160. package/bin/runners/lib/authority-badge.js +0 -425
  161. package/bin/runners/lib/engines/accessibility-engine.js +0 -190
  162. package/bin/runners/lib/engines/api-consistency-engine.js +0 -162
  163. package/bin/runners/lib/engines/ast-cache.js +0 -99
  164. package/bin/runners/lib/engines/code-quality-engine.js +0 -255
  165. package/bin/runners/lib/engines/console-logs-engine.js +0 -115
  166. package/bin/runners/lib/engines/cross-file-analysis-engine.js +0 -268
  167. package/bin/runners/lib/engines/dead-code-engine.js +0 -198
  168. package/bin/runners/lib/engines/deprecated-api-engine.js +0 -226
  169. package/bin/runners/lib/engines/empty-catch-engine.js +0 -150
  170. package/bin/runners/lib/engines/file-filter.js +0 -131
  171. package/bin/runners/lib/engines/hardcoded-secrets-engine.js +0 -251
  172. package/bin/runners/lib/engines/mock-data-engine.js +0 -272
  173. package/bin/runners/lib/engines/parallel-processor.js +0 -71
  174. package/bin/runners/lib/engines/performance-issues-engine.js +0 -265
  175. package/bin/runners/lib/engines/security-vulnerabilities-engine.js +0 -243
  176. package/bin/runners/lib/engines/todo-fixme-engine.js +0 -115
  177. package/bin/runners/lib/engines/type-aware-engine.js +0 -152
  178. package/bin/runners/lib/engines/unsafe-regex-engine.js +0 -225
  179. package/bin/runners/lib/engines/vibecheck-engines/README.md +0 -53
  180. package/bin/runners/lib/engines/vibecheck-engines/index.js +0 -15
  181. package/bin/runners/lib/engines/vibecheck-engines/lib/ast-cache.js +0 -164
  182. package/bin/runners/lib/engines/vibecheck-engines/lib/code-quality-engine.js +0 -291
  183. package/bin/runners/lib/engines/vibecheck-engines/lib/console-logs-engine.js +0 -83
  184. package/bin/runners/lib/engines/vibecheck-engines/lib/dead-code-engine.js +0 -198
  185. package/bin/runners/lib/engines/vibecheck-engines/lib/deprecated-api-engine.js +0 -275
  186. package/bin/runners/lib/engines/vibecheck-engines/lib/empty-catch-engine.js +0 -167
  187. package/bin/runners/lib/engines/vibecheck-engines/lib/file-filter.js +0 -217
  188. package/bin/runners/lib/engines/vibecheck-engines/lib/hardcoded-secrets-engine.js +0 -139
  189. package/bin/runners/lib/engines/vibecheck-engines/lib/mock-data-engine.js +0 -140
  190. package/bin/runners/lib/engines/vibecheck-engines/lib/parallel-processor.js +0 -164
  191. package/bin/runners/lib/engines/vibecheck-engines/lib/performance-issues-engine.js +0 -234
  192. package/bin/runners/lib/engines/vibecheck-engines/lib/type-aware-engine.js +0 -217
  193. package/bin/runners/lib/engines/vibecheck-engines/lib/unsafe-regex-engine.js +0 -78
  194. package/bin/runners/lib/engines/vibecheck-engines/package.json +0 -13
  195. package/bin/runners/lib/exit-codes.js +0 -275
  196. package/bin/runners/lib/fingerprint.js +0 -377
  197. package/bin/runners/lib/help-formatter.js +0 -413
  198. package/bin/runners/lib/logger.js +0 -38
  199. package/bin/runners/lib/ship-output-enterprise.js +0 -239
  200. package/bin/runners/lib/unified-cli-output.js +0 -604
  201. package/bin/runners/runAgent.d.ts +0 -5
  202. package/bin/runners/runAgent.js +0 -161
  203. package/bin/runners/runApprove.js +0 -1200
  204. package/bin/runners/runClassify.js +0 -859
  205. package/bin/runners/runContext.d.ts +0 -4
  206. package/bin/runners/runFirewall.d.ts +0 -5
  207. package/bin/runners/runFirewall.js +0 -134
  208. package/bin/runners/runFirewallHook.d.ts +0 -5
  209. package/bin/runners/runFirewallHook.js +0 -56
  210. package/bin/runners/runPolish.d.ts +0 -4
  211. package/bin/runners/runProof.zip +0 -0
  212. package/bin/runners/runTruth.d.ts +0 -5
  213. package/bin/runners/runTruth.js +0 -101
  214. package/mcp-server/HARDENING_SUMMARY.md +0 -299
  215. package/mcp-server/agent-firewall-interceptor.js +0 -500
  216. package/mcp-server/authority-tools.js +0 -569
  217. package/mcp-server/conductor/conflict-resolver.js +0 -588
  218. package/mcp-server/conductor/execution-planner.js +0 -544
  219. package/mcp-server/conductor/index.js +0 -377
  220. package/mcp-server/conductor/lock-manager.js +0 -615
  221. package/mcp-server/conductor/request-queue.js +0 -550
  222. package/mcp-server/conductor/session-manager.js +0 -500
  223. package/mcp-server/conductor/tools.js +0 -510
  224. package/mcp-server/lib/api-client.cjs +0 -13
  225. package/mcp-server/lib/logger.cjs +0 -30
  226. package/mcp-server/logger.js +0 -173
  227. package/mcp-server/tools-v3.js +0 -706
  228. package/mcp-server/vibecheck-mcp-server-3.2.0.tgz +0 -0
@@ -1,500 +0,0 @@
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
- };