mcp-proof 0.3.13 → 0.3.15

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.15
4
+
5
+ Narrowed kubeconfig detection to require credential-content access language, avoiding false positives on Kubernetes context-selector parameters. Added regression coverage for both selector boilerplate and explicit kubeconfig-content access.
6
+
7
+ ## 0.3.14
8
+
9
+ Severity-aware tool-poisoning summaries distinguish actionable patterns from low-severity URL mentions. Stdio launch errors now preserve server stderr for root-cause triage.
10
+
3
11
  ## 0.3.13
4
12
 
5
13
  Scanner-only release. Runtime authorization functionality is not included.
package/bin/mcp-proof.js CHANGED
@@ -399,10 +399,14 @@ async function fetchToolsList({ command, args, env, timeoutMs }) {
399
399
  windowsHide: true,
400
400
  });
401
401
 
402
- const timer = setTimeout(() => fail(new Error(`MCP server timed out after ${timeoutMs}ms`)), timeoutMs);
402
+ const timer = setTimeout(() => {
403
+ const tail = stderrBuf.trim() ? ` — stderr: ${stderrBuf.trim().slice(-500)}` : '';
404
+ fail(new Error(`MCP server timed out after ${timeoutMs}ms${tail}`));
405
+ }, timeoutMs);
403
406
 
407
+ let stderrBuf = '';
404
408
  child.on('error', fail);
405
- child.stderr.on('data', () => {});
409
+ child.stderr.on('data', (chunk) => { stderrBuf += chunk.toString('utf8'); });
406
410
  child.stdout.on('data', (chunk) => {
407
411
  buffer = Buffer.concat([buffer, chunk]);
408
412
  buffer = consumeMessages(buffer, (msg) => {
@@ -426,9 +430,14 @@ async function fetchToolsList({ command, args, env, timeoutMs }) {
426
430
  }
427
431
  });
428
432
  });
433
+ function childErrorContext(base, code, signal) {
434
+ const tail = stderrBuf.trim() ? ` — stderr: ${stderrBuf.trim().slice(-500)}` : '';
435
+ return new Error(`${base} (code=${code}, signal=${signal})${tail}`);
436
+ }
437
+
429
438
  child.on('exit', (code, signal) => {
430
439
  if (!settled && pending.size > 0) {
431
- fail(new Error(`MCP server exited before tools/list completed (code=${code}, signal=${signal})`));
440
+ fail(childErrorContext('MCP server exited before tools/list completed', code, signal));
432
441
  }
433
442
  });
434
443
 
package/engine.js CHANGED
@@ -78,8 +78,8 @@ const SIGNATURES = [
78
78
  {
79
79
  name: 'path_kube_creds',
80
80
  sev: 'high',
81
- re: /~?\/?\.kube(\/config)?|KUBECONFIG/i,
82
- reason: 'description references kubeconfig',
81
+ re: /(?:\b(?:read|open|load|access|cat|copy|dump|print|display|extract|send|upload|exfiltrate|include)\b[^.!?\n]{0,120}(?:~?\/?\.kube\/config|KUBECONFIG)|(?:~?\/?\.kube\/config|KUBECONFIG)[^.!?\n]{0,120}\b(?:contents?|credentials?|secrets?|token)\b)/i,
82
+ reason: 'description instructs access to kubeconfig contents',
83
83
  },
84
84
  {
85
85
  name: 'cross_tool_invocation',
@@ -423,6 +423,37 @@ function hasSuspiciousBase64(s) {
423
423
  return false;
424
424
  }
425
425
 
426
+ function summarizePatterns(perTool, severity) {
427
+ const counts = {};
428
+ for (const t of perTool) {
429
+ for (const p of t.patterns) {
430
+ if (p.severity === severity) counts[p.name] = (counts[p.name] || 0) + 1;
431
+ }
432
+ }
433
+ const entries = Object.entries(counts).sort((a, b) => b[1] - a[1]);
434
+ return entries.map(([name, count]) => count > 1 ? `${count} ${name} mentions` : `1 ${name} pattern`);
435
+ }
436
+
437
+ function formatToolPoisoningReason(perTool) {
438
+ if (perTool.length === 0) return 'no instruction-hijacking patterns detected';
439
+ const sevOrder = ['critical', 'high', 'medium', 'low'];
440
+ const bySev = {};
441
+ for (const t of perTool) {
442
+ for (const p of t.patterns) {
443
+ bySev[p.severity] = bySev[p.severity] || [];
444
+ bySev[p.severity].push({ patterns: [p] });
445
+ }
446
+ }
447
+ const parts = [];
448
+ for (const sev of sevOrder) {
449
+ const list = bySev[sev];
450
+ if (!list || list.length === 0) continue;
451
+ const names = summarizePatterns(list, sev);
452
+ parts.push(`${names.join(', ')} at ${sev} severity`);
453
+ }
454
+ return parts.join('; ');
455
+ }
456
+
426
457
  function scanTools(tools, opts) {
427
458
  const skillContext = (opts || {}).skillContext === true;
428
459
  if (!tools || tools.length === 0) {
@@ -499,9 +530,7 @@ function scanTools(tools, opts) {
499
530
  return {
500
531
  detected: perTool.length > 0,
501
532
  severity: maxSev,
502
- reason: perTool.length > 0
503
- ? `${perTool.length}/${tools.length} tools contain instruction-hijacking or sensitive-path references`
504
- : 'no instruction-hijacking patterns detected',
533
+ reason: formatToolPoisoningReason(perTool),
505
534
  perTool,
506
535
  };
507
536
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-proof",
3
- "version": "0.3.13",
3
+ "version": "0.3.15",
4
4
  "description": "Auditable, privacy-preserving MCP (Model Context Protocol) server verifier with local Trust Cards and SARIF output — test servers before connecting them to an AI system.",
5
5
  "main": "engine.js",
6
6
  "bin": {