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.

Files changed (54) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +149 -0
  3. package/action.yml +120 -0
  4. package/config/scoring.config.json +9 -0
  5. package/dist/src/adapter/mcpClient.js +72 -0
  6. package/dist/src/adapter/mcpClient.js.map +1 -0
  7. package/dist/src/cli.js +121 -0
  8. package/dist/src/cli.js.map +1 -0
  9. package/dist/src/conformance/stdio-proxy.js +102 -0
  10. package/dist/src/conformance/stdio-proxy.js.map +1 -0
  11. package/dist/src/conformance/wrapper.js +139 -0
  12. package/dist/src/conformance/wrapper.js.map +1 -0
  13. package/dist/src/functional/asserts.js +42 -0
  14. package/dist/src/functional/asserts.js.map +1 -0
  15. package/dist/src/functional/parsers.js +44 -0
  16. package/dist/src/functional/parsers.js.map +1 -0
  17. package/dist/src/functional/runner.js +27 -0
  18. package/dist/src/functional/runner.js.map +1 -0
  19. package/dist/src/orchestrator.js +96 -0
  20. package/dist/src/orchestrator.js.map +1 -0
  21. package/dist/src/report/badge.js +10 -0
  22. package/dist/src/report/badge.js.map +1 -0
  23. package/dist/src/report/console.js +100 -0
  24. package/dist/src/report/console.js.map +1 -0
  25. package/dist/src/report/prcomment.js +52 -0
  26. package/dist/src/report/prcomment.js.map +1 -0
  27. package/dist/src/report/sarif.js +118 -0
  28. package/dist/src/report/sarif.js.map +1 -0
  29. package/dist/src/report/score.js +59 -0
  30. package/dist/src/report/score.js.map +1 -0
  31. package/dist/src/security/engine.js +27 -0
  32. package/dist/src/security/engine.js.map +1 -0
  33. package/dist/src/security/owasp-map.js +56 -0
  34. package/dist/src/security/owasp-map.js.map +1 -0
  35. package/dist/src/security/probe/authz.js +17 -0
  36. package/dist/src/security/probe/authz.js.map +1 -0
  37. package/dist/src/security/probe/injection.js +79 -0
  38. package/dist/src/security/probe/injection.js.map +1 -0
  39. package/dist/src/security/static/deps-osv.js +129 -0
  40. package/dist/src/security/static/deps-osv.js.map +1 -0
  41. package/dist/src/security/static/secrets.js +57 -0
  42. package/dist/src/security/static/secrets.js.map +1 -0
  43. package/dist/src/security/static/unicode.js +37 -0
  44. package/dist/src/security/static/unicode.js.map +1 -0
  45. package/dist/src/snapshot/canonicalize.js +11 -0
  46. package/dist/src/snapshot/canonicalize.js.map +1 -0
  47. package/dist/src/snapshot/capture.js +26 -0
  48. package/dist/src/snapshot/capture.js.map +1 -0
  49. package/dist/src/snapshot/diff.js +26 -0
  50. package/dist/src/snapshot/diff.js.map +1 -0
  51. package/dist/src/types.js +3 -0
  52. package/dist/src/types.js.map +1 -0
  53. package/package.json +70 -0
  54. 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":"AAeA,MAAM,CAAC,MAAM,eAAe,GAAkB;IAC5C,OAAO,EAAE,EAAE,UAAU,EAAE,GAAG,EAAE,WAAW,EAAE,GAAG,EAAE,QAAQ,EAAE,GAAG,EAAE;IAC7D,iBAAiB,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE;IAChE,oBAAoB,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE;IAC1D,cAAc,EAAE,EAAE;IAClB,QAAQ,EAAE,EAAE;IACZ,KAAK,EAAE,EAAE,WAAW,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE;CAC3D,CAAC;AAEF,MAAM,UAAU,aAAa,CAAC,QAAmB,EAAE,MAAqB,eAAe;IACrF,IAAI,KAAK,GAAG,GAAG,CAAC;IAChB,KAAK,MAAM,CAAC,IAAI,QAAQ;QAAE,KAAK,IAAI,GAAG,CAAC,iBAAiB,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,GAAG,CAAC,oBAAoB,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC;IAC9G,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC;AACtB,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,QAAmB;IAC7C,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,KAAK,MAAM,IAAI,CAAC,CAAC,CAAC,QAAQ,KAAK,UAAU,IAAI,CAAC,CAAC,QAAQ,KAAK,MAAM,CAAC,CAAC,CAAC;AAC/G,CAAC;AAED,MAAM,UAAU,QAAQ,CACtB,KAAoG,EACpG,MAAqB,eAAe;IAEpC,MAAM,MAAM,GAA6C,EAAE,CAAC;IAC5D,IAAI,KAAK,CAAC,UAAU,CAAC,GAAG,IAAI,KAAK,CAAC,UAAU,CAAC,KAAK,KAAK,IAAI;QACzD,MAAM,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,OAAO,CAAC,UAAU,EAAE,KAAK,EAAE,KAAK,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC,CAAC;IACjF,IAAI,KAAK,CAAC,WAAW,CAAC,GAAG,IAAI,KAAK,CAAC,WAAW,CAAC,KAAK,KAAK,IAAI;QAC3D,MAAM,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,OAAO,CAAC,WAAW,EAAE,KAAK,EAAE,KAAK,CAAC,WAAW,CAAC,KAAK,EAAE,CAAC,CAAC;IACnF,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,IAAI,KAAK,CAAC,QAAQ,CAAC,KAAK,KAAK,IAAI;QACrD,MAAM,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,OAAO,CAAC,QAAQ,EAAE,KAAK,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC;IAE7E,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;IAClE,IAAI,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC;IAE3F,IAAI,KAAK,CAAC,UAAU,CAAC,KAAK,EAAE,eAAe,CAAC,MAAM;QAAE,KAAK,GAAG,KAAK,CAAC,KAAK,GAAG,GAAG,CAAC,cAAc,CAAC,CAAC;IAE9F,MAAM,MAAM,GAAG,KAAK,CAAC,QAAQ,CAAC,GAAG,IAAI,WAAW,CAAC,KAAK,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IAC1E,IAAI,MAAM;QAAE,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,QAAQ,CAAC,CAAC;IAElD,MAAM,mBAAmB,GAAG,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC,MAAM,KAAK,KAAK,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC;IAC7G,MAAM,OAAO,GAAoB,CAAC,MAAM,IAAI,mBAAmB,IAAI,KAAK,IAAI,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC;IAE/G,OAAO;QACL,OAAO;QACP,KAAK;QACL,MAAM;QACN,UAAU,EAAE,KAAK,CAAC,UAAU;QAC5B,WAAW,EAAE,KAAK,CAAC,WAAW;QAC9B,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,OAAO,EAAE,GAAG,CAAC,OAAO;KACrB,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,KAAa,EAAE,MAAe,EAAE,MAAqB,eAAe;IAC5F,IAAI,MAAM,IAAI,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG;QAAE,OAAO,KAAK,CAAC;IAClD,IAAI,KAAK,IAAI,GAAG,CAAC,KAAK,CAAC,WAAW;QAAE,OAAO,aAAa,CAAC;IACzD,IAAI,KAAK,IAAI,GAAG,CAAC,KAAK,CAAC,KAAK;QAAE,OAAO,OAAO,CAAC;IAC7C,IAAI,KAAK,IAAI,GAAG,CAAC,KAAK,CAAC,MAAM;QAAE,OAAO,QAAQ,CAAC;IAC/C,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,SAAS,KAAK,CAAC,CAAS;IACtB,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC;AACvC,CAAC"}
