Skip to content

Bug: header validation is inconsistent across resolvers (single-value list, repeated headers, Content-Type casing) #8581

Open

Description

@leandrodamascena

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.

  1. list[str] header with one value → 422 list_type on HTTP API, Function URL, ALB and VPC Lattice v1 (line 636, plus v.split(",") if "," in v else v in the data classes). x-country: US fails, x-country: US,PT works, REST v1 works. This is the docs example working_with_headers_multi_value.py run on another resolver.
  2. Repeated header lines for a scalar str → 422 string_type on REST v1 and ALB (_restore_scalar_parameter, line 644 skips the join when the name is in multiValueHeaders). The same request on HTTP API gives "a,b". RFC 9110 says multiple field lines equal the comma-joined list.
  3. Content-Type: Application/JSON → 415 on every resolver (line 144). Media types are case-insensitive. application/vnd.api+json and application/problem+json are 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!

Activity

  1. added this to the 3.39.0 milestone on Oct 18, 2026
  2. leandrodamascena commented on Oct 19, 2026

    @leandrodamascena
    ContributorAuthor

    Hey @yagruda thanks for jumping on this one, #8602 is yours. Will review.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions