mcp-vulnerabilities 1.0.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.
- mcp_vulnerabilities-1.0.0/PKG-INFO +110 -0
- mcp_vulnerabilities-1.0.0/README.md +99 -0
- mcp_vulnerabilities-1.0.0/pyproject.toml +28 -0
- mcp_vulnerabilities-1.0.0/setup.cfg +4 -0
- mcp_vulnerabilities-1.0.0/src/mcp_vulnerabilities/__init__.py +51 -0
- mcp_vulnerabilities-1.0.0/src/mcp_vulnerabilities/audit/__init__.py +14 -0
- mcp_vulnerabilities-1.0.0/src/mcp_vulnerabilities/audit/fixer.py +164 -0
- mcp_vulnerabilities-1.0.0/src/mcp_vulnerabilities/audit/matcher.py +493 -0
- mcp_vulnerabilities-1.0.0/src/mcp_vulnerabilities/audit/parsers.py +179 -0
- mcp_vulnerabilities-1.0.0/src/mcp_vulnerabilities/audit/reporters.py +150 -0
- mcp_vulnerabilities-1.0.0/src/mcp_vulnerabilities/catalog.py +148 -0
- mcp_vulnerabilities-1.0.0/src/mcp_vulnerabilities/cli.py +800 -0
- mcp_vulnerabilities-1.0.0/src/mcp_vulnerabilities/converters/__init__.py +17 -0
- mcp_vulnerabilities-1.0.0/src/mcp_vulnerabilities/converters/cve_json5.py +246 -0
- mcp_vulnerabilities-1.0.0/src/mcp_vulnerabilities/converters/ghsa.py +217 -0
- mcp_vulnerabilities-1.0.0/src/mcp_vulnerabilities/converters/markdown_cve.py +334 -0
- mcp_vulnerabilities-1.0.0/src/mcp_vulnerabilities/converters/nvd.py +251 -0
- mcp_vulnerabilities-1.0.0/src/mcp_vulnerabilities/converters/osv_dev.py +206 -0
- mcp_vulnerabilities-1.0.0/src/mcp_vulnerabilities/converters/verity.py +120 -0
- mcp_vulnerabilities-1.0.0/src/mcp_vulnerabilities/deduplicator.py +275 -0
- mcp_vulnerabilities-1.0.0/src/mcp_vulnerabilities/discovery/__init__.py +8 -0
- mcp_vulnerabilities-1.0.0/src/mcp_vulnerabilities/discovery/enricher.py +97 -0
- mcp_vulnerabilities-1.0.0/src/mcp_vulnerabilities/discovery/github_curated.py +103 -0
- mcp_vulnerabilities-1.0.0/src/mcp_vulnerabilities/discovery/glama.py +74 -0
- mcp_vulnerabilities-1.0.0/src/mcp_vulnerabilities/discovery/npm.py +80 -0
- mcp_vulnerabilities-1.0.0/src/mcp_vulnerabilities/discovery/orchestrator.py +112 -0
- mcp_vulnerabilities-1.0.0/src/mcp_vulnerabilities/discovery/pypi.py +85 -0
- mcp_vulnerabilities-1.0.0/src/mcp_vulnerabilities/discovery/registries.py +105 -0
- mcp_vulnerabilities-1.0.0/src/mcp_vulnerabilities/discovery/smithery.py +85 -0
- mcp_vulnerabilities-1.0.0/src/mcp_vulnerabilities/feed.py +223 -0
- mcp_vulnerabilities-1.0.0/src/mcp_vulnerabilities/filter.py +158 -0
- mcp_vulnerabilities-1.0.0/src/mcp_vulnerabilities/models.py +346 -0
- mcp_vulnerabilities-1.0.0/src/mcp_vulnerabilities/notifier.py +124 -0
- mcp_vulnerabilities-1.0.0/src/mcp_vulnerabilities/pipeline.py +776 -0
- mcp_vulnerabilities-1.0.0/src/mcp_vulnerabilities/probe/__init__.py +0 -0
- mcp_vulnerabilities-1.0.0/src/mcp_vulnerabilities/probe/client.py +83 -0
- mcp_vulnerabilities-1.0.0/src/mcp_vulnerabilities/probe/fingerprinter.py +104 -0
- mcp_vulnerabilities-1.0.0/src/mcp_vulnerabilities/probe/transport.py +135 -0
- mcp_vulnerabilities-1.0.0/src/mcp_vulnerabilities/site_builder.py +137 -0
- mcp_vulnerabilities-1.0.0/src/mcp_vulnerabilities/snapshot.py +328 -0
- mcp_vulnerabilities-1.0.0/src/mcp_vulnerabilities/state.py +116 -0
- mcp_vulnerabilities-1.0.0/src/mcp_vulnerabilities/transitive.py +296 -0
- mcp_vulnerabilities-1.0.0/src/mcp_vulnerabilities/validator.py +186 -0
- mcp_vulnerabilities-1.0.0/src/mcp_vulnerabilities.egg-info/PKG-INFO +110 -0
- mcp_vulnerabilities-1.0.0/src/mcp_vulnerabilities.egg-info/SOURCES.txt +73 -0
- mcp_vulnerabilities-1.0.0/src/mcp_vulnerabilities.egg-info/dependency_links.txt +1 -0
- mcp_vulnerabilities-1.0.0/src/mcp_vulnerabilities.egg-info/entry_points.txt +2 -0
- mcp_vulnerabilities-1.0.0/src/mcp_vulnerabilities.egg-info/requires.txt +5 -0
- mcp_vulnerabilities-1.0.0/src/mcp_vulnerabilities.egg-info/top_level.txt +1 -0
- mcp_vulnerabilities-1.0.0/tests/test_acceptance_ghsa_delta.py +151 -0
- mcp_vulnerabilities-1.0.0/tests/test_acceptance_index_preservation.py +234 -0
- mcp_vulnerabilities-1.0.0/tests/test_acceptance_pypi_versioning.py +48 -0
- mcp_vulnerabilities-1.0.0/tests/test_acceptance_range_integrity.py +189 -0
- mcp_vulnerabilities-1.0.0/tests/test_acceptance_real_repositories.py +172 -0
- mcp_vulnerabilities-1.0.0/tests/test_audit.py +308 -0
- mcp_vulnerabilities-1.0.0/tests/test_catalog_state.py +105 -0
- mcp_vulnerabilities-1.0.0/tests/test_cli_search.py +84 -0
- mcp_vulnerabilities-1.0.0/tests/test_cve_json5_converter.py +83 -0
- mcp_vulnerabilities-1.0.0/tests/test_discovery_providers.py +151 -0
- mcp_vulnerabilities-1.0.0/tests/test_feed.py +69 -0
- mcp_vulnerabilities-1.0.0/tests/test_hub_discovery.py +75 -0
- mcp_vulnerabilities-1.0.0/tests/test_mcp_relevance_filter.py +107 -0
- mcp_vulnerabilities-1.0.0/tests/test_notifier.py +84 -0
- mcp_vulnerabilities-1.0.0/tests/test_osv_converters.py +171 -0
- mcp_vulnerabilities-1.0.0/tests/test_osv_models.py +133 -0
- mcp_vulnerabilities-1.0.0/tests/test_osv_pipeline.py +479 -0
- mcp_vulnerabilities-1.0.0/tests/test_osv_sync_state.py +57 -0
- mcp_vulnerabilities-1.0.0/tests/test_osv_template_conformance.py +182 -0
- mcp_vulnerabilities-1.0.0/tests/test_osv_validator.py +199 -0
- mcp_vulnerabilities-1.0.0/tests/test_probe.py +145 -0
- mcp_vulnerabilities-1.0.0/tests/test_sarif_reporter.py +141 -0
- mcp_vulnerabilities-1.0.0/tests/test_site_builder.py +65 -0
- mcp_vulnerabilities-1.0.0/tests/test_snapshot.py +164 -0
- mcp_vulnerabilities-1.0.0/tests/test_transitive.py +211 -0
- mcp_vulnerabilities-1.0.0/tests/test_vulnerability_varieties.py +190 -0
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mcp-vulnerabilities
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Open-Source OSV-Compliant Model Context Protocol (MCP) Vulnerability Advisory Database
|
|
5
|
+
Requires-Python: >=3.10
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: packaging>=24.0
|
|
8
|
+
Provides-Extra: dev
|
|
9
|
+
Requires-Dist: pytest>=8.0.0; extra == "dev"
|
|
10
|
+
Requires-Dist: ruff>=0.4.0; extra == "dev"
|
|
11
|
+
|
|
12
|
+
# Open-Source MCP Vulnerability Advisory Database
|
|
13
|
+
|
|
14
|
+
[](https://github.com/JMartynov/mcp-vulnerabilities/actions/workflows/daily_sync.yml)
|
|
15
|
+
[](data/vulnerabilities)
|
|
16
|
+
[](https://ossf.github.io/osv-schema/)
|
|
17
|
+
[](LICENSE)
|
|
18
|
+
|
|
19
|
+
An open-source, automated **Open Source Vulnerability (OSV 1.6)** compliant security advisory database specifically curated for the **Model Context Protocol (MCP)** ecosystem.
|
|
20
|
+
|
|
21
|
+
## Sources
|
|
22
|
+
- **CVE List v5**: Official CVE JSON 5.0 records.
|
|
23
|
+
- **GitHub Security Advisories (GHSA)**: Live GraphQL/REST security alerts.
|
|
24
|
+
- **OSV.dev**: Unified open-source vulnerability database.
|
|
25
|
+
- **National Vulnerability Database (NVD)**: CVE 2.0 vulnerability metrics and CVSS scores.
|
|
26
|
+
|
|
27
|
+
## Fast Consumption
|
|
28
|
+
|
|
29
|
+
You can fetch the complete vulnerability database snapshot in a single GET request:
|
|
30
|
+
```bash
|
|
31
|
+
curl -sL https://raw.githubusercontent.com/JMartynov/mcp-vulnerabilities/main/vulnerabilities.json.gz | gzip -d > vulnerabilities.json
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Python
|
|
35
|
+
```python
|
|
36
|
+
import gzip, json, urllib.request
|
|
37
|
+
|
|
38
|
+
url = "https://raw.githubusercontent.com/JMartynov/mcp-vulnerabilities/main/vulnerabilities.json.gz"
|
|
39
|
+
with urllib.request.urlopen(url) as resp:
|
|
40
|
+
with gzip.GzipFile(fileobj=resp) as gz:
|
|
41
|
+
data = json.load(gz)
|
|
42
|
+
|
|
43
|
+
print(f"Loaded {data['total_vulnerabilities']} MCP security advisories.")
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## CI/CD Integration
|
|
47
|
+
|
|
48
|
+
You can easily prevent vulnerable MCP server dependencies from entering your production environment or being configured into AI client settings by adding this official, reusable GitHub Action to your CI workflows.
|
|
49
|
+
|
|
50
|
+
### Audit AI Client Configurations
|
|
51
|
+
```yaml
|
|
52
|
+
- name: Audit MCP Servers
|
|
53
|
+
uses: JMartynov/mcp-vulnerabilities@v1
|
|
54
|
+
with:
|
|
55
|
+
config-path: ".claude/claude_desktop_config.json"
|
|
56
|
+
fail-on-severity: "HIGH"
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Audit Dependency Manifests
|
|
60
|
+
```yaml
|
|
61
|
+
- name: Audit Transitive Dependencies
|
|
62
|
+
uses: JMartynov/mcp-vulnerabilities@v1
|
|
63
|
+
with:
|
|
64
|
+
manifest-path: "package.json"
|
|
65
|
+
fail-on-severity: "HIGH"
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## CLI Usage
|
|
69
|
+
```bash
|
|
70
|
+
# Ingest latest advisories from live APIs
|
|
71
|
+
python -m mcp_vulnerabilities.cli sync --live-api
|
|
72
|
+
|
|
73
|
+
# Validate OSV schema compliance
|
|
74
|
+
python -m mcp_vulnerabilities.cli validate --dir data/vulnerabilities
|
|
75
|
+
|
|
76
|
+
# Compile compressed snapshot
|
|
77
|
+
python -m mcp_vulnerabilities.cli snapshot
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Documentation
|
|
81
|
+
|
|
82
|
+
- [Specification](docs/SPECIFICATION.md): Details the OSV 1.6 schema extensions, semantics, and vulnerability invariants.
|
|
83
|
+
- [Implementation](docs/IMPLEMENTATION.md): Architecture blueprint covering ingestion pipelines, state machines, and acceptance tests.
|
|
84
|
+
|
|
85
|
+
## Testing
|
|
86
|
+
|
|
87
|
+
Execute the acceptance test suite to validate the end-to-end functionality:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
uv run pytest -v tests/test_acceptance_*.py
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Publishing to PyPI
|
|
94
|
+
|
|
95
|
+
### Final Optional Step: Tagging v1.0.0 for PyPI
|
|
96
|
+
When you're ready to publish the inaugural release to PyPI:
|
|
97
|
+
1. Ensure your [PyPI account](https://pypi.org/manage/account/publishing/) has a Pending Publisher configured:
|
|
98
|
+
- **Owner**: `JMartynov`
|
|
99
|
+
- **Repository**: `mcp-vulnerabilities`
|
|
100
|
+
- **Workflow name**: `release.yaml`
|
|
101
|
+
- **Environment**: `pypi` (or Any)
|
|
102
|
+
2. Create and push the release tag:
|
|
103
|
+
```bash
|
|
104
|
+
git tag -a v1.0.0 -m "Release v1.0.0: Inaugural open-source MCP vulnerability database & CLI"
|
|
105
|
+
git push origin v1.0.0
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## License
|
|
109
|
+
MIT
|
|
110
|
+
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# Open-Source MCP Vulnerability Advisory Database
|
|
2
|
+
|
|
3
|
+
[](https://github.com/JMartynov/mcp-vulnerabilities/actions/workflows/daily_sync.yml)
|
|
4
|
+
[](data/vulnerabilities)
|
|
5
|
+
[](https://ossf.github.io/osv-schema/)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
|
|
8
|
+
An open-source, automated **Open Source Vulnerability (OSV 1.6)** compliant security advisory database specifically curated for the **Model Context Protocol (MCP)** ecosystem.
|
|
9
|
+
|
|
10
|
+
## Sources
|
|
11
|
+
- **CVE List v5**: Official CVE JSON 5.0 records.
|
|
12
|
+
- **GitHub Security Advisories (GHSA)**: Live GraphQL/REST security alerts.
|
|
13
|
+
- **OSV.dev**: Unified open-source vulnerability database.
|
|
14
|
+
- **National Vulnerability Database (NVD)**: CVE 2.0 vulnerability metrics and CVSS scores.
|
|
15
|
+
|
|
16
|
+
## Fast Consumption
|
|
17
|
+
|
|
18
|
+
You can fetch the complete vulnerability database snapshot in a single GET request:
|
|
19
|
+
```bash
|
|
20
|
+
curl -sL https://raw.githubusercontent.com/JMartynov/mcp-vulnerabilities/main/vulnerabilities.json.gz | gzip -d > vulnerabilities.json
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### Python
|
|
24
|
+
```python
|
|
25
|
+
import gzip, json, urllib.request
|
|
26
|
+
|
|
27
|
+
url = "https://raw.githubusercontent.com/JMartynov/mcp-vulnerabilities/main/vulnerabilities.json.gz"
|
|
28
|
+
with urllib.request.urlopen(url) as resp:
|
|
29
|
+
with gzip.GzipFile(fileobj=resp) as gz:
|
|
30
|
+
data = json.load(gz)
|
|
31
|
+
|
|
32
|
+
print(f"Loaded {data['total_vulnerabilities']} MCP security advisories.")
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## CI/CD Integration
|
|
36
|
+
|
|
37
|
+
You can easily prevent vulnerable MCP server dependencies from entering your production environment or being configured into AI client settings by adding this official, reusable GitHub Action to your CI workflows.
|
|
38
|
+
|
|
39
|
+
### Audit AI Client Configurations
|
|
40
|
+
```yaml
|
|
41
|
+
- name: Audit MCP Servers
|
|
42
|
+
uses: JMartynov/mcp-vulnerabilities@v1
|
|
43
|
+
with:
|
|
44
|
+
config-path: ".claude/claude_desktop_config.json"
|
|
45
|
+
fail-on-severity: "HIGH"
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Audit Dependency Manifests
|
|
49
|
+
```yaml
|
|
50
|
+
- name: Audit Transitive Dependencies
|
|
51
|
+
uses: JMartynov/mcp-vulnerabilities@v1
|
|
52
|
+
with:
|
|
53
|
+
manifest-path: "package.json"
|
|
54
|
+
fail-on-severity: "HIGH"
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## CLI Usage
|
|
58
|
+
```bash
|
|
59
|
+
# Ingest latest advisories from live APIs
|
|
60
|
+
python -m mcp_vulnerabilities.cli sync --live-api
|
|
61
|
+
|
|
62
|
+
# Validate OSV schema compliance
|
|
63
|
+
python -m mcp_vulnerabilities.cli validate --dir data/vulnerabilities
|
|
64
|
+
|
|
65
|
+
# Compile compressed snapshot
|
|
66
|
+
python -m mcp_vulnerabilities.cli snapshot
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Documentation
|
|
70
|
+
|
|
71
|
+
- [Specification](docs/SPECIFICATION.md): Details the OSV 1.6 schema extensions, semantics, and vulnerability invariants.
|
|
72
|
+
- [Implementation](docs/IMPLEMENTATION.md): Architecture blueprint covering ingestion pipelines, state machines, and acceptance tests.
|
|
73
|
+
|
|
74
|
+
## Testing
|
|
75
|
+
|
|
76
|
+
Execute the acceptance test suite to validate the end-to-end functionality:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
uv run pytest -v tests/test_acceptance_*.py
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Publishing to PyPI
|
|
83
|
+
|
|
84
|
+
### Final Optional Step: Tagging v1.0.0 for PyPI
|
|
85
|
+
When you're ready to publish the inaugural release to PyPI:
|
|
86
|
+
1. Ensure your [PyPI account](https://pypi.org/manage/account/publishing/) has a Pending Publisher configured:
|
|
87
|
+
- **Owner**: `JMartynov`
|
|
88
|
+
- **Repository**: `mcp-vulnerabilities`
|
|
89
|
+
- **Workflow name**: `release.yaml`
|
|
90
|
+
- **Environment**: `pypi` (or Any)
|
|
91
|
+
2. Create and push the release tag:
|
|
92
|
+
```bash
|
|
93
|
+
git tag -a v1.0.0 -m "Release v1.0.0: Inaugural open-source MCP vulnerability database & CLI"
|
|
94
|
+
git push origin v1.0.0
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## License
|
|
98
|
+
MIT
|
|
99
|
+
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "mcp-vulnerabilities"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "Open-Source OSV-Compliant Model Context Protocol (MCP) Vulnerability Advisory Database"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
dependencies = [
|
|
12
|
+
"packaging>=24.0",
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
[project.optional-dependencies]
|
|
16
|
+
dev = [
|
|
17
|
+
"pytest>=8.0.0",
|
|
18
|
+
"ruff>=0.4.0",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
[project.scripts]
|
|
22
|
+
mcp-vuln = "mcp_vulnerabilities.cli:main"
|
|
23
|
+
|
|
24
|
+
[tool.setuptools.packages.find]
|
|
25
|
+
where = ["src"]
|
|
26
|
+
|
|
27
|
+
[tool.pytest.ini_options]
|
|
28
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""Open Source Vulnerability (OSV v1.6.0) ingestion and normalization engine."""
|
|
2
|
+
|
|
3
|
+
from mcp_vulnerabilities.converters.ghsa import GhsaConverter
|
|
4
|
+
from mcp_vulnerabilities.converters.markdown_cve import MarkdownAdvisoryConverter
|
|
5
|
+
from mcp_vulnerabilities.converters.nvd import NvdConverter
|
|
6
|
+
from mcp_vulnerabilities.converters.osv_dev import OsvDevConverter
|
|
7
|
+
from mcp_vulnerabilities.converters.verity import VerityCatalogConverter
|
|
8
|
+
from mcp_vulnerabilities.deduplicator import OsvDeduplicator
|
|
9
|
+
from mcp_vulnerabilities.models import (
|
|
10
|
+
AffectedPackage,
|
|
11
|
+
DatabaseSpecificMcp,
|
|
12
|
+
EventSpec,
|
|
13
|
+
OsvVulnerability,
|
|
14
|
+
PackageSpec,
|
|
15
|
+
RangeSpec,
|
|
16
|
+
RangeType,
|
|
17
|
+
ReferenceSpec,
|
|
18
|
+
ReferenceType,
|
|
19
|
+
SeveritySpec,
|
|
20
|
+
SeverityType,
|
|
21
|
+
build_purl,
|
|
22
|
+
parse_purl,
|
|
23
|
+
)
|
|
24
|
+
from mcp_vulnerabilities.pipeline import McpVulnerabilityPipeline, PipelineResult
|
|
25
|
+
from mcp_vulnerabilities.validator import OsvValidationError, OsvValidator
|
|
26
|
+
|
|
27
|
+
__all__ = [
|
|
28
|
+
"AffectedPackage",
|
|
29
|
+
"DatabaseSpecificMcp",
|
|
30
|
+
"EventSpec",
|
|
31
|
+
"GhsaConverter",
|
|
32
|
+
"MarkdownAdvisoryConverter",
|
|
33
|
+
"McpVulnerabilityPipeline",
|
|
34
|
+
"NvdConverter",
|
|
35
|
+
"OsvDeduplicator",
|
|
36
|
+
"OsvDevConverter",
|
|
37
|
+
"OsvValidationError",
|
|
38
|
+
"OsvValidator",
|
|
39
|
+
"OsvVulnerability",
|
|
40
|
+
"PackageSpec",
|
|
41
|
+
"PipelineResult",
|
|
42
|
+
"RangeSpec",
|
|
43
|
+
"RangeType",
|
|
44
|
+
"ReferenceSpec",
|
|
45
|
+
"ReferenceType",
|
|
46
|
+
"SeveritySpec",
|
|
47
|
+
"SeverityType",
|
|
48
|
+
"VerityCatalogConverter",
|
|
49
|
+
"build_purl",
|
|
50
|
+
"parse_purl",
|
|
51
|
+
]
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""Audit subpackage for MCP client configs and installed packages."""
|
|
2
|
+
|
|
3
|
+
from .fixer import ClientConfigFixer
|
|
4
|
+
from .matcher import AuditFinding, AuditReport, VulnerabilityMatcher
|
|
5
|
+
from .parsers import ClientConfigParser, DiscoveredClientServer
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"AuditFinding",
|
|
9
|
+
"AuditReport",
|
|
10
|
+
"ClientConfigFixer",
|
|
11
|
+
"ClientConfigParser",
|
|
12
|
+
"DiscoveredClientServer",
|
|
13
|
+
"VulnerabilityMatcher",
|
|
14
|
+
]
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
"""Automated remediation for vulnerable MCP servers in client configurations."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import logging
|
|
5
|
+
import os
|
|
6
|
+
import re
|
|
7
|
+
import shutil
|
|
8
|
+
from datetime import datetime
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
from packaging.version import InvalidVersion, Version
|
|
13
|
+
|
|
14
|
+
from .matcher import VulnerabilityMatcher, AuditFinding
|
|
15
|
+
from .parsers import ClientConfigParser
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ClientConfigFixer:
|
|
21
|
+
"""Identifies and remediates vulnerable MCP servers in client configurations."""
|
|
22
|
+
|
|
23
|
+
def __init__(self, matcher: VulnerabilityMatcher):
|
|
24
|
+
self.matcher = matcher
|
|
25
|
+
|
|
26
|
+
def _calculate_minimum_safe_version(self, findings: list[AuditFinding]) -> str | None:
|
|
27
|
+
"""Calculates the highest fixed version among all findings."""
|
|
28
|
+
if not findings:
|
|
29
|
+
return None
|
|
30
|
+
|
|
31
|
+
max_version: Version | None = None
|
|
32
|
+
best_version_str: str | None = None
|
|
33
|
+
|
|
34
|
+
for f in findings:
|
|
35
|
+
if not f.fixed_version:
|
|
36
|
+
continue
|
|
37
|
+
|
|
38
|
+
try:
|
|
39
|
+
v = Version(f.fixed_version)
|
|
40
|
+
if max_version is None or v > max_version:
|
|
41
|
+
max_version = v
|
|
42
|
+
best_version_str = f.fixed_version
|
|
43
|
+
except InvalidVersion:
|
|
44
|
+
# If we can't parse semver, just take the first one or continue
|
|
45
|
+
if max_version is None:
|
|
46
|
+
best_version_str = f.fixed_version
|
|
47
|
+
|
|
48
|
+
return best_version_str
|
|
49
|
+
|
|
50
|
+
def fix_config(self, config_path: Path, dry_run: bool = False) -> dict[str, Any]:
|
|
51
|
+
"""Fixes a client configuration file by replacing vulnerable versions."""
|
|
52
|
+
if not config_path.exists():
|
|
53
|
+
raise FileNotFoundError(f"Configuration file not found: {config_path}")
|
|
54
|
+
|
|
55
|
+
servers = ClientConfigParser.parse_file(config_path)
|
|
56
|
+
with open(config_path, "r", encoding="utf-8") as f:
|
|
57
|
+
data = json.load(f)
|
|
58
|
+
|
|
59
|
+
report = self.matcher.audit_servers(servers)
|
|
60
|
+
|
|
61
|
+
# Group findings by server name
|
|
62
|
+
findings_by_server: dict[str, list[AuditFinding]] = {}
|
|
63
|
+
for f in report.findings:
|
|
64
|
+
findings_by_server.setdefault(f.server_name, []).append(f)
|
|
65
|
+
|
|
66
|
+
changes_made = []
|
|
67
|
+
|
|
68
|
+
# We need to find the specific mcpServers key or use root
|
|
69
|
+
raw_servers = data.get("mcpServers") or data.get("mcp_servers") or data.get("servers")
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
if not isinstance(raw_servers, dict):
|
|
73
|
+
if all(isinstance(v, dict) and ("command" in v or "args" in v) for v in data.values()):
|
|
74
|
+
raw_servers = data
|
|
75
|
+
|
|
76
|
+
else:
|
|
77
|
+
return {"status": "no_servers_found"}
|
|
78
|
+
|
|
79
|
+
for server in servers:
|
|
80
|
+
findings = findings_by_server.get(server.server_name)
|
|
81
|
+
if not findings:
|
|
82
|
+
continue
|
|
83
|
+
|
|
84
|
+
safe_version = self._calculate_minimum_safe_version(findings)
|
|
85
|
+
if not safe_version:
|
|
86
|
+
logger.warning(f"No safe version found for {server.server_name}, skipping.")
|
|
87
|
+
continue
|
|
88
|
+
|
|
89
|
+
# If the current version is already >= safe_version (e.g. unpinned), skip
|
|
90
|
+
try:
|
|
91
|
+
if server.version and Version(server.version) >= Version(safe_version):
|
|
92
|
+
continue
|
|
93
|
+
except InvalidVersion:
|
|
94
|
+
pass
|
|
95
|
+
|
|
96
|
+
old_version = server.version or "unpinned"
|
|
97
|
+
|
|
98
|
+
# Find the args in the original dict
|
|
99
|
+
srv_dict = raw_servers.get(server.server_name)
|
|
100
|
+
if not srv_dict or not isinstance(srv_dict, dict):
|
|
101
|
+
continue
|
|
102
|
+
|
|
103
|
+
args = srv_dict.get("args", [])
|
|
104
|
+
new_args = list(args)
|
|
105
|
+
|
|
106
|
+
# We need to rewrite the args
|
|
107
|
+
# Simple approach: find the arg that contains the package name and old version
|
|
108
|
+
for i, arg in enumerate(new_args):
|
|
109
|
+
if server.package_name in str(arg):
|
|
110
|
+
# Rewrite this arg based on ecosystem
|
|
111
|
+
if server.ecosystem == "npm":
|
|
112
|
+
if "@" in server.package_name and server.package_name.startswith("@"):
|
|
113
|
+
# scoped package
|
|
114
|
+
new_args[i] = f"{server.package_name}@{safe_version}"
|
|
115
|
+
else:
|
|
116
|
+
new_args[i] = f"{server.package_name}@{safe_version}"
|
|
117
|
+
elif server.ecosystem == "PyPI":
|
|
118
|
+
# We use == for pipx/uvx
|
|
119
|
+
if "==" in arg or ">=" in arg:
|
|
120
|
+
new_args[i] = re.sub(r"(==|>=|<=|>|<|~=|@).*", f"=={safe_version}", arg)
|
|
121
|
+
else:
|
|
122
|
+
new_args[i] = f"{arg}=={safe_version}"
|
|
123
|
+
elif server.ecosystem == "Docker":
|
|
124
|
+
if ":" in arg:
|
|
125
|
+
pkg = arg.rsplit(":", 1)[0]
|
|
126
|
+
new_args[i] = f"{pkg}:{safe_version}"
|
|
127
|
+
else:
|
|
128
|
+
new_args[i] = f"{arg}:{safe_version}"
|
|
129
|
+
break
|
|
130
|
+
|
|
131
|
+
srv_dict["args"] = new_args
|
|
132
|
+
changes_made.append({
|
|
133
|
+
"server_name": server.server_name,
|
|
134
|
+
"package_name": server.package_name,
|
|
135
|
+
"old_version": old_version,
|
|
136
|
+
"new_version": safe_version
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
if not changes_made:
|
|
140
|
+
return {"status": "no_changes_needed", "changes": []}
|
|
141
|
+
|
|
142
|
+
if not dry_run:
|
|
143
|
+
timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
|
|
144
|
+
backup_path = config_path.with_name(f"{config_path.name}.bak.{timestamp}")
|
|
145
|
+
shutil.copy2(config_path, backup_path)
|
|
146
|
+
|
|
147
|
+
# Atomically write
|
|
148
|
+
tmp_path = config_path.with_name(f"{config_path.name}.tmp")
|
|
149
|
+
with open(tmp_path, "w", encoding="utf-8") as f:
|
|
150
|
+
json.dump(data, f, indent=2)
|
|
151
|
+
f.write("\n")
|
|
152
|
+
os.replace(tmp_path, config_path)
|
|
153
|
+
|
|
154
|
+
return {
|
|
155
|
+
"status": "success",
|
|
156
|
+
"changes": changes_made,
|
|
157
|
+
"backup_path": str(backup_path)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
"status": "dry_run",
|
|
162
|
+
"changes": changes_made
|
|
163
|
+
}
|
|
164
|
+
|