bizteamai-smcp 1.13.1__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.
- bizteamai_smcp-1.13.1/PKG-INFO +117 -0
- bizteamai_smcp-1.13.1/README.md +89 -0
- bizteamai_smcp-1.13.1/bizteamai_smcp.egg-info/PKG-INFO +117 -0
- bizteamai_smcp-1.13.1/bizteamai_smcp.egg-info/SOURCES.txt +27 -0
- bizteamai_smcp-1.13.1/bizteamai_smcp.egg-info/dependency_links.txt +1 -0
- bizteamai_smcp-1.13.1/bizteamai_smcp.egg-info/entry_points.txt +3 -0
- bizteamai_smcp-1.13.1/bizteamai_smcp.egg-info/requires.txt +11 -0
- bizteamai_smcp-1.13.1/bizteamai_smcp.egg-info/top_level.txt +1 -0
- bizteamai_smcp-1.13.1/pyproject.toml +53 -0
- bizteamai_smcp-1.13.1/setup.cfg +4 -0
- bizteamai_smcp-1.13.1/smcp/__init__.py +29 -0
- bizteamai_smcp-1.13.1/smcp/allowlist.py +169 -0
- bizteamai_smcp-1.13.1/smcp/app_wrapper.py +216 -0
- bizteamai_smcp-1.13.1/smcp/cli/__init__.py +3 -0
- bizteamai_smcp-1.13.1/smcp/cli/approve.py +261 -0
- bizteamai_smcp-1.13.1/smcp/cli/gen_key.py +73 -0
- bizteamai_smcp-1.13.1/smcp/cli/mkcert.py +327 -0
- bizteamai_smcp-1.13.1/smcp/cli/revoke.py +73 -0
- bizteamai_smcp-1.13.1/smcp/confirm.py +262 -0
- bizteamai_smcp-1.13.1/smcp/cpu.py +67 -0
- bizteamai_smcp-1.13.1/smcp/decorators.py +97 -0
- bizteamai_smcp-1.13.1/smcp/enforce.py +100 -0
- bizteamai_smcp-1.13.1/smcp/filters.py +176 -0
- bizteamai_smcp-1.13.1/smcp/license.py +113 -0
- bizteamai_smcp-1.13.1/smcp/logchain.py +270 -0
- bizteamai_smcp-1.13.1/smcp/tls.py +160 -0
- bizteamai_smcp-1.13.1/tests/test_app_wrapper.py +230 -0
- bizteamai_smcp-1.13.1/tests/test_decorators.py +204 -0
- bizteamai_smcp-1.13.1/tests/test_integration.py +223 -0
@@ -0,0 +1,117 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: bizteamai-smcp
|
3
|
+
Version: 1.13.1
|
4
|
+
Summary: Secure Model Context Protocol - Security layers for MCP servers
|
5
|
+
Author: SMCP Contributors
|
6
|
+
License: MIT
|
7
|
+
Classifier: Development Status :: 3 - Alpha
|
8
|
+
Classifier: Intended Audience :: Developers
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
11
|
+
Classifier: Programming Language :: Python :: 3.8
|
12
|
+
Classifier: Programming Language :: Python :: 3.9
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
16
|
+
Requires-Python: >=3.8
|
17
|
+
Description-Content-Type: text/markdown
|
18
|
+
Requires-Dist: mcp
|
19
|
+
Requires-Dist: fastmcp
|
20
|
+
Requires-Dist: cryptography
|
21
|
+
Requires-Dist: pyyaml
|
22
|
+
Provides-Extra: dev
|
23
|
+
Requires-Dist: pytest; extra == "dev"
|
24
|
+
Requires-Dist: pytest-asyncio; extra == "dev"
|
25
|
+
Requires-Dist: black; extra == "dev"
|
26
|
+
Requires-Dist: isort; extra == "dev"
|
27
|
+
Requires-Dist: mypy; extra == "dev"
|
28
|
+
|
29
|
+
# SMCP - Secure Model Context Protocol
|
30
|
+
|
31
|
+
A security-focused wrapper library for MCP (Model Context Protocol) servers, providing multiple layers of protection through conditional guards that activate only when needed.
|
32
|
+
|
33
|
+
## Features
|
34
|
+
|
35
|
+
- **Conditional Security Guards**: Each security layer activates only when its required configuration is present
|
36
|
+
- **Mutual TLS Support**: Automatic certificate-based authentication
|
37
|
+
- **Host Allowlisting**: Outbound connection validation
|
38
|
+
- **Input Sanitization**: Prompt and parameter filtering
|
39
|
+
- **Destructive Action Confirmation**: Queue-based approval system for dangerous operations
|
40
|
+
- **Tamper-proof Logging**: SHA-chained append-only audit logs
|
41
|
+
- **Universal Coverage**: Same decorator factory works for tools, prompts, and retrieval
|
42
|
+
|
43
|
+
## Quick Start
|
44
|
+
|
45
|
+
```python
|
46
|
+
from smcp import FastSMCP as FastMCP
|
47
|
+
from smcp import tool, prompt
|
48
|
+
|
49
|
+
# Configure security features (all optional)
|
50
|
+
cfg = {
|
51
|
+
"ca_path": "ca.pem",
|
52
|
+
"cert_path": "server.pem",
|
53
|
+
"key_path": "server.key",
|
54
|
+
"ALLOWED_HOSTS": ["api.internal.local", "10.0.0.5"],
|
55
|
+
"SAFE_RE": r"^[\w\s.,:;!?-]{1,2048}$",
|
56
|
+
"LOG_PATH": "/var/log/smcp.log"
|
57
|
+
}
|
58
|
+
|
59
|
+
app = FastMCP("myserver", smcp_cfg=cfg)
|
60
|
+
|
61
|
+
@tool(confirm=True) # Requires approval
|
62
|
+
def delete_user(uid: str):
|
63
|
+
...
|
64
|
+
|
65
|
+
@prompt() # Auto-filtered if SAFE_RE present
|
66
|
+
def chat(prompt: str):
|
67
|
+
...
|
68
|
+
```
|
69
|
+
|
70
|
+
## Security Guards
|
71
|
+
|
72
|
+
| Feature | Activation Trigger | Purpose |
|
73
|
+
|---------|-------------------|---------|
|
74
|
+
| Mutual TLS | `ca_path`, `cert_path`, `key_path` in config | Certificate-based authentication |
|
75
|
+
| Host Allowlist | Non-empty `ALLOWED_HOSTS` | Outbound connection validation |
|
76
|
+
| Input Filtering | `SAFE_RE` or `MAX_LEN` defined | Sanitize prompts and parameters |
|
77
|
+
| Action Confirmation | `confirm=True` on decorator | Queue destructive operations for approval |
|
78
|
+
| Audit Logging | `LOG_PATH` set | Tamper-proof operation logging |
|
79
|
+
|
80
|
+
## CLI Tools
|
81
|
+
|
82
|
+
```bash
|
83
|
+
# Generate certificates
|
84
|
+
smcp-mkcert --ca-name "MyCA" --server-name "myserver.local"
|
85
|
+
|
86
|
+
# Approve queued actions
|
87
|
+
smcp-approve <action-id>
|
88
|
+
```
|
89
|
+
|
90
|
+
## Installation
|
91
|
+
|
92
|
+
### From PyPI.org (Public)
|
93
|
+
|
94
|
+
```bash
|
95
|
+
pip install bizteam-smcp
|
96
|
+
```
|
97
|
+
|
98
|
+
### From Private PyPI Server
|
99
|
+
|
100
|
+
```bash
|
101
|
+
# Using private PyPI server
|
102
|
+
pip install --extra-index-url https://bizteamai.com/pypi/simple/ bizteam-smcp
|
103
|
+
```
|
104
|
+
|
105
|
+
### Upgrading to Business Edition
|
106
|
+
|
107
|
+
For additional features and enterprise support, a business edition is available:
|
108
|
+
|
109
|
+
```bash
|
110
|
+
pip install --extra-index-url https://bizteamai.com/pypi/simple/ bizteam-smcp-biz
|
111
|
+
```
|
112
|
+
|
113
|
+
**Contact**: [business@bizteamai.com](mailto:business@bizteamai.com) for more information.
|
114
|
+
|
115
|
+
## License
|
116
|
+
|
117
|
+
MIT License
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# SMCP - Secure Model Context Protocol
|
2
|
+
|
3
|
+
A security-focused wrapper library for MCP (Model Context Protocol) servers, providing multiple layers of protection through conditional guards that activate only when needed.
|
4
|
+
|
5
|
+
## Features
|
6
|
+
|
7
|
+
- **Conditional Security Guards**: Each security layer activates only when its required configuration is present
|
8
|
+
- **Mutual TLS Support**: Automatic certificate-based authentication
|
9
|
+
- **Host Allowlisting**: Outbound connection validation
|
10
|
+
- **Input Sanitization**: Prompt and parameter filtering
|
11
|
+
- **Destructive Action Confirmation**: Queue-based approval system for dangerous operations
|
12
|
+
- **Tamper-proof Logging**: SHA-chained append-only audit logs
|
13
|
+
- **Universal Coverage**: Same decorator factory works for tools, prompts, and retrieval
|
14
|
+
|
15
|
+
## Quick Start
|
16
|
+
|
17
|
+
```python
|
18
|
+
from smcp import FastSMCP as FastMCP
|
19
|
+
from smcp import tool, prompt
|
20
|
+
|
21
|
+
# Configure security features (all optional)
|
22
|
+
cfg = {
|
23
|
+
"ca_path": "ca.pem",
|
24
|
+
"cert_path": "server.pem",
|
25
|
+
"key_path": "server.key",
|
26
|
+
"ALLOWED_HOSTS": ["api.internal.local", "10.0.0.5"],
|
27
|
+
"SAFE_RE": r"^[\w\s.,:;!?-]{1,2048}$",
|
28
|
+
"LOG_PATH": "/var/log/smcp.log"
|
29
|
+
}
|
30
|
+
|
31
|
+
app = FastMCP("myserver", smcp_cfg=cfg)
|
32
|
+
|
33
|
+
@tool(confirm=True) # Requires approval
|
34
|
+
def delete_user(uid: str):
|
35
|
+
...
|
36
|
+
|
37
|
+
@prompt() # Auto-filtered if SAFE_RE present
|
38
|
+
def chat(prompt: str):
|
39
|
+
...
|
40
|
+
```
|
41
|
+
|
42
|
+
## Security Guards
|
43
|
+
|
44
|
+
| Feature | Activation Trigger | Purpose |
|
45
|
+
|---------|-------------------|---------|
|
46
|
+
| Mutual TLS | `ca_path`, `cert_path`, `key_path` in config | Certificate-based authentication |
|
47
|
+
| Host Allowlist | Non-empty `ALLOWED_HOSTS` | Outbound connection validation |
|
48
|
+
| Input Filtering | `SAFE_RE` or `MAX_LEN` defined | Sanitize prompts and parameters |
|
49
|
+
| Action Confirmation | `confirm=True` on decorator | Queue destructive operations for approval |
|
50
|
+
| Audit Logging | `LOG_PATH` set | Tamper-proof operation logging |
|
51
|
+
|
52
|
+
## CLI Tools
|
53
|
+
|
54
|
+
```bash
|
55
|
+
# Generate certificates
|
56
|
+
smcp-mkcert --ca-name "MyCA" --server-name "myserver.local"
|
57
|
+
|
58
|
+
# Approve queued actions
|
59
|
+
smcp-approve <action-id>
|
60
|
+
```
|
61
|
+
|
62
|
+
## Installation
|
63
|
+
|
64
|
+
### From PyPI.org (Public)
|
65
|
+
|
66
|
+
```bash
|
67
|
+
pip install bizteam-smcp
|
68
|
+
```
|
69
|
+
|
70
|
+
### From Private PyPI Server
|
71
|
+
|
72
|
+
```bash
|
73
|
+
# Using private PyPI server
|
74
|
+
pip install --extra-index-url https://bizteamai.com/pypi/simple/ bizteam-smcp
|
75
|
+
```
|
76
|
+
|
77
|
+
### Upgrading to Business Edition
|
78
|
+
|
79
|
+
For additional features and enterprise support, a business edition is available:
|
80
|
+
|
81
|
+
```bash
|
82
|
+
pip install --extra-index-url https://bizteamai.com/pypi/simple/ bizteam-smcp-biz
|
83
|
+
```
|
84
|
+
|
85
|
+
**Contact**: [business@bizteamai.com](mailto:business@bizteamai.com) for more information.
|
86
|
+
|
87
|
+
## License
|
88
|
+
|
89
|
+
MIT License
|
@@ -0,0 +1,117 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: bizteamai-smcp
|
3
|
+
Version: 1.13.1
|
4
|
+
Summary: Secure Model Context Protocol - Security layers for MCP servers
|
5
|
+
Author: SMCP Contributors
|
6
|
+
License: MIT
|
7
|
+
Classifier: Development Status :: 3 - Alpha
|
8
|
+
Classifier: Intended Audience :: Developers
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
11
|
+
Classifier: Programming Language :: Python :: 3.8
|
12
|
+
Classifier: Programming Language :: Python :: 3.9
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
16
|
+
Requires-Python: >=3.8
|
17
|
+
Description-Content-Type: text/markdown
|
18
|
+
Requires-Dist: mcp
|
19
|
+
Requires-Dist: fastmcp
|
20
|
+
Requires-Dist: cryptography
|
21
|
+
Requires-Dist: pyyaml
|
22
|
+
Provides-Extra: dev
|
23
|
+
Requires-Dist: pytest; extra == "dev"
|
24
|
+
Requires-Dist: pytest-asyncio; extra == "dev"
|
25
|
+
Requires-Dist: black; extra == "dev"
|
26
|
+
Requires-Dist: isort; extra == "dev"
|
27
|
+
Requires-Dist: mypy; extra == "dev"
|
28
|
+
|
29
|
+
# SMCP - Secure Model Context Protocol
|
30
|
+
|
31
|
+
A security-focused wrapper library for MCP (Model Context Protocol) servers, providing multiple layers of protection through conditional guards that activate only when needed.
|
32
|
+
|
33
|
+
## Features
|
34
|
+
|
35
|
+
- **Conditional Security Guards**: Each security layer activates only when its required configuration is present
|
36
|
+
- **Mutual TLS Support**: Automatic certificate-based authentication
|
37
|
+
- **Host Allowlisting**: Outbound connection validation
|
38
|
+
- **Input Sanitization**: Prompt and parameter filtering
|
39
|
+
- **Destructive Action Confirmation**: Queue-based approval system for dangerous operations
|
40
|
+
- **Tamper-proof Logging**: SHA-chained append-only audit logs
|
41
|
+
- **Universal Coverage**: Same decorator factory works for tools, prompts, and retrieval
|
42
|
+
|
43
|
+
## Quick Start
|
44
|
+
|
45
|
+
```python
|
46
|
+
from smcp import FastSMCP as FastMCP
|
47
|
+
from smcp import tool, prompt
|
48
|
+
|
49
|
+
# Configure security features (all optional)
|
50
|
+
cfg = {
|
51
|
+
"ca_path": "ca.pem",
|
52
|
+
"cert_path": "server.pem",
|
53
|
+
"key_path": "server.key",
|
54
|
+
"ALLOWED_HOSTS": ["api.internal.local", "10.0.0.5"],
|
55
|
+
"SAFE_RE": r"^[\w\s.,:;!?-]{1,2048}$",
|
56
|
+
"LOG_PATH": "/var/log/smcp.log"
|
57
|
+
}
|
58
|
+
|
59
|
+
app = FastMCP("myserver", smcp_cfg=cfg)
|
60
|
+
|
61
|
+
@tool(confirm=True) # Requires approval
|
62
|
+
def delete_user(uid: str):
|
63
|
+
...
|
64
|
+
|
65
|
+
@prompt() # Auto-filtered if SAFE_RE present
|
66
|
+
def chat(prompt: str):
|
67
|
+
...
|
68
|
+
```
|
69
|
+
|
70
|
+
## Security Guards
|
71
|
+
|
72
|
+
| Feature | Activation Trigger | Purpose |
|
73
|
+
|---------|-------------------|---------|
|
74
|
+
| Mutual TLS | `ca_path`, `cert_path`, `key_path` in config | Certificate-based authentication |
|
75
|
+
| Host Allowlist | Non-empty `ALLOWED_HOSTS` | Outbound connection validation |
|
76
|
+
| Input Filtering | `SAFE_RE` or `MAX_LEN` defined | Sanitize prompts and parameters |
|
77
|
+
| Action Confirmation | `confirm=True` on decorator | Queue destructive operations for approval |
|
78
|
+
| Audit Logging | `LOG_PATH` set | Tamper-proof operation logging |
|
79
|
+
|
80
|
+
## CLI Tools
|
81
|
+
|
82
|
+
```bash
|
83
|
+
# Generate certificates
|
84
|
+
smcp-mkcert --ca-name "MyCA" --server-name "myserver.local"
|
85
|
+
|
86
|
+
# Approve queued actions
|
87
|
+
smcp-approve <action-id>
|
88
|
+
```
|
89
|
+
|
90
|
+
## Installation
|
91
|
+
|
92
|
+
### From PyPI.org (Public)
|
93
|
+
|
94
|
+
```bash
|
95
|
+
pip install bizteam-smcp
|
96
|
+
```
|
97
|
+
|
98
|
+
### From Private PyPI Server
|
99
|
+
|
100
|
+
```bash
|
101
|
+
# Using private PyPI server
|
102
|
+
pip install --extra-index-url https://bizteamai.com/pypi/simple/ bizteam-smcp
|
103
|
+
```
|
104
|
+
|
105
|
+
### Upgrading to Business Edition
|
106
|
+
|
107
|
+
For additional features and enterprise support, a business edition is available:
|
108
|
+
|
109
|
+
```bash
|
110
|
+
pip install --extra-index-url https://bizteamai.com/pypi/simple/ bizteam-smcp-biz
|
111
|
+
```
|
112
|
+
|
113
|
+
**Contact**: [business@bizteamai.com](mailto:business@bizteamai.com) for more information.
|
114
|
+
|
115
|
+
## License
|
116
|
+
|
117
|
+
MIT License
|
@@ -0,0 +1,27 @@
|
|
1
|
+
README.md
|
2
|
+
pyproject.toml
|
3
|
+
bizteamai_smcp.egg-info/PKG-INFO
|
4
|
+
bizteamai_smcp.egg-info/SOURCES.txt
|
5
|
+
bizteamai_smcp.egg-info/dependency_links.txt
|
6
|
+
bizteamai_smcp.egg-info/entry_points.txt
|
7
|
+
bizteamai_smcp.egg-info/requires.txt
|
8
|
+
bizteamai_smcp.egg-info/top_level.txt
|
9
|
+
smcp/__init__.py
|
10
|
+
smcp/allowlist.py
|
11
|
+
smcp/app_wrapper.py
|
12
|
+
smcp/confirm.py
|
13
|
+
smcp/cpu.py
|
14
|
+
smcp/decorators.py
|
15
|
+
smcp/enforce.py
|
16
|
+
smcp/filters.py
|
17
|
+
smcp/license.py
|
18
|
+
smcp/logchain.py
|
19
|
+
smcp/tls.py
|
20
|
+
smcp/cli/__init__.py
|
21
|
+
smcp/cli/approve.py
|
22
|
+
smcp/cli/gen_key.py
|
23
|
+
smcp/cli/mkcert.py
|
24
|
+
smcp/cli/revoke.py
|
25
|
+
tests/test_app_wrapper.py
|
26
|
+
tests/test_decorators.py
|
27
|
+
tests/test_integration.py
|
@@ -0,0 +1 @@
|
|
1
|
+
|
@@ -0,0 +1 @@
|
|
1
|
+
smcp
|
@@ -0,0 +1,53 @@
|
|
1
|
+
[build-system]
|
2
|
+
requires = ["setuptools>=61.0", "wheel"]
|
3
|
+
build-backend = "setuptools.build_meta"
|
4
|
+
|
5
|
+
[project]
|
6
|
+
name = "bizteamai-smcp"
|
7
|
+
version = "1.13.1"
|
8
|
+
description = "Secure Model Context Protocol - Security layers for MCP servers"
|
9
|
+
authors = [{name = "SMCP Contributors"}]
|
10
|
+
license = {text = "MIT"}
|
11
|
+
readme = "README.md"
|
12
|
+
requires-python = ">=3.8"
|
13
|
+
classifiers = [
|
14
|
+
"Development Status :: 3 - Alpha",
|
15
|
+
"Intended Audience :: Developers",
|
16
|
+
"License :: OSI Approved :: MIT License",
|
17
|
+
"Programming Language :: Python :: 3",
|
18
|
+
"Programming Language :: Python :: 3.8",
|
19
|
+
"Programming Language :: Python :: 3.9",
|
20
|
+
"Programming Language :: Python :: 3.10",
|
21
|
+
"Programming Language :: Python :: 3.11",
|
22
|
+
"Programming Language :: Python :: 3.12",
|
23
|
+
]
|
24
|
+
dependencies = [
|
25
|
+
"mcp",
|
26
|
+
"fastmcp",
|
27
|
+
"cryptography",
|
28
|
+
"pyyaml",
|
29
|
+
]
|
30
|
+
|
31
|
+
[project.optional-dependencies]
|
32
|
+
dev = [
|
33
|
+
"pytest",
|
34
|
+
"pytest-asyncio",
|
35
|
+
"black",
|
36
|
+
"isort",
|
37
|
+
"mypy",
|
38
|
+
]
|
39
|
+
|
40
|
+
[project.scripts]
|
41
|
+
smcp-mkcert = "smcp.cli.mkcert:main"
|
42
|
+
smcp-approve = "smcp.cli.approve:main"
|
43
|
+
|
44
|
+
[tool.setuptools.packages.find]
|
45
|
+
where = ["."]
|
46
|
+
include = ["smcp*"]
|
47
|
+
|
48
|
+
[tool.black]
|
49
|
+
line-length = 88
|
50
|
+
target-version = ['py38']
|
51
|
+
|
52
|
+
[tool.isort]
|
53
|
+
profile = "black"
|
@@ -0,0 +1,29 @@
|
|
1
|
+
"""
|
2
|
+
SMCP - Secure Model Context Protocol
|
3
|
+
|
4
|
+
A security-focused wrapper library for MCP servers providing conditional
|
5
|
+
security guards that activate only when their required configuration is present.
|
6
|
+
"""
|
7
|
+
|
8
|
+
import logging
|
9
|
+
|
10
|
+
from .app_wrapper import FastSMCP
|
11
|
+
from .decorators import tool, prompt, retrieval
|
12
|
+
|
13
|
+
__version__ = "0.1.0"
|
14
|
+
__all__ = ["FastSMCP", "tool", "prompt", "retrieval"]
|
15
|
+
|
16
|
+
# Non-intrusive watermark for community edition
|
17
|
+
def _show_watermark():
|
18
|
+
"""Display a subtle watermark message for the community edition."""
|
19
|
+
logger = logging.getLogger(__name__)
|
20
|
+
logger.info("SMCP Community Edition - For commercial licensing visit: https://smcp.dev/business")
|
21
|
+
|
22
|
+
# Show watermark on import (only once)
|
23
|
+
try:
|
24
|
+
if not hasattr(_show_watermark, '_shown'):
|
25
|
+
_show_watermark()
|
26
|
+
_show_watermark._shown = True
|
27
|
+
except Exception:
|
28
|
+
# Silently fail if logging isn't configured
|
29
|
+
pass
|
@@ -0,0 +1,169 @@
|
|
1
|
+
"""
|
2
|
+
Host allowlist validation for outbound connections.
|
3
|
+
"""
|
4
|
+
|
5
|
+
import ipaddress
|
6
|
+
import re
|
7
|
+
from typing import Dict, List, Union
|
8
|
+
from urllib.parse import urlparse
|
9
|
+
|
10
|
+
|
11
|
+
class HostValidationError(Exception):
|
12
|
+
"""Raised when a host fails allowlist validation."""
|
13
|
+
pass
|
14
|
+
|
15
|
+
|
16
|
+
def validate_host(target: str, cfg: Dict[str, Union[str, List[str]]]) -> None:
|
17
|
+
"""
|
18
|
+
Validate that a target host is in the allowlist.
|
19
|
+
|
20
|
+
Args:
|
21
|
+
target: Target host, URL, or IP address to validate
|
22
|
+
cfg: Configuration dictionary containing ALLOWED_HOSTS
|
23
|
+
|
24
|
+
Raises:
|
25
|
+
HostValidationError: If the host is not in the allowlist
|
26
|
+
"""
|
27
|
+
allowed_hosts = cfg.get("ALLOWED_HOSTS", [])
|
28
|
+
if not allowed_hosts:
|
29
|
+
return # No allowlist configured, allow all
|
30
|
+
|
31
|
+
# Extract hostname from URL if needed
|
32
|
+
hostname = _extract_hostname(target)
|
33
|
+
|
34
|
+
# Check against allowlist
|
35
|
+
if not _is_host_allowed(hostname, allowed_hosts):
|
36
|
+
raise HostValidationError(f"Host '{hostname}' not in allowlist")
|
37
|
+
|
38
|
+
|
39
|
+
def _extract_hostname(target: str) -> str:
|
40
|
+
"""
|
41
|
+
Extract hostname from a target string (URL, hostname, or IP).
|
42
|
+
|
43
|
+
Args:
|
44
|
+
target: Target string to parse
|
45
|
+
|
46
|
+
Returns:
|
47
|
+
Extracted hostname or IP address
|
48
|
+
"""
|
49
|
+
# If it looks like a URL, parse it
|
50
|
+
if "://" in target:
|
51
|
+
parsed = urlparse(target)
|
52
|
+
return parsed.hostname or parsed.netloc
|
53
|
+
|
54
|
+
# If it contains a port, strip it
|
55
|
+
if ":" in target and not _is_ipv6(target):
|
56
|
+
return target.split(":")[0]
|
57
|
+
|
58
|
+
return target
|
59
|
+
|
60
|
+
|
61
|
+
def _is_ipv6(address: str) -> bool:
|
62
|
+
"""Check if a string is an IPv6 address."""
|
63
|
+
try:
|
64
|
+
ipaddress.IPv6Address(address)
|
65
|
+
return True
|
66
|
+
except ipaddress.AddressValueError:
|
67
|
+
return False
|
68
|
+
|
69
|
+
|
70
|
+
def _is_host_allowed(hostname: str, allowed_hosts: List[str]) -> bool:
|
71
|
+
"""
|
72
|
+
Check if a hostname is in the allowlist.
|
73
|
+
|
74
|
+
Args:
|
75
|
+
hostname: Hostname to check
|
76
|
+
allowed_hosts: List of allowed hosts (can include patterns)
|
77
|
+
|
78
|
+
Returns:
|
79
|
+
True if the hostname is allowed
|
80
|
+
"""
|
81
|
+
for allowed in allowed_hosts:
|
82
|
+
if _host_matches(hostname, allowed):
|
83
|
+
return True
|
84
|
+
return False
|
85
|
+
|
86
|
+
|
87
|
+
def _host_matches(hostname: str, pattern: str) -> bool:
|
88
|
+
"""
|
89
|
+
Check if a hostname matches an allowlist pattern.
|
90
|
+
|
91
|
+
Supports:
|
92
|
+
- Exact matches: "api.example.com"
|
93
|
+
- Wildcard subdomains: "*.example.com"
|
94
|
+
- IP addresses: "192.168.1.1"
|
95
|
+
- IP ranges: "192.168.1.0/24"
|
96
|
+
|
97
|
+
Args:
|
98
|
+
hostname: Hostname to check
|
99
|
+
pattern: Pattern to match against
|
100
|
+
|
101
|
+
Returns:
|
102
|
+
True if the hostname matches the pattern
|
103
|
+
"""
|
104
|
+
# Exact match
|
105
|
+
if hostname == pattern:
|
106
|
+
return True
|
107
|
+
|
108
|
+
# Wildcard subdomain match
|
109
|
+
if pattern.startswith("*."):
|
110
|
+
domain = pattern[2:]
|
111
|
+
return hostname.endswith(f".{domain}") or hostname == domain
|
112
|
+
|
113
|
+
# IP range match
|
114
|
+
if "/" in pattern:
|
115
|
+
try:
|
116
|
+
network = ipaddress.ip_network(pattern, strict=False)
|
117
|
+
address = ipaddress.ip_address(hostname)
|
118
|
+
return address in network
|
119
|
+
except (ipaddress.AddressValueError, ValueError):
|
120
|
+
pass
|
121
|
+
|
122
|
+
# Regex pattern match (if pattern contains regex characters)
|
123
|
+
if any(char in pattern for char in r"[](){}+?^$|\\"):
|
124
|
+
try:
|
125
|
+
return bool(re.match(pattern, hostname))
|
126
|
+
except re.error:
|
127
|
+
pass
|
128
|
+
|
129
|
+
return False
|
130
|
+
|
131
|
+
|
132
|
+
def add_host_to_allowlist(cfg: Dict[str, List[str]], host: str) -> None:
|
133
|
+
"""
|
134
|
+
Add a host to the allowlist configuration.
|
135
|
+
|
136
|
+
Args:
|
137
|
+
cfg: Configuration dictionary to modify
|
138
|
+
host: Host to add to the allowlist
|
139
|
+
"""
|
140
|
+
if "ALLOWED_HOSTS" not in cfg:
|
141
|
+
cfg["ALLOWED_HOSTS"] = []
|
142
|
+
|
143
|
+
if host not in cfg["ALLOWED_HOSTS"]:
|
144
|
+
cfg["ALLOWED_HOSTS"].append(host)
|
145
|
+
|
146
|
+
|
147
|
+
def remove_host_from_allowlist(cfg: Dict[str, List[str]], host: str) -> None:
|
148
|
+
"""
|
149
|
+
Remove a host from the allowlist configuration.
|
150
|
+
|
151
|
+
Args:
|
152
|
+
cfg: Configuration dictionary to modify
|
153
|
+
host: Host to remove from the allowlist
|
154
|
+
"""
|
155
|
+
if "ALLOWED_HOSTS" in cfg and host in cfg["ALLOWED_HOSTS"]:
|
156
|
+
cfg["ALLOWED_HOSTS"].remove(host)
|
157
|
+
|
158
|
+
|
159
|
+
def get_allowed_hosts(cfg: Dict[str, List[str]]) -> List[str]:
|
160
|
+
"""
|
161
|
+
Get the current allowlist.
|
162
|
+
|
163
|
+
Args:
|
164
|
+
cfg: Configuration dictionary
|
165
|
+
|
166
|
+
Returns:
|
167
|
+
List of allowed hosts
|
168
|
+
"""
|
169
|
+
return cfg.get("ALLOWED_HOSTS", [])
|