Skip to content

@aws-smithy/server-apigateway: convertEvent mangles Cookie headers from HTTP/2 clients on API Gateway REST (v1) #1997

Description

@PeterUlb

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:

a=1, session=<value>

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);

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions