dfx-mcp-scanner 0.1.0__tar.gz
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.
- dfx_mcp_scanner-0.1.0/LICENSE +22 -0
- dfx_mcp_scanner-0.1.0/PKG-INFO +94 -0
- dfx_mcp_scanner-0.1.0/README.md +65 -0
- dfx_mcp_scanner-0.1.0/dfx_mcp_scanner.egg-info/PKG-INFO +94 -0
- dfx_mcp_scanner-0.1.0/dfx_mcp_scanner.egg-info/SOURCES.txt +15 -0
- dfx_mcp_scanner-0.1.0/dfx_mcp_scanner.egg-info/dependency_links.txt +1 -0
- dfx_mcp_scanner-0.1.0/dfx_mcp_scanner.egg-info/entry_points.txt +2 -0
- dfx_mcp_scanner-0.1.0/dfx_mcp_scanner.egg-info/requires.txt +9 -0
- dfx_mcp_scanner-0.1.0/dfx_mcp_scanner.egg-info/top_level.txt +1 -0
- dfx_mcp_scanner-0.1.0/mcp_scanner/__init__.py +3 -0
- dfx_mcp_scanner-0.1.0/mcp_scanner/cli.py +74 -0
- dfx_mcp_scanner-0.1.0/mcp_scanner/models.py +60 -0
- dfx_mcp_scanner-0.1.0/mcp_scanner/parser.py +54 -0
- dfx_mcp_scanner-0.1.0/mcp_scanner/scanner.py +83 -0
- dfx_mcp_scanner-0.1.0/pyproject.toml +35 -0
- dfx_mcp_scanner-0.1.0/setup.cfg +4 -0
- dfx_mcp_scanner-0.1.0/setup.py +18 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Dockfix Labs
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dfx-mcp-scanner
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Security scanner for MCP (Model Context Protocol) servers — detect malicious tools, data exfiltration, and supply chain risks.
|
|
5
|
+
Home-page: https://github.com/dockfixlabs/mcp-scanner
|
|
6
|
+
Author: Dockfix Labs
|
|
7
|
+
Author-email: Dockfix Labs <security@dockfixlabs.dev>
|
|
8
|
+
License: MIT
|
|
9
|
+
Keywords: mcp,model-context-protocol,security,scanner,ai-agents,claude,cursor
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Topic :: Security
|
|
14
|
+
Requires-Python: >=3.10
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
License-File: LICENSE
|
|
17
|
+
Requires-Dist: click>=8.1
|
|
18
|
+
Requires-Dist: rich>=13.0
|
|
19
|
+
Requires-Dist: pydantic>=2.0
|
|
20
|
+
Requires-Dist: pyyaml>=6.0
|
|
21
|
+
Provides-Extra: dev
|
|
22
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
23
|
+
Requires-Dist: pytest-cov; extra == "dev"
|
|
24
|
+
Requires-Dist: ruff; extra == "dev"
|
|
25
|
+
Dynamic: author
|
|
26
|
+
Dynamic: home-page
|
|
27
|
+
Dynamic: license-file
|
|
28
|
+
Dynamic: requires-python
|
|
29
|
+
|
|
30
|
+
# 🔍 MCP Scanner
|
|
31
|
+
|
|
32
|
+
> **Security scanner for MCP (Model Context Protocol) servers.** Detect malicious tools, data exfiltration, and supply chain risks before connecting an MCP server to your AI agent.
|
|
33
|
+
|
|
34
|
+
[](https://python.org)
|
|
35
|
+
[](LICENSE)
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Why MCP Scanner?
|
|
40
|
+
|
|
41
|
+
MCP servers give AI agents (Claude Code, Cursor, Copilot) direct access to tools, filesystems, and APIs. **But nobody is checking if those servers are safe.**
|
|
42
|
+
|
|
43
|
+
MCP Scanner analyzes:
|
|
44
|
+
- MCP server config files (Claude Code, Cursor, generic)
|
|
45
|
+
- Command-level risks (`npx --yes`, `curl|bash`, `sudo`)
|
|
46
|
+
- Secret exposure in environment variables
|
|
47
|
+
- Filesystem and network access patterns
|
|
48
|
+
- Source code of MCP server implementations (with AgentGuard integration)
|
|
49
|
+
|
|
50
|
+
## Quick Start
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
pip install mcp-scanner
|
|
54
|
+
|
|
55
|
+
# Scan your Claude Code MCP config
|
|
56
|
+
mcp-scanner
|
|
57
|
+
|
|
58
|
+
# Scan a specific config
|
|
59
|
+
mcp-scanner ~/.cursor/mcp.json
|
|
60
|
+
|
|
61
|
+
# JSON output
|
|
62
|
+
mcp-scanner .mcp.json --format json
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## What It Detects
|
|
66
|
+
|
|
67
|
+
| Rule | Severity | Description |
|
|
68
|
+
|------|----------|-------------|
|
|
69
|
+
| Remote code execution | CRITICAL | `curl | bash` patterns in server startup |
|
|
70
|
+
| Auto-install packages | HIGH | `npx --yes` without version pinning |
|
|
71
|
+
| Privileged execution | CRITICAL | Server running as root/sudo |
|
|
72
|
+
| Secret exposure | CRITICAL | Real API keys/tokens in config env vars |
|
|
73
|
+
| Host filesystem access | HIGH | Server accessing `/etc`, `/root`, `/proc` |
|
|
74
|
+
| External network access | MEDIUM | Server connecting to non-localhost URLs |
|
|
75
|
+
| Excessive tool count | LOW | Server registering >20 tools |
|
|
76
|
+
|
|
77
|
+
## Supported Configs
|
|
78
|
+
|
|
79
|
+
- Claude Code (`~/.claude/claude_code_config.json`)
|
|
80
|
+
- Cursor (`~/.cursor/mcp.json`)
|
|
81
|
+
- Project-level (`.mcp.json`)
|
|
82
|
+
- Generic MCP server configs
|
|
83
|
+
|
|
84
|
+
## AgentGuard Integration
|
|
85
|
+
|
|
86
|
+
When [AgentGuard](https://github.com/dockfixlabs/agentguard) is installed, MCP Scanner performs deep source code analysis on MCP server implementations using all 10 OWASP ASI detection rules.
|
|
87
|
+
|
|
88
|
+
## License
|
|
89
|
+
|
|
90
|
+
MIT — see [LICENSE](LICENSE).
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
Built by [Dockfix Labs](https://github.com/dockfixlabs).
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# 🔍 MCP Scanner
|
|
2
|
+
|
|
3
|
+
> **Security scanner for MCP (Model Context Protocol) servers.** Detect malicious tools, data exfiltration, and supply chain risks before connecting an MCP server to your AI agent.
|
|
4
|
+
|
|
5
|
+
[](https://python.org)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Why MCP Scanner?
|
|
11
|
+
|
|
12
|
+
MCP servers give AI agents (Claude Code, Cursor, Copilot) direct access to tools, filesystems, and APIs. **But nobody is checking if those servers are safe.**
|
|
13
|
+
|
|
14
|
+
MCP Scanner analyzes:
|
|
15
|
+
- MCP server config files (Claude Code, Cursor, generic)
|
|
16
|
+
- Command-level risks (`npx --yes`, `curl|bash`, `sudo`)
|
|
17
|
+
- Secret exposure in environment variables
|
|
18
|
+
- Filesystem and network access patterns
|
|
19
|
+
- Source code of MCP server implementations (with AgentGuard integration)
|
|
20
|
+
|
|
21
|
+
## Quick Start
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pip install mcp-scanner
|
|
25
|
+
|
|
26
|
+
# Scan your Claude Code MCP config
|
|
27
|
+
mcp-scanner
|
|
28
|
+
|
|
29
|
+
# Scan a specific config
|
|
30
|
+
mcp-scanner ~/.cursor/mcp.json
|
|
31
|
+
|
|
32
|
+
# JSON output
|
|
33
|
+
mcp-scanner .mcp.json --format json
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## What It Detects
|
|
37
|
+
|
|
38
|
+
| Rule | Severity | Description |
|
|
39
|
+
|------|----------|-------------|
|
|
40
|
+
| Remote code execution | CRITICAL | `curl | bash` patterns in server startup |
|
|
41
|
+
| Auto-install packages | HIGH | `npx --yes` without version pinning |
|
|
42
|
+
| Privileged execution | CRITICAL | Server running as root/sudo |
|
|
43
|
+
| Secret exposure | CRITICAL | Real API keys/tokens in config env vars |
|
|
44
|
+
| Host filesystem access | HIGH | Server accessing `/etc`, `/root`, `/proc` |
|
|
45
|
+
| External network access | MEDIUM | Server connecting to non-localhost URLs |
|
|
46
|
+
| Excessive tool count | LOW | Server registering >20 tools |
|
|
47
|
+
|
|
48
|
+
## Supported Configs
|
|
49
|
+
|
|
50
|
+
- Claude Code (`~/.claude/claude_code_config.json`)
|
|
51
|
+
- Cursor (`~/.cursor/mcp.json`)
|
|
52
|
+
- Project-level (`.mcp.json`)
|
|
53
|
+
- Generic MCP server configs
|
|
54
|
+
|
|
55
|
+
## AgentGuard Integration
|
|
56
|
+
|
|
57
|
+
When [AgentGuard](https://github.com/dockfixlabs/agentguard) is installed, MCP Scanner performs deep source code analysis on MCP server implementations using all 10 OWASP ASI detection rules.
|
|
58
|
+
|
|
59
|
+
## License
|
|
60
|
+
|
|
61
|
+
MIT — see [LICENSE](LICENSE).
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
Built by [Dockfix Labs](https://github.com/dockfixlabs).
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dfx-mcp-scanner
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Security scanner for MCP (Model Context Protocol) servers — detect malicious tools, data exfiltration, and supply chain risks.
|
|
5
|
+
Home-page: https://github.com/dockfixlabs/mcp-scanner
|
|
6
|
+
Author: Dockfix Labs
|
|
7
|
+
Author-email: Dockfix Labs <security@dockfixlabs.dev>
|
|
8
|
+
License: MIT
|
|
9
|
+
Keywords: mcp,model-context-protocol,security,scanner,ai-agents,claude,cursor
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Topic :: Security
|
|
14
|
+
Requires-Python: >=3.10
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
License-File: LICENSE
|
|
17
|
+
Requires-Dist: click>=8.1
|
|
18
|
+
Requires-Dist: rich>=13.0
|
|
19
|
+
Requires-Dist: pydantic>=2.0
|
|
20
|
+
Requires-Dist: pyyaml>=6.0
|
|
21
|
+
Provides-Extra: dev
|
|
22
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
23
|
+
Requires-Dist: pytest-cov; extra == "dev"
|
|
24
|
+
Requires-Dist: ruff; extra == "dev"
|
|
25
|
+
Dynamic: author
|
|
26
|
+
Dynamic: home-page
|
|
27
|
+
Dynamic: license-file
|
|
28
|
+
Dynamic: requires-python
|
|
29
|
+
|
|
30
|
+
# 🔍 MCP Scanner
|
|
31
|
+
|
|
32
|
+
> **Security scanner for MCP (Model Context Protocol) servers.** Detect malicious tools, data exfiltration, and supply chain risks before connecting an MCP server to your AI agent.
|
|
33
|
+
|
|
34
|
+
[](https://python.org)
|
|
35
|
+
[](LICENSE)
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Why MCP Scanner?
|
|
40
|
+
|
|
41
|
+
MCP servers give AI agents (Claude Code, Cursor, Copilot) direct access to tools, filesystems, and APIs. **But nobody is checking if those servers are safe.**
|
|
42
|
+
|
|
43
|
+
MCP Scanner analyzes:
|
|
44
|
+
- MCP server config files (Claude Code, Cursor, generic)
|
|
45
|
+
- Command-level risks (`npx --yes`, `curl|bash`, `sudo`)
|
|
46
|
+
- Secret exposure in environment variables
|
|
47
|
+
- Filesystem and network access patterns
|
|
48
|
+
- Source code of MCP server implementations (with AgentGuard integration)
|
|
49
|
+
|
|
50
|
+
## Quick Start
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
pip install mcp-scanner
|
|
54
|
+
|
|
55
|
+
# Scan your Claude Code MCP config
|
|
56
|
+
mcp-scanner
|
|
57
|
+
|
|
58
|
+
# Scan a specific config
|
|
59
|
+
mcp-scanner ~/.cursor/mcp.json
|
|
60
|
+
|
|
61
|
+
# JSON output
|
|
62
|
+
mcp-scanner .mcp.json --format json
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## What It Detects
|
|
66
|
+
|
|
67
|
+
| Rule | Severity | Description |
|
|
68
|
+
|------|----------|-------------|
|
|
69
|
+
| Remote code execution | CRITICAL | `curl | bash` patterns in server startup |
|
|
70
|
+
| Auto-install packages | HIGH | `npx --yes` without version pinning |
|
|
71
|
+
| Privileged execution | CRITICAL | Server running as root/sudo |
|
|
72
|
+
| Secret exposure | CRITICAL | Real API keys/tokens in config env vars |
|
|
73
|
+
| Host filesystem access | HIGH | Server accessing `/etc`, `/root`, `/proc` |
|
|
74
|
+
| External network access | MEDIUM | Server connecting to non-localhost URLs |
|
|
75
|
+
| Excessive tool count | LOW | Server registering >20 tools |
|
|
76
|
+
|
|
77
|
+
## Supported Configs
|
|
78
|
+
|
|
79
|
+
- Claude Code (`~/.claude/claude_code_config.json`)
|
|
80
|
+
- Cursor (`~/.cursor/mcp.json`)
|
|
81
|
+
- Project-level (`.mcp.json`)
|
|
82
|
+
- Generic MCP server configs
|
|
83
|
+
|
|
84
|
+
## AgentGuard Integration
|
|
85
|
+
|
|
86
|
+
When [AgentGuard](https://github.com/dockfixlabs/agentguard) is installed, MCP Scanner performs deep source code analysis on MCP server implementations using all 10 OWASP ASI detection rules.
|
|
87
|
+
|
|
88
|
+
## License
|
|
89
|
+
|
|
90
|
+
MIT — see [LICENSE](LICENSE).
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
Built by [Dockfix Labs](https://github.com/dockfixlabs).
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
setup.py
|
|
5
|
+
dfx_mcp_scanner.egg-info/PKG-INFO
|
|
6
|
+
dfx_mcp_scanner.egg-info/SOURCES.txt
|
|
7
|
+
dfx_mcp_scanner.egg-info/dependency_links.txt
|
|
8
|
+
dfx_mcp_scanner.egg-info/entry_points.txt
|
|
9
|
+
dfx_mcp_scanner.egg-info/requires.txt
|
|
10
|
+
dfx_mcp_scanner.egg-info/top_level.txt
|
|
11
|
+
mcp_scanner/__init__.py
|
|
12
|
+
mcp_scanner/cli.py
|
|
13
|
+
mcp_scanner/models.py
|
|
14
|
+
mcp_scanner/parser.py
|
|
15
|
+
mcp_scanner/scanner.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
mcp_scanner
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""CLI for MCP Scanner."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
import sys
|
|
5
|
+
import json
|
|
6
|
+
import click
|
|
7
|
+
from rich.console import Console
|
|
8
|
+
from rich.panel import Panel
|
|
9
|
+
from rich.table import Table
|
|
10
|
+
|
|
11
|
+
from mcp_scanner.scanner import scan_config
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@click.command()
|
|
15
|
+
@click.argument("config_path", default="~/.claude/claude_code_config.json")
|
|
16
|
+
@click.option("--format", "fmt", type=click.Choice(["text", "json"]), default="text")
|
|
17
|
+
@click.option("--exit-code/--no-exit-code", default=True)
|
|
18
|
+
def main(config_path: str, fmt: str, exit_code: bool) -> None:
|
|
19
|
+
"""MCP Scanner — Security scanner for MCP server configurations.
|
|
20
|
+
|
|
21
|
+
CONFIG_PATH: Path to MCP config file (default: ~/.claude/claude_code_config.json)
|
|
22
|
+
|
|
23
|
+
Examples:
|
|
24
|
+
mcp-scanner # Scan default Claude Code config
|
|
25
|
+
mcp-scanner .mcp.json # Scan project-level MCP config
|
|
26
|
+
mcp-scanner ~/.cursor/mcp.json # Scan Cursor config
|
|
27
|
+
"""
|
|
28
|
+
console = Console()
|
|
29
|
+
result = scan_config(config_path)
|
|
30
|
+
|
|
31
|
+
if fmt == "json":
|
|
32
|
+
print(json.dumps(result.model_dump(), indent=2, default=str))
|
|
33
|
+
else:
|
|
34
|
+
console.print()
|
|
35
|
+
console.print(Panel.fit(
|
|
36
|
+
f"[bold cyan]MCP Scanner[/bold cyan] — MCP Server Security Scan\n"
|
|
37
|
+
f"Config: [white]{result.target}[/white]\n"
|
|
38
|
+
f"Servers found: [white]{result.files_scanned - 1}[/white]",
|
|
39
|
+
border_style="cyan",
|
|
40
|
+
))
|
|
41
|
+
|
|
42
|
+
if result.clean:
|
|
43
|
+
console.print("[bold green]✓ No vulnerabilities found.[/bold green]")
|
|
44
|
+
else:
|
|
45
|
+
table = Table(show_header=True, header_style="bold", border_style="dim")
|
|
46
|
+
table.add_column("Severity", width=12)
|
|
47
|
+
table.add_column("Rule", width=25)
|
|
48
|
+
table.add_column("Description")
|
|
49
|
+
|
|
50
|
+
for f in result.findings:
|
|
51
|
+
sev_color = {
|
|
52
|
+
"CRITICAL": "bold red", "HIGH": "red",
|
|
53
|
+
"MEDIUM": "yellow", "LOW": "blue", "INFO": "dim",
|
|
54
|
+
}.get(f.severity.value, "white")
|
|
55
|
+
table.add_row(
|
|
56
|
+
f"[{sev_color}]{f.severity.value}[/{sev_color}]",
|
|
57
|
+
f.rule_name,
|
|
58
|
+
f.description,
|
|
59
|
+
)
|
|
60
|
+
console.print(table)
|
|
61
|
+
console.print()
|
|
62
|
+
|
|
63
|
+
console.print("[bold]Recommendations:[/bold]")
|
|
64
|
+
for f in result.findings:
|
|
65
|
+
console.print(f" • {f.rule_name}: {f.recommendation}")
|
|
66
|
+
|
|
67
|
+
console.print()
|
|
68
|
+
|
|
69
|
+
if exit_code and not result.clean:
|
|
70
|
+
sys.exit(1)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
if __name__ == "__main__":
|
|
74
|
+
main()
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""Core models for MCP Server security scanning."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
from enum import Enum
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Severity(str, Enum):
|
|
9
|
+
CRITICAL = "CRITICAL"
|
|
10
|
+
HIGH = "HIGH"
|
|
11
|
+
MEDIUM = "MEDIUM"
|
|
12
|
+
LOW = "LOW"
|
|
13
|
+
INFO = "INFO"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Finding(BaseModel):
|
|
17
|
+
rule_id: str
|
|
18
|
+
rule_name: str
|
|
19
|
+
severity: Severity
|
|
20
|
+
file: str
|
|
21
|
+
line: int = 0
|
|
22
|
+
snippet: str = ""
|
|
23
|
+
description: str
|
|
24
|
+
recommendation: str
|
|
25
|
+
confidence: float = Field(ge=0.0, le=1.0)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class ScanResult(BaseModel):
|
|
29
|
+
target: str
|
|
30
|
+
files_scanned: int
|
|
31
|
+
findings: list[Finding] = Field(default_factory=list)
|
|
32
|
+
scan_duration_ms: int = 0
|
|
33
|
+
|
|
34
|
+
@property
|
|
35
|
+
def clean(self) -> bool:
|
|
36
|
+
return len(self.findings) == 0
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def critical_count(self) -> int:
|
|
40
|
+
return sum(1 for f in self.findings if f.severity == Severity.CRITICAL)
|
|
41
|
+
|
|
42
|
+
@property
|
|
43
|
+
def high_count(self) -> int:
|
|
44
|
+
return sum(1 for f in self.findings if f.severity == Severity.HIGH)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class MCPToolSpec(BaseModel):
|
|
48
|
+
"""Parsed MCP tool definition from config or source."""
|
|
49
|
+
name: str
|
|
50
|
+
description: str = ""
|
|
51
|
+
command: str = ""
|
|
52
|
+
args: list[str] = Field(default_factory=list)
|
|
53
|
+
env: dict[str, str] = Field(default_factory=list)
|
|
54
|
+
source_file: str = ""
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class MCPServerConfig(BaseModel):
|
|
58
|
+
"""Parsed MCP server configuration (e.g. from claude_code_config.json)."""
|
|
59
|
+
servers: dict[str, MCPToolSpec] = Field(default_factory=dict)
|
|
60
|
+
source_file: str = ""
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""MCP Server config parser — reads Claude Code, Cursor, and generic MCP configs."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
import json
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from mcp_scanner.models import MCPServerConfig, MCPToolSpec
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
CONFIG_PATHS = [
|
|
10
|
+
"~/.claude/claude_code_config.json",
|
|
11
|
+
"~/.cursor/mcp.json",
|
|
12
|
+
"~/.config/mcp/servers.json",
|
|
13
|
+
".mcp.json",
|
|
14
|
+
"mcp.json",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def parse_config(config_path: str) -> MCPServerConfig:
|
|
19
|
+
"""Parse an MCP server config file."""
|
|
20
|
+
path = Path(config_path).expanduser()
|
|
21
|
+
|
|
22
|
+
if not path.exists():
|
|
23
|
+
return MCPServerConfig(source_file=str(path))
|
|
24
|
+
|
|
25
|
+
try:
|
|
26
|
+
data = json.loads(path.read_text(encoding="utf-8"))
|
|
27
|
+
except (json.JSONDecodeError, OSError):
|
|
28
|
+
return MCPServerConfig(source_file=str(path))
|
|
29
|
+
|
|
30
|
+
servers = {}
|
|
31
|
+
raw_servers = data.get("mcpServers", data.get("servers", {}))
|
|
32
|
+
|
|
33
|
+
for name, spec in raw_servers.items():
|
|
34
|
+
servers[name] = MCPToolSpec(
|
|
35
|
+
name=name,
|
|
36
|
+
description=spec.get("description", ""),
|
|
37
|
+
command=spec.get("command", ""),
|
|
38
|
+
args=spec.get("args", []),
|
|
39
|
+
env=spec.get("env", {}),
|
|
40
|
+
source_file=str(path),
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
return MCPServerConfig(servers=servers, source_file=str(path))
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def scan_mcp_source(server_name: str, spec: MCPToolSpec) -> list[str]:
|
|
47
|
+
"""Get source files referenced by an MCP server config."""
|
|
48
|
+
files = []
|
|
49
|
+
for arg in spec.args:
|
|
50
|
+
if arg.endswith((".py", ".js", ".ts", ".sh")):
|
|
51
|
+
p = Path(arg).expanduser()
|
|
52
|
+
if p.exists():
|
|
53
|
+
files.append(str(p))
|
|
54
|
+
return files
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""Scanner engine for MCP server configs and source files."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
import time
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from mcp_scanner.models import ScanResult, Finding, Severity
|
|
8
|
+
from mcp_scanner.parser import parse_config
|
|
9
|
+
from mcp_scanner.rules.checks import (
|
|
10
|
+
check_dangerous_command,
|
|
11
|
+
check_env_secrets,
|
|
12
|
+
check_unrestricted_access,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def scan_config(config_path: str) -> ScanResult:
|
|
17
|
+
"""Scan an MCP server configuration file for security issues."""
|
|
18
|
+
start = time.time()
|
|
19
|
+
config = parse_config(config_path)
|
|
20
|
+
findings: list[Finding] = []
|
|
21
|
+
|
|
22
|
+
for name, spec in config.servers.items():
|
|
23
|
+
findings.extend(check_dangerous_command(name, spec.command, spec.args, config.source_file))
|
|
24
|
+
findings.extend(check_env_secrets(name, spec.env, config.source_file))
|
|
25
|
+
findings.extend(check_unrestricted_access(name, spec.args, config.source_file))
|
|
26
|
+
|
|
27
|
+
# Also scan referenced source files
|
|
28
|
+
files_scanned = 1 # the config file
|
|
29
|
+
for name, spec in config.servers.items():
|
|
30
|
+
from mcp_scanner.parser import scan_mcp_source
|
|
31
|
+
source_files = scan_mcp_source(name, spec)
|
|
32
|
+
files_scanned += len(source_files)
|
|
33
|
+
|
|
34
|
+
duration = int((time.time() - start) * 1000)
|
|
35
|
+
|
|
36
|
+
return ScanResult(
|
|
37
|
+
target=config_path,
|
|
38
|
+
files_scanned=files_scanned,
|
|
39
|
+
findings=findings,
|
|
40
|
+
scan_duration_ms=duration,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def scan_source_file(file_path: str) -> list[Finding]:
|
|
45
|
+
"""Scan an MCP server source file for security issues."""
|
|
46
|
+
findings: list[Finding] = []
|
|
47
|
+
path = Path(file_path)
|
|
48
|
+
|
|
49
|
+
if not path.exists():
|
|
50
|
+
return findings
|
|
51
|
+
|
|
52
|
+
try:
|
|
53
|
+
content = path.read_text(encoding="utf-8", errors="ignore")
|
|
54
|
+
except OSError:
|
|
55
|
+
return findings
|
|
56
|
+
|
|
57
|
+
# Import AgentGuard rules for deep scanning
|
|
58
|
+
try:
|
|
59
|
+
from agentguard.scanner import scan_file as ag_scan
|
|
60
|
+
ag_findings = ag_scan(path)
|
|
61
|
+
findings.extend(ag_findings)
|
|
62
|
+
except ImportError:
|
|
63
|
+
# AgentGuard not installed — do basic checks
|
|
64
|
+
import re
|
|
65
|
+
lines = content.splitlines()
|
|
66
|
+
for i, line in enumerate(lines, 1):
|
|
67
|
+
stripped = line.strip()
|
|
68
|
+
if stripped.startswith("#"):
|
|
69
|
+
continue
|
|
70
|
+
if re.search(r'\beval\s*\(', stripped):
|
|
71
|
+
findings.append(Finding(
|
|
72
|
+
rule_id="MCP-EVAL",
|
|
73
|
+
rule_name="eval() in MCP server",
|
|
74
|
+
severity=Severity.CRITICAL,
|
|
75
|
+
file=str(path),
|
|
76
|
+
line=i,
|
|
77
|
+
snippet=stripped[:200],
|
|
78
|
+
description="eval() in MCP server source — code execution vulnerability",
|
|
79
|
+
recommendation="Remove eval(). Use safe alternatives.",
|
|
80
|
+
confidence=0.95,
|
|
81
|
+
))
|
|
82
|
+
|
|
83
|
+
return findings
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.backends._legacy:_Backend"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "dfx-mcp-scanner"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Security scanner for MCP (Model Context Protocol) servers — detect malicious tools, data exfiltration, and supply chain risks."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = {text = "MIT"}
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
authors = [{name = "Dockfix Labs", email = "security@dockfixlabs.dev"}]
|
|
13
|
+
keywords = ["mcp", "model-context-protocol", "security", "scanner", "ai-agents", "claude", "cursor"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 3 - Alpha",
|
|
16
|
+
"License :: OSI Approved :: MIT License",
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
"Topic :: Security",
|
|
19
|
+
]
|
|
20
|
+
dependencies = [
|
|
21
|
+
"click>=8.1",
|
|
22
|
+
"rich>=13.0",
|
|
23
|
+
"pydantic>=2.0",
|
|
24
|
+
"pyyaml>=6.0",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
[project.optional-dependencies]
|
|
28
|
+
dev = ["pytest>=7.0", "pytest-cov", "ruff"]
|
|
29
|
+
|
|
30
|
+
[project.scripts]
|
|
31
|
+
mcp-scanner = "mcp_scanner.cli:main"
|
|
32
|
+
|
|
33
|
+
[tool.ruff]
|
|
34
|
+
line-length = 100
|
|
35
|
+
target-version = "py310"
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from setuptools import setup, find_packages
|
|
2
|
+
|
|
3
|
+
setup(
|
|
4
|
+
name="dfx-mcp-scanner",
|
|
5
|
+
version="0.1.0",
|
|
6
|
+
description="Security scanner for MCP (Model Context Protocol) servers.",
|
|
7
|
+
long_description=open("README.md").read(),
|
|
8
|
+
long_description_content_type="text/markdown",
|
|
9
|
+
license="MIT",
|
|
10
|
+
python_requires=">=3.10",
|
|
11
|
+
packages=find_packages(),
|
|
12
|
+
install_requires=["click>=8.1", "rich>=13.0", "pydantic>=2.0", "pyyaml>=6.0"],
|
|
13
|
+
extras_require={"dev": ["pytest>=7.0", "pytest-cov", "ruff"]},
|
|
14
|
+
entry_points={"console_scripts": ["mcp-scanner=mcp_scanner.cli:main"]},
|
|
15
|
+
author="Dockfix Labs",
|
|
16
|
+
author_email="security@dockfixlabs.dev",
|
|
17
|
+
url="https://github.com/dockfixlabs/mcp-scanner",
|
|
18
|
+
)
|