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 +8 -0
- package/bin/mcp-proof.js +12 -3
- package/engine.js +34 -5
- package/package.json +1 -1
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(() =>
|
|
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(
|
|
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:
|
|
82
|
-
reason: 'description
|
|
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
|
|
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.
|
|
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": {
|