agent-mcp-guard 0.3.0 → 0.4.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.
- package/README.md +50 -10
- package/action.yml +61 -1
- package/docs/baseline.md +70 -0
- package/docs/business-playbook.md +15 -14
- package/docs/examples/e2e-example.md +60 -0
- package/docs/github-action.md +33 -3
- package/docs/launch-checklist.md +14 -1
- package/docs/marketplace-action-readme.md +121 -0
- package/docs/marketplace.md +121 -0
- package/docs/operator-runbook.md +1 -1
- package/docs/paid-audit.md +6 -4
- package/docs/roadmap.md +4 -2
- package/examples/sample-report.md +15 -15
- package/package.json +5 -1
- package/scripts/action-comment.js +72 -0
- package/scripts/action-summary.js +7 -3
- package/scripts/prepare-marketplace-action.js +74 -0
- package/site/e2e/claude_desktop_config.json +28 -0
- package/site/e2e/index.html +107 -0
- package/site/e2e/report.html +451 -0
- package/site/e2e/report.json +173 -0
- package/site/e2e/report.md +47 -0
- package/site/e2e/report.sarif +570 -0
- package/src/baseline.js +162 -0
- package/src/cli.js +35 -5
- package/src/fingerprint.js +33 -0
- package/src/report.js +112 -42
- package/src/scan.js +8 -24
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# GitHub Marketplace Plan
|
|
2
|
+
|
|
3
|
+
GitHub Marketplace has stricter packaging rules than normal action usage. The main `mcp-guard` repository should stay as the product repository because it contains the CLI, website, tests, CI, Pages, docs, and examples.
|
|
4
|
+
|
|
5
|
+
Official GitHub docs: https://docs.github.com/en/actions/how-tos/create-and-publish-actions/publish-in-github-marketplace
|
|
6
|
+
|
|
7
|
+
Use a dedicated public repository for Marketplace:
|
|
8
|
+
|
|
9
|
+
```text
|
|
10
|
+
ChaoYue0307/mcp-guard-action
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Why a Dedicated Repository
|
|
14
|
+
|
|
15
|
+
GitHub requires Marketplace action repositories to:
|
|
16
|
+
|
|
17
|
+
- be public;
|
|
18
|
+
- contain a single root `action.yml` or `action.yaml`;
|
|
19
|
+
- have a unique action metadata `name`;
|
|
20
|
+
- avoid workflow files in the repository.
|
|
21
|
+
|
|
22
|
+
The main repo intentionally contains `.github/workflows`, so it should not be the Marketplace repo.
|
|
23
|
+
|
|
24
|
+
## Prepared Action Package
|
|
25
|
+
|
|
26
|
+
Generate the clean action repository payload:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npm run marketplace:prepare
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
This creates:
|
|
33
|
+
|
|
34
|
+
```text
|
|
35
|
+
dist/mcp-guard-action/
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
The generated directory includes only the files needed by the action:
|
|
39
|
+
|
|
40
|
+
- `action.yml`
|
|
41
|
+
- `README.md`
|
|
42
|
+
- `LICENSE`
|
|
43
|
+
- `package.json`
|
|
44
|
+
- `bin/`
|
|
45
|
+
- `src/`
|
|
46
|
+
- `scripts/action-summary.js`
|
|
47
|
+
|
|
48
|
+
It intentionally excludes `.github/workflows`.
|
|
49
|
+
|
|
50
|
+
## Recommended Marketplace Metadata
|
|
51
|
+
|
|
52
|
+
Repository name:
|
|
53
|
+
|
|
54
|
+
```text
|
|
55
|
+
mcp-guard-action
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Action name:
|
|
59
|
+
|
|
60
|
+
```text
|
|
61
|
+
mcp-guard MCP Security Scanner
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Description:
|
|
65
|
+
|
|
66
|
+
```text
|
|
67
|
+
Scan MCP and AI agent tool configuration for risky commands, leaked secrets, broad filesystem access, remote endpoints, and unpinned packages.
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Primary category:
|
|
71
|
+
|
|
72
|
+
```text
|
|
73
|
+
Security
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Secondary category:
|
|
77
|
+
|
|
78
|
+
```text
|
|
79
|
+
Code quality
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Release title:
|
|
83
|
+
|
|
84
|
+
```text
|
|
85
|
+
v0.4.0
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Release notes:
|
|
89
|
+
|
|
90
|
+
```text
|
|
91
|
+
Baseline and pull request comment release.
|
|
92
|
+
|
|
93
|
+
- Runs mcp-guard from the pinned action tag.
|
|
94
|
+
- Generates Markdown, HTML, JSON, and SARIF reports.
|
|
95
|
+
- Writes a GitHub Step Summary for pull request review.
|
|
96
|
+
- Supports baseline/allowlist files so known accepted findings do not fail CI.
|
|
97
|
+
- Can post or update a pull request comment with active findings.
|
|
98
|
+
- Can upload SARIF to GitHub code scanning with `upload-sarif: "true"`.
|
|
99
|
+
- Fails workflows by configurable severity threshold.
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Manual Publishing Steps
|
|
103
|
+
|
|
104
|
+
Completed:
|
|
105
|
+
|
|
106
|
+
- Public repository created: <https://github.com/ChaoYue0307/mcp-guard-action>
|
|
107
|
+
- `dist/mcp-guard-action/` exported, committed, and pushed.
|
|
108
|
+
- Release created: <https://github.com/ChaoYue0307/mcp-guard-action/releases/tag/v0.4.0>
|
|
109
|
+
- README, docs, and website examples now use:
|
|
110
|
+
|
|
111
|
+
```yaml
|
|
112
|
+
- uses: ChaoYue0307/mcp-guard-action@v0.4.0
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Remaining Marketplace web step:
|
|
116
|
+
|
|
117
|
+
1. Open `action.yml` or the release page on GitHub and click the Marketplace banner.
|
|
118
|
+
2. Select `Publish this Action to the GitHub Marketplace`.
|
|
119
|
+
3. Accept the GitHub Marketplace Developer Agreement if prompted.
|
|
120
|
+
4. Choose `Security` as the primary category.
|
|
121
|
+
5. Publish the release with 2FA.
|
package/docs/operator-runbook.md
CHANGED
|
@@ -98,5 +98,5 @@ Send this to 20 teams using MCP or AI agents:
|
|
|
98
98
|
```text
|
|
99
99
|
I am building mcp-guard, an open-source security scanner for MCP and AI agent tool configs. It checks for risky shell access, unpinned remote packages, over-broad file permissions, exposed secrets, and unsafe remote server setup.
|
|
100
100
|
|
|
101
|
-
I am
|
|
101
|
+
I am collecting real-world MCP config patterns from teams using agents in real workflows. If you can share a redacted config or run the CLI locally, your feedback can help improve the scanner's rules and reports.
|
|
102
102
|
```
|
package/docs/paid-audit.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Future Service Concept
|
|
2
2
|
|
|
3
|
-
This is
|
|
3
|
+
This is a planning note for a possible future service attached to `mcp-guard`.
|
|
4
|
+
|
|
5
|
+
It is not currently advertised as an active consulting service. Keep public website and README copy focused on the automated scanner, early pilots, and CI setup feedback until this offer is actually available.
|
|
4
6
|
|
|
5
7
|
## Who It Is For
|
|
6
8
|
|
|
@@ -24,6 +26,6 @@ Use `docs/templates/audit-report-template.md` as the starting point for client d
|
|
|
24
26
|
- Small startup: USD 1,000-3,000.
|
|
25
27
|
- Funded team or private deployment pilot: USD 3,000-8,000.
|
|
26
28
|
|
|
27
|
-
## Sales Copy
|
|
29
|
+
## Draft Sales Copy
|
|
28
30
|
|
|
29
|
-
I am building `mcp-guard`, an open-source scanner for MCP and AI agent tool security. It checks for risky shell access, unpinned remote packages, over-broad file permissions, exposed secrets, and unsafe remote server setup. I am
|
|
31
|
+
I am building `mcp-guard`, an open-source scanner for MCP and AI agent tool security. It checks for risky shell access, unpinned remote packages, over-broad file permissions, exposed secrets, and unsafe remote server setup. I am collecting real-world config patterns from teams using agents in real workflows.
|
package/docs/roadmap.md
CHANGED
|
@@ -9,14 +9,16 @@
|
|
|
9
9
|
- Rules for shell wrappers, remote package runners, unpinned packages, broad filesystem access, secret-like env vars/headers, and remote MCP URLs.
|
|
10
10
|
- CI usage with `--fail-on`.
|
|
11
11
|
- GitHub Action wrapper that writes a job summary, uploads Markdown/HTML/JSON/SARIF artifacts, and can upload SARIF to GitHub code scanning.
|
|
12
|
+
- Baseline/allowlist mode for accepting known findings and failing only on new risks.
|
|
13
|
+
- Optional GitHub pull request comments from the Marketplace Action.
|
|
12
14
|
|
|
13
15
|
## Next
|
|
14
16
|
|
|
15
17
|
1. More MCP client discovery paths.
|
|
16
18
|
2. Rule packs mapped to MCP security best practices.
|
|
17
19
|
3. Policy file for approved commands, packages, directories, and remote URLs.
|
|
18
|
-
4.
|
|
19
|
-
5.
|
|
20
|
+
4. `mcp-guard audit` mode for review-ready reports.
|
|
21
|
+
5. Safer default remediation snippets for common MCP servers.
|
|
20
22
|
|
|
21
23
|
## Later
|
|
22
24
|
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# mcp-guard Scan Report
|
|
2
2
|
|
|
3
|
-
Generated: 2026-05-
|
|
3
|
+
Generated: 2026-05-10T12:58:03.265Z
|
|
4
4
|
|
|
5
5
|
## Summary
|
|
6
6
|
|
|
7
7
|
- Scanned files: 1
|
|
8
8
|
- MCP servers: 3
|
|
9
|
-
-
|
|
9
|
+
- Active findings: 9
|
|
10
10
|
- Risk score: 98
|
|
11
11
|
- Critical: 2
|
|
12
12
|
- High: 5
|
|
@@ -25,19 +25,19 @@ Generated: 2026-05-10T06:56:59.977Z
|
|
|
25
25
|
| shell-installer | bash | -c curl https://example.com/install.sh \| bash | - | - | - |
|
|
26
26
|
| remote-prod | - | - | - | https://mcp.example.com/sse | - |
|
|
27
27
|
|
|
28
|
-
## Findings
|
|
29
|
-
|
|
30
|
-
| Severity | Rule | Server | Finding | Evidence | Recommendation |
|
|
31
|
-
| --- | --- | --- | --- | --- | --- |
|
|
32
|
-
| critical | MCP010 | shell-installer | Shell command executes inline script | command=bash args=-c curl https://example.com/install.sh \| bash | Use a direct, pinned executable instead of a shell wrapper. If a shell is required, place the script in source control and review it. |
|
|
33
|
-
| critical | MCP050 | shell-installer | MCP server command includes a dangerous operation | curl pipe to shell | Remove the dangerous operation from MCP startup. Run destructive setup steps manually and review them separately. |
|
|
34
|
-
| high | MCP021 | filesystem-all-home | Remote MCP package is not version pinned | package=@modelcontextprotocol/server-filesystem | Pin the package to an exact version such as package@1.2.3 and review updates before changing it. |
|
|
35
|
-
| high | MCP030 | filesystem-all-home | Secret-like environment variable is exposed to MCP server | GITHUB_TOKEN=ghp...890 (32 chars) | Pass the least privileged token possible. Prefer scoped tokens, short-lived credentials, and a dedicated service account. |
|
|
36
|
-
| high | MCP040 | filesystem-all-home | MCP server has a broad working directory | cwd=/ | Run the server in a narrow project directory or sandbox with only the files it needs. |
|
|
37
|
-
| high | MCP041 | filesystem-all-home | MCP server argument grants broad filesystem access | arg=/ | Replace broad filesystem paths with a dedicated project folder or read-only sandbox path. |
|
|
38
|
-
| high | MCP061 | remote-prod | Secret-like header is configured for remote MCP server | Authorization=Bea...ken (27 chars) | Use scoped, short-lived credentials and avoid placing long-lived secrets directly in MCP config files. |
|
|
39
|
-
| medium | MCP020 | filesystem-all-home | MCP server is launched through a remote package runner | command=npx package=@modelcontextprotocol/server-filesystem | Pin the package version, review the package source, and prefer a local lockfile or vendored executable for sensitive tools. |
|
|
40
|
-
| medium | MCP060 | remote-prod | Remote MCP server URL is configured | url=https://mcp.example.com/sse | Verify the provider, use HTTPS, document the data sent to this server, and keep an allowlist of approved remote endpoints. |
|
|
28
|
+
## Active Findings
|
|
29
|
+
|
|
30
|
+
| Severity | Rule | Server | Finding | Evidence | Fingerprint | Recommendation |
|
|
31
|
+
| --- | --- | --- | --- | --- | --- | --- |
|
|
32
|
+
| critical | MCP010 | shell-installer | Shell command executes inline script | command=bash args=-c curl https://example.com/install.sh \| bash | mcpg_a009b2c2 | Use a direct, pinned executable instead of a shell wrapper. If a shell is required, place the script in source control and review it. |
|
|
33
|
+
| critical | MCP050 | shell-installer | MCP server command includes a dangerous operation | curl pipe to shell | mcpg_6bd13204 | Remove the dangerous operation from MCP startup. Run destructive setup steps manually and review them separately. |
|
|
34
|
+
| high | MCP021 | filesystem-all-home | Remote MCP package is not version pinned | package=@modelcontextprotocol/server-filesystem | mcpg_d0af49fa | Pin the package to an exact version such as package@1.2.3 and review updates before changing it. |
|
|
35
|
+
| high | MCP030 | filesystem-all-home | Secret-like environment variable is exposed to MCP server | GITHUB_TOKEN=ghp...890 (32 chars) | mcpg_a5f382b0 | Pass the least privileged token possible. Prefer scoped tokens, short-lived credentials, and a dedicated service account. |
|
|
36
|
+
| high | MCP040 | filesystem-all-home | MCP server has a broad working directory | cwd=/ | mcpg_31aaa689 | Run the server in a narrow project directory or sandbox with only the files it needs. |
|
|
37
|
+
| high | MCP041 | filesystem-all-home | MCP server argument grants broad filesystem access | arg=/ | mcpg_dbc08d76 | Replace broad filesystem paths with a dedicated project folder or read-only sandbox path. |
|
|
38
|
+
| high | MCP061 | remote-prod | Secret-like header is configured for remote MCP server | Authorization=Bea...ken (27 chars) | mcpg_5abd4cbd | Use scoped, short-lived credentials and avoid placing long-lived secrets directly in MCP config files. |
|
|
39
|
+
| medium | MCP020 | filesystem-all-home | MCP server is launched through a remote package runner | command=npx package=@modelcontextprotocol/server-filesystem | mcpg_a3493a53 | Pin the package version, review the package source, and prefer a local lockfile or vendored executable for sensitive tools. |
|
|
40
|
+
| medium | MCP060 | remote-prod | Remote MCP server URL is configured | url=https://mcp.example.com/sse | mcpg_cf1296e4 | Verify the provider, use HTTPS, document the data sent to this server, and keep an allowlist of approved remote endpoints. |
|
|
41
41
|
|
|
42
42
|
## Notes
|
|
43
43
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-mcp-guard",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Open-source CLI scanner for risky MCP server and AI agent tool configuration.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"homepage": "https://chaoyue0307.github.io/mcp-guard/",
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
"scan:example": "node ./bin/mcp-guard.js scan --config examples/unsafe-claude_desktop_config.json --output examples/sample-report.md",
|
|
20
20
|
"release:check": "node ./scripts/release-check.js",
|
|
21
21
|
"launch:github": "node ./scripts/launch-github.js",
|
|
22
|
+
"marketplace:prepare": "node ./scripts/prepare-marketplace-action.js",
|
|
22
23
|
"publish:npm": "node ./scripts/publish-npm.js",
|
|
23
24
|
"start": "node ./bin/mcp-guard.js"
|
|
24
25
|
},
|
|
@@ -44,10 +45,13 @@
|
|
|
44
45
|
"README.md",
|
|
45
46
|
"action.yml",
|
|
46
47
|
"scripts/action-summary.js",
|
|
48
|
+
"scripts/action-comment.js",
|
|
49
|
+
"scripts/prepare-marketplace-action.js",
|
|
47
50
|
"LICENSE",
|
|
48
51
|
"SECURITY.md",
|
|
49
52
|
"docs",
|
|
50
53
|
"examples",
|
|
54
|
+
"site/e2e",
|
|
51
55
|
"site/assets/readme-hero.svg",
|
|
52
56
|
"site/assets/brand-mark.svg"
|
|
53
57
|
]
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
|
|
6
|
+
const [jsonReportPath, markdownReportPath, htmlReportPath, sarifReportPath, failOn] = process.argv.slice(2);
|
|
7
|
+
|
|
8
|
+
if (!jsonReportPath) {
|
|
9
|
+
process.stderr.write("Usage: action-comment.js <json-report> <markdown-report> <html-report> <sarif-report> <fail-on>\n");
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const report = JSON.parse(fs.readFileSync(jsonReportPath, "utf8"));
|
|
14
|
+
const counts = report.summary.counts;
|
|
15
|
+
const findings = report.findings || [];
|
|
16
|
+
const acceptedCount = report.summary.acceptedFindingCount || 0;
|
|
17
|
+
const runUrl = process.env.GITHUB_SERVER_URL && process.env.GITHUB_REPOSITORY && process.env.GITHUB_RUN_ID
|
|
18
|
+
? `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}`
|
|
19
|
+
: "";
|
|
20
|
+
const status = findings.length === 0 ? "passed" : "needs review";
|
|
21
|
+
|
|
22
|
+
const lines = [];
|
|
23
|
+
lines.push("<!-- mcp-guard-comment -->");
|
|
24
|
+
lines.push("## mcp-guard MCP security scan");
|
|
25
|
+
lines.push("");
|
|
26
|
+
lines.push(`Status: **${status}**`);
|
|
27
|
+
lines.push(`Risk score: **${report.summary.riskScore}**`);
|
|
28
|
+
lines.push(`Active findings: **${report.summary.findingCount}**`);
|
|
29
|
+
if (acceptedCount > 0 || report.baseline?.enabled) {
|
|
30
|
+
lines.push(`Accepted by baseline: **${acceptedCount}**`);
|
|
31
|
+
}
|
|
32
|
+
lines.push(`Fail threshold: **${failOn || "high"}**`);
|
|
33
|
+
if (runUrl) {
|
|
34
|
+
lines.push(`Workflow run: ${runUrl}`);
|
|
35
|
+
}
|
|
36
|
+
lines.push("");
|
|
37
|
+
lines.push("| Critical | High | Medium | Low |");
|
|
38
|
+
lines.push("| ---: | ---: | ---: | ---: |");
|
|
39
|
+
lines.push(`| ${counts.critical} | ${counts.high} | ${counts.medium} | ${counts.low} |`);
|
|
40
|
+
lines.push("");
|
|
41
|
+
|
|
42
|
+
if (findings.length === 0) {
|
|
43
|
+
lines.push("No active findings met the current scan. Baseline-accepted findings do not block this PR.");
|
|
44
|
+
} else {
|
|
45
|
+
lines.push("### Top active findings");
|
|
46
|
+
lines.push("");
|
|
47
|
+
lines.push("| Severity | Rule | Server | Finding |");
|
|
48
|
+
lines.push("| --- | --- | --- | --- |");
|
|
49
|
+
for (const finding of findings.slice(0, 6)) {
|
|
50
|
+
lines.push(`| ${cell(finding.severity)} | ${cell(finding.id)} | ${cell(finding.serverName)} | ${cell(finding.title)} |`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
lines.push("");
|
|
55
|
+
lines.push("### Artifacts");
|
|
56
|
+
lines.push("");
|
|
57
|
+
lines.push(`- Markdown: \`${relative(markdownReportPath)}\``);
|
|
58
|
+
lines.push(`- HTML: \`${relative(htmlReportPath)}\``);
|
|
59
|
+
lines.push(`- JSON: \`${relative(jsonReportPath)}\``);
|
|
60
|
+
lines.push(`- SARIF: \`${relative(sarifReportPath)}\``);
|
|
61
|
+
lines.push("");
|
|
62
|
+
|
|
63
|
+
process.stdout.write(`${lines.join("\n")}\n`);
|
|
64
|
+
|
|
65
|
+
function cell(value) {
|
|
66
|
+
return String(value ?? "").replaceAll("|", "\\|").replaceAll("\n", " ");
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function relative(filePath) {
|
|
70
|
+
if (!filePath) return "";
|
|
71
|
+
return path.relative(process.cwd(), path.resolve(filePath)) || ".";
|
|
72
|
+
}
|
|
@@ -13,6 +13,7 @@ if (!jsonReportPath) {
|
|
|
13
13
|
const report = JSON.parse(fs.readFileSync(jsonReportPath, "utf8"));
|
|
14
14
|
const counts = report.summary.counts;
|
|
15
15
|
const topFindings = report.findings.slice(0, 8);
|
|
16
|
+
const acceptedCount = report.summary.acceptedFindingCount || 0;
|
|
16
17
|
|
|
17
18
|
const lines = [];
|
|
18
19
|
lines.push("## mcp-guard scan");
|
|
@@ -28,14 +29,17 @@ lines.push(`| Low | ${counts.low} |`);
|
|
|
28
29
|
lines.push("");
|
|
29
30
|
lines.push(`Scanned files: **${report.summary.scannedFileCount}**`);
|
|
30
31
|
lines.push(`MCP servers: **${report.summary.serverCount}**`);
|
|
31
|
-
lines.push(`
|
|
32
|
+
lines.push(`Active findings: **${report.summary.findingCount}**`);
|
|
33
|
+
if (acceptedCount > 0 || report.baseline?.enabled) {
|
|
34
|
+
lines.push(`Accepted by baseline: **${acceptedCount}**`);
|
|
35
|
+
}
|
|
32
36
|
lines.push(`Fail threshold: **${failOn || "high"}**`);
|
|
33
37
|
lines.push("");
|
|
34
38
|
|
|
35
39
|
if (topFindings.length === 0) {
|
|
36
|
-
lines.push("No findings.");
|
|
40
|
+
lines.push("No active findings.");
|
|
37
41
|
} else {
|
|
38
|
-
lines.push("### Top findings");
|
|
42
|
+
lines.push("### Top active findings");
|
|
39
43
|
lines.push("");
|
|
40
44
|
lines.push("| Severity | Rule | Server | Finding |");
|
|
41
45
|
lines.push("| --- | --- | --- | --- |");
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
|
|
6
|
+
const root = path.resolve(import.meta.dirname, "..");
|
|
7
|
+
const outputDir = path.join(root, "dist", "mcp-guard-action");
|
|
8
|
+
const packageJson = JSON.parse(fs.readFileSync(path.join(root, "package.json"), "utf8"));
|
|
9
|
+
|
|
10
|
+
fs.rmSync(outputDir, { recursive: true, force: true });
|
|
11
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
12
|
+
|
|
13
|
+
copyFile("action.yml", "action.yml");
|
|
14
|
+
copyFile("LICENSE", "LICENSE");
|
|
15
|
+
copyFile("docs/marketplace-action-readme.md", "README.md");
|
|
16
|
+
copyDir("bin", "bin");
|
|
17
|
+
copyDir("src", "src");
|
|
18
|
+
copyFile("scripts/action-summary.js", "scripts/action-summary.js");
|
|
19
|
+
copyFile("scripts/action-comment.js", "scripts/action-comment.js");
|
|
20
|
+
writePackageJson();
|
|
21
|
+
validateExport();
|
|
22
|
+
|
|
23
|
+
process.stdout.write(`Marketplace action package prepared at ${path.relative(root, outputDir)}\n`);
|
|
24
|
+
|
|
25
|
+
function copyFile(from, to) {
|
|
26
|
+
const source = path.join(root, from);
|
|
27
|
+
const target = path.join(outputDir, to);
|
|
28
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
29
|
+
fs.copyFileSync(source, target);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function copyDir(from, to) {
|
|
33
|
+
fs.cpSync(path.join(root, from), path.join(outputDir, to), {
|
|
34
|
+
recursive: true,
|
|
35
|
+
filter: (source) => !source.includes(`${path.sep}.DS_Store`)
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function writePackageJson() {
|
|
40
|
+
const actionPackage = {
|
|
41
|
+
name: "mcp-guard-action",
|
|
42
|
+
version: packageJson.version,
|
|
43
|
+
private: true,
|
|
44
|
+
description: "GitHub Action wrapper for mcp-guard MCP and AI agent security scanning.",
|
|
45
|
+
type: "module",
|
|
46
|
+
engines: packageJson.engines,
|
|
47
|
+
license: packageJson.license
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
fs.writeFileSync(path.join(outputDir, "package.json"), `${JSON.stringify(actionPackage, null, 2)}\n`, "utf8");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function validateExport() {
|
|
54
|
+
const required = [
|
|
55
|
+
"action.yml",
|
|
56
|
+
"README.md",
|
|
57
|
+
"LICENSE",
|
|
58
|
+
"package.json",
|
|
59
|
+
"bin/mcp-guard.js",
|
|
60
|
+
"src/cli.js",
|
|
61
|
+
"src/report.js",
|
|
62
|
+
"scripts/action-summary.js",
|
|
63
|
+
"scripts/action-comment.js"
|
|
64
|
+
];
|
|
65
|
+
|
|
66
|
+
const missing = required.filter((file) => !fs.existsSync(path.join(outputDir, file)));
|
|
67
|
+
if (missing.length > 0) {
|
|
68
|
+
throw new Error(`Marketplace export is missing: ${missing.join(", ")}`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (fs.existsSync(path.join(outputDir, ".github"))) {
|
|
72
|
+
throw new Error("Marketplace export must not include .github workflows.");
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"mcpServers": {
|
|
3
|
+
"filesystem-all-home": {
|
|
4
|
+
"command": "npx",
|
|
5
|
+
"args": [
|
|
6
|
+
"@modelcontextprotocol/server-filesystem",
|
|
7
|
+
"/"
|
|
8
|
+
],
|
|
9
|
+
"env": {
|
|
10
|
+
"GITHUB_TOKEN": "ghp_exampleSecretValue1234567890"
|
|
11
|
+
},
|
|
12
|
+
"cwd": "/"
|
|
13
|
+
},
|
|
14
|
+
"shell-installer": {
|
|
15
|
+
"command": "bash",
|
|
16
|
+
"args": [
|
|
17
|
+
"-c",
|
|
18
|
+
"curl https://example.com/install.sh | bash"
|
|
19
|
+
]
|
|
20
|
+
},
|
|
21
|
+
"remote-prod": {
|
|
22
|
+
"url": "https://mcp.example.com/sse",
|
|
23
|
+
"headers": {
|
|
24
|
+
"Authorization": "Bearer example-secret-token"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>mcp-guard E2E Example</title>
|
|
7
|
+
<meta name="description" content="A transparent end-to-end mcp-guard example with committed input, reproducible CLI commands, and generated reports.">
|
|
8
|
+
<link rel="icon" href="../assets/brand-mark.svg" type="image/svg+xml">
|
|
9
|
+
<link rel="stylesheet" href="../styles.css">
|
|
10
|
+
</head>
|
|
11
|
+
<body>
|
|
12
|
+
<header class="site-header">
|
|
13
|
+
<a class="brand" href="../" aria-label="mcp-guard home">
|
|
14
|
+
<img src="../assets/brand-mark.svg" alt="">
|
|
15
|
+
<span>mcp-guard</span>
|
|
16
|
+
</a>
|
|
17
|
+
<nav aria-label="Primary navigation">
|
|
18
|
+
<a href="../#scan">Scan</a>
|
|
19
|
+
<a href="../#action">Action</a>
|
|
20
|
+
<a href="../#baseline">Baseline</a>
|
|
21
|
+
<a href="../#reports">Reports</a>
|
|
22
|
+
<a href="../#pilot">Pilot</a>
|
|
23
|
+
<a class="github-link" href="https://github.com/ChaoYue0307/mcp-guard">GitHub</a>
|
|
24
|
+
</nav>
|
|
25
|
+
</header>
|
|
26
|
+
|
|
27
|
+
<main>
|
|
28
|
+
<section class="example-hero">
|
|
29
|
+
<div>
|
|
30
|
+
<span class="eyebrow">End-to-end proof</span>
|
|
31
|
+
<h1>Real scanner output from a reproducible MCP config.</h1>
|
|
32
|
+
<p>The input is synthetic so it is safe to publish, but every report below was generated by the current `mcp-guard` CLI from the committed config file.</p>
|
|
33
|
+
</div>
|
|
34
|
+
<div class="example-score">
|
|
35
|
+
<span>Risk Score</span>
|
|
36
|
+
<strong>98</strong>
|
|
37
|
+
<p>9 active findings across 3 MCP servers</p>
|
|
38
|
+
</div>
|
|
39
|
+
</section>
|
|
40
|
+
|
|
41
|
+
<section class="example-layout">
|
|
42
|
+
<div class="example-main">
|
|
43
|
+
<h2>1. Input config</h2>
|
|
44
|
+
<p>The config intentionally includes risky patterns a real MCP setup can contain: remote package execution, root filesystem access, shell startup, remote MCP URL, and secret-like values.</p>
|
|
45
|
+
<pre><code>{
|
|
46
|
+
"mcpServers": {
|
|
47
|
+
"filesystem-all-home": {
|
|
48
|
+
"command": "npx",
|
|
49
|
+
"args": ["@modelcontextprotocol/server-filesystem", "/"],
|
|
50
|
+
"env": { "GITHUB_TOKEN": "ghp_exampleSecretValue1234567890" },
|
|
51
|
+
"cwd": "/"
|
|
52
|
+
},
|
|
53
|
+
"shell-installer": {
|
|
54
|
+
"command": "bash",
|
|
55
|
+
"args": ["-c", "curl https://example.com/install.sh | bash"]
|
|
56
|
+
},
|
|
57
|
+
"remote-prod": {
|
|
58
|
+
"url": "https://mcp.example.com/sse",
|
|
59
|
+
"headers": { "Authorization": "Bearer example-secret-token" }
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}</code></pre>
|
|
63
|
+
|
|
64
|
+
<h2>2. Reproduce the scan</h2>
|
|
65
|
+
<pre><code>node ./bin/mcp-guard.js scan --config site/e2e/claude_desktop_config.json --format markdown --output site/e2e/report.md
|
|
66
|
+
node ./bin/mcp-guard.js scan --config site/e2e/claude_desktop_config.json --format html --output site/e2e/report.html
|
|
67
|
+
node ./bin/mcp-guard.js scan --config site/e2e/claude_desktop_config.json --format json --output site/e2e/report.json
|
|
68
|
+
node ./bin/mcp-guard.js scan --config site/e2e/claude_desktop_config.json --format sarif --output site/e2e/report.sarif</code></pre>
|
|
69
|
+
|
|
70
|
+
<h2>3. What it found</h2>
|
|
71
|
+
<div class="finding-table">
|
|
72
|
+
<div><span>Critical</span><strong>MCP010</strong><p>Shell command executes inline script.</p></div>
|
|
73
|
+
<div><span>Critical</span><strong>MCP050</strong><p>Startup command includes curl-pipe-shell.</p></div>
|
|
74
|
+
<div><span>High</span><strong>MCP021</strong><p>Remote MCP package is not version pinned.</p></div>
|
|
75
|
+
<div><span>High</span><strong>MCP030</strong><p>Secret-like environment variable is exposed.</p></div>
|
|
76
|
+
<div><span>High</span><strong>MCP040/MCP041</strong><p>Working directory and argument grant broad filesystem access.</p></div>
|
|
77
|
+
<div><span>High</span><strong>MCP061</strong><p>Secret-like authorization header is configured.</p></div>
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
|
|
81
|
+
<aside class="example-side">
|
|
82
|
+
<h2>Generated artifacts</h2>
|
|
83
|
+
<p>Open these files and compare them with the input config.</p>
|
|
84
|
+
<a href="claude_desktop_config.json">Input JSON</a>
|
|
85
|
+
<a href="report.md">Markdown report</a>
|
|
86
|
+
<a href="report.html">HTML report</a>
|
|
87
|
+
<a href="report.json">JSON report</a>
|
|
88
|
+
<a href="report.sarif">SARIF report</a>
|
|
89
|
+
<h2>Output summary</h2>
|
|
90
|
+
<ul>
|
|
91
|
+
<li>Scanned files: 1</li>
|
|
92
|
+
<li>MCP servers: 3</li>
|
|
93
|
+
<li>Active findings: 9</li>
|
|
94
|
+
<li>Critical: 2</li>
|
|
95
|
+
<li>High: 5</li>
|
|
96
|
+
<li>Medium: 2</li>
|
|
97
|
+
<li>Low: 0</li>
|
|
98
|
+
</ul>
|
|
99
|
+
</aside>
|
|
100
|
+
</section>
|
|
101
|
+
</main>
|
|
102
|
+
|
|
103
|
+
<footer>
|
|
104
|
+
<p>mcp-guard is Apache-2.0 open source. This example is generated from files committed in the repository.</p>
|
|
105
|
+
</footer>
|
|
106
|
+
</body>
|
|
107
|
+
</html>
|