Skip to content

fix(event-handler): handle set-cookie header values with multiple attributes#4990

Open
nateiler wants to merge 3 commits intoaws-powertools:mainfrom
nateiler:event-handler-set-cookie
Open

fix(event-handler): handle set-cookie header values with multiple attributes#4990
nateiler wants to merge 3 commits intoaws-powertools:mainfrom
nateiler:event-handler-set-cookie

Conversation

@nateiler
Copy link

@nateiler nateiler commented Feb 4, 2026

Summary

This fixes the scenario where a single set-cookie header value foo=bar; Max-Age=3600 is treated as two set-cookie values because multiple values may be separated by a ;.

Changes

When parsing the headers, set-cookie headers are handled using the getSetCookie method.

Logic to detect multiple header entries has been removed as multiple header values are appended to a single header (via comma).

Issue number: closes #4986


By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

Disclaimer: We value your time and bandwidth. As such, any pull requests created on non-triaged issues might not be successful.

@pull-request-size pull-request-size bot added the size/L PRs between 100-499 LOC label Feb 4, 2026
@boring-cyborg boring-cyborg bot added event-handler This item relates to the Event Handler Utility tests PRs that add or change tests labels Feb 4, 2026
@boring-cyborg
Copy link

boring-cyborg bot commented Feb 4, 2026

Thanks a lot for your first contribution! Please check out our contributing guidelines and don't hesitate to ask whatever you need.
In the meantime, check out the #typescript channel on our Powertools for AWS Lambda Discord: Invite link

Copy link
Contributor

@dreamorosi dreamorosi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the PR @nateiler - appreciate it.

I have left a couple comments on the diff, would like to also get @svozza's review on the PR before we merge/

Comment on lines 231 to 247
// Handle set-cookie headers specially using getSetCookie()
const cookies = webHeaders.getSetCookie();
// Some legacy implementations may concatenate multiple set-cookie values with commas
// Split them out while preserving the cookie attributes (which use semicolons)
const allCookies: string[] = [];
for (const cookie of cookies) {
allCookies.push(...cookie.split(',').map((v) => v.trimStart()));
}

if (allCookies.length > 1) {
multiValueHeaders['set-cookie'] = allCookies;
} else if (allCookies.length === 1) {
headers['set-cookie'] = allCookies[0];
}

for (const [key, value] of webHeaders.entries()) {
// Skip set-cookie as it's already handled above
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's remove these comments please

Comment on lines -234 to +254
if (headers[key]) {
multiValueHeaders[key] = [headers[key], ...values];
delete headers[key];
} else if (values.length > 1) {
if (values.length > 1) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we removing this logic?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To my knowledge set-cookie is the only header that can have multiple keys. Other headers, when appended to, will append the value to the end via a comma. This logic would never be evaluated (which was verified through the code coverage)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a lot of diffs that seem to be unrelated to this PR - any chance you could revert?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, yeah. Looks like my own biome got the best of me.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, it looks like these changes were automatically made via lint-staged commit hook. After reverting them and attempting to commit, my revert gets stashed and I'm left with an empty commit which is prevented.

@dreamorosi dreamorosi requested a review from svozza February 5, 2026 08:56
// Prepare
const headers = new Headers({
'set-cookie': 'session=abc123; theme=dark',
'cache-control': 'no-cache; no-store',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How come we're changing these?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

set-cookie is a header that shouldn't be separated by a semicolon. Keeping it as it were would result in a single set-cookie header with an unsupported 'theme' attribute which would cause the test to fail and ultimately seems to defeat the purpose of the test. cache-control represents a normal header that can be separated by a semicolon. Other tests cover the nuances of 'set-cookie'.

headers: { 'content-type': 'application/json' },
multiValueHeaders: { 'set-cookie': ['session=abc123', 'theme=dark'] },
multiValueHeaders: {
'cache-control': ['no-cache', 'no-store'],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where are these cache-control headers coming from?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added them to illustrate a 'simple' multi-value header pair along with the set-cookie multi-value header which contains a semicolon.

continue;
}

const values = value.split(/[;,]/).map((v) => v.trimStart());
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ultimately, splitting on a semicolon seems out of place and is the root of this entire issue. If a goal wasn't to keep backwards compatibility I would suggest removing it and using a comma only. Headers by default a separated by a comma.

const headers = new Headers({
  'accept': 'application/json'
})

headers.append('accept', 'text/html');

const acceptHeaders = headers.get('accept'); // Results in "application/json, text/html"

Copy link
Contributor

@svozza svozza Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the reason I wrote it this way was because I was under the impression that cookies were an exception in that they could be separated by a semicolon. I got this from the RFC 6265:

The user agent sends stored cookies to the origin server in the
Cookie header. If the server conforms to the requirements in
Section 4.1 (and the user agent conforms to the requirements in
Section 5), the user agent will send a Cookie header that conforms to
the following grammar:

cookie-header = "Cookie:" OWS cookie-string OWS
cookie-string = cookie-pair *( ";" SP cookie-pair )

It looks like I made a mistake and thought this referred to the Set-Cookie header rather than the Cookie header as indicated in the quote above.

Copy link
Contributor

@svozza svozza Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What this means though is that to handle the Cookie header case correctly we do need to have special handling for the semicolon. Btw, regarding this:

If a goal wasn't to keep backwards compatibility

We don't need to preserve backward compatibility for what was a bug. If we change this so only Cookie gets this handling and that's fine.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the references to the RFC. In this particular circumstance the webHeadersToApiGatewayV1Headers function is converting from server to user agent (not also user agent to server) so we should be able to omit special handling of the Cookie header as per RFC6265 4.2 the Cookie header is a user agent to server header.

It seems like the semicolon was included to handle the special usage of the 'Cookie' header which we don't actually need here.

We don't need to preserve backward compatibility for what was a bug. If we change this so only Cookie gets this handling and that's fine.

I see your point here; however I think only set-cookie with a semicolon is a bug. Since the current implementation accepts any header with a semicolon separated value (while it may not be popular) removing it might break current implementations.

I'm unsure of the best path here. Ultimately it seems like dropping the semicolon would simplify and unify the usage across the board (all headers are treated the same).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The issue here is larger though, splitting on ; causes worse problems. The accepted way to inidicate parameters in a header is with a semi colon, e.g., Accept: text/html; q=0.5, text/plain; q=0.2. With the current implementation, this ends up with a multivalue header value which is clearly wrong:

// ...
multiValueHeaders: {
  accept: [
    "text/html",
    "q=0.5",
    "text/plain",
    "q=0.2",
  ]
}
// ...

It's far more important that we handle this case correctly than headers that aren't Cookie erroneously delimited by ;.

@sonarqubecloud
Copy link

sonarqubecloud bot commented Feb 5, 2026

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

event-handler This item relates to the Event Handler Utility size/L PRs between 100-499 LOC tests PRs that add or change tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: Event Handler Response Headers incorrectly handling Set-Cookie values

3 participants