mcp-security-framework 2.3.0__tar.gz → 2.3.2__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.
Files changed (66) hide show
  1. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.2}/PKG-INFO +5 -1
  2. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.2}/mcp_security_framework/__init__.py +1 -1
  3. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.2}/mcp_security_framework/core/cert_manager.py +23 -2
  4. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.2}/mcp_security_framework/core/ssl_manager.py +52 -13
  5. mcp_security_framework-2.3.2/mcp_security_framework/examples/tls_certificate_regressions_example.py +1197 -0
  6. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.2}/mcp_security_framework/middleware/fastapi_middleware.py +1 -1
  7. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.2}/mcp_security_framework/schemas/config.py +47 -13
  8. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.2}/mcp_security_framework.egg-info/PKG-INFO +5 -1
  9. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.2}/mcp_security_framework.egg-info/SOURCES.txt +1 -0
  10. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.2}/mcp_security_framework.egg-info/requires.txt +4 -0
  11. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.2}/pyproject.toml +6 -2
  12. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.2}/LICENSE +0 -0
  13. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.2}/README.md +0 -0
  14. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.2}/mcp_security_framework/cli/__init__.py +0 -0
  15. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.2}/mcp_security_framework/cli/cert_cli.py +0 -0
  16. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.2}/mcp_security_framework/cli/security_cli.py +0 -0
  17. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.2}/mcp_security_framework/constants.py +0 -0
  18. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.2}/mcp_security_framework/core/__init__.py +0 -0
  19. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.2}/mcp_security_framework/core/adapter_wrapper.py +0 -0
  20. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.2}/mcp_security_framework/core/audit_logger.py +0 -0
  21. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.2}/mcp_security_framework/core/auth_manager.py +0 -0
  22. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.2}/mcp_security_framework/core/permission_manager.py +0 -0
  23. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.2}/mcp_security_framework/core/rate_limiter.py +0 -0
  24. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.2}/mcp_security_framework/core/security_adapter.py +0 -0
  25. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.2}/mcp_security_framework/core/security_manager.py +0 -0
  26. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.2}/mcp_security_framework/examples/__init__.py +0 -0
  27. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.2}/mcp_security_framework/examples/adapter_example.py +0 -0
  28. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.2}/mcp_security_framework/examples/auth_methods_example.py +0 -0
  29. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.2}/mcp_security_framework/examples/cert_inspection_example.py +0 -0
  30. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.2}/mcp_security_framework/examples/certificate_roles_example.py +0 -0
  31. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.2}/mcp_security_framework/examples/comprehensive_example.py +0 -0
  32. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.2}/mcp_security_framework/examples/crl_retrieval_example.py +0 -0
  33. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.2}/mcp_security_framework/examples/fastapi_example.py +0 -0
  34. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.2}/mcp_security_framework/examples/flask_example.py +0 -0
  35. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.2}/mcp_security_framework/examples/gateway_example.py +0 -0
  36. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.2}/mcp_security_framework/examples/microservice_example.py +0 -0
  37. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.2}/mcp_security_framework/examples/protected_keys_example.py +0 -0
  38. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.2}/mcp_security_framework/examples/standalone_example.py +0 -0
  39. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.2}/mcp_security_framework/middleware/__init__.py +0 -0
  40. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.2}/mcp_security_framework/middleware/auth_middleware.py +0 -0
  41. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.2}/mcp_security_framework/middleware/fastapi_auth_middleware.py +0 -0
  42. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.2}/mcp_security_framework/middleware/flask_auth_middleware.py +0 -0
  43. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.2}/mcp_security_framework/middleware/flask_middleware.py +0 -0
  44. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.2}/mcp_security_framework/middleware/mtls_middleware.py +0 -0
  45. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.2}/mcp_security_framework/middleware/rate_limit_middleware.py +0 -0
  46. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.2}/mcp_security_framework/middleware/security_middleware.py +0 -0
  47. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.2}/mcp_security_framework/schemas/__init__.py +0 -0
  48. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.2}/mcp_security_framework/schemas/models.py +0 -0
  49. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.2}/mcp_security_framework/schemas/operation_context.py +0 -0
  50. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.2}/mcp_security_framework/schemas/responses.py +0 -0
  51. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.2}/mcp_security_framework/utils/__init__.py +0 -0
  52. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.2}/mcp_security_framework/utils/cert_utils/__init__.py +0 -0
  53. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.2}/mcp_security_framework/utils/cert_utils/chain.py +0 -0
  54. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.2}/mcp_security_framework/utils/cert_utils/errors.py +0 -0
  55. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.2}/mcp_security_framework/utils/cert_utils/inspection.py +0 -0
  56. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.2}/mcp_security_framework/utils/cert_utils/parsing.py +0 -0
  57. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.2}/mcp_security_framework/utils/cert_utils/revocation.py +0 -0
  58. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.2}/mcp_security_framework/utils/cert_utils/roles.py +0 -0
  59. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.2}/mcp_security_framework/utils/crl_client.py +0 -0
  60. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.2}/mcp_security_framework/utils/crypto_utils.py +0 -0
  61. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.2}/mcp_security_framework/utils/datetime_compat.py +0 -0
  62. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.2}/mcp_security_framework/utils/validation_utils.py +0 -0
  63. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.2}/mcp_security_framework.egg-info/dependency_links.txt +0 -0
  64. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.2}/mcp_security_framework.egg-info/entry_points.txt +0 -0
  65. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.2}/mcp_security_framework.egg-info/top_level.txt +0 -0
  66. {mcp_security_framework-2.3.0 → mcp_security_framework-2.3.2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mcp-security-framework
3
- Version: 2.3.0
3
+ Version: 2.3.2
4
4
  Summary: Universal security framework for microservices with SSL/TLS, authentication, authorization, and rate limiting. Added extensible security adapter system for custom operation validation, structured audit logging, and operation context support. Comprehensive test coverage with 1104+ tests. Fully backward compatible. Requires cryptography>=42.0.0 for certificate operations.
5
5
  Author-email: Vasiliy Zdanovskiy <vasilyvz@gmail.com>
6
6
  Maintainer-email: Vasiliy Zdanovskiy <vasilyvz@gmail.com>
@@ -60,6 +60,10 @@ Requires-Dist: pytest>=7.0.0; extra == "test"
60
60
  Requires-Dist: pytest-cov>=4.0.0; extra == "test"
61
61
  Requires-Dist: pytest-asyncio>=0.21.0; extra == "test"
62
62
  Requires-Dist: pytest-mock>=3.8.0; extra == "test"
63
+ Requires-Dist: fastapi>=0.68.0; extra == "test"
64
+ Requires-Dist: uvicorn[standard]>=0.15.0; extra == "test"
65
+ Requires-Dist: flask>=2.0.0; extra == "test"
66
+ Requires-Dist: flask-cors>=3.0.0; extra == "test"
63
67
  Requires-Dist: httpx>=0.24.0; extra == "test"
64
68
  Dynamic: license-file
65
69
 
@@ -79,7 +79,7 @@ from mcp_security_framework.schemas.responses import (
79
79
  )
80
80
 
81
81
  # Version information
82
- __version__ = "2.3.0"
82
+ __version__ = "2.3.2"
83
83
  __author__ = "Vasiliy Zdanovskiy"
84
84
  __email__ = "vasilyvz@gmail.com"
85
85
  __license__ = "MIT"
@@ -27,6 +27,7 @@ Version: 1.0.0
27
27
  License: MIT
28
28
  """
29
29
 
30
+ import ipaddress
30
31
  import logging
31
32
  import os
32
33
  from datetime import datetime, timedelta, timezone
@@ -248,6 +249,14 @@ def _certificate_type_of(certificate) -> CertificateType:
248
249
  return CertificateType.CLIENT
249
250
 
250
251
 
252
+ def _subject_alt_name(value: str) -> x509.GeneralName:
253
+ """Return the correct SAN type for a DNS name or IP literal string."""
254
+ try:
255
+ return x509.IPAddress(ipaddress.ip_address(value))
256
+ except ValueError:
257
+ return x509.DNSName(value)
258
+
259
+
251
260
  class CertificateManager:
252
261
  """
253
262
  Certificate Management Class
@@ -1023,6 +1032,10 @@ class CertificateManager:
1023
1032
  x509.SubjectKeyIdentifier.from_public_key(private_key.public_key()),
1024
1033
  critical=False,
1025
1034
  )
