Skip to content

Commit e9a60b7

Browse files
authored
fix: enforce tls version (#110)
* fix: tls v1.2 * fix: force tls version
1 parent 3acd375 commit e9a60b7

File tree

5 files changed

+59
-70
lines changed

5 files changed

+59
-70
lines changed

src/mcp_scan/Storage.py

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
from mcp_scan_server.models import DEFAULT_GUARDRAIL_CONFIG, GuardrailConfigFile
1414

1515
from .models import Entity, ScannedEntities, ScannedEntity, entity_type_to_str, hash_entity
16-
from .utils import upload_whitelist_entry
1716

1817
# Set up logger for this module
1918
logger = logging.getLogger(__name__)
@@ -167,19 +166,11 @@ def print_whitelist(self) -> None:
167166
rich.print(entity_type, name, self.whitelist[key])
168167
rich.print(f"[bold]{len(whitelist_keys)} entries in whitelist[/bold]")
169168

170-
def add_to_whitelist(self, entity_type: str, name: str, hash: str, base_url: str | None = None) -> None:
169+
def add_to_whitelist(self, entity_type: str, name: str, hash: str) -> None:
171170
key = f"{entity_type}.{name}"
172171
logger.info("Adding to whitelist: %s with hash: %s", key, hash)
173172
self.whitelist[key] = hash
174173
self.save()
175-
if base_url is not None:
176-
logger.debug("Uploading whitelist entry to base URL: %s", base_url)
177-
with contextlib.suppress(Exception):
178-
try:
179-
asyncio.run(upload_whitelist_entry(name, hash, base_url))
180-
logger.info("Successfully uploaded whitelist entry to remote server")
181-
except Exception as e:
182-
logger.warning("Failed to upload whitelist entry: %s", e)
183174

184175
def is_whitelisted(self, entity: Entity) -> bool:
185176
hash = hash_entity(entity)

src/mcp_scan/cli.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -517,12 +517,7 @@ def server(on_exit=None):
517517
sf.print_whitelist()
518518
sys.exit(0)
519519
elif all(x is not None for x in [args.type, args.name, args.hash]):
520-
sf.add_to_whitelist(
521-
args.type,
522-
args.name,
523-
args.hash,
524-
base_url=args.base_url if not args.local_only else None,
525-
)
520+
sf.add_to_whitelist(args.type, args.name, args.hash)
526521
sf.print_whitelist()
527522
sys.exit(0)
528523
else:
@@ -580,7 +575,14 @@ async def run_scan_inspect(mode="scan", args=None):
580575
and args.control_server
581576
and hasattr(args, "opt_out")
582577
):
583-
await upload(result, args.control_server, args.control_identifier, args.opt_out, additional_headers=parse_headers(args.control_server_H))
578+
await upload(
579+
result,
580+
args.control_server,
581+
args.control_identifier,
582+
args.opt_out,
583+
verbose=args.verbose,
584+
additional_headers=parse_headers(args.control_server_H),
585+
)
584586
return result
585587

586588
async def print_scan_inspect(mode="scan", args=None):

src/mcp_scan/upload.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from mcp_scan.identity import IdentityManager
88
from mcp_scan.models import ScanPathResult, ScanUserInfo, ScanPathResultsCreate
99
from mcp_scan.well_known_clients import get_client_from_path
10+
from mcp_scan.verify_api import setup_aiohttp_debug_logging, setup_tcp_connector
1011

1112
logger = logging.getLogger(__name__)
1213

@@ -51,7 +52,12 @@ def get_user_info(identifier: str | None = None, opt_out: bool = False) -> ScanU
5152

5253

