Repository navigation
Bug: header validation is inconsistent across resolvers (single-value list, repeated headers, Content-Type casing) #8581
Description
Expected Behaviour
Header validation behaves the same on every resolver: a list[T] header accepts a single value, repeated header lines for a scalar are joined, and Content-Type is compared case-insensitively.
Current Behaviour
Three inconsistencies in middlewares/openapi_validation.py, the first and third confirmed through a real API Gateway HTTP API and REST API.
list[str]header with one value → 422list_typeon HTTP API, Function URL, ALB and VPC Lattice v1 (line 636, plusv.split(",") if "," in v else vin the data classes).x-country: USfails,x-country: US,PTworks, REST v1 works. This is the docs exampleworking_with_headers_multi_value.pyrun on another resolver.- Repeated header lines for a scalar
str→ 422string_typeon REST v1 and ALB (_restore_scalar_parameter, line 644 skips the join when the name is inmultiValueHeaders). The same request on HTTP API gives"a,b". RFC 9110 says multiple field lines equal the comma-joined list. Content-Type: Application/JSON→ 415 on every resolver (line 144). Media types are case-insensitive.application/vnd.api+jsonandapplication/problem+jsonare rejected too.
Code snippet
from typing import Annotated
from pydantic import BaseModel
from aws_lambda_powertools.event_handler import APIGatewayHttpResolver, APIGatewayRestResolver
from aws_lambda_powertools.event_handler.openapi.params import Header
from tests.functional.utils import load_event
v2 = load_event("apiGatewayProxyV2Event.json")
v2["rawPath"] = v2["requestContext"]["http"]["path"] = "/hello"; v2["requestContext"]["http"]["method"] = "GET"
v2["headers"] = {"x-country": "US"}
http = APIGatewayHttpResolver(enable_validation=True)
@http.get("/hello")
def hello(x_country: Annotated[list[str], Header(alias="x-country")]): return {"c": x_country}
print(http.resolve(v2, None)["statusCode"]) # 422 (expected 200 with ["US"])
v1 = load_event("apiGatewayProxyEvent.json"); v1["path"] = "/h"
v1["headers"] = {"X-Req": "a"}; v1["multiValueHeaders"] = {"X-Req": ["a", "b"]}
rest = APIGatewayRestResolver(enable_validation=True)
@rest.get("/h")
def h(x_req: Annotated[str, Header()]): return {"x": x_req}
print(rest.resolve(v1, None)["statusCode"]) # 422 (HTTP API gives 200 "a,b")
class Item(BaseModel):
name: str
@rest.post("/items")
def create(item: Item): return item.model_dump()
v1["path"] = "/items"; v1["httpMethod"] = "POST"; v1["body"] = '{"name":"a"}'
v1["headers"] = {"Content-Type": "Application/JSON"}; v1["multiValueHeaders"] = {"Content-Type": ["Application/JSON"]}
print(rest.resolve(v1, None)["statusCode"]) # 415 (expected 200)Possible Solution
In _normalize_multi_params, wrap non-list values in a list for sequence fields and split on , with strip(); for scalar header fields join list values with ", "; lower-case the content type before comparing and accept the +json suffix.
Steps to Reproduce
Run the snippet above with the tests/events fixtures. Full context in #8576.
Powertools for AWS Lambda (Python) version
latest
AWS Lambda function runtime
3.13
Packaging format used
PyPi
Debugging logs
Warning
This issue is ready to be picked up, by humans or by agents. If you want it, comment here first so we can assign it to you, and please take only one or two issues at a time so others can join in too. Thanks!
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsTriage
Activity
leandrodamascena commentedon Oct 19, 2026
Hey @yagruda thanks for jumping on this one, #8602 is yours. Will review.