Repository navigation
Bug: body validation crashes on non-ASCII multipart data and accepts a JSON array for an object parameter #8578
Description
Expected Behaviour
With enable_validation=True:
- a
multipart/form-databody with non-ASCII text, or without aboundary, returns 422/415; - a JSON array sent to a parameter declared as an object returns 422;
- a
Content-Dispositionheader withfilename=beforename=is parsed.
Current Behaviour
Three body validation problems in middlewares/openapi_validation.py, all confirmed through a real API Gateway REST API:
- Non-ASCII multipart crashes (502). A text part containing
caféraisesUnicodeDecodeError, which escapes becauseexcept ValueError: raisere-raises Unicode errors (they subclassValueError). Non-base64 bodies are encoded withlatin-1, which is lossy, and then decoded asutf-8. A missingboundaryraises a bareValueError. Lines 221-241, 761, 785. - JSON array accepted for an object parameter.
[{"name":"a","price":1},{"name":"b","price":2}]sent todef create(item: Item)returns 200 with the first element only. The list unwrapping in_normalize_field_value(line 541) exists forparse_qsform values but is applied to JSON bodies too. Same withAnnotated[str, Body()]and["a","b"]. filename=beforename=misparsed.find('name="')matches insidefilename="…"(line 800), so the part is not found and the request gets 422 missing.
Code snippet
from typing import Annotated
from pydantic import BaseModel
from aws_lambda_powertools.event_handler import APIGatewayRestResolver
from aws_lambda_powertools.event_handler.openapi.params import File, Form
from tests.functional.utils import load_event
def v1(method, path, body, ct):
e = load_event("apiGatewayProxyEvent.json")
e["httpMethod"] = method; e["path"] = path; e["body"] = body; e["isBase64Encoded"] = False
e["headers"] = {"content-type": ct}; e["multiValueHeaders"] = {"content-type": [ct]}
return e
app = APIGatewayRestResolver(enable_validation=True)
@app.post("/up")
def up(file: Annotated[bytes, File()], note: Annotated[str, Form()]):
return {"note": note}
class Item(BaseModel):
name: str
price: float
@app.post("/items")
def create(item: Item):
return item.model_dump()
multipart = ('--XX\r\nContent-Disposition: form-data; name="file"; filename="a.txt"\r\n\r\nhello\r\n'
'--XX\r\nContent-Disposition: form-data; name="note"\r\n\r\ncafé\r\n--XX--\r\n')
app.resolve(v1("POST", "/up", multipart, "multipart/form-data; boundary=XX"), None)
# UnicodeDecodeError: 'utf-8' codec can't decode byte 0xe9
r = app.resolve(v1("POST", "/items", '[{"name":"a","price":1},{"name":"b","price":2}]', "application/json"), None)
print(r["statusCode"], r["body"]) # 200 {"name":"a","price":1.0} (expected 422)Possible Solution
Encode non-base64 bodies as utf-8; decode text parts inside the generic except Exception and raise RequestValidationError; turn the missing-boundary case into RequestUnsupportedContentType. Only unwrap lists for form/multipart-sourced values. Parse Content-Disposition with a proper parameter parser or match (^|;\s*)name=.
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
MTirnova commentedon Oct 18, 2026
Hi! I'd like to pick this one up (I also have #8620 open for #8587, so this would be my second). Plan:
UnicodeDecodeError/ a missingboundaryinto a proper 422/415 instead of letting them escape as a 502._normalize_field_valueto form/query values, not to JSON bodies, so a JSON array sent to an object parameter returns 422.Content-Dispositionparameters properly sofilename=beforename=works.I'll add functional tests for each case.
leandrodamascena commentedon Oct 18, 2026
Hey @MTirnova pls go ahead.