Expected Behaviour
With enable_validation=True, a scalar query string or header parameter should receive its value unchanged, including any commas. For example, ?search=hello,world into search: str should give "hello,world", and X-Forwarded-For: 203.0.113.9, 10.0.0.1 into a str header should give the whole chain.
Splitting on commas should only apply to sequence parameters such as List[str], which is what it's there for.
Current Behaviour
resolved_query_string_parameters (and resolved_headers_field for HTTP API / Lambda Function URL events) splits every value on commas. After that, _process_scalar_param only unwraps lists with one item, so a scalar parameter whose value contains a comma gets a list:
- A plain
str query or header parameter fails validation with 422 string_type.
- A
str field on a Pydantic model used with Query() gets only the part before the first comma, and the handler runs with truncated data. _normalize_field_value takes value[0].
I can reproduce this with APIGatewayHttpResolver, LambdaFunctionUrlResolver, ALBResolver (single-value mode, where ALB never joins values with commas) and VPCLatticeV2Resolver. It breaks ordinary inputs like search text, ?coords=49.26,-123.25, ?sort=name,asc, and X-Forwarded-For on any request that went through a proxy.
This is the same problem as #6520, which was fixed for BedrockAgentResolver only (#6777).
Code snippet
from typing import Annotated
from pydantic import BaseModel
from aws_lambda_powertools.event_handler import APIGatewayHttpResolver
from aws_lambda_powertools.event_handler.openapi.params import Header, Query
app = APIGatewayHttpResolver(enable_validation=True)
class SearchParams(BaseModel):
search: str
tags: list[str]
@app.get("/scalar")
def scalar(search: str):
return {"search": search}
@app.get("/header")
def header(x_forwarded_for: Annotated[str, Header()]):
return {"x_forwarded_for": x_forwarded_for}
@app.get("/model")
def model(params: Annotated[SearchParams, Query()]):
return params.model_dump()
def event(path, query=None, headers=None):
return {
"version": "2.0",
"routeKey": f"GET {path}",
"rawPath": path,
"rawQueryString": "",
"headers": headers or {},
"queryStringParameters": query,
"requestContext": {
"http": {"method": "GET", "path": path, "protocol": "HTTP/1.1", "sourceIp": "203.0.113.9", "userAgent": "x"},
"stage": "$default",
"requestId": "id",
},
"isBase64Encoded": False,
}
for e in [
event("/scalar", query={"search": "hello,world"}),
event("/header", headers={"x-forwarded-for": "203.0.113.9, 10.0.0.1"}),
event("/model", query={"search": "hello,world", "tags": "a,b"}),
]:
result = app.resolve(e, {})
print(int(result["statusCode"]), result["body"])
Output:
422 {"statusCode":422,"detail":[{"loc":["query","search"],"type":"string_type"}]}
422 {"statusCode":422,"detail":[{"loc":["header","x-forwarded-for"],"type":"string_type"}]}
200 {"search":"hello","tags":["a","b"]}
Possible Solution
Join the split values back together for non-sequence parameters, in _process_scalar_param and in _process_model_param for non-sequence model fields. Sequence parameters would still be split as they are today.
Values are split with a plain str.split(","), so joining them with "," gives back exactly what the event contained, spaces included. It also keeps working with ALB's decode_query_parameters, which decodes after splitting.
I have this change with tests ready and would like to open the PR.
Steps to Reproduce
- Create any HTTP resolver with
enable_validation=True.
- Add a route with a
str query or header parameter, or a Pydantic model with a str field used with Query().
- Send a request where that value contains a comma, e.g.
?search=hello,world.
- The request is rejected with 422, or the model field only contains
hello.
Powertools for AWS Lambda (Python) version
latest (3.35.0)
AWS Lambda function runtime
3.10
Packaging format used
PyPi
Debugging logs
N/A: reproduced locally with the snippet above.
Expected Behaviour
With
enable_validation=True, a scalar query string or header parameter should receive its value unchanged, including any commas. For example,?search=hello,worldintosearch: strshould give"hello,world", andX-Forwarded-For: 203.0.113.9, 10.0.0.1into astrheader should give the whole chain.Splitting on commas should only apply to sequence parameters such as
List[str], which is what it's there for.Current Behaviour
resolved_query_string_parameters(andresolved_headers_fieldfor HTTP API / Lambda Function URL events) splits every value on commas. After that,_process_scalar_paramonly unwraps lists with one item, so a scalar parameter whose value contains a comma gets a list:strquery or header parameter fails validation with 422string_type.strfield on a Pydantic model used withQuery()gets only the part before the first comma, and the handler runs with truncated data._normalize_field_valuetakesvalue[0].I can reproduce this with
APIGatewayHttpResolver,LambdaFunctionUrlResolver,ALBResolver(single-value mode, where ALB never joins values with commas) andVPCLatticeV2Resolver. It breaks ordinary inputs like search text,?coords=49.26,-123.25,?sort=name,asc, andX-Forwarded-Foron any request that went through a proxy.This is the same problem as #6520, which was fixed for
BedrockAgentResolveronly (#6777).Code snippet
Output:
Possible Solution
Join the split values back together for non-sequence parameters, in
_process_scalar_paramand in_process_model_paramfor non-sequence model fields. Sequence parameters would still be split as they are today.Values are split with a plain
str.split(","), so joining them with","gives back exactly what the event contained, spaces included. It also keeps working with ALB'sdecode_query_parameters, which decodes after splitting.I have this change with tests ready and would like to open the PR.
Steps to Reproduce
enable_validation=True.strquery or header parameter, or a Pydantic model with astrfield used withQuery().?search=hello,world.hello.Powertools for AWS Lambda (Python) version
latest (3.35.0)
AWS Lambda function runtime
3.10
Packaging format used
PyPi
Debugging logs