From 485873e5aab7a0c8b75a2669643ef9c0505b94de Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 11 Aug 2026 09:06:07 +0900 Subject: [PATCH 01/12] test: reproduce timeout-policy subclass dispatch --- tests/test_timeout_policy_type_boundary.py | 33 ++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 tests/test_timeout_policy_type_boundary.py diff --git a/tests/test_timeout_policy_type_boundary.py b/tests/test_timeout_policy_type_boundary.py new file mode 100644 index 00000000..180b9b32 --- /dev/null +++ b/tests/test_timeout_policy_type_boundary.py @@ -0,0 +1,33 @@ +"""Regression contracts for the exact request-timeout policy type boundary.""" + +from __future__ import annotations + +import pytest + +from egressweave import EgressPolicy, EgressTimeoutPolicy + + +class _HostileTimeoutPolicy(EgressTimeoutPolicy): + """Model a subclass that can replace the reviewed timeout export method.""" + + def as_httpcore_timeout(self) -> dict[str, float]: + """Fail if a later transport dynamically dispatches this override.""" + raise AssertionError("subclass-controlled timeout export executed") + + +def test_host_policy_rejects_timeout_policy_subclass() -> None: + """Reject non-exact timeout policy types at trusted policy construction.""" + with pytest.raises(TypeError, match="request_timeout_policy"): + EgressPolicy.from_hosts( + "api.example.com", + request_timeout_policy=_HostileTimeoutPolicy(), + ) + + +def test_exact_authority_policy_rejects_timeout_policy_subclass() -> None: + """Apply the same exact-type boundary to the authority-pair constructor.""" + with pytest.raises(TypeError, match="request_timeout_policy"): + EgressPolicy.from_authorities( + [("api.example.com", 443)], + request_timeout_policy=_HostileTimeoutPolicy(), + ) From 949c543870543f10de3d56efb4ab62ab9045d2be Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 11 Aug 2026 09:07:47 +0900 Subject: [PATCH 02/12] fix: seal request-timeout policy to exact type --- src/egressweave/policy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/egressweave/policy.py b/src/egressweave/policy.py index 1c69a1ff..8fbcaa26 100644 --- a/src/egressweave/policy.py +++ b/src/egressweave/policy.py @@ -171,7 +171,7 @@ def __post_init__(self) -> None: """Validate and canonicalize every immutable policy field.""" if not isinstance(self.allow_local, bool): raise TypeError("allow_local must be a boolean") - if not isinstance(self.request_timeout_policy, EgressTimeoutPolicy): + if type(self.request_timeout_policy) is not EgressTimeoutPolicy: raise TypeError( "request_timeout_policy must be an EgressTimeoutPolicy" ) From 24dbd89ca6f89657380702b235d03f1bafdad3e0 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 11 Aug 2026 09:09:33 +0900 Subject: [PATCH 03/12] docs(security): preserve timeout-policy boundary guidance --- CHANGELOG.md | 4 ++ docs/research/request-timeout-boundaries.md | 12 ++++++ .../test_timeout_policy_type_documentation.py | 39 +++++++++++++++++++ 3 files changed, 55 insertions(+) create mode 100644 tests/test_timeout_policy_type_documentation.py diff --git a/CHANGELOG.md b/CHANGELOG.md index a1e9dc0c..98600e19 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -60,6 +60,10 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). without changing the centrally managed review-agent credential contract. ### Security +- Require the request timeout policy to use the exact `EgressTimeoutPolicy` type + during trusted construction. Timeout-policy subclasses are rejected before + transport dispatch can dynamically invoke an overridden `as_httpcore_timeout()`, + preserving the reviewed finite ceilings as the authoritative configuration. - Canonicalize the public manifest writer's optional `forbidden_root` before any output-parent creation or output-path access. Missing, non-directory, symlinked, unresolvable, or otherwise noncanonical roots now fail with one diff --git a/docs/research/request-timeout-boundaries.md b/docs/research/request-timeout-boundaries.md index 8b8ec4d8..1d0996b7 100644 --- a/docs/research/request-timeout-boundaries.md +++ b/docs/research/request-timeout-boundaries.md @@ -14,6 +14,16 @@ HTTPX timeout extension immediately before HTTPCore dispatch: - malformed maps, unknown keys, booleans, negative values, and non-finite numbers fail through the generic `EgressNotAllowedError` boundary. +The trusted policy construction boundary accepts only the exact +`EgressTimeoutPolicy` type. Subclass polymorphism is not a supported extension +mechanism because transport binding later invokes `as_httpcore_timeout()`: a +subclass could otherwise replace that reviewed export path after startup +validation. Applications that previously supplied an `EgressTimeoutPolicy` +subclass must migrate to an exact instance configured through the documented +immutable timeout fields. This secure-default boundary keeps declarative values +authoritative; it does not claim to sandbox arbitrary trusted Python executing +inside the embedding process. + Policy maxima must be greater than zero. A request may still choose zero as an immediate, stricter timeout. The sanitized mapping is detached from caller-owned state and preserves unrelated safe extensions, including the validated TLS @@ -49,6 +59,8 @@ response data. ## Security properties +- **Exact trusted policy type:** construction rejects timeout-policy subclasses + before any later transport export can dynamically dispatch subclass code. - **No timeout disablement:** missing and `None` phase values become finite. - **No weaker override:** a request cannot exceed the immutable policy cap. - **Stricter caller control:** non-negative values below the cap are retained. diff --git a/tests/test_timeout_policy_type_documentation.py b/tests/test_timeout_policy_type_documentation.py new file mode 100644 index 00000000..4da53b1b --- /dev/null +++ b/tests/test_timeout_policy_type_documentation.py @@ -0,0 +1,39 @@ +"""Documentation contracts for exact request-timeout policy configuration.""" + +from __future__ import annotations + +from pathlib import Path + +REPOSITORY_ROOT = Path(__file__).resolve().parents[1] +TIMEOUT_GUIDE_PATH = REPOSITORY_ROOT / "docs" / "research" / "request-timeout-boundaries.md" +CHANGELOG_PATH = REPOSITORY_ROOT / "CHANGELOG.md" + + +def _read(path: Path) -> str: + """Return one repository text file as UTF-8.""" + return " ".join(path.read_text(encoding="utf-8").split()) + + +def test_timeout_guide_requires_exact_reviewed_policy_type() -> None: + """Explain why timeout-policy subclass polymorphism is not a supported boundary.""" + guide = _read(TIMEOUT_GUIDE_PATH) + + for fragment in ( + "exact `EgressTimeoutPolicy` type", + "subclass", + "trusted policy construction", + "`as_httpcore_timeout()`", + ): + assert fragment in guide + + +def test_changelog_records_timeout_policy_type_hardening() -> None: + """Expose the pre-1.0 policy-integrity tightening to integrators.""" + changelog = _read(CHANGELOG_PATH) + + for fragment in ( + "request timeout policy", + "exact `EgressTimeoutPolicy`", + "subclass", + ): + assert fragment in changelog From 370d6d942c5f9f5a97b2d34cbb1f29d76f42047a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 11 Aug 2026 15:09:49 +0900 Subject: [PATCH 04/12] test: detect malformed timeout changelog structure --- tests/test_timeout_policy_type_documentation.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/tests/test_timeout_policy_type_documentation.py b/tests/test_timeout_policy_type_documentation.py index 4da53b1b..d0c336f9 100644 --- a/tests/test_timeout_policy_type_documentation.py +++ b/tests/test_timeout_policy_type_documentation.py @@ -10,7 +10,7 @@ def _read(path: Path) -> str: - """Return one repository text file as UTF-8.""" + """Return one repository text file as normalized UTF-8 prose.""" return " ".join(path.read_text(encoding="utf-8").split()) @@ -37,3 +37,13 @@ def test_changelog_records_timeout_policy_type_hardening() -> None: "subclass", ): assert fragment in changelog + + +def test_changelog_keeps_security_heading_and_entries_at_markdown_root() -> None: + """Prevent whitespace drift from turning release-history structure into code.""" + changelog = CHANGELOG_PATH.read_text(encoding="utf-8") + + assert "\n### Security\n" in changelog + assert "\n- Require the request timeout policy" in changelog + assert "\n ### Security\n" not in changelog + assert "\n - Require the request timeout policy" not in changelog From 1f0397cea23ff5eebac8f78ea2a89d0c7553a490 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 11 Aug 2026 15:16:25 +0900 Subject: [PATCH 05/12] docs: restore changelog security structure --- CHANGELOG.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 920705bb..e30666b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -58,17 +58,17 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - Correct the buyer-facing autonomous-maintainer identity from the retired Codex wording to the pinned OpenCode execution path backed by `NVIDIA_NIM_API_KEY`, without changing the centrally managed review-agent credential contract. - - ### Security - - Require the request timeout policy to use the exact `EgressTimeoutPolicy` type - during trusted construction. Timeout-policy subclasses are rejected before - transport dispatch can dynamically invoke an overridden `as_httpcore_timeout()`, - preserving the reviewed finite ceilings as the authoritative configuration. - - 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 - operator-controlled under normal protection. - - Canonicalize the public manifest writer's optional `forbidden_root` before any + +### Security +- Require the request timeout policy to use the exact `EgressTimeoutPolicy` type + during trusted construction. Timeout-policy subclasses are rejected before + transport dispatch can dynamically invoke an overridden `as_httpcore_timeout()`, + preserving the reviewed finite ceilings as the authoritative configuration. +- 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 + operator-controlled under normal protection. +- Canonicalize the public manifest writer's optional `forbidden_root` before any output-parent creation or output-path access. Missing, non-directory, symlinked, unresolvable, or otherwise noncanonical roots now fail with one stable non-leaking error, and every pre-write, descriptor-bound, and post-sync From 92437b8d13297ee4e8fb4239e42b7bd8fe63f09a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 11 Aug 2026 17:28:32 +0900 Subject: [PATCH 06/12] test: keep timeout policy PR 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", From b56ba1df37427526e0d70223f3b783e077adc85c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 12 Aug 2026 00:20:40 +0900 Subject: [PATCH 07/12] test: reproduce connection-pool policy subclass boundary --- ...st_connection_pool_policy_type_boundary.py | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 tests/test_connection_pool_policy_type_boundary.py diff --git a/tests/test_connection_pool_policy_type_boundary.py b/tests/test_connection_pool_policy_type_boundary.py new file mode 100644 index 00000000..275357e3 --- /dev/null +++ b/tests/test_connection_pool_policy_type_boundary.py @@ -0,0 +1,52 @@ +"""Regression contracts for the exact connection-pool policy type boundary.""" + +from __future__ import annotations + +import pytest + +from egressweave import EgressConnectionPoolPolicy, EgressPolicy + + +class _HostileConnectionPoolPolicy(EgressConnectionPoolPolicy): + """Model a subclass that changes a reviewed pool limit after construction.""" + + def __init__(self) -> None: + """Build valid base state before arming hostile attribute dispatch.""" + super().__init__() + object.__setattr__(self, "_armed", True) + + def __getattribute__(self, name: str) -> object: + """Replace the retained connection ceiling after base normalization.""" + if name == "max_connections": + try: + armed = object.__getattribute__(self, "_armed") + except AttributeError: + armed = False + if armed: + return 1_000_000_000 + return super().__getattribute__(name) + + +def _hostile_pool_policy() -> EgressConnectionPoolPolicy: + """Return one valid subclass whose later pool ceiling is dynamically replaced.""" + policy = _HostileConnectionPoolPolicy() + assert policy.max_connections == 1_000_000_000 + return policy + + +def test_host_policy_rejects_connection_pool_policy_subclass() -> None: + """Reject non-exact pool policy types at trusted policy construction.""" + with pytest.raises(TypeError, match="connection_pool_policy"): + EgressPolicy.from_hosts( + "api.example.com", + connection_pool_policy=_hostile_pool_policy(), + ) + + +def test_exact_authority_policy_rejects_connection_pool_policy_subclass() -> None: + """Apply the same exact-type boundary to the authority-pair constructor.""" + with pytest.raises(TypeError, match="connection_pool_policy"): + EgressPolicy.from_authorities( + [("api.example.com", 443)], + connection_pool_policy=_hostile_pool_policy(), + ) From fffc0d5ab89bc7fe9fc2afaa3b77fa4276754f20 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 12 Aug 2026 01:05:48 +0900 Subject: [PATCH 08/12] fix: require exact connection-pool policy type --- src/egressweave/policy.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/egressweave/policy.py b/src/egressweave/policy.py index 8fbcaa26..9221a70a 100644 --- a/src/egressweave/policy.py +++ b/src/egressweave/policy.py @@ -175,9 +175,7 @@ def __post_init__(self) -> None: raise TypeError( "request_timeout_policy must be an EgressTimeoutPolicy" ) - if not isinstance( - self.connection_pool_policy, EgressConnectionPoolPolicy - ): + if type(self.connection_pool_policy) is not EgressConnectionPoolPolicy: raise TypeError( "connection_pool_policy must be an EgressConnectionPoolPolicy" ) From b94bf7f4293f3ddcbaf1733ece73e05656470032 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 12 Aug 2026 01:08:59 +0900 Subject: [PATCH 09/12] test: require pool-policy type documentation --- ...nnection_pool_policy_type_documentation.py | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 tests/test_connection_pool_policy_type_documentation.py diff --git a/tests/test_connection_pool_policy_type_documentation.py b/tests/test_connection_pool_policy_type_documentation.py new file mode 100644 index 00000000..dd6b61d5 --- /dev/null +++ b/tests/test_connection_pool_policy_type_documentation.py @@ -0,0 +1,41 @@ +"""Documentation contracts for exact connection-pool policy configuration.""" + +from __future__ import annotations + +from pathlib import Path + +REPOSITORY_ROOT = Path(__file__).resolve().parents[1] +POOL_GUIDE_PATH = ( + REPOSITORY_ROOT / "docs" / "research" / "connection-pool-resource-limits.md" +) +CHANGELOG_PATH = REPOSITORY_ROOT / "CHANGELOG.md" + + +def _read(path: Path) -> str: + """Return one repository text file as normalized UTF-8 text.""" + return " ".join(path.read_text(encoding="utf-8").split()) + + +def test_pool_guide_requires_exact_reviewed_policy_type() -> None: + """Explain why pool-policy subclass polymorphism is not a supported boundary.""" + guide = _read(POOL_GUIDE_PATH) + + for fragment in ( + "exact `EgressConnectionPoolPolicy` type", + "subclass", + "trusted policy construction", + "`connection_pool_policy`", + ): + assert fragment in guide + + +def test_changelog_records_connection_pool_policy_type_hardening() -> None: + """Expose the pre-1.0 pool-policy integrity tightening to integrators.""" + changelog = _read(CHANGELOG_PATH) + + for fragment in ( + "connection pool policy", + "exact `EgressConnectionPoolPolicy`", + "subclass", + ): + assert fragment in changelog From 859e3ac35f0eb705087028d097c323789b4e66ee Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 12 Aug 2026 01:10:43 +0900 Subject: [PATCH 10/12] docs: record exact pool-policy boundary --- .../connection-pool-resource-limits.md | 36 +++++++++++++------ 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/docs/research/connection-pool-resource-limits.md b/docs/research/connection-pool-resource-limits.md index e7364fa4..88471aa5 100644 --- a/docs/research/connection-pool-resource-limits.md +++ b/docs/research/connection-pool-resource-limits.md @@ -9,6 +9,15 @@ idle connections, and a five-second idle keep-alive lifetime. Operators can set smaller or larger finite values for a specific integration without importing HTTPX private configuration or constructing an HTTPCore-specific object. +At trusted policy construction, `connection_pool_policy` must be the exact +`EgressConnectionPoolPolicy` type. Subclass polymorphism is not a supported +configuration extension mechanism because retaining a subclass would allow +later attribute dispatch to diverge from the finite capacities that were +reviewed and fingerprinted. Integrations that need different limits construct +an exact `EgressConnectionPoolPolicy` with different documented field values +instead. This boundary does not claim EgressWeave sandboxes arbitrary Python +code already executing inside the embedding process. + `max_connections` must be a positive integer or ASCII decimal string. `max_keepalive_connections` may be zero to retain no idle connections but must not exceed total capacity. `keepalive_expiry_seconds` must be a finite @@ -43,18 +52,20 @@ and portable across standalone and modular integrations. ## Enforcement invariants 1. Both public `EgressPolicy` constructors accept the same immutable pool policy. -2. Total connection capacity is always positive and finite. -3. Idle capacity is finite, may be zero, and cannot exceed total capacity. -4. Idle expiry is finite and non-negative; `None` cannot disable reclamation. -5. Synchronous and asynchronous HTTPCore pools receive the exact normalized +2. Trusted construction accepts only the exact `EgressConnectionPoolPolicy` + type; subclasses are rejected before transport pool values are read. +3. Total connection capacity is always positive and finite. +4. Idle capacity is finite, may be zero, and cannot exceed total capacity. +5. Idle expiry is finite and non-negative; `None` cannot disable reclamation. +6. Synchronous and asynchronous HTTPCore pools receive the exact normalized values from the policy. -6. No transport imports HTTPX's private `DEFAULT_LIMITS` object. -7. The normalized pool policy participates in deterministic policy and decision +7. No transport imports HTTPX's private `DEFAULT_LIMITS` object. +8. The normalized pool policy participates in deterministic policy and decision fingerprints without recording live connection state. -8. Defaults, valid environment-style count text, invalid configuration, - relational invariants, sync/async delegation, public API exposure, and - fingerprint drift are covered by offline regression tests with complete - production statement and branch coverage. +9. Defaults, valid environment-style count text, invalid configuration, + relational invariants, exact policy-type enforcement, sync/async delegation, + public API exposure, and fingerprint drift are covered by offline regression + tests with complete production statement and branch coverage. ## Operational guidance @@ -67,6 +78,11 @@ and expiry to zero disables reuse and is stricter for retention, but it can increase connection and TLS-handshake cost; measure that trade-off rather than assuming it is universally safer. +Applications that previously subclassed `EgressConnectionPoolPolicy` must +migrate to an exact instance and configure the supported finite fields directly. +The exact-type check runs during trusted startup, before a pool or request can +consume those values. + ## References Encode OSS Ltd. (n.d.). *Resource limits*. HTTPX. Retrieved August 5, 2026, from From 53d0731862549b3d94d436526243a308b5d7b071 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 12 Aug 2026 01:13:13 +0900 Subject: [PATCH 11/12] docs: record pool-policy type hardening --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c7287b1..e6350558 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -75,6 +75,10 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). during trusted construction. Timeout-policy subclasses are rejected before transport dispatch can dynamically invoke an overridden `as_httpcore_timeout()`, preserving the reviewed finite ceilings as the authoritative configuration. +- Require the connection pool policy to use the exact `EgressConnectionPoolPolicy` + type during trusted construction. Connection-pool policy subclasses are + rejected before subclass-controlled attributes can diverge from reviewed + finite pool capacity and fingerprinting. - Pin the credential-free verifier to a reviewed Python 3.13 `python@sha256:<64-hex>` digest, validate it before Docker execution, and remove mutable-tag and `RepoDigests` promotion from the verifier boundary. From 496d81709ad63be6b921510c447e7d2393daed96 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 12 Aug 2026 10:06:43 +0900 Subject: [PATCH 12/12] docs: restore timeout-policy release history --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e8f61c04..ffee9cd7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -74,6 +74,10 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). disable the recurring loop. ### Security +- Require the request timeout policy to use the exact `EgressTimeoutPolicy` type + during trusted construction. Timeout-policy subclasses are rejected before + transport dispatch can dynamically invoke an overridden `as_httpcore_timeout()`, + preserving the reviewed finite ceilings as the authoritative configuration. - Pin the credential-free verifier to a reviewed Python 3.13 `python@sha256:<64-hex>` digest, validate it before Docker execution, and remove mutable-tag and `RepoDigests` promotion from the verifier boundary.