1035
+ builder = builder.add_extension(
1036
+ x509.AuthorityKeyIdentifier.from_issuer_public_key(ca_key.public_key()),
1037
+ critical=False,
1038
+ )
1026
1039
 
1027
1040
  # Add roles and permissions to certificate extensions
1028
1041
  # Validate and normalize roles using CertificateRole enum
@@ -1333,11 +1346,15 @@ class CertificateManager:
1333
1346
  x509.SubjectKeyIdentifier.from_public_key(private_key.public_key()),
1334
1347
  critical=False,
1335
1348
  )
1349
+ builder = builder.add_extension(
1350
+ x509.AuthorityKeyIdentifier.from_issuer_public_key(ca_key.public_key()),
1351
+ critical=False,
1352
+ )
1336
1353
 
1337
1354
  # Add Subject Alternative Name (SAN) if provided
1338
1355
  if server_config.subject_alt_names:
1339
1356
  san_names = [
1340
- x509.DNSName(name) for name in server_config.subject_alt_names
1357
+ _subject_alt_name(name) for name in server_config.subject_alt_names
1341
1358
  ]
1342
1359
 
1343
1360
  builder = builder.add_extension(
@@ -2112,8 +2129,12 @@ class CertificateManager:
2112
2129
  )
2113
2130
 
