Repository navigation
Bug: routing and Response issues: unescaped route regex, regex strip_prefixes, gzip q=0, bytes JSON body, is_json() casing, missing Vary: Origin #8580
Description
Expected Behaviour
- Literal route segments match literally, and a regex
strip_prefixesonly strips the prefix once at the start of the path. Responsehandles a bytes body with a JSON content type, finds the content type header case-insensitively, and only compresses when the client accepts gzip.- CORS responses that echo a specific origin carry
Vary: Origin.
Current Behaviour
Six routing and response bugs in api_gateway.py, the first and third confirmed through a real API Gateway REST API and the last through a real HTTP API.
- Rules compiled as unescaped regex (
_compile_regex, line 2778)./items.jsonmatches/itemsXjson(200). A literal/a+breturns 404 for/a+b./a|bmatchesb. - Regex
strip_prefixesreplaces every occurrence (line 2809).re.sub(prefix, "", path)has nocount=1and no anchor:strip_prefixes=[re.compile(r"/v[1-9]+")]on/v1/users/v2yields/users, 404. Accept-Encoding: gzip;q=0still compresses,GZIPdoes not (line 755). The check is a substring test. Real API:content-encoding: gzipreturned to a client that refused gzip.Response(content_type=APPLICATION_JSON, body=b'...')raisesTypeError(line 838) whenenable_validation=False. The docstring says bytes are base64 encoded.is_json()only matchesContent-Typewith exact casing (line 364).Response(headers={"content-type": "application/json"}, body={...})returns the dict unserialized. Real API: 502.- No
Vary: Origin(CORSConfig.to_dict, line 232). Withallow_origin="https://a.com", extra_origins=["https://b.com"]the response echoeshttps://a.comwith noVary, so a shared cache can serve it to origin B.
Code snippet
import re
from aws_lambda_powertools.event_handler import APIGatewayRestResolver, ALBResolver, CORSConfig, Response, content_types
from tests.functional.utils import load_event
ev = load_event("apiGatewayProxyEvent.json")
def get(rule, path, **kw):
app = APIGatewayRestResolver(**kw)
@app.get(rule)
def r(**args): return {"args": args}
e = dict(ev); e["path"] = path
return app.resolve(e, None)["statusCode"]
print(get("/items.json", "/itemsXjson")) # 200 (expected 404)
print(get("/a+b", "/a+b")) # 404 (expected 200)
print(get("/users/<name>", "/v1/users/v2", strip_prefixes=[re.compile(r"/v[1-9]+")])) # 404 (expected 200)
app = APIGatewayRestResolver()
@app.get("/my/path", compress=True)
def gz(): return {"a": "b" * 50}
e = dict(ev); e["headers"] = {"Accept-Encoding": "gzip;q=0"}; e["multiValueHeaders"] = {"Accept-Encoding": ["gzip;q=0"]}
print(app.resolve(e, None)["multiValueHeaders"].get("Content-Encoding")) # ['gzip'] (expected None)
app = APIGatewayRestResolver()
@app.get("/my/path")
def by(): return Response(200, content_types.APPLICATION_JSON, body=b'{"a":1}')
app.resolve(ev, None) # TypeError: bytes is not JSON serializable
app = APIGatewayRestResolver()
@app.get("/my/path")
def lower(): return Response(200, headers={"content-type": "application/json"}, body={"a": 1})
print(type(app.resolve(ev, None)["body"])) # <class 'dict'>
alb = ALBResolver(cors=CORSConfig(allow_origin="https://a.com", extra_origins=["https://b.com"]))
@alb.get("/lambda")
def c(): return {}
e = load_event("albEvent.json"); e["headers"]["origin"] = "https://a.com"
print(alb.resolve(e, None)["headers"]) # no VaryPossible Solution
re.escape the literal fragments in _compile_regex; re.sub(..., count=1) anchored at the start; parse Accept-Encoding tokens with q values and lower-case them; in ResponseBuilder.build skip serialization for bytes; case-insensitive header lookup in is_json(); add Vary: Origin when the echoed origin is not *.
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
Oakcube4life commentedon Oct 18, 2026
Hi! I’d like to take this one. I’m planning to fix all six items in api_gateway.py as described, with a functional test for each. Could you assign it to me?
leandrodamascena commentedon Oct 19, 2026
Hey @Oakcube4life pls go ahead. It's six small fixes, feel free to split into two PRs if it gets big.