Skip to content

Properly handle negative Max-Age in RFC6265SetCookieParser#14311

Open
Petersoj wants to merge 1 commit intojetty:jetty-12.1.xfrom
Petersoj:fix-set-cookie-parser-negative-max-age
Open

Properly handle negative Max-Age in RFC6265SetCookieParser#14311
Petersoj wants to merge 1 commit intojetty:jetty-12.1.xfrom
Petersoj:fix-set-cookie-parser-negative-max-age

Conversation

@Petersoj
Copy link
Contributor

Parsing a Set-Cookie header with a negative Max-Age should be treated as the cookie expiring immediately, which is the same as Max-Age=0. RFC6265SetCookieParser and HttpCookie currently treat a negative Max-Age attribute the same as removing the Max-Age attribute, which is incorrect according to rfc6265#section-5.2.2:

If delta-seconds is less than or equal to zero (0), let expiry-time be the earliest representable date and time.

@joakime joakime added Bug For general bugs on Jetty side Specification For all industry Specifications (IETF / Servlet / etc) labels Jan 10, 2026
@joakime joakime requested review from joakime and sbordet January 10, 2026 14:07
@joakime joakime moved this to 👀 In review in Jetty 12.1.6 - FROZEN Jan 10, 2026
@joakime joakime moved this to 👀 In review in Jetty 12.1.7 FROZEN Jan 10, 2026
@joakime
Copy link
Contributor

joakime commented Jan 10, 2026

The spec at https://datatracker.ietf.org/doc/html/rfc6265#section-5.2.2 also says ...

If the first character of the attribute-value is not a DIGIT or a "-"
character, ignore the cookie-av.

I think this is where that (bad) logic came from.

@afarber
Copy link
Contributor

afarber commented Jan 26, 2026

An alternative fix would be in HttpCookie.java:

case "max-age" -> maxAge(StringUtil.isBlank(value) ? -1 : Math.max(0, Long.parseLong(value))); 

@Petersoj
Copy link
Contributor Author

@afarber my initial goal was to not modify the existing behavior of HttpCookie.Builder since users may expect that passing a negative value to HttpCookie.Builder.maxAge() will remove the attribute from the Builder. However, if we're ok with changing the HttpCookie.Builder.maxAge() behavior, then yes, I think your solution is better.

@sbordet
Copy link
Contributor

sbordet commented Feb 17, 2026

I think the matter is more complicated.

JDK's HttpCookie states (https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/net/HttpCookie.html#setMaxAge(long)):

  • -1 keep around until client session expired
  • 0 already expired.

Jakarta's Cookie has the exact identical javadocs 😮 .
From Java EE7 to Jakarta EE 11, this has always been consistent: -1 to keep around, 0 as expired.

Jetty's HttpCookieFacade just forwards the call to the JDK instance.
Same for JavaNetHttpCookie.

Jetty's HttpCookieUtils translates the cookie into a String, retaining the max-age value (the value is not processed).

Jetty's RFC6265SetCookieParser does nothing special in the parsing, it just takes whatever attribute there is and calls HttpCookie.Builder.attribute(...), so I don't think we should change this parser.

Jetty's HttpCookie.Builder.maxAge() incorrectly removes the attribute if the value is negative. I think this is a bug, as we want to retain the value to be interpreted by clients.

There are other cases where we do not treat a negative value as significant, so I'll rework those as well.

@Petersoj thanks for raising this issue! I'm going to close this PR and rework max-age handling across the code.

The point is that the cookie max-age value must be preserved in any case. Is it the client that interprets it, and only the client must eventually take action following the cookie max-age value.

Also, the Java/Jakarta javadocs are in contrast with RFC 6265, which also contradits itself.
RFC 6265 ABNF does not allow for negative values:

max-age-av        = "Max-Age=" non-zero-digit *DIGIT
non-zero-digit    = %x31-39; digits 1 through 9

However, section 5.2.2 mentions the - character and the fact that the value may be less than 0, and treats values <= 0 the same.

MDN follows the RFC: https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Set-Cookie#max-agenumber

I will check what browsers actually do, but I suspect they will follow the RFC. If that's the case, we have a problem because the Java/Jakarta javadocs say one thing, but RFC another.

@afarber
Copy link
Contributor

afarber commented Feb 17, 2026

Context Max-Age absent Max-Age=0 Max-Age=-1
RFC 6265 ABNF (section 4.1.1) No attribute Valid Invalid (grammar only allows non-zero-digit *DIGIT)
RFC 6265 parsing (section 5.2.2) N/A Expire immediately Accept it, treat as expire immediately (same as 0)
JDK HttpCookie / Jakarta Cookie getMaxAge() returns -1 ("session cookie") Expire immediately "Session cookie" - but this is a Java API convention for "not set", not a wire-protocol value
Jetty HttpCookie.Builder.maxAge() currently No attribute stored Stores Max-Age=0 Removes the attribute entirely (turns it into session cookie instead of expiring it)

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

Labels

Bug For general bugs on Jetty side Specification For all industry Specifications (IETF / Servlet / etc)

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

4 participants