2114
2131
  # Add Subject Alternative Name extension if common name looks like a domain
2132
+ # or an IP literal. Reuse _subject_alt_name so an IP-literal common_name
2133
+ # (e.g. "127.0.0.1") is encoded as x509.IPAddress, never x509.DNSName.
2115
2134
  if "." in common_name and not common_name.startswith("*"):
2116
- san_extension = x509.SubjectAlternativeName([x509.DNSName(common_name)])
2135
+ san_extension = x509.SubjectAlternativeName(
2136
+ [_subject_alt_name(common_name)]
2137
+ )
2117
2138
  csr_builder = csr_builder.add_extension(san_extension, critical=False)
2118
2139
 
2119
2140
  # Build and sign CSR
@@ -432,9 +432,27 @@ class SSLManager:
432
432
  if key_data is None:
433
433
  client_key_file = client_key_file or self.config.client_key_file
434
434
  ca_cert_file = ca_cert_file or self.config.ca_cert_file
435
- verify_mode = verify_mode or self.config.verify_mode
436
435
  min_version = min_version or self.config.min_tls_version
437
436
 
437
+ # Validate client_cert_file/client_key_file compatibility (bug B6),
438
+ # on the EFFECTIVE values resolved just above (call-site argument or
439
+ # config fallback). Skipped when cert_data/key_data supply the pair
440
+ # instead: cert_data/key_data are already validated all-or-none
441
+ # above, and both loading branches below try cert_data/key_data
442
+ # FIRST, falling back to client_cert_file/client_key_file only when
443
+ # cert_data/key_data are absent — so a lone client_cert_file/
444
+ # client_key_file value is never actually used together with
445
+ # cert_data/key_data. Raised here, once, before the cache lookup
446
+ # and before any context is built, so it covers both the CERT_NONE
447
+ # branch and the verifying branch below and no partially
448
+ # configured context is ever cached.
449
+ if cert_data is None and key_data is None:
450
+ if bool(client_cert_file) != bool(client_key_file):
451
+ raise SSLConfigurationError(
452
+ "client_cert_file and client_key_file must be provided "
453
+ "together; got one without the other"
454
+ )
455
+
438
456
  # Validate that material_from_memory promises are kept
