diff --git a/CHANGELOG.md b/CHANGELOG.md index 448b5e4..38c29e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -74,6 +74,11 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). disable the recurring loop. ### Security +- Reject non-exact integer subclasses in connection-pool count fields before + finite capacity is retained. Exact built-in integers and reviewed ASCII + decimal strings remain supported and normalize to built-in integers; callers + using custom integer subclasses must convert them deliberately before trusted + policy construction. - 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()`, diff --git a/docs/research/connection-pool-resource-limits.md b/docs/research/connection-pool-resource-limits.md index 88471aa..0a825f0 100644 --- a/docs/research/connection-pool-resource-limits.md +++ b/docs/research/connection-pool-resource-limits.md @@ -18,6 +18,13 @@ 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. +Count fields accept only exact built-in integers when callers use the integer +form; the reviewed ASCII decimal-string form remains supported and normalizes to +exact integers, while integer subclasses are rejected before finite pool-capacity +values are retained. This supported configuration-integrity boundary +does not make EgressWeave a Python sandbox for arbitrary code already running in +the host 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 diff --git a/src/egressweave/connection_pool_policy.py b/src/egressweave/connection_pool_policy.py index f372dc7..9357460 100644 --- a/src/egressweave/connection_pool_policy.py +++ b/src/egressweave/connection_pool_policy.py @@ -22,7 +22,7 @@ def _normalize_connection_count( """Return one exact ASCII-compatible connection-count limit.""" if isinstance(value, bool): raise TypeError(f"{field_name} must be an integer or ASCII decimal string") - if isinstance(value, int): + if type(value) is int: normalized = value elif isinstance(value, str): if not value or not value.isascii() or not value.isdecimal(): diff --git a/tests/test_connection_pool_count_value_documentation.py b/tests/test_connection_pool_count_value_documentation.py new file mode 100644 index 0000000..fa38af4 --- /dev/null +++ b/tests/test_connection_pool_count_value_documentation.py @@ -0,0 +1,27 @@ +"""Documentation contracts for exact connection-pool count value types.""" + +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 test_connection_pool_guide_documents_exact_builtin_count_values() -> None: + """Keep operator guidance aligned with the primitive-value integrity boundary.""" + guide = POOL_GUIDE_PATH.read_text(encoding="utf-8") + + assert "Count fields accept only exact built-in integers" in guide + assert "integer subclasses are rejected" in guide + assert "does not make EgressWeave a Python sandbox" in guide + + +def test_changelog_records_connection_count_value_sealing() -> None: + """Record the pre-1.0 primitive-value tightening in release history.""" + changelog = CHANGELOG_PATH.read_text(encoding="utf-8") + + assert "Reject non-exact integer subclasses in connection-pool count fields" in changelog diff --git a/tests/test_connection_pool_count_value_types.py b/tests/test_connection_pool_count_value_types.py new file mode 100644 index 0000000..e1a5a45 --- /dev/null +++ b/tests/test_connection_pool_count_value_types.py @@ -0,0 +1,41 @@ +"""Security contracts for exact connection-pool count value types.""" + +from __future__ import annotations + +import pytest + +from egressweave import EgressConnectionPoolPolicy + + +class _ConnectionCountSubclass(int): + """Represent an unreviewed integer subclass crossing trusted configuration.""" + + +@pytest.mark.parametrize( + "field_name", + ["max_connections", "max_keepalive_connections"], +) +def test_connection_pool_policy_rejects_integer_subclasses(field_name: str) -> None: + """Reject non-exact integers before retaining finite pool-capacity values.""" + with pytest.raises(TypeError, match=field_name): + EgressConnectionPoolPolicy( + **{field_name: _ConnectionCountSubclass(1)} # type: ignore[arg-type] + ) + + +def test_connection_pool_policy_keeps_reviewed_count_input_forms() -> None: + """Continue accepting exact integers and reviewed ASCII decimal strings.""" + exact_integer = EgressConnectionPoolPolicy( + max_connections=8, + max_keepalive_connections=2, + ) + decimal_string = EgressConnectionPoolPolicy( + max_connections="8", + max_keepalive_connections="2", + ) + + assert type(exact_integer.max_connections) is int + assert type(exact_integer.max_keepalive_connections) is int + assert decimal_string == exact_integer + assert type(decimal_string.max_connections) is int + assert type(decimal_string.max_keepalive_connections) is int