From 6bfacd71b19c47cadf251807507cc35b34d0d4a2 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 11 Aug 2026 14:50:13 +0900 Subject: [PATCH 1/8] test: expose TLS configuration subclass dispatch --- tests/test_tls_configuration_exact_type.py | 25 ++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 tests/test_tls_configuration_exact_type.py diff --git a/tests/test_tls_configuration_exact_type.py b/tests/test_tls_configuration_exact_type.py new file mode 100644 index 00000000..055a9d50 --- /dev/null +++ b/tests/test_tls_configuration_exact_type.py @@ -0,0 +1,25 @@ +"""Exact-type security contracts for enterprise TLS configuration.""" + +from __future__ import annotations + +import ssl + +import pytest + +from egressweave.tls import TLSConfiguration, create_egress_ssl_context + + +class _VerificationDisablingTLSConfiguration(TLSConfiguration): + """Fail if subclass-controlled TLS context dispatch occurs.""" + + def create_ssl_context(self) -> ssl.SSLContext: + """Reject any attempt to execute subclass-controlled TLS policy code.""" + raise AssertionError("subclass dispatch must not occur") + + +def test_context_helper_rejects_tls_configuration_subclasses_before_dispatch() -> None: + """Reject polymorphic configuration before subclass code can replace TLS policy.""" + configuration = _VerificationDisablingTLSConfiguration() + + with pytest.raises(TypeError, match="TLSConfiguration or None"): + create_egress_ssl_context(configuration) From d0567c46e63b0d94a5b9636dd3ee04485b950a8e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 11 Aug 2026 14:54:23 +0900 Subject: [PATCH 2/8] fix: reject TLS configuration subclasses before dispatch --- src/egressweave/tls.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/egressweave/tls.py b/src/egressweave/tls.py index 556de828..e242b37c 100644 --- a/src/egressweave/tls.py +++ b/src/egressweave/tls.py @@ -212,7 +212,7 @@ def create_egress_ssl_context( """Create the default HTTPX context or a fresh configured enterprise context.""" if configuration is None: return _create_httpx_ssl_context(verify=True, trust_env=False) - if not isinstance(configuration, TLSConfiguration): + if type(configuration) is not TLSConfiguration: raise TypeError("tls_configuration must be TLSConfiguration or None") return configuration.create_ssl_context() From b193dc556adfad39b34f32f577f84de3c56f3386 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 11 Aug 2026 16:03:28 +0900 Subject: [PATCH 3/8] test: require TLS exact-type documentation --- ..._configuration_exact_type_documentation.py | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 tests/test_tls_configuration_exact_type_documentation.py diff --git a/tests/test_tls_configuration_exact_type_documentation.py b/tests/test_tls_configuration_exact_type_documentation.py new file mode 100644 index 00000000..6d2f01d5 --- /dev/null +++ b/tests/test_tls_configuration_exact_type_documentation.py @@ -0,0 +1,33 @@ +"""Documentation contracts for the exact TLS configuration type boundary.""" + +from __future__ import annotations + +from pathlib import Path + +REPOSITORY_ROOT = Path(__file__).resolve().parents[1] +TLS_GUIDE = REPOSITORY_ROOT / "docs" / "research" / "tls-configuration.md" +CHANGELOG = REPOSITORY_ROOT / "CHANGELOG.md" + + +def _normalized(path: Path) -> str: + """Return repository documentation with insignificant whitespace collapsed.""" + return " ".join(path.read_text(encoding="utf-8").split()) + + +def test_tls_guide_requires_exact_configuration_type_before_dispatch() -> None: + """Explain why subclass dispatch cannot replace the reviewed TLS policy.""" + guide = _normalized(TLS_GUIDE) + + assert "exact `TLSConfiguration` type" in guide + assert "subclass" in guide + assert "before" in guide and "create_ssl_context" in guide + assert "hostname verification" in guide + assert "certificate verification" in guide + + +def test_changelog_records_exact_tls_configuration_type_boundary() -> None: + """Keep the pre-1.0 TLS policy-integrity tightening visible to integrators.""" + changelog = _normalized(CHANGELOG) + + assert "exact `TLSConfiguration` type" in changelog + assert "subclass" in changelog From 311e07eb2658e0b40c729334360f2d9dc50ec048 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 11 Aug 2026 16:07:14 +0900 Subject: [PATCH 4/8] docs: explain exact TLS configuration boundary --- docs/research/tls-configuration.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/docs/research/tls-configuration.md b/docs/research/tls-configuration.md index 7ee06bf1..e9cd100c 100644 --- a/docs/research/tls-configuration.md +++ b/docs/research/tls-configuration.md @@ -50,6 +50,16 @@ trusted integration point for deferred secret retrieval. Every transport owns the fresh context that results, eliminating post-validation caller mutation as an authority channel. +The public context helper accepts only the exact `TLSConfiguration` type before +it invokes `create_ssl_context()`. Subclassing this security value object is not +an extension mechanism: a subclass could otherwise replace +`create_ssl_context()` with caller-controlled dispatch and return a context that +disables hostname verification or certificate verification while still passing +an `isinstance` check. Rejecting subclasses before that method is invoked keeps +the reviewed configuration fields, not polymorphic code, authoritative for TLS +policy. This is a pre-1.0 secure-default tightening and does not claim to sandbox +arbitrary trusted Python executing inside the embedding process. + ## Trust-store semantics The default preserves EgressWeave's existing HTTPX trust behavior while @@ -103,6 +113,11 @@ default. An existing endpoint that cannot yet negotiate TLS 1.3 can opt into `minimum_version=ssl.TLSVersion.TLSv1_2`; this is an explicit compatibility exception that should be inventoried and removed after the peer is upgraded. +Applications that previously subclassed `TLSConfiguration` must migrate to an +exact instance using the documented declarative fields. Private trust roots, +mutual-TLS identities, deferred private-key passwords, and the explicit TLS 1.2 +compatibility floor remain supported without subclassing. + The configuration is threaded through both public builders and both already-validated pinned-client builders. It changes only TLS trust and client identity; exact authority, DNS pinning, proxy isolation, request framing and @@ -114,6 +129,9 @@ independently enforced. Aviram, N. (2026). *Deprecating obsolete key exchange methods in TLS 1.2 and DTLS 1.2* (RFC 10015). RFC Editor. https://www.rfc-editor.org/rfc/rfc10015.html +MITRE Corporation. (2026). *CWE-295: Improper certificate validation* (CWE +Version 4.20). https://cwe.mitre.org/data/definitions/295.html + OpenSSL Project Authors. (2026). *openssl-ciphers*. OpenSSL 3.0 documentation. https://docs.openssl.org/3.0/man1/openssl-ciphers/ From 4b07c81e1b04c517bb70c7233714fce9fd81ad59 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 11 Aug 2026 16:07:32 +0900 Subject: [PATCH 5/8] docs: record exact TLS configuration boundary --- CHANGELOG.md | 4 +++- docs/research/tls-configuration.md | 7 +++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cf81de88..500ab158 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,7 +32,9 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - Add immutable provider-neutral `TLSConfiguration` dependency injection for private trust stores and mutual-TLS client identities across synchronous and asynchronous DNS-pinned builders. TLS 1.3 is the default; explicit TLS 1.2 - compatibility remains restricted to forward-secret ECDHE suites. + compatibility remains restricted to forward-secret ECDHE suites. Before + dispatch, builders require the exact `TLSConfiguration` type so a subclass + cannot replace context construction or inject subclass-controlled TLS policy. - Add explicit, deterministic `EgressDecisionEvidence` for successful egress decisions. Evidence revalidates signed state and records canonical authority, method policy, aggregate address-family counts, and correlation fingerprints diff --git a/docs/research/tls-configuration.md b/docs/research/tls-configuration.md index 7ee06bf1..43c8f6b9 100644 --- a/docs/research/tls-configuration.md +++ b/docs/research/tls-configuration.md @@ -50,6 +50,13 @@ trusted integration point for deferred secret retrieval. Every transport owns the fresh context that results, eliminating post-validation caller mutation as an authority channel. +Before dispatch, transport construction requires the exact `TLSConfiguration` +type. A subclass cannot replace `create_ssl_context` or otherwise inject +subclass-controlled TLS policy after validation; callers that need a different +trusted integration must provide a separate provider-neutral configuration +value instead. This exact-type boundary complements the independent +certificate verification and hostname verification requirements above. + ## Trust-store semantics The default preserves EgressWeave's existing HTTPX trust behavior while From 72d25e51bd0f7e6876800613aeb619689e4e4ce1 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 11 Aug 2026 16:08:30 +0900 Subject: [PATCH 6/8] docs: record exact TLS configuration security change --- CHANGELOG.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cf81de88..cb9c574f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -60,6 +60,11 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). without changing the centrally managed review-agent credential contract. ### Security +- Require the exact `TLSConfiguration` type before TLS context creation. A + subclass can no longer override `create_ssl_context()` to replace the reviewed + immutable policy with a context that disables hostname or certificate + verification; private trust, mTLS, and explicit TLS 1.2 compatibility remain + available through the documented declarative fields. - Remove the repository-write publisher from the autonomous product scheduler and disable hourly scheduler auto-merge. Verified model output now ends at a short-lived handoff; any pull-request merge remains current-head reviewed and @@ -365,4 +370,4 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). gap (CWE-350), with redirects and environment proxies disabled. - `EgressNotAllowedError` (a `ValueError` subclass) and `ValidatedEgressURL`. - 35 tests covering URL rejection, address classification, the `allow_local` - container case, DNS-to-private rejection, and transport pinning. + container case, DNS-to-private rejection, and transport pinning. \ No newline at end of file From cc7508dde0b84199f5119cb31af87a665a10e46b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 11 Aug 2026 16:31:34 +0900 Subject: [PATCH 7/8] test: bind TLS boundary documentation wording --- ..._configuration_exact_type_documentation.py | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/tests/test_tls_configuration_exact_type_documentation.py b/tests/test_tls_configuration_exact_type_documentation.py index 6d2f01d5..d3ffc627 100644 --- a/tests/test_tls_configuration_exact_type_documentation.py +++ b/tests/test_tls_configuration_exact_type_documentation.py @@ -18,16 +18,26 @@ def test_tls_guide_requires_exact_configuration_type_before_dispatch() -> None: """Explain why subclass dispatch cannot replace the reviewed TLS policy.""" guide = _normalized(TLS_GUIDE) - assert "exact `TLSConfiguration` type" in guide - assert "subclass" in guide - assert "before" in guide and "create_ssl_context" in guide - assert "hostname verification" in guide - assert "certificate verification" in guide + assert ( + "The public context helper accepts only the exact `TLSConfiguration` type " + "before it invokes `create_ssl_context()`." + ) in guide + assert ( + "a subclass could otherwise replace `create_ssl_context()` with " + "caller-controlled dispatch and return a context that disables hostname " + "verification or certificate verification" + ) in guide def test_changelog_records_exact_tls_configuration_type_boundary() -> None: """Keep the pre-1.0 TLS policy-integrity tightening visible to integrators.""" changelog = _normalized(CHANGELOG) - assert "exact `TLSConfiguration` type" in changelog - assert "subclass" in changelog + assert ( + "Require the exact `TLSConfiguration` type before TLS context creation." + ) in changelog + assert ( + "A subclass can no longer override `create_ssl_context()` to replace the " + "reviewed immutable policy with a context that disables hostname or " + "certificate verification" + ) in changelog From 500e96048a0b8c6f3f921006071d777f0242875a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 11 Aug 2026 16:50:55 +0900 Subject: [PATCH 8/8] fix: keep TLS fixture lint-clean --- tests/test_tls_configuration.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_tls_configuration.py b/tests/test_tls_configuration.py index 143bf6b8..5f1efe80 100644 --- a/tests/test_tls_configuration.py +++ b/tests/test_tls_configuration.py @@ -223,7 +223,8 @@ def capture_identity( ) monkeypatch.setattr("egressweave.tls._load_client_identity", capture_identity) - password = lambda: "secret" + def password() -> str: + return "secret" configuration = TLSConfiguration( client_certificate_file="client.pem", client_private_key_file="client.key",