diff --git a/README.md b/README.md index 0108576..e478947 100644 --- a/README.md +++ b/README.md @@ -301,6 +301,14 @@ arrive as strings and usually need a separate model that keeps coercion. See [Strict API boundary models](docs/strict-models.md) for runnable examples and exact validation responses. +For a strict JSON endpoint, use +`@validate_http(body=CreateUserRequest, response_model=CreateUserResponse, require_json_content_type=True)`. +The compatibility default is `False`: valid JSON is parsed even when labeled +`text/plain` or sent without `Content-Type`. Strict mode accepts `application/json` +and `application/*+json`; a non-empty body with another or missing media type +returns `415`. An empty body still returns the missing-body `422`, not `415`. +See [JSON Content-Type configuration](docs/configuration.md#require_json_content_type). + Start the Functions host locally: ```bash diff --git a/docs/configuration.md b/docs/configuration.md index 900b915..e12c9be 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -105,6 +105,17 @@ their parsing limits. ### `require_json_content_type` +**Recommended for new public JSON endpoints:** set this option explicitly to +`True`. Existing applications keep the relaxed `False` default for compatibility; +it parses valid JSON regardless of the header, including `text/plain` or an absent +header. The relaxed default alone is not evidence of an exploitable vulnerability. + +Migration: first update clients to send `application/json` (or a supported +`application/*+json` type), then opt individual routes into strict mode and test +their `415` responses. No runtime warning or default change is introduced here. +Any future default change requires a separately reviewed breaking-release +migration decision and usage evidence; it must not silently ship in a patch. + Set `require_json_content_type=True` with `body=` to reject non-empty bodies whose media type is neither `application/json` nor `application/*+json` with `415 Unsupported Media Type`. Matching is case-insensitive and ignores diff --git a/tests/test_body_policies.py b/tests/test_body_policies.py index 724cf0c..603c0be 100644 --- a/tests/test_body_policies.py +++ b/tests/test_body_policies.py @@ -141,12 +141,19 @@ def test_missing_media_type_with_empty_body_keeps_missing_body_response( assert response.status_code == 422 assert json.loads(response.get_body())["detail"][0]["type"] == "missing" + @pytest.mark.parametrize( + "headers", + [ + {"Content-Type": "application/json"}, + {"Content-Type": "application/problem+json"}, + {"Content-Type": "text/plain"}, + {}, + ], + ) def test_default_still_parses_json_regardless_of_media_type( - self, mock_request_factory: RequestFactory + self, headers: dict[str, str], mock_request_factory: RequestFactory ) -> None: - response = _body_handler()( - mock_request_factory(body=b'{"name":"Ada"}', headers={"Content-Type": "text/plain"}) - ) + response = _body_handler()(mock_request_factory(body=b'{"name":"Ada"}', headers=headers)) assert response.status_code == 200