@vaultcompass/vault-guard-mcp 1.4.7 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +49 -14
  2. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -59,9 +59,11 @@ var BINARY_EXTENSIONS = /* @__PURE__ */ new Set([
59
59
  function isBinaryFile(filePath) {
60
60
  return BINARY_EXTENSIONS.has(import_path.default.extname(filePath).toLowerCase());
61
61
  }
62
- async function scanWorkspaceDirectory(root, scanner, concurrency = 10, configIgnorePatterns2 = []) {
62
+ var MCP_SCAN_BUDGET_MS = 5e3;
63
+ async function scanWorkspaceDirectory(root, scanner, concurrency = 10, configIgnorePatterns2 = [], scanBudgetMs = MCP_SCAN_BUDGET_MS) {
63
64
  const files = await (0, import_vault_guard_core.getFilesToScanAsync)(root, false, void 0, configIgnorePatterns2);
64
65
  const results = [];
66
+ const overBudget = [];
65
67
  let filesScanned = 0;
66
68
  let bytesScanned = 0;
67
69
  const scanOne = async (file) => {
@@ -71,10 +73,15 @@ async function scanWorkspaceDirectory(root, scanner, concurrency = 10, configIgn
71
73
  if (!st.isFile()) return;
72
74
  filesScanned += 1;
73
75
  bytesScanned += st.size;
76
+ const t0 = Date.now();
74
77
  const matches = await (0, import_vault_guard_core.scanTextFileAsync)(scanner, file, { maxFileBytes: MAX_FILE_SIZE });
78
+ const elapsed = Date.now() - t0;
75
79
  if (matches.length > 0) {
76
80
  results.push({ file, matches });
77
81
  }
82
+ if (elapsed > scanBudgetMs) {
83
+ overBudget.push({ file, elapsed_ms: elapsed, budget_ms: scanBudgetMs });
84
+ }
78
85
  } catch {
79
86
  }
80
87
  };
@@ -82,11 +89,11 @@ async function scanWorkspaceDirectory(root, scanner, concurrency = 10, configIgn
82
89
  const batch = files.slice(i, i + concurrency);
83
90
  await Promise.all(batch.map(scanOne));
84
91
  }
85
- return { results, filesScanned, bytesScanned };
92
+ return { results, filesScanned, bytesScanned, overBudget };
86
93
  }
87
94
 
88
95
  // src/server.ts
89
- var SERVER_VERSION = true ? "1.4.7" : "0.0.0-dev";
96
+ var SERVER_VERSION = true ? "1.6.0" : "0.0.0-dev";
90
97
  function loadMcpConfig(workspaceRoot) {
91
98
  let cfg;
92
99
  try {
@@ -107,6 +114,7 @@ function loadMcpConfig(workspaceRoot) {
107
114
  function makeScanner(config) {
108
115
  return new import_vault_guard_core2.SecretScanner(config);
109
116
  }
117
+ var MAX_SCAN_BYTES = 10 * 1024 * 1024;
110
118
  function toolPayload(obj) {
111
119
  return {
112
120
  content: [{ type: "text", text: JSON.stringify(obj, null, 2) }]
@@ -204,7 +212,7 @@ function createMcpServer(options = {}) {
204
212
  }
205
213
  const dir = resolved.path;
206
214
  const t0 = Date.now();
207
- const { results, filesScanned, bytesScanned } = await scanWorkspaceDirectory(
215
+ const { results, filesScanned, bytesScanned, overBudget } = await scanWorkspaceDirectory(
208
216
  dir,
209
217
  scanner,
210
218
  10,
@@ -219,8 +227,13 @@ function createMcpServer(options = {}) {
219
227
  return toolPayload({
220
228
  summary: {
221
229
  files_with_secrets: results.length,
222
- total_matches: results.reduce((n, r) => n + r.matches.length, 0)
230
+ total_matches: results.reduce((n, r) => n + r.matches.length, 0),
231
+ // Post-hoc: these files WERE scanned, but took long enough that the
232
+ // result is not trusted. Surfaced so an agent is not handed a silent
233
+ // "clean" for a file whose scan behaved pathologically.
234
+ ...overBudget.length > 0 ? { files_over_scan_budget: overBudget.length } : {}
223
235
  },
236
+ ...overBudget.length > 0 ? { over_budget: overBudget } : {},
224
237
  json: JSON.parse((0, import_vault_guard_core2.formatJson)(results, { cwd: dir, run })),
225
238
  sarif: (0, import_vault_guard_core2.formatSarif)(results, { cwd: dir, run }),
226
239
  results
@@ -247,18 +260,28 @@ function createMcpServer(options = {}) {
247
260
  if (!import_fs2.default.existsSync(fp) || !import_fs2.default.statSync(fp).isFile()) {
248
261
  return toolPayload({ error: "not_a_file", path: fp });
249
262
  }
263
+ const st = import_fs2.default.statSync(fp);
264
+ if (st.size > MAX_SCAN_BYTES) {
265
+ return toolPayload({ error: "file_too_large", path: fp, bytes: st.size, max_bytes: MAX_SCAN_BYTES });
266
+ }
250
267
  const t0 = Date.now();
251
268
  const matches = scanner.scan(fp);
252
- const st = import_fs2.default.statSync(fp);
269
+ const elapsed = Date.now() - t0;
253
270
  const run = {
254
- duration_ms: Date.now() - t0,
271
+ duration_ms: elapsed,
255
272
  files_scanned: 1,
256
273
  bytes_scanned: st.size,
257
274
  patterns_active: scanner.getActivePatternCount()
258
275
  };
259
276
  const results = matches.length ? [{ file: fp, matches }] : [];
277
+ const overBudget = elapsed > MCP_SCAN_BUDGET_MS;
260
278
  return toolPayload({
261
- summary: { files_with_secrets: results.length, total_matches: matches.length },
279
+ summary: {
280
+ files_with_secrets: results.length,
281
+ total_matches: matches.length,
282
+ ...overBudget ? { over_scan_budget: true } : {}
283
+ },
284
+ ...overBudget ? { over_budget: [{ file: fp, elapsed_ms: elapsed, budget_ms: MCP_SCAN_BUDGET_MS }] } : {},
262
285
  json: JSON.parse((0, import_vault_guard_core2.formatJson)(results, { cwd: workspaceRoot, run })),
263
286
  sarif: (0, import_vault_guard_core2.formatSarif)(results, { cwd: workspaceRoot, run })
264
287
  });
@@ -275,12 +298,15 @@ function createMcpServer(options = {}) {
275
298
  }
276
299
  },
277
300
  async ({ text, virtual_path }) => {
301
+ const bytes = Buffer.byteLength(text, "utf8");
302
+ if (bytes > MAX_SCAN_BYTES) {
303
+ return toolPayload({ error: "text_too_large", bytes, max_bytes: MAX_SCAN_BYTES });
304
+ }
278
305
  const scanner = makeScanner(loadMcpConfig(workspaceRoot));
279
306
  const t0 = Date.now();
280
307
  const matches = scanner.scanContent(text);
281
308
  const label = virtual_path ?? "inline://snippet";
282
309
  const results = matches.length ? [{ file: label, matches }] : [];
283
- const bytes = Buffer.byteLength(text, "utf8");
284
310
  const run = {
285
311
  duration_ms: Date.now() - t0,
286
312
  files_scanned: 1,
@@ -308,16 +334,20 @@ function createMcpServer(options = {}) {
308
334
  const targets = paths && paths.length > 0 ? paths : ["."];
309
335
  let total = 0;
310
336
  const breakdown = {};
337
+ const visited = /* @__PURE__ */ new Set();
311
338
  const walkFile = (file) => {
312
339
  try {
313
- if (!import_fs2.default.existsSync(file)) return;
314
- const st = import_fs2.default.statSync(file);
315
- if (st.isFile()) {
340
+ const lst = import_fs2.default.lstatSync(file);
341
+ if (lst.isSymbolicLink()) return;
342
+ if (lst.isFile()) {
316
343
  const n = tokenCounter.countTokensInFile(file);
317
344
  total += n;
318
345
  const ext = import_path2.default.extname(file) || "(no-ext)";
319
346
  breakdown[ext] = (breakdown[ext] ?? 0) + n;
320
- } else if (st.isDirectory()) {
347
+ } else if (lst.isDirectory()) {
348
+ const real = import_fs2.default.realpathSync(file);
349
+ if (visited.has(real)) return;
350
+ visited.add(real);
321
351
  const entries = import_fs2.default.readdirSync(file, { withFileTypes: true });
322
352
  for (const e of entries) {
323
353
  if (e.name === "node_modules" || e.name === ".git") continue;
@@ -332,7 +362,12 @@ function createMcpServer(options = {}) {
332
362
  if (!resolved.ok) {
333
363
  return toolPayload({ error: resolved.error, path: resolved.path });
334
364
  }
335
- walkFile(resolved.path);
365
+ let start = resolved.path;
366
+ try {
367
+ start = import_fs2.default.realpathSync(resolved.path);
368
+ } catch {
369
+ }
370
+ walkFile(start);
336
371
  }
337
372
  const estCostAnthropic = tokenCounter.calculateCost("anthropic", total, 0);
338
373
  return toolPayload({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vaultcompass/vault-guard-mcp",
3
- "version": "1.4.7",
3
+ "version": "1.6.0",
4
4
  "description": "Let Cursor and Claude scan AI-proposed edits for secrets before applying. Local MCP server.",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -26,8 +26,8 @@
26
26
  "dependencies": {
27
27
  "@modelcontextprotocol/sdk": "^1.30.0",
28
28
  "zod": "^4.4.3",
29
- "@vaultcompass/vault-guard-core": "1.4.7",
30
- "@vaultcompass/vault-guard-telemetry": "1.4.7"
29
+ "@vaultcompass/vault-guard-core": "1.6.0",
30
+ "@vaultcompass/vault-guard-telemetry": "1.6.0"
31
31
  },
32
32
  "devDependencies": {
33
33
  "@types/jest": "^30.0.0",