From e9638ef9acc4c94acd00c571b11d893499220a54 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 12 Aug 2026 19:07:53 +0900 Subject: [PATCH 1/6] test(security): reproduce non-exact HTTP method policy acceptance --- tests/test_policy_method_value_integrity.py | 73 +++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 tests/test_policy_method_value_integrity.py diff --git a/tests/test_policy_method_value_integrity.py b/tests/test_policy_method_value_integrity.py new file mode 100644 index 00000000..f3a03d18 --- /dev/null +++ b/tests/test_policy_method_value_integrity.py @@ -0,0 +1,73 @@ +"""Regression tests for exact built-in HTTP method policy values.""" + +from __future__ import annotations + +import pytest + +from egressweave.policy import EgressPolicy + + +class _NonExactMethod(str): + """Keep subclass identity if trusted normalization invokes polymorphic methods.""" + + def strip(self, chars: str | None = None) -> _NonExactMethod: + """Return this subclass instead of a canonical built-in string.""" + return self + + def upper(self) -> _NonExactMethod: + """Return this subclass instead of a canonical built-in string.""" + return self + + +class _ExplodingMethodList(str): + """Expose polymorphic dispatch in comma-separated method parsing.""" + + def split(self, sep: str | None = None, maxsplit: int = -1) -> list[str]: + """Fail if trusted construction invokes subclass-controlled splitting.""" + raise AssertionError("string subclass split executed") + + +def test_method_policy_rejects_str_subclass_before_normalization() -> None: + """Reject subclass-controlled method normalization during policy construction.""" + with pytest.raises(TypeError, match="allowed_methods"): + EgressPolicy.from_hosts( + "api.example.com", + allowed_methods={_NonExactMethod("GET")}, + ) + + +def test_direct_policy_rejects_str_subclass_before_comma_split() -> None: + """Reject a direct comma-string subclass before invoking its split method.""" + with pytest.raises(TypeError, match="allowed_methods"): + EgressPolicy( + allowed_hosts=frozenset({"api.example.com"}), + allowed_methods=_ExplodingMethodList("GET,POST"), + ) + + +def test_from_hosts_rejects_str_subclass_before_comma_split() -> None: + """Reject a host-factory comma-string subclass before invoking split.""" + with pytest.raises(TypeError, match="allowed_methods"): + EgressPolicy.from_hosts( + "api.example.com", + allowed_methods=_ExplodingMethodList("GET,POST"), + ) + + +def test_from_authorities_rejects_str_subclass_before_comma_split() -> None: + """Reject an authority-factory comma-string subclass before invoking split.""" + with pytest.raises(TypeError, match="allowed_methods"): + EgressPolicy.from_authorities( + [("api.example.com", 443)], + allowed_methods=_ExplodingMethodList("GET,POST"), + ) + + +def test_runtime_method_authorization_rejects_str_subclass_before_normalization() -> None: + """Reject subclass-controlled normalization at the request authorization boundary.""" + policy = EgressPolicy.from_hosts( + "api.example.com", + allowed_methods={"GET"}, + ) + + assert policy.allows_http_method(_NonExactMethod("GET")) is False From 2428bef30b72d2eb54394406cebd2111d14b88b8 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 12 Aug 2026 19:16:07 +0900 Subject: [PATCH 2/6] fix(security): reject non-exact HTTP method values --- src/egressweave/_policy_normalization.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/egressweave/_policy_normalization.py b/src/egressweave/_policy_normalization.py index 0a190a5d..52ecdb86 100644 --- a/src/egressweave/_policy_normalization.py +++ b/src/egressweave/_policy_normalization.py @@ -164,7 +164,7 @@ def _normalize_allowed_method(value: object) -> str: is never accepted: its semantics create an application-layer tunnel whose destination is independent of the validated URL authority. """ - if not isinstance(value, str): + if type(value) is not str: raise TypeError("allowed_methods entries must be HTTP method strings") normalized = value.strip().upper() From 45b2bda71ee1736f9f8b36ca84ef9ade868e9c99 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 12 Aug 2026 19:18:11 +0900 Subject: [PATCH 3/6] fix(security): reject subclass-controlled method list splitting --- src/egressweave/policy.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/egressweave/policy.py b/src/egressweave/policy.py index 1c69a1ff..80afe952 100644 --- a/src/egressweave/policy.py +++ b/src/egressweave/policy.py @@ -69,6 +69,13 @@ ) +def _split_exact_method_string(value: object) -> list[str]: + """Split a comma-separated method list only after exact-string validation.""" + if type(value) is not str: + raise TypeError("allowed_methods must use exact built-in strings") + return value.split(",") + + @dataclass(frozen=True) class EgressPolicy: """Immutable outbound-egress allowlist and resource policy. @@ -244,7 +251,7 @@ def __post_init__(self) -> None: method_values: Iterable[object] if isinstance(self.allowed_methods, str): - method_values = self.allowed_methods.split(",") + method_values = _split_exact_method_string(self.allowed_methods) else: method_values = self.allowed_methods normalized_methods = frozenset( @@ -371,7 +378,7 @@ def from_hosts( method_items: Iterable[str] if isinstance(allowed_methods, str): - method_items = allowed_methods.split(",") + method_items = _split_exact_method_string(allowed_methods) else: method_items = allowed_methods @@ -434,7 +441,7 @@ def from_authorities( ) method_items: Iterable[str] if isinstance(allowed_methods, str): - method_items = allowed_methods.split(",") + method_items = _split_exact_method_string(allowed_methods) else: method_items = allowed_methods @@ -504,4 +511,4 @@ def allows_http_method(self, method: str) -> bool: normalized = _normalize_allowed_method(method) except (TypeError, ValueError): return False - return normalized in self.allowed_methods + return normalized in self.allowed_methods \ No newline at end of file From 4af871e5ed1f0f7ddb141bf15430c6aee0fec5dd Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 12 Aug 2026 19:21:00 +0900 Subject: [PATCH 4/6] docs(security): document exact HTTP method policy values --- .../policy-configuration-integrity.md | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/docs/research/policy-configuration-integrity.md b/docs/research/policy-configuration-integrity.md index ea35b13f..9c0c4dfb 100644 --- a/docs/research/policy-configuration-integrity.md +++ b/docs/research/policy-configuration-integrity.md @@ -20,6 +20,21 @@ request/response byte budgets. It does not change configured defaults, allowed ranges, authority pairing, DNS policy, TLS identity, proxy isolation, HTTP method policy, request/response framing, or the generic request-time denial boundary. +HTTP method policy values are sealed at the same trusted startup boundary. Each +method value must be an exact built-in `str` before trimming, uppercase +canonicalization, or RFC 9110 token validation can invoke string behavior. +Supported comma-separated operator syntax remains available only when the outer +configuration value itself is an exact built-in `str`; non-exact string +subclasses are rejected before `split()` can run. Runtime method authorization +uses the same exact-string boundary and returns the existing generic denial for +unsupported caller values. + +This method-value restriction preserves the documented default and deny-all sets, +ordinary exact strings, comma-separated ergonomics, uppercase canonicalization, +RFC 9110 token validation, and unconditional `CONNECT` denial. It does not make +EgressWeave a Python sandbox: arbitrary trusted Python already executing in the +embedding process retains ordinary Python capabilities. + ## Why exact type matters at this boundary Python deliberately supports subclassing immutable built-in types such as `int`, @@ -61,6 +76,11 @@ integrations. 7. Regression tests exercise the public `EgressPolicy` constructors so the contract is proven at the API boundary rather than only against internal helpers. +8. HTTP method entries and supported comma-separated method configuration must + be exact built-in strings before any subclass-controllable normalization or + splitting operation. +9. Runtime HTTP method authorization rejects non-exact string subclasses without + invoking their normalization methods. ## Operator migration @@ -70,6 +90,12 @@ resource budgets should materialize an exact built-in integer before policy construction. This is a pre-1.0 tightening of an ambiguous configuration shape; it does not widen egress authority or change any finite default. +Applications that supply ordinary method strings or the existing comma-separated +method syntax also need no change. Integrations that pass subclasses of `str` for +method configuration should materialize exact built-in strings before policy +construction. This is likewise a supported-value tightening, not an expansion of +HTTP authority. + ## Reference — APA 7th Python Software Foundation. (2026). *Data model — Python 3.14.6 documentation*. From 67d7e8d24f607cb92db38d139dd19fbe92d5f9c9 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 12 Aug 2026 19:21:54 +0900 Subject: [PATCH 5/6] test(docs): require method-policy release traceability --- tests/test_policy_method_value_integrity.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tests/test_policy_method_value_integrity.py b/tests/test_policy_method_value_integrity.py index f3a03d18..c80c8ee3 100644 --- a/tests/test_policy_method_value_integrity.py +++ b/tests/test_policy_method_value_integrity.py @@ -2,6 +2,8 @@ from __future__ import annotations +from pathlib import Path + import pytest from egressweave.policy import EgressPolicy @@ -71,3 +73,22 @@ def test_runtime_method_authorization_rejects_str_subclass_before_normalization( ) assert policy.allows_http_method(_NonExactMethod("GET")) is False + + +def test_policy_configuration_integrity_guide_covers_exact_method_strings() -> None: + """Document the exact HTTP method value boundary and preserved string syntax.""" + guide = Path("docs/research/policy-configuration-integrity.md").read_text( + encoding="utf-8" + ) + + assert "exact built-in `str`" in guide + assert "HTTP method" in guide + assert "comma-separated" in guide + assert "does not make EgressWeave a Python sandbox" in guide + + +def test_changelog_records_http_method_value_sealing() -> None: + """Record the method-string policy tightening in release history.""" + changelog = Path("CHANGELOG.md").read_text(encoding="utf-8") + + assert "Reject non-exact string subclasses in HTTP method policy values" in changelog From 5b62c18f3776860dee63db0c226f8be7c9d40bff Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 12 Aug 2026 19:57:33 +0900 Subject: [PATCH 6/6] docs(changelog): record exact method policy values --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1dfaecbc..4b60593d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -100,6 +100,8 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). retaining trusted configuration state. Exact built-in integers and existing ASCII decimal strings remain supported, preserving defaults and ranges while preventing subclass-controlled values from crossing immutable policy construction. +- Reject non-exact string subclasses in HTTP method policy values before + normalization, comma-separated parsing, or runtime authorization. - Erase private request-method normalization exception provenance from caller-visible policy denials. - Pin the credential-free verifier to a reviewed Python 3.13