@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
@@ -0,0 +1,365 @@
1
+ /**
2
+ * Metrics Collection for MCP Server
3
+ *
4
+ * Provides structured metrics for:
5
+ * - Tool execution duration
6
+ * - Error rates by type
7
+ * - Cache hit rates
8
+ * - Findings counts by severity
9
+ *
10
+ * Metrics are aggregated in memory and can be flushed to storage.
11
+ */
12
+
13
+ import { createHash } from 'crypto';
14
+
15
+ /**
16
+ * Metric types
17
+ */
18
+ export type MetricType = 'counter' | 'gauge' | 'histogram';
19
+
20
+ /**
21
+ * Metric entry structure
22
+ */
23
+ export interface MetricEntry {
24
+ name: string;
25
+ type: MetricType;
26
+ value: number;
27
+ unit: 'ms' | 'count' | 'bytes' | 'percent' | 'ratio';
28
+ timestamp: number;
29
+ tags?: Record<string, string>;
30
+ }
31
+
32
+ /**
33
+ * Histogram statistics result type
34
+ */
35
+ export interface HistogramStats {
36
+ count: number;
37
+ min: number;
38
+ max: number;
39
+ avg: number;
40
+ p50: number;
41
+ p95: number;
42
+ p99: number;
43
+ }
44
+
45
+ /**
46
+ * Histogram buckets for duration metrics
47
+ */
48
+ const DURATION_BUCKETS_MS = [10, 50, 100, 250, 500, 1000, 2500, 5000, 10000, 30000, 60000];
49
+
50
+ /**
51
+ * Metrics aggregator
52
+ */
53
+ export class MetricsCollector {
54
+ private counters = new Map<string, number>();
55
+ private gauges = new Map<string, number>();
56
+ private histograms = new Map<string, number[]>();
57
+ private entries: MetricEntry[] = [];
58
+ private flushInterval?: NodeJS.Timeout;
59
+ private maxEntries: number;
60
+
61
+ constructor(options: { maxEntries?: number; flushIntervalMs?: number } = {}) {
62
+ this.maxEntries = options.maxEntries ?? 10000;
63
+
64
+ if (options.flushIntervalMs) {
65
+ this.flushInterval = setInterval(() => {
66
+ this.trimEntries();
67
+ }, options.flushIntervalMs);
68
+ }
69
+ }
70
+
71
+ /**
72
+ * Increment a counter
73
+ */
74
+ incCounter(name: string, value = 1, tags?: Record<string, string>): void {
75
+ const key = this.makeKey(name, tags);
76
+ const current = this.counters.get(key) ?? 0;
77
+ this.counters.set(key, current + value);
78
+
79
+ this.record({
80
+ name,
81
+ type: 'counter',
82
+ value: current + value,
83
+ unit: 'count',
84
+ timestamp: Date.now(),
85
+ tags,
86
+ });
87
+ }
88
+
89
+ /**
90
+ * Set a gauge value
91
+ */
92
+ setGauge(name: string, value: number, tags?: Record<string, string>): void {
93
+ const key = this.makeKey(name, tags);
94
+ this.gauges.set(key, value);
95
+
96
+ this.record({
97
+ name,
98
+ type: 'gauge',
99
+ value,
100
+ unit: 'count',
101
+ timestamp: Date.now(),
102
+ tags,
103
+ });
104
+ }
105
+
106
+ /**
107
+ * Record a histogram value (e.g., duration)
108
+ */
109
+ recordHistogram(
110
+ name: string,
111
+ value: number,
112
+ unit: 'ms' | 'bytes' = 'ms',
113
+ tags?: Record<string, string>
114
+ ): void {
115
+ const key = this.makeKey(name, tags);
116
+ const values = this.histograms.get(key) ?? [];
117
+ values.push(value);
118
+ this.histograms.set(key, values);
119
+
120
+ this.record({
121
+ name,
122
+ type: 'histogram',
123
+ value,
124
+ unit,
125
+ timestamp: Date.now(),
126
+ tags,
127
+ });
128
+ }
129
+
130
+ /**
131
+ * Record tool execution metrics
132
+ */
133
+ recordToolExecution(
134
+ tool: string,
135
+ durationMs: number,
136
+ success: boolean,
137
+ cached: boolean
138
+ ): void {
139
+ const tags = { tool, status: success ? 'success' : 'error', cached: String(cached) };
140
+
141
+ this.recordHistogram('tool_duration_ms', durationMs, 'ms', tags);
142
+ this.incCounter('tool_executions_total', 1, tags);
143
+
144
+ if (!success) {
145
+ this.incCounter('tool_errors_total', 1, { tool });
146
+ }
147
+
148
+ if (cached) {
149
+ this.incCounter('cache_hits_total', 1, { tool });
150
+ } else {
151
+ this.incCounter('cache_misses_total', 1, { tool });
152
+ }
153
+ }
154
+
155
+ /**
156
+ * Record findings by severity
157
+ */
158
+ recordFindings(
159
+ tool: string,
160
+ findings: { severity?: string }[]
161
+ ): void {
162
+ const bySeverity: Record<string, number> = {};
163
+
164
+ for (const finding of findings) {
165
+ const severity = finding.severity || 'INFO';
166
+ bySeverity[severity] = (bySeverity[severity] || 0) + 1;
167
+ }
168
+
169
+ for (const [severity, count] of Object.entries(bySeverity)) {
170
+ this.incCounter('findings_total', count, { tool, severity });
171
+ }
172
+
173
+ this.setGauge('findings_current', findings.length, { tool });
174
+ }
175
+
176
+ /**
177
+ * Get cache hit rate
178
+ */
179
+ getCacheHitRate(tool?: string): number {
180
+ const tags = tool ? { tool } : undefined;
181
+ const hitsKey = this.makeKey('cache_hits_total', tags);
182
+ const missesKey = this.makeKey('cache_misses_total', tags);
183
+
184
+ const hits = this.counters.get(hitsKey) ?? 0;
185
+ const misses = this.counters.get(missesKey) ?? 0;
186
+ const total = hits + misses;
187
+
188
+ return total > 0 ? hits / total : 0;
189
+ }
190
+
191
+ /**
192
+ * Get histogram statistics
193
+ */
194
+ getHistogramStats(name: string, tags?: Record<string, string>): HistogramStats | null {
195
+ const key = this.makeKey(name, tags);
196
+ const values = this.histograms.get(key);
197
+
198
+ if (!values || values.length === 0) return null;
199
+
200
+ const sorted = [...values].sort((a, b) => a - b);
201
+ const count = sorted.length;
202
+
203
+ return {
204
+ count,
205
+ min: sorted[0],
206
+ max: sorted[count - 1],
207
+ avg: sorted.reduce((a, b) => a + b, 0) / count,
208
+ p50: sorted[Math.floor(count * 0.5)],
209
+ p95: sorted[Math.floor(count * 0.95)],
210
+ p99: sorted[Math.floor(count * 0.99)],
211
+ };
212
+ }
213
+
214
+ /**
215
+ * Get all metrics as summary
216
+ */
217
+ getSummary(): {
218
+ counters: Record<string, number>;
219
+ gauges: Record<string, number>;
220
+ histograms: Record<string, HistogramStats | null>;
221
+ cacheHitRate: number;
222
+ } {
223
+ const counters: Record<string, number> = {};
224
+ const gauges: Record<string, number> = {};
225
+ const histograms: Record<string, HistogramStats | null> = {};
226
+
227
+ for (const [key, value] of this.counters.entries()) {
228
+ counters[key] = value;
229
+ }
230
+
231
+ for (const [key, value] of this.gauges.entries()) {
232
+ gauges[key] = value;
233
+ }
234
+
235
+ for (const key of this.histograms.keys()) {
236
+ histograms[key] = this.getHistogramStats(key);
237
+ }
238
+
239
+ return {
240
+ counters,
241
+ gauges,
242
+ histograms,
243
+ cacheHitRate: this.getCacheHitRate(),
244
+ };
245
+ }
246
+
247
+ /**
248
+ * Get raw entries (for export)
249
+ */
250
+ getEntries(): MetricEntry[] {
251
+ return [...this.entries];
252
+ }
253
+
254
+ /**
255
+ * Clear all metrics
256
+ */
257
+ clear(): void {
258
+ this.counters.clear();
259
+ this.gauges.clear();
260
+ this.histograms.clear();
261
+ this.entries = [];
262
+ }
263
+
264
+ /**
265
+ * Stop the collector
266
+ */
267
+ stop(): void {
268
+ if (this.flushInterval) {
269
+ clearInterval(this.flushInterval);
270
+ this.flushInterval = undefined;
271
+ }
272
+ }
273
+
274
+ /**
275
+ * Internal: record a metric entry
276
+ */
277
+ private record(entry: MetricEntry): void {
278
+ this.entries.push(entry);
279
+ this.trimEntries();
280
+ }
281
+
282
+ /**
283
+ * Internal: trim entries if over limit
284
+ */
285
+ private trimEntries(): void {
286
+ if (this.entries.length > this.maxEntries) {
287
+ this.entries = this.entries.slice(-this.maxEntries);
288
+ }
289
+ }
290
+
291
+ /**
292
+ * Internal: make a unique key from name and tags
293
+ */
294
+ private makeKey(name: string, tags?: Record<string, string>): string {
295
+ if (!tags || Object.keys(tags).length === 0) {
296
+ return name;
297
+ }
298
+
299
+ const sortedTags = Object.entries(tags)
300
+ .sort(([a], [b]) => a.localeCompare(b))
301
+ .map(([k, v]) => `${k}=${v}`)
302
+ .join(',');
303
+
304
+ return `${name}{${sortedTags}}`;
305
+ }
306
+ }
307
+
308
+ // Singleton instance
309
+ let globalCollector: MetricsCollector | null = null;
310
+
311
+ /**
312
+ * Get global metrics collector
313
+ */
314
+ export function getMetricsCollector(): MetricsCollector {
315
+ if (!globalCollector) {
316
+ globalCollector = new MetricsCollector({
317
+ maxEntries: 10000,
318
+ flushIntervalMs: 60000, // 1 minute
319
+ });
320
+ }
321
+ return globalCollector;
322
+ }
323
+
324
+ /**
325
+ * Initialize global metrics collector
326
+ */
327
+ export function initMetricsCollector(options?: {
328
+ maxEntries?: number;
329
+ flushIntervalMs?: number;
330
+ }): void {
331
+ if (globalCollector) {
332
+ globalCollector.stop();
333
+ }
334
+ globalCollector = new MetricsCollector(options);
335
+ }
336
+
337
+ /**
338
+ * Convenience: record tool execution
339
+ */
340
+ export function recordToolExecution(
341
+ tool: string,
342
+ durationMs: number,
343
+ success: boolean,
344
+ cached: boolean
345
+ ): void {
346
+ getMetricsCollector().recordToolExecution(tool, durationMs, success, cached);
347
+ }
348
+
349
+ /**
350
+ * Convenience: record findings
351
+ */
352
+ export function recordFindings(
353
+ tool: string,
354
+ findings: { severity?: string }[]
355
+ ): void {
356
+ getMetricsCollector().recordFindings(tool, findings);
357
+ }
358
+
359
+ export default {
360
+ MetricsCollector,
361
+ getMetricsCollector,
362
+ initMetricsCollector,
363
+ recordToolExecution,
364
+ recordFindings,
365
+ };
@@ -0,0 +1,337 @@
1
+ /**
2
+ * Path Sandboxing & Security
3
+ *
4
+ * Ensures all file operations stay within allowed workspace.
5
+ * Prevents path traversal and access to sensitive files.
6
+ */
7
+
8
+ import { resolve, normalize, relative, isAbsolute } from 'path';
9
+ import { existsSync, statSync } from 'fs';
10
+ import { Errors } from './errors.js';
11
+
12
+ /**
13
+ * Sensitive paths that should never be accessed
14
+ */
15
+ const SENSITIVE_PATTERNS = [
16
+ /^\/etc\//,
17
+ /^\/root\//,
18
+ /^\/home\/[^/]+\/\.ssh\//,
19
+ /^\/home\/[^/]+\/\.gnupg\//,
20
+ /^\/home\/[^/]+\/\.aws\//,
21
+ /^\/home\/[^/]+\/\.config\//,
22
+ /^C:\\Windows\\/i,
23
+ /^C:\\Users\\[^\\]+\\\.ssh\\/i,
24
+ /^C:\\Users\\[^\\]+\\\.aws\\/i,
25
+ /^C:\\Users\\[^\\]+\\AppData\\/i,
26
+ /\.env$/,
27
+ /\.env\.\w+$/,
28
+ /credentials\.json$/,
29
+ /secrets\.json$/,
30
+ /\.pem$/,
31
+ /\.key$/,
32
+ /id_rsa/,
33
+ /id_ed25519/,
34
+ ];
35
+
36
+ /**
37
+ * Paths that should be excluded from scanning (but not blocked)
38
+ *
39
+ * CRITICAL: Default exclusions enforced at MCP layer even if CLI forgets.
40
+ * These are the canonical exclusions matching the architecture spec.
41
+ */
42
+ const EXCLUDE_PATTERNS = [
43
+ // Package managers
44
+ /node_modules\//,
45
+ /node_modules\\/,
46
+ /\.npm\//,
47
+ /\.yarn\//,
48
+ /\.pnpm-store\//,
49
+
50
+ // Python environments
51
+ /venv\//,
52
+ /venv\\/,
53
+ /\.venv\//,
54
+ /\.venv\\/,
55
+ /env\//,
56
+ /\.env\//,
57
+ /__pycache__\//,
58
+ /\.pytest_cache\//,
59
+ /\.mypy_cache\//,
60
+
61
+ // Version control
62
+ /\.git\//,
63
+ /\.git\\/,
64
+ /\.svn\//,
65
+ /\.hg\//,
66
+
67
+ // Build outputs
68
+ /dist\//,
69
+ /dist\\/,
70
+ /build\//,
71
+ /build\\/,
72
+ /out\//,
73
+ /out\\/,
74
+ /\.next\//,
75
+ /\.next\\/,
76
+ /\.nuxt\//,
77
+ /\.output\//,
78
+
79
+ // Cache directories
80
+ /\.cache\//,
81
+ /\.cache\\/,
82
+ /\.turbo\//,
83
+ /\.turbo\\/,
84
+ /\.parcel-cache\//,
85
+ /\.webpack\//,
86
+
87
+ // Coverage
88
+ /coverage\//,
89
+ /coverage\\/,
90
+ /\.nyc_output\//,
91
+
92
+ // Vendor directories
93
+ /vendor\//,
94
+ /\.bundle\//,
95
+ /bower_components\//,
96
+
97
+ // IDE/Editor
98
+ /\.idea\//,
99
+ /\.vscode\//,
100
+ /\.vs\//,
101
+
102
+ // Temp files
103
+ /\.tmp\//,
104
+ /\.temp\//,
105
+ /tmp\//,
106
+ ];
107
+
108
+ /**
109
+ * Sandbox configuration
110
+ */
111
+ export interface SandboxConfig {
112
+ workspace: string;
113
+ allowSymlinks?: boolean;
114
+ maxDepth?: number;
115
+ excludePatterns?: RegExp[];
116
+ }
117
+
118
+ /**
119
+ * Sandbox result
120
+ */
121
+ export interface SandboxResult {
122
+ allowed: boolean;
123
+ path: string;
124
+ relativePath: string;
125
+ reason?: string;
126
+ }
127
+
128
+ /**
129
+ * Create a sandbox validator for a workspace
130
+ */
131
+ export function createSandbox(config: SandboxConfig) {
132
+ const workspace = resolve(normalize(config.workspace));
133
+ const maxDepth = config.maxDepth ?? 20;
134
+ const excludePatterns = [...EXCLUDE_PATTERNS, ...(config.excludePatterns ?? [])];
135
+
136
+ return {
137
+ /**
138
+ * Validate a path is within the sandbox
139
+ */
140
+ validate(inputPath: string): SandboxResult {
141
+ // Normalize the input path
142
+ const normalizedInput = normalize(inputPath);
143
+
144
+ // Resolve to absolute
145
+ const absolutePath = isAbsolute(normalizedInput)
146
+ ? normalizedInput
147
+ : resolve(workspace, normalizedInput);
148
+
149
+ // Re-normalize after resolution
150
+ const normalized = normalize(absolutePath);
151
+
152
+ // Get relative path
153
+ const relativePath = relative(workspace, normalized);
154
+
155
+ // Check for path traversal (relative path should not start with ..)
156
+ if (relativePath.startsWith('..') || relativePath.startsWith('/..')) {
157
+ return {
158
+ allowed: false,
159
+ path: normalized,
160
+ relativePath,
161
+ reason: 'Path traversal detected',
162
+ };
163
+ }
164
+
165
+ // Check depth
166
+ const depth = relativePath.split(/[/\\]/).filter(Boolean).length;
167
+ if (depth > maxDepth) {
168
+ return {
169
+ allowed: false,
170
+ path: normalized,
171
+ relativePath,
172
+ reason: `Path depth exceeds maximum (${maxDepth})`,
173
+ };
174
+ }
175
+
176
+ // Check sensitive patterns
177
+ for (const pattern of SENSITIVE_PATTERNS) {
178
+ if (pattern.test(normalized) || pattern.test(relativePath)) {
179
+ return {
180
+ allowed: false,
181
+ path: normalized,
182
+ relativePath,
183
+ reason: 'Sensitive file access not allowed',
184
+ };
185
+ }
186
+ }
187
+
188
+ // Check if path is within workspace
189
+ if (!normalized.startsWith(workspace)) {
190
+ return {
191
+ allowed: false,
192
+ path: normalized,
193
+ relativePath,
194
+ reason: 'Path outside workspace',
195
+ };
196
+ }
197
+
198
+ return {
199
+ allowed: true,
200
+ path: normalized,
201
+ relativePath,
202
+ };
203
+ },
204
+
205
+ /**
206
+ * Check if path should be excluded from scanning
207
+ */
208
+ shouldExclude(inputPath: string): boolean {
209
+ const normalized = normalize(inputPath);
210
+ const relativePath = relative(workspace, normalized);
211
+
212
+ for (const pattern of excludePatterns) {
213
+ if (pattern.test(relativePath) || pattern.test(normalized)) {
214
+ return true;
215
+ }
216
+ }
217
+
218
+ return false;
219
+ },
220
+
221
+ /**
222
+ * Validate and return path or throw
223
+ */
224
+ requireValid(inputPath: string): string {
225
+ const result = this.validate(inputPath);
226
+
227
+ if (!result.allowed) {
228
+ throw Errors.pathOutsideSandbox(inputPath, workspace);
229
+ }
230
+
231
+ return result.path;
232
+ },
233
+
234
+ /**
235
+ * Check if path exists within sandbox
236
+ */
237
+ exists(inputPath: string): boolean {
238
+ const result = this.validate(inputPath);
239
+ if (!result.allowed) return false;
240
+ return existsSync(result.path);
241
+ },
242
+
243
+ /**
244
+ * Get workspace root
245
+ */
246
+ getWorkspace(): string {
247
+ return workspace;
248
+ },
249
+
250
+ /**
251
+ * Convert absolute path to relative
252
+ */
253
+ toRelative(absolutePath: string): string {
254
+ return relative(workspace, absolutePath);
255
+ },
256
+
257
+ /**
258
+ * Convert relative path to absolute
259
+ */
260
+ toAbsolute(relativePath: string): string {
261
+ return resolve(workspace, relativePath);
262
+ },
263
+ };
264
+ }
265
+
266
+ /**
267
+ * Default sandbox for current working directory
268
+ */
269
+ let defaultSandbox: ReturnType<typeof createSandbox> | null = null;
270
+
271
+ export function getDefaultSandbox(): ReturnType<typeof createSandbox> {
272
+ if (!defaultSandbox) {
273
+ defaultSandbox = createSandbox({ workspace: process.cwd() });
274
+ }
275
+ return defaultSandbox;
276
+ }
277
+
278
+ export function setDefaultSandbox(workspace: string): void {
279
+ defaultSandbox = createSandbox({ workspace });
280
+ }
281
+
282
+ /**
283
+ * Redact sensitive information from paths in output
284
+ */
285
+ export function redactPath(path: string, workspace: string): string {
286
+ // Convert to relative path
287
+ const relativePath = relative(workspace, path);
288
+
289
+ // If it's outside workspace, redact completely
290
+ if (relativePath.startsWith('..')) {
291
+ return '[REDACTED]';
292
+ }
293
+
294
+ return relativePath;
295
+ }
296
+
297
+ /**
298
+ * Redact sensitive values from objects
299
+ */
300
+ export function redactSensitive(obj: Record<string, unknown>): Record<string, unknown> {
301
+ const sensitiveKeys = [
302
+ 'password', 'secret', 'token', 'apiKey', 'api_key', 'auth',
303
+ 'credential', 'private', 'key', 'cert', 'cookie', 'session',
304
+ ];
305
+
306
+ const result: Record<string, unknown> = {};
307
+
308
+ for (const [key, value] of Object.entries(obj)) {
309
+ const lowerKey = key.toLowerCase();
310
+
311
+ if (sensitiveKeys.some(s => lowerKey.includes(s))) {
312
+ result[key] = '[REDACTED]';
313
+ } else if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
314
+ result[key] = redactSensitive(value as Record<string, unknown>);
315
+ } else if (Array.isArray(value)) {
316
+ result[key] = value.map(item =>
317
+ typeof item === 'object' && item !== null
318
+ ? redactSensitive(item as Record<string, unknown>)
319
+ : item
320
+ );
321
+ } else {
322
+ result[key] = value;
323
+ }
324
+ }
325
+
326
+ return result;
327
+ }
328
+
329
+ export default {
330
+ createSandbox,
331
+ getDefaultSandbox,
332
+ setDefaultSandbox,
333
+ redactPath,
334
+ redactSensitive,
335
+ SENSITIVE_PATTERNS,
336
+ EXCLUDE_PATTERNS,
337
+ };