@@ -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":"AAAA,oFAAoF;AACpF,sFAAsF;AACtF,yFAAyF;AACzF,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAQjC,MAAM,SAAS,GAAG,mCAAmC,CAAC;AACtD,MAAM,QAAQ,GAAG,+BAA+B,CAAC;AACjD,MAAM,iBAAiB,GAAG,EAAE,CAAC;AAE7B,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,MAAc;IAC3C,MAAM,IAAI,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC;IACpC,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAEjC,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;IAEvG,IAAI,OAAiD,CAAC;IACtD,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,SAAS,EAAE;YACjC,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;YAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,CAAC;SAClC,CAAC,CAAC;QACH,IAAI,CAAC,GAAG,CAAC,EAAE;YAAE,OAAO,EAAE,CAAC;QACvB,OAAO,GAAI,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAA4D,CAAC,OAAO,IAAI,EAAE,CAAC;IACzG,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC,CAAC,iBAAiB;IAC9B,CAAC;IAED,+EAA+E;IAC/E,MAAM,IAAI,GAAoC,EAAE,CAAC;IACjD,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;QACvB,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QACpB,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,IAAI,EAAE;YAAE,IAAI,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAC9D,CAAC,CAAC,CAAC;IAEH,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,iBAAiB,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAEhF,MAAM,QAAQ,GAAc,EAAE,CAAC;IAC/B,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;QACrB,MAAM,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QAC5B,QAAQ,CAAC,IAAI,CAAC;YACZ,MAAM,EAAE,WAAW;YACnB,KAAK,EAAE,YAAY;YACnB,QAAQ,EAAE,CAAC,EAAE,QAAQ,IAAI,QAAQ;YACjC,UAAU,EAAE,MAAM;YAClB,OAAO,EAAE,GAAG,CAAC,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,CAAC,GAAG,CAAC,OAAO,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE;YACxF,QAAQ,EAAE,iBAAiB,CAAC,CAAC,GAAG,CAAC,IAAI,GAAG;SACzC,CAAC,CAAC;IACL,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,KAAK,UAAU,MAAM,CAAC,GAAa;IACjC,MAAM,GAAG,GAAG,IAAI,GAAG,EAAoD,CAAC;IACxE,MAAM,OAAO,CAAC,GAAG,CACf,GAAG,CAAC,GAAG,CAAC,KAAK,EAAE,EAAE,EAAE,EAAE;QACnB,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,QAAQ,GAAG,EAAE,CAAC,CAAC;YACvC,IAAI,CAAC,GAAG,CAAC,EAAE;gBAAE,OAAO;YACpB,MAAM,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAA+D,CAAC;YAC3F,GAAG,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,gBAAgB,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;QAC9E,CAAC;QAAC,MAAM,CAAC;YACP,wCAAwC;QAC1C,CAAC;IACH,CAAC,CAAC,CACH,CAAC;IACF,OAAO,GAAG,CAAC;AACb,CAAC;AAED,SAAS,gBAAgB,CAAC,QAAoC;IAC5D,wFAAwF;IACxF,MAAM,GAAG,GAAG,QAAQ,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,KAAK,IAAI,EAAE,CAAC;IACxD,MAAM,CAAC,GAAG,2CAA2C,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAChE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;IACjC,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;QAAE,OAAO,QAAQ,CAAC;IACrC,IAAI,CAAC,IAAI,GAAG;QAAE,OAAO,UAAU,CAAC;IAChC,IAAI,CAAC,IAAI,GAAG;QAAE,OAAO,MAAM,CAAC;IAC5B,IAAI,CAAC,IAAI,GAAG;QAAE,OAAO,QAAQ,CAAC;IAC9B,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,QAAQ,CAAC,IAAY;IAC5B,OAAO,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC;AAClE,CAAC;AAED,SAAS,cAAc,CAAC,MAAc;IACpC,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;IAC/C,IAAI,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QACrB,IAAI,CAAC;YACH,MAAM,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAwD,CAAC;YAChF,MAAM,GAAG,GAAU,EAAE,CAAC;YACtB,KAAK,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,IAAI,EAAE,CAAC,EAAE,CAAC;gBAC5D,MAAM,MAAM,GAAG,eAAe,CAAC;gBAC/B,MAAM,GAAG,GAAG,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;gBACrC,IAAI,GAAG,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO;oBAAE,SAAS;gBAC1C,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,MAAM,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC;YAC7E,CAAC;YACD,IAAI,GAAG,CAAC,MAAM;gBAAE,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC;QACrC,CAAC;QAAC,MAAM,CAAC;YACP,kCAAkC;QACpC,CAAC;IACH,CAAC;IACD,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IAC7C,IAAI,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;QACxB,IAAI,CAAC;YACH,MAAM,CAAC,GAAG,QAAQ,CAAC,OAAO,CAGzB,CAAC;YACF,MAAM,GAAG,GAAG,EAAE,GAAG,CAAC,CAAC,YAAY,EAAE,GAAG,CAAC,CAAC,eAAe,EAAE,CAAC;YACxD,OAAO,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,cAAc,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;QAChH,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,EAAE,CAAC;QACZ,CAAC;IACH,CAAC;IACD,OAAO,EAAE,CAAC;AACZ,CAAC;AAED,SAAS,MAAM,CAAC,IAAW;IACzB,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,MAAM,GAAG,GAAU,EAAE,CAAC;IACtB,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;QACrB,MAAM,CAAC,GAAG,GAAG,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,OAAO,EAAE,CAAC;QACnC,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;YAAE,SAAS;QAC1B,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QACZ,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACd,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC"}
@@ -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