5354
async def upload(
54-
results: list[ScanPathResult], control_server: str, identifier: str | None = None, opt_out: bool = False, additional_headers: dict = {}
55+
results: list[ScanPathResult],
56+
control_server: str,
57+
identifier: str | None = None,
58+
opt_out: bool = False,
59+
verbose: bool = False,
60+
additional_headers: dict | None = None,
5561
) -> None:
5662
"""
5763
Upload the scan results to the control server.
@@ -80,8 +86,12 @@ async def upload(
8086
scan_user_info=user_info
8187
)
8288

89+
trace_configs = setup_aiohttp_debug_logging(verbose=verbose)
90+
tcp_connector = setup_tcp_connector()
91+
additional_headers = additional_headers or {}
92+
8393
try:
84-
async with aiohttp.ClientSession() as session:
94+
async with aiohttp.ClientSession(trace_configs=trace_configs, connector=tcp_connector) as session:
8595
headers = {"Content-Type": "application/json"}
8696
headers.update(additional_headers)
8797

src/mcp_scan/utils.py

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
1-
import json
21
import os
32
import tempfile
43

5-
import aiohttp
64
from lark import Lark
75
from rapidfuzz.distance import Levenshtein
86

@@ -49,19 +47,6 @@ def rebalance_command_args(command, args):
4947
return command, args
5048

5149

52-
async def upload_whitelist_entry(name: str, hash: str, base_url: str):
53-
url = base_url + "/api/v1/public/mcp-whitelist"
54-
headers = {"Content-Type": "application/json"}
55-
data = {
56-
"name": name,
57-
"hash": hash,
58-
}
59-
async with aiohttp.ClientSession() as session:
60-
async with session.post(url, headers=headers, data=json.dumps(data)) as response:
61-
if response.status != 200:
62-
raise Exception(f"Failed to upload whitelist entry: {response.status} - {response.text}")
63-
64-
6550
class TempFile:
6651
"""A windows compatible version of tempfile.NamedTemporaryFile."""
6752

src/mcp_scan/verify_api.py

Lines changed: 37 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -17,55 +17,58 @@
1717
identity_manager = IdentityManager()
1818

1919

20-
def setup_aiohttp_debug_logging():
20+
def setup_aiohttp_debug_logging(verbose: bool) -> list[aiohttp.TraceConfig]:
2121
"""Setup detailed aiohttp logging and tracing for debugging purposes."""
2222
# Enable aiohttp internal logging
2323
aiohttp_logger = logging.getLogger('aiohttp')
2424
aiohttp_logger.setLevel(logging.DEBUG)
2525
aiohttp_client_logger = logging.getLogger('aiohttp.client')
2626
aiohttp_client_logger.setLevel(logging.DEBUG)
27-
27+
2828
# Create trace config for detailed aiohttp logging
2929
trace_config = aiohttp.TraceConfig()
30-
30+
31+
if verbose:
32+
return []
33+
3134
async def on_request_start(session, trace_config_ctx, params):
3235
logger.debug("aiohttp: Starting request %s %s", params.method, params.url)
33-
36+
3437
async def on_request_end(session, trace_config_ctx, params):
35-
logger.debug("aiohttp: Request completed %s %s -> %s",
38+
logger.debug("aiohttp: Request completed %s %s -> %s",
3639
params.method, params.url, params.response.status)
37-
40+
3841
async def on_connection_create_start(session, trace_config_ctx, params):
3942
logger.debug("aiohttp: Creating connection")
40-
43+
4144
async def on_connection_create_end(session, trace_config_ctx, params):
4245
logger.debug("aiohttp: Connection created")
43-
46+
4447
async def on_dns_resolvehost_start(session, trace_config_ctx, params):
4548
logger.debug("aiohttp: Starting DNS resolution for %s", params.host)
46-
49+
4750
async def on_dns_resolvehost_end(session, trace_config_ctx, params):
4851
logger.debug("aiohttp: DNS resolution completed for %s", params.host)
49-
52+
5053
async def on_connection_queued_start(session, trace_config_ctx, params):
5154
logger.debug("aiohttp: Connection queued")
52-
55+
5356
async def on_connection_queued_end(session, trace_config_ctx, params):
5457
logger.debug("aiohttp: Connection dequeued")
55-
58+
5659
async def on_request_exception(session, trace_config_ctx, params):
57-
logger.error("aiohttp: Request exception for %s %s: %s",
60+
logger.error("aiohttp: Request exception for %s %s: %s",
5861
params.method, params.url, params.exception)
5962
# Check if it's an SSL-related exception
6063
if hasattr(params.exception, '__class__'):
6164
exc_name = params.exception.__class__.__name__
6265
if 'ssl' in exc_name.lower() or 'certificate' in str(params.exception).lower():
6366
logger.error("aiohttp: SSL/Certificate error detected: %s", params.exception)
64-
67+
6568
async def on_request_redirect(session, trace_config_ctx, params):
6669
logger.debug("aiohttp: Request redirected from %s %s to %s",
6770
params.method, params.url, params.response.headers.get('Location', 'unknown'))
68-
71+
6972
trace_config.on_request_start.append(on_request_start)
7073
trace_config.on_request_end.append(on_request_end)
7174
trace_config.on_connection_create_start.append(on_connection_create_start)
@@ -76,8 +79,21 @@ async def on_request_redirect(session, trace_config_ctx, params):
7679
trace_config.on_connection_queued_end.append(on_connection_queued_end)
7780
trace_config.on_request_exception.append(on_request_exception)
7881
trace_config.on_request_redirect.append(on_request_redirect)
79-
80-
return trace_config
82+
83+
return [trace_config]
84+
85+
86+
def setup_tcp_connector() -> aiohttp.TCPConnector:
87+
"""
88+
Setup a TCP connector with a default SSL context and cleanup enabled.
89+
"""
90+
ssl_context = ssl.create_default_context(cafile=certifi.where())
91+
ssl_context.minimum_version = ssl.TLSVersion.TLSv1_2
92+
connector = aiohttp.TCPConnector(
93+
ssl=ssl_context,
94+
enable_cleanup_closed=True
95+
)
96+
return connector
8197

8298

8399
async def analyze_scan_path(
@@ -106,28 +122,13 @@ async def analyze_scan_path(
106122

107123
# Server signatures do not contain any information about the user setup. Only about the server itself.
108124
try:
109-
# Setup debugging if verbose mode is enabled
110-
trace_configs = []
111-
if verbose:
112-
trace_config = setup_aiohttp_debug_logging()
113-
trace_configs.append(trace_config)
125+
trace_configs = setup_aiohttp_debug_logging(verbose=verbose)
126+
tcp_connector = setup_tcp_connector()
114127

115-
# explicitly creating the ssl context sidesepts SSL issues
116-
ssl_context = ssl.create_default_context(cafile=certifi.where())
117-
118-
if verbose:
119-
logger.debug("aiohttp: SSL context created - verify_mode=%s, check_hostname=%s",
120-
ssl_context.verify_mode, ssl_context.check_hostname)
121-
122-
connector = aiohttp.TCPConnector(
123-
ssl=ssl_context,
124-
enable_cleanup_closed=True
125-
)
126-
127128
if verbose:
128129
logger.debug("aiohttp: TCPConnector created")
129-
130-
async with aiohttp.ClientSession(connector=connector, trace_configs=trace_configs) as session:
130+
131+
async with aiohttp.ClientSession(connector=tcp_connector, trace_configs=trace_configs) as session:
131132
async with session.post(url, headers=headers, data=payload.model_dump_json()) as response:
132133
if response.status == 200:
133134
results = AnalysisServerResponse.model_validate_json(await response.read())

0 commit comments

Comments
 (0)