Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
485873e
test: reproduce timeout-policy subclass dispatch
seonghobae Aug 11, 2026
949c543
fix: seal request-timeout policy to exact type
seonghobae Aug 11, 2026
24dbd89
docs(security): preserve timeout-policy boundary guidance
seonghobae Aug 11, 2026
0109bd3
merge: refresh timeout boundary on protected main 72e7e79
seonghobae Aug 11, 2026
c6400d7
Merge origin/main into timeout policy reconstruction
seonghobae Aug 11, 2026
370d6d9
test: detect malformed timeout changelog structure
seonghobae Aug 11, 2026
1f0397c
docs: restore changelog security structure
seonghobae Aug 11, 2026
5a83a11
Merge remote-tracking branch 'origin/main' into HEAD
seonghobae Aug 11, 2026
0794893
Merge remote-tracking branch 'origin/main' into HEAD
seonghobae Aug 11, 2026
cda34a9
merge: update timeout policy on current main
seonghobae Aug 11, 2026
0694961
Merge branch 'main' of https://github.com/ContextualWisdomLab/EgressW…
seonghobae Aug 11, 2026
92437b8
test: keep timeout policy PR lint-clean
seonghobae Aug 11, 2026
b6f795a
Merge branch 'main' of https://github.com/ContextualWisdomLab/EgressW…
seonghobae Aug 11, 2026
a1fbc27
Merge remote-tracking branch 'origin/main' into HEAD
seonghobae Aug 11, 2026
bbca2f7
Merge remote-tracking branch 'origin/main' into HEAD
seonghobae Aug 11, 2026
f227105
Merge remote-tracking branch 'origin/main' into HEAD
seonghobae Aug 11, 2026
0fb67e8
Merge remote-tracking branch 'origin/main' into HEAD
seonghobae Aug 11, 2026
aacd5b3
Merge remote-tracking branch 'origin/main' into HEAD
seonghobae Aug 11, 2026
b56ba1d
test: reproduce connection-pool policy subclass boundary
seonghobae Aug 11, 2026
fffc0d5
fix: require exact connection-pool policy type
seonghobae Aug 11, 2026
b94bf7f
test: require pool-policy type documentation
seonghobae Aug 11, 2026
859e3ac
docs: record exact pool-policy boundary
seonghobae Aug 11, 2026
53d0731
docs: record pool-policy type hardening
seonghobae Aug 11, 2026
f4d14ae
Merge protected main into timeout-policy reconstruction
seonghobae Aug 12, 2026
496d817
docs: restore timeout-policy release history
seonghobae Aug 12, 2026
1573873
merge: refresh connection-pool policy on current main
seonghobae Aug 12, 2026
2cce1de
merge: refresh connection-pool policy on latest timeout head
seonghobae Aug 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,14 @@ 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.
- 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.
Expand Down
36 changes: 26 additions & 10 deletions docs/research/connection-pool-resource-limits.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand All @@ -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
Expand Down
12 changes: 12 additions & 0 deletions docs/research/request-timeout-boundaries.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down
6 changes: 2 additions & 4 deletions src/egressweave/policy.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,13 +171,11 @@ 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"
)
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"
)
Expand Down
52 changes: 52 additions & 0 deletions tests/test_connection_pool_policy_type_boundary.py
Original file line number Diff line number Diff line change
@@ -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(),
)
41 changes: 41 additions & 0 deletions tests/test_connection_pool_policy_type_documentation.py
Original file line number Diff line number Diff line change
@@ -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
33 changes: 33 additions & 0 deletions tests/test_timeout_policy_type_boundary.py
Original file line number Diff line number Diff line change
@@ -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(),
)
49 changes: 49 additions & 0 deletions tests/test_timeout_policy_type_documentation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
"""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 normalized UTF-8 prose."""
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


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
Loading