convertEvent joins all values in multiValueHeaders with ", ", including Cookie. This produces a non-RFC-6265 Cookie header and breaks any downstream @httpHeader("cookie") consumer when the client sent more than one cookie header frame, which HTTP/2 clients routinely do.
The mirror bug exists on the response path: convertVersion1Response splits every response header value on , into an array, which mangles Set-Cookie values containing comma-bearing attributes like Expires=Wed, 09 Jun 2021 10:18:14 GMT.
- Package:
@aws-smithy/server-apigateway
- Observed on version:
1.0.0-alpha.10
- Runtime: AWS Lambda behind API Gateway REST (payload format 1.0)
Background
Per RFC 7540 §8.1.2.5, HTTP/2 clients MAY, and in practice Chrome/Firefox/curl commonly do, send each cookie as its own cookie header frame for better HPACK compression:
cookie: foo=1
cookie: bar=2
The same RFC says any intermediary converting to HTTP/1.1 semantics MUST concatenate those with "; " (0x3B 0x20), not ", ", because the Cookie grammar in RFC 6265 §4.2.1 uses "; " as its separator.
API Gateway REST (payload v1) surfaces the two frames in event.multiValueHeaders.cookie = ["foo=1", "bar=2"]. It does not pre-join them for you, the adapter is responsible for producing a correct Cookie string.
Where the bug is
smithy-typescript-ssdk-libs/server-apigateway/src/lambda.ts (paths below reflect 1.0.0-alpha.10):
function convertV1Event(event) {
return new HttpRequest({
method: event.httpMethod,
headers: convertMultiValueHeaders(event.multiValueHeaders),
...
});
}
function convertMultiValueHeaders(multiValueHeaders) {
const retVal = {};
for (const [key, val] of Object.entries(multiValueHeaders)) {
if (val !== undefined) {
retVal[key] = val.join(", "); // ← wrong separator for Cookie
}
}
return retVal;
}
For most headers (Accept, Via, X-Forwarded-For, ...) ", " is fine per RFC 9110 §5.3. Cookie is the well-known exception.
And on the response side:
function convertResponseHeaders(headers) {
const retVal = {};
for (const [key, val] of Object.entries(headers)) {
retVal[key] = val.split(",").map((v) => v.trim()); // ← mangles Set-Cookie
}
return retVal;
}
Set-Cookie must never be comma-split because valid cookie attributes contain commas (e.g. Expires=Wed, 09 Jun 2021 10:18:14 GMT).
Reproduction
Smithy model with a @httpHeader("cookie") input binding, deployed as a Lambda behind API Gateway REST. Invoke over HTTP/2 from a browser that has two cookies scoped to different Path attributes (so the browser emits two cookie frames):
cookie: a=1
cookie: session=<value>
Corresponding Lambda event:
{
"multiValueHeaders": {
"cookie": ["a=1", "session=<value>"]
}
}
Inside the Smithy handler, the deserialized cookie input field is:
An RFC 6265 parser splits on ; and sees a single pair whose name is a and whose value is 1, session=<value>. The expected session cookie is unreachable.
Single-cookie requests work because there's only one entry in the array, so the ", " join is a no-op.
Expected behavior
For the Cookie header specifically, join multi-value entries with "; " so the result is a valid RFC 6265 Cookie string.
For the Set-Cookie response header, emit the values as separate array entries without splitting on , (i.e. trust the service to hand back an array, or treat Set-Cookie as already-one-per-entry).
Suggested fix
Request path: special-case cookie in convertMultiValueHeaders:
function convertMultiValueHeaders(multiValueHeaders) {
const retVal = {};
if (multiValueHeaders === null) return retVal;
for (const [key, val] of Object.entries(multiValueHeaders)) {
if (val === undefined) continue;
const sep = key.toLowerCase() === "cookie" ? "; " : ", ";
retVal[key] = val.join(sep);
}
return retVal;
}
Response path: skip the comma-split for Set-Cookie, or preferably accept that the service layer should hand back string[] for multi-valued headers (the existing val.split(",") is a lossy round-trip for any header whose value legitimately contains ,).
Impact
Any service using @aws-smithy/server-apigateway on API Gateway REST with @httpHeader("cookie") or @httpHeader("Set-Cookie") bindings silently corrupts cookies when called by HTTP/2 clients with more than one cookie. Failure mode is confusing: the cookie header "looks" present (non-empty string, contains the expected cookie name as a substring) but parses wrong, so the usual debugging instinct ("is the cookie being sent?") points away from the real cause.
Workaround
Pre-process the event before calling convertEvent, collapsing multiValueHeaders.cookie to a single entry joined with "; ":
const cookieValues = event.multiValueHeaders?.cookie;
if (cookieValues && cookieValues.length > 0) {
event.multiValueHeaders.cookie = [cookieValues.join("; ")];
}
const converted = convertEvent(event);
convertEventjoins all values inmultiValueHeaderswith", ", includingCookie. This produces a non-RFC-6265Cookieheader and breaks any downstream@httpHeader("cookie")consumer when the client sent more than one cookie header frame, which HTTP/2 clients routinely do.The mirror bug exists on the response path:
convertVersion1Responsesplits every response header value on,into an array, which manglesSet-Cookievalues containing comma-bearing attributes likeExpires=Wed, 09 Jun 2021 10:18:14 GMT.@aws-smithy/server-apigateway1.0.0-alpha.10Background
Per RFC 7540 §8.1.2.5, HTTP/2 clients MAY, and in practice Chrome/Firefox/curl commonly do, send each cookie as its own
cookieheader frame for better HPACK compression:The same RFC says any intermediary converting to HTTP/1.1 semantics MUST concatenate those with
"; "(0x3B 0x20), not", ", because theCookiegrammar in RFC 6265 §4.2.1 uses"; "as its separator.API Gateway REST (payload v1) surfaces the two frames in
event.multiValueHeaders.cookie = ["foo=1", "bar=2"]. It does not pre-join them for you, the adapter is responsible for producing a correctCookiestring.Where the bug is
smithy-typescript-ssdk-libs/server-apigateway/src/lambda.ts(paths below reflect1.0.0-alpha.10):For most headers (
Accept,Via,X-Forwarded-For, ...)", "is fine per RFC 9110 §5.3.Cookieis the well-known exception.And on the response side:
Set-Cookiemust never be comma-split because valid cookie attributes contain commas (e.g.Expires=Wed, 09 Jun 2021 10:18:14 GMT).Reproduction
Smithy model with a
@httpHeader("cookie")input binding, deployed as a Lambda behind API Gateway REST. Invoke over HTTP/2 from a browser that has two cookies scoped to differentPathattributes (so the browser emits twocookieframes):Corresponding Lambda event:
{ "multiValueHeaders": { "cookie": ["a=1", "session=<value>"] } }Inside the Smithy handler, the deserialized
cookieinput field is:An RFC 6265 parser splits on
;and sees a single pair whose name isaand whose value is1, session=<value>. The expectedsessioncookie is unreachable.Single-cookie requests work because there's only one entry in the array, so the
", "join is a no-op.Expected behavior
For the
Cookieheader specifically, join multi-value entries with"; "so the result is a valid RFC 6265Cookiestring.For the
Set-Cookieresponse header, emit the values as separate array entries without splitting on,(i.e. trust the service to hand back an array, or treatSet-Cookieas already-one-per-entry).Suggested fix
Request path: special-case
cookieinconvertMultiValueHeaders:Response path: skip the comma-split for
Set-Cookie, or preferably accept that the service layer should hand backstring[]for multi-valued headers (the existingval.split(",")is a lossy round-trip for any header whose value legitimately contains,).Impact
Any service using
@aws-smithy/server-apigatewayon API Gateway REST with@httpHeader("cookie")or@httpHeader("Set-Cookie")bindings silently corrupts cookies when called by HTTP/2 clients with more than one cookie. Failure mode is confusing: the cookie header "looks" present (non-empty string, contains the expected cookie name as a substring) but parses wrong, so the usual debugging instinct ("is the cookie being sent?") points away from the real cause.Workaround
Pre-process the event before calling
convertEvent, collapsingmultiValueHeaders.cookieto a single entry joined with"; ":