meritmcp 0.1.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.
Potentially problematic release.
This version of meritmcp might be problematic. Click here for more details.
- package/LICENSE +21 -0
- package/README.md +149 -0
- package/action.yml +120 -0
- package/config/scoring.config.json +9 -0
- package/dist/src/adapter/mcpClient.js +72 -0
- package/dist/src/adapter/mcpClient.js.map +1 -0
- package/dist/src/cli.js +121 -0
- package/dist/src/cli.js.map +1 -0
- package/dist/src/conformance/stdio-proxy.js +102 -0
- package/dist/src/conformance/stdio-proxy.js.map +1 -0
- package/dist/src/conformance/wrapper.js +139 -0
- package/dist/src/conformance/wrapper.js.map +1 -0
- package/dist/src/functional/asserts.js +42 -0
- package/dist/src/functional/asserts.js.map +1 -0
- package/dist/src/functional/parsers.js +44 -0
- package/dist/src/functional/parsers.js.map +1 -0
- package/dist/src/functional/runner.js +27 -0
- package/dist/src/functional/runner.js.map +1 -0
- package/dist/src/orchestrator.js +96 -0
- package/dist/src/orchestrator.js.map +1 -0
- package/dist/src/report/badge.js +10 -0
- package/dist/src/report/badge.js.map +1 -0
- package/dist/src/report/console.js +100 -0
- package/dist/src/report/console.js.map +1 -0
- package/dist/src/report/prcomment.js +52 -0
- package/dist/src/report/prcomment.js.map +1 -0
- package/dist/src/report/sarif.js +118 -0
- package/dist/src/report/sarif.js.map +1 -0
- package/dist/src/report/score.js +59 -0
- package/dist/src/report/score.js.map +1 -0
- package/dist/src/security/engine.js +27 -0
- package/dist/src/security/engine.js.map +1 -0
- package/dist/src/security/owasp-map.js +56 -0
- package/dist/src/security/owasp-map.js.map +1 -0
- package/dist/src/security/probe/authz.js +17 -0
- package/dist/src/security/probe/authz.js.map +1 -0
- package/dist/src/security/probe/injection.js +79 -0
- package/dist/src/security/probe/injection.js.map +1 -0
- package/dist/src/security/static/deps-osv.js +129 -0
- package/dist/src/security/static/deps-osv.js.map +1 -0
- package/dist/src/security/static/secrets.js +57 -0
- package/dist/src/security/static/secrets.js.map +1 -0
- package/dist/src/security/static/unicode.js +37 -0
- package/dist/src/security/static/unicode.js.map +1 -0
- package/dist/src/snapshot/canonicalize.js +11 -0
- package/dist/src/snapshot/canonicalize.js.map +1 -0
- package/dist/src/snapshot/capture.js +26 -0
- package/dist/src/snapshot/capture.js.map +1 -0
- package/dist/src/snapshot/diff.js +26 -0
- package/dist/src/snapshot/diff.js.map +1 -0
- package/dist/src/types.js +3 -0
- package/dist/src/types.js.map +1 -0
- package/package.json +70 -0
- package/schemas/tests.schema.json +53 -0
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { RULES, securitySeverityNumber, sarifLevel } from "../security/owasp-map.js";
|
|
2
|
+
import { sha256 } from "../snapshot/canonicalize.js";
|
|
3
|
+
const INFO_URI = "https://github.com/meritmcp/meritmcp";
|
|
4
|
+
export function toSarif(report, version = "0.1.0") {
|
|
5
|
+
const issues = collectIssues(report);
|
|
6
|
+
const ruleMap = new Map();
|
|
7
|
+
for (const i of issues)
|
|
8
|
+
if (!ruleMap.has(i.ruleId))
|
|
9
|
+
ruleMap.set(i.ruleId, i);
|
|
10
|
+
const rules = [...ruleMap.values()].map((i) => ({
|
|
11
|
+
id: i.ruleId,
|
|
12
|
+
name: i.ruleName,
|
|
13
|
+
shortDescription: { text: i.shortDescription },
|
|
14
|
+
helpUri: i.helpUri ?? INFO_URI,
|
|
15
|
+
defaultConfiguration: { level: i.level },
|
|
16
|
+
properties: {
|
|
17
|
+
...(i.securitySeverity ? { "security-severity": i.securitySeverity } : {}),
|
|
18
|
+
tags: i.tags,
|
|
19
|
+
},
|
|
20
|
+
}));
|
|
21
|
+
const results = issues.map((i) => ({
|
|
22
|
+
ruleId: i.ruleId,
|
|
23
|
+
level: i.level,
|
|
24
|
+
message: { text: i.message },
|
|
25
|
+
locations: [
|
|
26
|
+
{
|
|
27
|
+
physicalLocation: {
|
|
28
|
+
artifactLocation: { uri: i.location },
|
|
29
|
+
region: { startLine: 1 },
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
partialFingerprints: { merit: sha256(`${i.ruleId}|${i.location}|${i.message}`).slice(0, 20) },
|
|
34
|
+
}));
|
|
35
|
+
return {
|
|
36
|
+
$schema: "https://json.schemastore.org/sarif-2.1.0.json",
|
|
37
|
+
version: "2.1.0",
|
|
38
|
+
runs: [
|
|
39
|
+
{
|
|
40
|
+
tool: {
|
|
41
|
+
driver: {
|
|
42
|
+
name: "Merit",
|
|
43
|
+
informationUri: INFO_URI,
|
|
44
|
+
version,
|
|
45
|
+
rules,
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
results,
|
|
49
|
+
},
|
|
50
|
+
],
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
function collectIssues(report) {
|
|
54
|
+
const issues = [];
|
|
55
|
+
// --- Security findings ---
|
|
56
|
+
for (const f of report.security.findings) {
|
|
57
|
+
const meta = RULES[f.ruleId];
|
|
58
|
+
issues.push({
|
|
59
|
+
ruleId: f.ruleId,
|
|
60
|
+
ruleName: meta?.title ?? f.ruleId,
|
|
61
|
+
shortDescription: meta?.title ?? f.ruleId,
|
|
62
|
+
level: sarifLevel(f.severity),
|
|
63
|
+
message: f.message,
|
|
64
|
+
location: f.location ?? "mcp-server://",
|
|
65
|
+
helpUri: meta?.helpUri,
|
|
66
|
+
securitySeverity: securitySeverityNumber(f.severity),
|
|
67
|
+
tags: ["security", "owasp-mcp", f.owasp ?? "", `confidence:${f.confidence}`].filter(Boolean),
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
// --- Functional test failures ---
|
|
71
|
+
for (const t of report.functional.results.filter((r) => !r.passed)) {
|
|
72
|
+
issues.push({
|
|
73
|
+
ruleId: "FUNCTIONAL-test-failed",
|
|
74
|
+
ruleName: "Functional test failed",
|
|
75
|
+
shortDescription: "A Merit functional test did not pass",
|
|
76
|
+
level: "error",
|
|
77
|
+
message: `${t.name}: ${t.detail}`,
|
|
78
|
+
location: "mcp-server://functional",
|
|
79
|
+
tags: ["functional"],
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
// --- Schema drift ---
|
|
83
|
+
const d = report.functional.drift;
|
|
84
|
+
if (d?.hasSnapshot) {
|
|
85
|
+
for (const n of d.removed)
|
|
86
|
+
issues.push(driftIssue("DRIFT-tool-removed", "error", `Tool removed since snapshot: ${n}`, n, false));
|
|
87
|
+
for (const n of d.schemaChanged)
|
|
88
|
+
issues.push(driftIssue("DRIFT-schema-changed", "error", `Breaking schema change since snapshot: ${n}`, n, false));
|
|
89
|
+
for (const n of d.descriptionOnly)
|
|
90
|
+
issues.push(driftIssue("DRIFT-rug-pull", "warning", `Description-only change (possible tool-poisoning rug-pull): ${n}`, n, true));
|
|
91
|
+
}
|
|
92
|
+
// --- Conformance failures (applicable only) ---
|
|
93
|
+
for (const s of report.conformance.scenarios.filter((x) => x.applicable && !x.passed)) {
|
|
94
|
+
issues.push({
|
|
95
|
+
ruleId: "CONFORMANCE-scenario-failed",
|
|
96
|
+
ruleName: "Conformance scenario failed",
|
|
97
|
+
shortDescription: "A scenario from the official MCP conformance suite failed",
|
|
98
|
+
level: "warning",
|
|
99
|
+
message: `${s.scenario}: ${s.error ?? "failed"}`,
|
|
100
|
+
location: `mcp-server://conformance/${s.scenario}`,
|
|
101
|
+
tags: ["conformance"],
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
return issues;
|
|
105
|
+
}
|
|
106
|
+
function driftIssue(ruleId, level, message, tool, security) {
|
|
107
|
+
return {
|
|
108
|
+
ruleId,
|
|
109
|
+
ruleName: ruleId,
|
|
110
|
+
shortDescription: "Tool surface changed since the committed snapshot",
|
|
111
|
+
level,
|
|
112
|
+
message,
|
|
113
|
+
location: `mcp-server://tool/${tool}`,
|
|
114
|
+
securitySeverity: security ? securitySeverityNumber("medium") : undefined,
|
|
115
|
+
tags: security ? ["security", "schema-drift"] : ["schema-drift"],
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
//# sourceMappingURL=sarif.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sarif.js","sourceRoot":"","sources":["../../../src/report/sarif.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
export const DEFAULT_SCORING = {
|
|
2
|
+
weights: { functional: 0.3, conformance: 0.3, security: 0.4 },
|
|
3
|
+
securityPenalties: { critical: 40, high: 20, medium: 8, low: 3 },
|
|
4
|
+
confidenceMultiplier: { high: 1.0, medium: 0.5, low: 0.2 },
|
|
5
|
+
rugPullPenalty: 15,
|
|
6
|
+
capScore: 49,
|
|
7
|
+
bands: { brightgreen: 85, green: 70, yellow: 50, red: 30 },
|
|
8
|
+
};
|
|
9
|
+
export function securityScore(findings, cfg = DEFAULT_SCORING) {
|
|
10
|
+
let score = 100;
|
|
11
|
+
for (const f of findings)
|
|
12
|
+
score -= cfg.securityPenalties[f.severity] * cfg.confidenceMultiplier[f.confidence];
|
|
13
|
+
return clamp(score);
|
|
14
|
+
}
|
|
15
|
+
export function hasHardFail(findings) {
|
|
16
|
+
return findings.some((f) => f.confidence === "high" && (f.severity === "critical" || f.severity === "high"));
|
|
17
|
+
}
|
|
18
|
+
export function assemble(parts, cfg = DEFAULT_SCORING) {
|
|
19
|
+
const active = [];
|
|
20
|
+
if (parts.functional.ran && parts.functional.score !== null)
|
|
21
|
+
active.push({ weight: cfg.weights.functional, score: parts.functional.score });
|
|
22
|
+
if (parts.conformance.ran && parts.conformance.score !== null)
|
|
23
|
+
active.push({ weight: cfg.weights.conformance, score: parts.conformance.score });
|
|
24
|
+
if (parts.security.ran && parts.security.score !== null)
|
|
25
|
+
active.push({ weight: cfg.weights.security, score: parts.security.score });
|
|
26
|
+
const totalWeight = active.reduce((s, a) => s + a.weight, 0) || 1;
|
|
27
|
+
let score = Math.round(active.reduce((s, a) => s + (a.weight / totalWeight) * a.score, 0));
|
|
28
|
+
if (parts.functional.drift?.descriptionOnly.length)
|
|
29
|
+
score = clamp(score - cfg.rugPullPenalty);
|
|
30
|
+
const capped = parts.security.ran && hasHardFail(parts.security.findings);
|
|
31
|
+
if (capped)
|
|
32
|
+
score = Math.min(score, cfg.capScore);
|
|
33
|
+
const allFunctionalPassed = parts.functional.ran ? parts.functional.passed === parts.functional.total : true;
|
|
34
|
+
const verdict = !capped && allFunctionalPassed && score >= cfg.bands.yellow ? "PASS" : "FAIL";
|
|
35
|
+
return {
|
|
36
|
+
verdict,
|
|
37
|
+
score,
|
|
38
|
+
capped,
|
|
39
|
+
functional: parts.functional,
|
|
40
|
+
conformance: parts.conformance,
|
|
41
|
+
security: parts.security,
|
|
42
|
+
weights: cfg.weights,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
export function bandColor(score, failed, cfg = DEFAULT_SCORING) {
|
|
46
|
+
if (failed || score < cfg.bands.red)
|
|
47
|
+
return "red";
|
|
48
|
+
if (score >= cfg.bands.brightgreen)
|
|
49
|
+
return "brightgreen";
|
|
50
|
+
if (score >= cfg.bands.green)
|
|
51
|
+
return "green";
|
|
52
|
+
if (score >= cfg.bands.yellow)
|
|
53
|
+
return "yellow";
|
|
54
|
+
return "orange";
|
|
55
|
+
}
|
|
56
|
+
function clamp(n) {
|
|
57
|
+
return Math.max(0, Math.min(100, n));
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=score.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"score.js","sourceRoot":"","sources":["../../../src/report/score.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { scanSecrets } from "./static/secrets.js";
|
|
2
|
+
import { scanUnicode } from "./static/unicode.js";
|
|
3
|
+
import { scanDeps } from "./static/deps-osv.js";
|
|
4
|
+
import { probeInjection } from "./probe/injection.js";
|
|
5
|
+
import { probeAuth } from "./probe/authz.js";
|
|
6
|
+
import { securityScore } from "../report/score.js";
|
|
7
|
+
export async function runSecurity(input) {
|
|
8
|
+
const findings = [];
|
|
9
|
+
// --- STATIC: tool metadata (names + descriptions) ---
|
|
10
|
+
const metaInputs = input.tools.map((t) => ({
|
|
11
|
+
label: `tool "${t.name}" metadata`,
|
|
12
|
+
text: `${t.name}\n${t.description ?? ""}\n${JSON.stringify(t.inputSchema ?? {})}`,
|
|
13
|
+
location: `mcp-server://tool/${t.name}`,
|
|
14
|
+
}));
|
|
15
|
+
findings.push(...scanSecrets(metaInputs));
|
|
16
|
+
findings.push(...scanUnicode(metaInputs));
|
|
17
|
+
// --- STATIC: dependencies (requires --src) ---
|
|
18
|
+
if (input.srcDir)
|
|
19
|
+
findings.push(...(await scanDeps(input.srcDir)));
|
|
20
|
+
// --- PROBE: command injection ---
|
|
21
|
+
if (input.probe)
|
|
22
|
+
findings.push(...(await probeInjection(input.client, input.tools)));
|
|
23
|
+
// --- PROBE: auth (HTTP only) ---
|
|
24
|
+
findings.push(...probeAuth(input.transport, input.url, input.transport === "http"));
|
|
25
|
+
return { findings, score: securityScore(findings) };
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=engine.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"engine.js","sourceRoot":"","sources":["../../../src/security/engine.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAgBnD,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,KAAoB;IACpD,MAAM,QAAQ,GAAc,EAAE,CAAC;IAE/B,uDAAuD;IACvD,MAAM,UAAU,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACzC,KAAK,EAAE,SAAS,CAAC,CAAC,IAAI,YAAY;QAClC,IAAI,EAAE,GAAG,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,WAAW,IAAI,EAAE,KAAK,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,WAAW,IAAI,EAAE,CAAC,EAAE;QACjF,QAAQ,EAAE,qBAAqB,CAAC,CAAC,IAAI,EAAE;KACxC,CAAC,CAAC,CAAC;IACJ,QAAQ,CAAC,IAAI,CAAC,GAAG,WAAW,CAAC,UAAU,CAAC,CAAC,CAAC;IAC1C,QAAQ,CAAC,IAAI,CAAC,GAAG,WAAW,CAAC,UAAU,CAAC,CAAC,CAAC;IAE1C,gDAAgD;IAChD,IAAI,KAAK,CAAC,MAAM;QAAE,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IAEnE,mCAAmC;IACnC,IAAI,KAAK,CAAC,KAAK;QAAE,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,cAAc,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAErF,kCAAkC;IAClC,QAAQ,CAAC,IAAI,CAAC,GAAG,SAAS,CAAC,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,GAAG,EAAE,KAAK,CAAC,SAAS,KAAK,MAAM,CAAC,CAAC,CAAC;IAEpF,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,aAAa,CAAC,QAAQ,CAAC,EAAE,CAAC;AACtD,CAAC"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export const OWASP_HELP = "https://owasp.org/www-project-mcp-top-10/";
|
|
2
|
+
export const RULES = {
|
|
3
|
+
"MCP01-secret": {
|
|
4
|
+
ruleId: "MCP01-secret",
|
|
5
|
+
owasp: "MCP01:2025",
|
|
6
|
+
title: "Exposed secret or token",
|
|
7
|
+
defaultSeverity: "high",
|
|
8
|
+
remediation: "Remove the credential from tool metadata/output; load it from an environment variable or secret store at runtime and rotate the leaked value.",
|
|
9
|
+
helpUri: OWASP_HELP,
|
|
10
|
+
},
|
|
11
|
+
"MCP03a-unicode": {
|
|
12
|
+
ruleId: "MCP03a-unicode",
|
|
13
|
+
owasp: "MCP03:2025",
|
|
14
|
+
title: "Invisible/abusable Unicode in tool metadata (tool poisoning)",
|
|
15
|
+
defaultSeverity: "high",
|
|
16
|
+
remediation: "Strip zero-width, bidirectional-override and tag characters from tool names/descriptions/schemas; they hide instructions from humans while the model still reads them.",
|
|
17
|
+
helpUri: OWASP_HELP,
|
|
18
|
+
},
|
|
19
|
+
"MCP04-dep": {
|
|
20
|
+
ruleId: "MCP04-dep",
|
|
21
|
+
owasp: "MCP04:2025",
|
|
22
|
+
title: "Vulnerable dependency",
|
|
23
|
+
defaultSeverity: "medium",
|
|
24
|
+
remediation: "Upgrade the dependency to a fixed version (see the linked OSV/GHSA advisory).",
|
|
25
|
+
helpUri: OWASP_HELP,
|
|
26
|
+
},
|
|
27
|
+
"MCP05-cmd-injection": {
|
|
28
|
+
ruleId: "MCP05-cmd-injection",
|
|
29
|
+
owasp: "MCP05:2025",
|
|
30
|
+
title: "Command injection",
|
|
31
|
+
defaultSeverity: "critical",
|
|
32
|
+
remediation: "Never pass tool input to a shell. Use argument arrays (execFile/spawn without a shell), strict allow-lists, and input validation.",
|
|
33
|
+
helpUri: OWASP_HELP,
|
|
34
|
+
},
|
|
35
|
+
"MCP07-auth": {
|
|
36
|
+
ruleId: "MCP07-auth",
|
|
37
|
+
owasp: "MCP07:2025",
|
|
38
|
+
title: "Insufficient authentication / authorization",
|
|
39
|
+
defaultSeverity: "high",
|
|
40
|
+
remediation: "Require auth on the MCP endpoint; return 401 with an RFC 9728 WWW-Authenticate header and publish /.well-known/oauth-protected-resource.",
|
|
41
|
+
helpUri: OWASP_HELP,
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
/** SARIF security-severity is a 0.1–10.0 string (drives GitHub code-scanning ranking). */
|
|
45
|
+
export function securitySeverityNumber(sev) {
|
|
46
|
+
return { critical: "9.5", high: "8.0", medium: "5.0", low: "3.0" }[sev];
|
|
47
|
+
}
|
|
48
|
+
/** SARIF level for defaultConfiguration. */
|
|
49
|
+
export function sarifLevel(sev) {
|
|
50
|
+
if (sev === "critical" || sev === "high")
|
|
51
|
+
return "error";
|
|
52
|
+
if (sev === "medium")
|
|
53
|
+
return "warning";
|
|
54
|
+
return "note";
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=owasp-map.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"owasp-map.js","sourceRoot":"","sources":["../../../src/security/owasp-map.ts"],"names":[],"mappings":"AAIA,MAAM,CAAC,MAAM,UAAU,GAAG,2CAA2C,CAAC;AAWtE,MAAM,CAAC,MAAM,KAAK,GAA6B;IAC7C,cAAc,EAAE;QACd,MAAM,EAAE,cAAc;QACtB,KAAK,EAAE,YAAY;QACnB,KAAK,EAAE,yBAAyB;QAChC,eAAe,EAAE,MAAM;QACvB,WAAW,EAAE,+IAA+I;QAC5J,OAAO,EAAE,UAAU;KACpB;IACD,gBAAgB,EAAE;QAChB,MAAM,EAAE,gBAAgB;QACxB,KAAK,EAAE,YAAY;QACnB,KAAK,EAAE,8DAA8D;QACrE,eAAe,EAAE,MAAM;QACvB,WAAW,EAAE,wKAAwK;QACrL,OAAO,EAAE,UAAU;KACpB;IACD,WAAW,EAAE;QACX,MAAM,EAAE,WAAW;QACnB,KAAK,EAAE,YAAY;QACnB,KAAK,EAAE,uBAAuB;QAC9B,eAAe,EAAE,QAAQ;QACzB,WAAW,EAAE,+EAA+E;QAC5F,OAAO,EAAE,UAAU;KACpB;IACD,qBAAqB,EAAE;QACrB,MAAM,EAAE,qBAAqB;QAC7B,KAAK,EAAE,YAAY;QACnB,KAAK,EAAE,mBAAmB;QAC1B,eAAe,EAAE,UAAU;QAC3B,WAAW,EAAE,mIAAmI;QAChJ,OAAO,EAAE,UAAU;KACpB;IACD,YAAY,EAAE;QACZ,MAAM,EAAE,YAAY;QACpB,KAAK,EAAE,YAAY;QACnB,KAAK,EAAE,6CAA6C;QACpD,eAAe,EAAE,MAAM;QACvB,WAAW,EAAE,0IAA0I;QACvJ,OAAO,EAAE,UAAU;KACpB;CACF,CAAC;AAEF,0FAA0F;AAC1F,MAAM,UAAU,sBAAsB,CAAC,GAAa;IAClD,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC,CAAC;AAC1E,CAAC;AAED,4CAA4C;AAC5C,MAAM,UAAU,UAAU,CAAC,GAAa;IACtC,IAAI,GAAG,KAAK,UAAU,IAAI,GAAG,KAAK,MAAM;QAAE,OAAO,OAAO,CAAC;IACzD,IAAI,GAAG,KAAK,QAAQ;QAAE,OAAO,SAAS,CAAC;IACvC,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export function probeAuth(transport, url, unauthenticatedSucceeded) {
|
|
2
|
+
if (transport !== "http")
|
|
3
|
+
return [];
|
|
4
|
+
if (!unauthenticatedSucceeded)
|
|
5
|
+
return [];
|
|
6
|
+
return [
|
|
7
|
+
{
|
|
8
|
+
ruleId: "MCP07-auth",
|
|
9
|
+
owasp: "MCP07:2025",
|
|
10
|
+
severity: "medium",
|
|
11
|
+
confidence: "medium",
|
|
12
|
+
message: "Server accepted an unauthenticated MCP session (no token supplied). If this endpoint is not meant to be public, require OAuth per RFC 9728.",
|
|
13
|
+
location: url ?? "(http endpoint)",
|
|
14
|
+
},
|
|
15
|
+
];
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=authz.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"authz.js","sourceRoot":"","sources":["../../../../src/security/probe/authz.ts"],"names":[],"mappings":"AAMA,MAAM,UAAU,SAAS,CAAC,SAA2B,EAAE,GAAuB,EAAE,wBAAiC;IAC/G,IAAI,SAAS,KAAK,MAAM;QAAE,OAAO,EAAE,CAAC;IACpC,IAAI,CAAC,wBAAwB;QAAE,OAAO,EAAE,CAAC;IACzC,OAAO;QACL;YACE,MAAM,EAAE,YAAY;YACpB,KAAK,EAAE,YAAY;YACnB,QAAQ,EAAE,QAAQ;YAClB,UAAU,EAAE,QAAQ;YACpB,OAAO,EAAE,6IAA6I;YACtJ,QAAQ,EAAE,GAAG,IAAI,iBAAiB;SACnC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
export async function probeInjection(client, tools) {
|
|
2
|
+
const findings = [];
|
|
3
|
+
for (const tool of tools) {
|
|
4
|
+
const stringParams = stringPropsOf(tool.inputSchema);
|
|
5
|
+
if (stringParams.length === 0)
|
|
6
|
+
continue;
|
|
7
|
+
let confirmed = null;
|
|
8
|
+
for (const param of stringParams) {
|
|
9
|
+
const marker = `MCPV${Math.random().toString(36).slice(2, 8).toUpperCase()}`;
|
|
10
|
+
const a = 30 + Math.floor(Math.random() * 60);
|
|
11
|
+
const b = 30 + Math.floor(Math.random() * 60);
|
|
12
|
+
const product = a * b;
|
|
13
|
+
const proof = `${marker}${product}`; // appears only if a shell echoed the marker AND expanded $((a*b))
|
|
14
|
+
const arith = `$((${a}*${b}))`;
|
|
15
|
+
const payloads = [
|
|
16
|
+
`echo ${marker}`, // whole-input exec → exact-echo match (bash + cmd.exe)
|
|
17
|
+
`${marker}${arith}`, // bash whole-input → "<marker><product>: not found"
|
|
18
|
+
`; echo ${marker}${arith}`, // bash concatenation injection
|
|
19
|
+
`& echo ${marker}${arith}`, // bash/cmd concatenation injection
|
|
20
|
+
"`echo " + marker + arith + "`", // bash backtick injection
|
|
21
|
+
];
|
|
22
|
+
for (const payload of payloads) {
|
|
23
|
+
const args = baselineArgs(tool.inputSchema);
|
|
24
|
+
args[param] = payload;
|
|
25
|
+
let out = "";
|
|
26
|
+
try {
|
|
27
|
+
out = (await client.callTool(tool.name, args)).text;
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
continue; // call rejected (e.g. validation) — not an injection signal
|
|
31
|
+
}
|
|
32
|
+
if (out.trim() === marker) {
|
|
33
|
+
confirmed = { param, payload, how: "echo executed by a shell" };
|
|
34
|
+
break;
|
|
35
|
+
}
|
|
36
|
+
if (out.includes(proof)) {
|
|
37
|
+
confirmed = { param, payload, how: "shell arithmetic $((a*b)) expansion" };
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if (confirmed)
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
if (confirmed) {
|
|
45
|
+
findings.push({
|
|
46
|
+
ruleId: "MCP05-cmd-injection",
|
|
47
|
+
owasp: "MCP05:2025",
|
|
48
|
+
severity: "critical",
|
|
49
|
+
confidence: "high",
|
|
50
|
+
message: `Tool "${tool.name}" passed argument "${confirmed.param}" to an OS shell (confirmed via ${confirmed.how}; canary: ${confirmed.payload}).`,
|
|
51
|
+
location: `mcp-server://tool/${tool.name}`,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return findings;
|
|
56
|
+
}
|
|
57
|
+
function stringPropsOf(schema) {
|
|
58
|
+
const s = schema;
|
|
59
|
+
if (!s?.properties)
|
|
60
|
+
return [];
|
|
61
|
+
return Object.entries(s.properties)
|
|
62
|
+
.filter(([, def]) => def.type === "string")
|
|
63
|
+
.map(([name]) => name);
|
|
64
|
+
}
|
|
65
|
+
/** Fill required params with benign placeholders so unrelated validation doesn't reject the probe. */
|
|
66
|
+
function baselineArgs(schema) {
|
|
67
|
+
const s = schema;
|
|
68
|
+
const out = {};
|
|
69
|
+
if (!s?.properties)
|
|
70
|
+
return out;
|
|
71
|
+
for (const name of s.required ?? []) {
|
|
72
|
+
const def = s.properties[name];
|
|
73
|
+
if (!def)
|
|
74
|
+
continue;
|
|
75
|
+
out[name] = def.type === "number" || def.type === "integer" ? 1 : def.type === "boolean" ? true : "test";
|
|
76
|
+
}
|
|
77
|
+
return out;
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=injection.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"injection.js","sourceRoot":"","sources":["../../../../src/security/probe/injection.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
// MCP04 — supply-chain / dependency CVEs via OSV.dev (authoritative; we don't build
|
|
2
|
+
// a vuln DB). Needs --src pointing at the server's project. Prefers package-lock.json
|
|
3
|
+
// (resolved versions) over package.json. Network failure => skip (never crash the gate).
|
|
4
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
const OSV_BATCH = "https://api.osv.dev/v1/querybatch";
|
|
7
|
+
const OSV_VULN = "https://api.osv.dev/v1/vulns/";
|
|
8
|
+
const DETAIL_LOOKUP_CAP = 40;
|
|
9
|
+
export async function scanDeps(srcDir) {
|
|
10
|
+
const deps = collectNpmDeps(srcDir);
|
|
11
|
+
if (deps.length === 0)
|
|
12
|
+
return [];
|
|
13
|
+
const queries = deps.map((d) => ({ package: { name: d.name, ecosystem: "npm" }, version: d.version }));
|
|
14
|
+
let results;
|
|
15
|
+
try {
|
|
16
|
+
const res = await fetch(OSV_BATCH, {
|
|
17
|
+
method: "POST",
|
|
18
|
+
headers: { "content-type": "application/json" },
|
|
19
|
+
body: JSON.stringify({ queries }),
|
|
20
|
+
});
|
|
21
|
+
if (!res.ok)
|
|
22
|
+
return [];
|
|
23
|
+
results = (await res.json()).results ?? [];
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return []; // offline → skip
|
|
27
|
+
}
|
|
28
|
+
// Collect unique vuln ids, then enrich a bounded number with severity/summary.
|
|
29
|
+
const hits = [];
|
|
30
|
+
results.forEach((r, i) => {
|
|
31
|
+
const dep = deps[i];
|
|
32
|
+
for (const v of r.vulns ?? [])
|
|
33
|
+
hits.push({ dep, id: v.id });
|
|
34
|
+
});
|
|
35
|
+
const details = await enrich(hits.slice(0, DETAIL_LOOKUP_CAP).map((h) => h.id));
|
|
36
|
+
const findings = [];
|
|
37
|
+
for (const h of hits) {
|
|
38
|
+
const d = details.get(h.id);
|
|
39
|
+
findings.push({
|
|
40
|
+
ruleId: "MCP04-dep",
|
|
41
|
+
owasp: "MCP04:2025",
|
|
42
|
+
severity: d?.severity ?? "medium",
|
|
43
|
+
confidence: "high",
|
|
44
|
+
message: `${h.dep.name}@${h.dep.version}: ${h.id}${d?.summary ? ` — ${d.summary}` : ""}`,
|
|
45
|
+
location: `package.json (${h.dep.name})`,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
return findings;
|
|
49
|
+
}
|
|
50
|
+
async function enrich(ids) {
|
|
51
|
+
const out = new Map();
|
|
52
|
+
await Promise.all(ids.map(async (id) => {
|
|
53
|
+
try {
|
|
54
|
+
const res = await fetch(OSV_VULN + id);
|
|
55
|
+
if (!res.ok)
|
|
56
|
+
return;
|
|
57
|
+
const v = (await res.json());
|
|
58
|
+
out.set(id, { severity: severityFromCvss(v.severity), summary: v.summary });
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
/* ignore single-vuln lookup failures */
|
|
62
|
+
}
|
|
63
|
+
}));
|
|
64
|
+
return out;
|
|
65
|
+
}
|
|
66
|
+
function severityFromCvss(severity) {
|
|
67
|
+
// OSV severity[].score is a CVSS vector string; pull the numeric base score if present.
|
|
68
|
+
const vec = severity?.find((s) => s.score)?.score ?? "";
|
|
69
|
+
const m = /\/?(?:CVSS:[\d.]+\/)?.*?(\d+(?:\.\d+)?)\b/.exec(vec);
|
|
70
|
+
const n = m ? Number(m[1]) : NaN;
|
|
71
|
+
if (Number.isNaN(n))
|
|
72
|
+
return "medium";
|
|
73
|
+
if (n >= 9.0)
|
|
74
|
+
return "critical";
|
|
75
|
+
if (n >= 7.0)
|
|
76
|
+
return "high";
|
|
77
|
+
if (n >= 4.0)
|
|
78
|
+
return "medium";
|
|
79
|
+
return "low";
|
|
80
|
+
}
|
|
81
|
+
function readJson(path) {
|
|
82
|
+
return JSON.parse(readFileSync(path, "utf8").replace(/^/, ""));
|
|
83
|
+
}
|
|
84
|
+
function collectNpmDeps(srcDir) {
|
|
85
|
+
const lock = join(srcDir, "package-lock.json");
|
|
86
|
+
if (existsSync(lock)) {
|
|
87
|
+
try {
|
|
88
|
+
const j = readJson(lock);
|
|
89
|
+
const out = [];
|
|
90
|
+
for (const [path, info] of Object.entries(j.packages ?? {})) {
|
|
91
|
+
const marker = "node_modules/";
|
|
92
|
+
const idx = path.lastIndexOf(marker);
|
|
93
|
+
if (idx === -1 || !info.version)
|
|
94
|
+
continue;
|
|
95
|
+
out.push({ name: path.slice(idx + marker.length), version: info.version });
|
|
96
|
+
}
|
|
97
|
+
if (out.length)
|
|
98
|
+
return dedupe(out);
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
/* fall through to package.json */
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
const pkgPath = join(srcDir, "package.json");
|
|
105
|
+
if (existsSync(pkgPath)) {
|
|
106
|
+
try {
|
|
107
|
+
const j = readJson(pkgPath);
|
|
108
|
+
const all = { ...j.dependencies, ...j.devDependencies };
|
|
109
|
+
return Object.entries(all).map(([name, ver]) => ({ name, version: String(ver).replace(/^[\^~>=<\s]+/, "") }));
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
return [];
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return [];
|
|
116
|
+
}
|
|
117
|
+
function dedupe(deps) {
|
|
118
|
+
const seen = new Set();
|
|
119
|
+
const out = [];
|
|
120
|
+
for (const d of deps) {
|
|
121
|
+
const k = `${d.name}@${d.version}`;
|
|
122
|
+
if (seen.has(k))
|
|
123
|
+
continue;
|
|
124
|
+
seen.add(k);
|
|
125
|
+
out.push(d);
|
|
126
|
+
}
|
|
127
|
+
return out;
|
|
128
|
+
}
|
|
129
|
+
//# sourceMappingURL=deps-osv.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deps-osv.js","sourceRoot":"","sources":["../../../../src/security/static/deps-osv.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
const PATTERNS = [
|
|
2
|
+
{ name: "AWS access key id", re: /\bAKIA[0-9A-Z]{16}\b/g, structured: true },
|
|
3
|
+
{ name: "GitHub token", re: /\bgh[pousr]_[A-Za-z0-9]{36,}\b/g, structured: true },
|
|
4
|
+
{ name: "OpenAI/Anthropic-style key", re: /\bsk-(?:live|test|proj|ant)?-?[A-Za-z0-9]{16,}\b/g, structured: true },
|
|
5
|
+
{ name: "Google API key", re: /\bAIza[0-9A-Za-z\-_]{35}\b/g, structured: true },
|
|
6
|
+
{ name: "Slack token", re: /\bxox[baprs]-[0-9A-Za-z-]{10,}\b/g, structured: true },
|
|
7
|
+
{ name: "Stripe secret key", re: /\bsk_live_[0-9A-Za-z]{16,}\b/g, structured: true },
|
|
8
|
+
{ name: "generic high-entropy secret", re: /\b[A-Za-z0-9+/_-]{24,}\b/g, structured: false },
|
|
9
|
+
];
|
|
10
|
+
const SECRET_KEYWORDS = /(secret|token|api[_-]?key|password|passwd|auth|credential|bearer|access[_-]?key)/i;
|
|
11
|
+
export function scanSecrets(inputs) {
|
|
12
|
+
const findings = [];
|
|
13
|
+
const seen = new Set();
|
|
14
|
+
for (const inp of inputs) {
|
|
15
|
+
for (const p of PATTERNS) {
|
|
16
|
+
p.re.lastIndex = 0;
|
|
17
|
+
let m;
|
|
18
|
+
while ((m = p.re.exec(inp.text)) !== null) {
|
|
19
|
+
const secret = m[0];
|
|
20
|
+
const corroborated = p.structured || (shannonEntropy(secret) >= 3.2 && SECRET_KEYWORDS.test(window(inp.text, m.index)));
|
|
21
|
+
if (!corroborated)
|
|
22
|
+
continue;
|
|
23
|
+
const dedupeKey = `${inp.location}:${secret.slice(0, 16)}`;
|
|
24
|
+
if (seen.has(dedupeKey))
|
|
25
|
+
continue;
|
|
26
|
+
seen.add(dedupeKey);
|
|
27
|
+
findings.push({
|
|
28
|
+
ruleId: "MCP01-secret",
|
|
29
|
+
owasp: "MCP01:2025",
|
|
30
|
+
severity: "high",
|
|
31
|
+
confidence: p.structured ? "high" : "medium",
|
|
32
|
+
message: `Possible ${p.name} exposed in ${inp.label}: ${redact(secret)}`,
|
|
33
|
+
location: inp.location,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return findings;
|
|
39
|
+
}
|
|
40
|
+
function window(text, idx) {
|
|
41
|
+
return text.slice(Math.max(0, idx - 40), idx + 40);
|
|
42
|
+
}
|
|
43
|
+
function redact(s) {
|
|
44
|
+
return s.length <= 8 ? "****" : `${s.slice(0, 4)}…${s.slice(-2)}`;
|
|
45
|
+
}
|
|
46
|
+
function shannonEntropy(s) {
|
|
47
|
+
const freq = {};
|
|
48
|
+
for (const c of s)
|
|
49
|
+
freq[c] = (freq[c] ?? 0) + 1;
|
|
50
|
+
let e = 0;
|
|
51
|
+
for (const k of Object.keys(freq)) {
|
|
52
|
+
const pr = freq[k] / s.length;
|
|
53
|
+
e -= pr * Math.log2(pr);
|
|
54
|
+
}
|
|
55
|
+
return e;
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=secrets.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"secrets.js","sourceRoot":"","sources":["../../../../src/security/static/secrets.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
const CATEGORIES = [
|
|
2
|
+
{ name: "zero-width character", match: (cp) => [0x200b, 0x200c, 0x200d, 0x2060, 0xfeff].includes(cp) },
|
|
3
|
+
{ name: "bidirectional override", match: (cp) => (cp >= 0x202a && cp <= 0x202e) || (cp >= 0x2066 && cp <= 0x2069) },
|
|
4
|
+
{ name: "Unicode tag character", match: (cp) => cp >= 0xe0000 && cp <= 0xe007f },
|
|
5
|
+
{ name: "soft hyphen / format control", match: (cp) => cp === 0x00ad },
|
|
6
|
+
];
|
|
7
|
+
export function scanUnicode(inputs) {
|
|
8
|
+
const findings = [];
|
|
9
|
+
for (const inp of inputs) {
|
|
10
|
+
const hits = new Map();
|
|
11
|
+
for (const ch of inp.text) {
|
|
12
|
+
const cp = ch.codePointAt(0);
|
|
13
|
+
if (cp === undefined)
|
|
14
|
+
continue;
|
|
15
|
+
for (const cat of CATEGORIES) {
|
|
16
|
+
if (cat.match(cp)) {
|
|
17
|
+
const set = hits.get(cat.name) ?? new Set();
|
|
18
|
+
set.add(cp);
|
|
19
|
+
hits.set(cat.name, set);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
for (const [cat, cps] of hits) {
|
|
24
|
+
const labels = [...cps].map((c) => `U+${c.toString(16).toUpperCase().padStart(4, "0")}`);
|
|
25
|
+
findings.push({
|
|
26
|
+
ruleId: "MCP03a-unicode",
|
|
27
|
+
owasp: "MCP03:2025",
|
|
28
|
+
severity: "high",
|
|
29
|
+
confidence: "high",
|
|
30
|
+
message: `Hidden ${cat} (${labels.join(", ")}) in ${inp.label} — classic tool-poisoning vector.`,
|
|
31
|
+
location: inp.location,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return findings;
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=unicode.js.map
|