439
457
  if cert_data is None and self.config.material_from_memory:
440
458
  raise SSLConfigurationError(
@@ -447,17 +465,13 @@ class SSLManager:
447
465
  "provide key_data as PEM bytes or set material_from_memory=False"
448
466
  )
449
467
 
450
- # Check cache (only for file-based contexts)
451
- cache_key = None
452
- if cert_data is None and key_data is None:
453
- cache_key = (
454
- f"client_{ca_cert_file}_{client_cert_file}_{client_key_file}"
455
- f"_{verify_mode}_{min_version}"
456
- )
457
- if cache_key in self._contexts:
458
- return self._contexts[cache_key]
459
-
460
- # Determine actual verify mode (explicit parameter takes precedence)
468
+ # Determine actual verify mode. Precedence (bug B5):
469
+ # 1) an explicit call-site verify_mode argument
470
+ # 2) self.config.verify is False -> CERT_NONE
471
+ # 3) self.config.verify_mode
472
+ # NOTE: verify_mode must NOT be defaulted from self.config.verify_mode
473
+ # before this check runs, otherwise it is never falsy here and
474
+ # self.config.verify is never consulted (bug B5's root cause).
461
475
  actual_verify_mode = (
462
476
  verify_mode
463
477
  if verify_mode
@@ -466,6 +480,30 @@ class SSLManager:
466
480
  )
467
481
  )
468
482
 
483
+ # Effective check_hostname actually applied to the context: always
484
+ # False when verification is disabled (ssl refuses check_hostname=True
485
+ # with CERT_NONE), otherwise the configured value (bug B1).
486
+ effective_check_hostname = (
487
+ False
488
+ if actual_verify_mode == "CERT_NONE"
489
+ else bool(self.config.check_hostname)
490
+ )
491
+
492
+ # Check cache (only for file-based contexts). The key must reflect
493
+ # the RESOLVED verify mode and the check_hostname actually applied
494
+ # to the context (bugs B1, B5c) so that mutating
495
+ # manager.config.check_hostname or manager.config.verify between
496
+ # two calls on the same instance, with the raw inputs otherwise
497
+ # unchanged, never returns a context built for different values.
498
+ cache_key = None
499
+ if cert_data is None and key_data is None:
500
+ cache_key = (
501
+ f"client_{ca_cert_file}_{client_cert_file}_{client_key_file}"
502
+ f"_{actual_verify_mode}_{effective_check_hostname}_{min_version}"
503
+ )
504
+ if cache_key in self._contexts:
505
+ return self._contexts[cache_key]
506
+
469
507
  # Check if verification is disabled
470
508
  if actual_verify_mode == "CERT_NONE":
471
509
  # For disabled verification or CERT_NONE, create a basic context
@@ -518,6 +556,7 @@ class SSLManager:
518
556
  else:
519
557
  # For other modes, use default context with verification
520
558
  context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)
559
+ context.check_hostname = effective_check_hostname
521
560
  context.verify_mode = self._get_verify_mode(actual_verify_mode)
522
561
 
523
562
  # Set minimum TLS version
@@ -582,7 +621,7 @@ class SSLManager:
582
621
  "ca_cert_file": ca_cert_file,
583
622
  "client_cert_file": client_cert_file,
584
623
  "cert_data_provided": cert_data is not None,
585
- "verify_mode": verify_mode,
624
+ "verify_mode": actual_verify_mode,
586
625
  "min_version": min_version,
587
626
  },
588
627
  )