Repository navigation
Conversation
A redirect or authentication retry decoded a caller-set Cookie header
leniently, then re-encoded it strictly, whenever the cookie store also
contributed cookies to the hop. Values accepted on the first hop could
therefore fail on the next one, pairs could be reordered, and valueless
or unbalanced-quote pairs were dropped.
Keep caller-set header pairs as text and cut out only the pairs whose
names the response replaced. The rest stays as written, separators
included, so prefs={"a":"x;y"} or A=1;B=2 is not rewritten, and a
header that loses no pair is returned unchanged. Cookie objects are
still encoded normally, and caller cookies are still stripped at
cross-origin redirect boundaries.
Find the pairs by splitting on every ';', as
NettyRequestFactory.mergeCookies does when it decides which names the
caller set; RFC 6265 does not allow a ';' in a cookie value, quoted or
not. Treating a double quote as opening a quoted string would let a
header such as X=a"b; SID=old hide a SID that a redirect, 401 or 407
response rotated or deleted, so that the next request resent the stale
session.
Restore the Javadoc of CallerCookies.of, and fix the comment on the
Cookie header in Redirect30xInterceptor, which still said the caller's
pairs travel in the cookie list. Add tests for redirects with cookie
rotation, Digest, proxy and NTLM retries, the lax encoder, several
caller Cookie headers, a raw pair deleted with Max-Age=0, quotes in
front of a replaced pair, and byte-exact kept text.
Follow-up to fd97636 (GHSA-2jwh-9rmr-j4xf).
OpenAI Codex and Claude Code on behalf of Matthias Kurz
Co-Authored-By: Codex <codex@openai.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
11 of 15 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Cookieheader as text, removing only the pairs that the response replaced, instead of decoding it leniently and re-encoding it strictly.;, asNettyRequestFactory.mergeCookiesdoes, so that a quote in front of a replaced pair cannot hide it and resend a stale session.Problem
Since fd97636, a caller-set
Cookieheader is kept when the cookie store also contributes cookies to a hop, with the pairs that the response replaced removed. The cookie store is enabled by default (ThreadSafeCookieStore), so this path covers ordinary clients. It applies to redirects and to authentication retries: 401 (Basic, Digest, NTLM) and proxy 407.To find the replaced pairs,
CallerCookiesdecoded the caller's header leniently and re-encoded the remaining pairs strictly. That broke headers that the first request had sent without complaint:Change
;, asmergeCookiesdoes when it decides which names the caller set. RFC 6265 does not allow a;in a cookie value, quoted or not. Keeping the text while splitting like the lenient decoder, which treats a double quote as opening a quoted string, would be unsafe. A header such asX=a"b; SID=oldorX="a; SID=oldwould become a single pair namedX. ASIDthat a redirect, 401 or 407 response rotated, or deleted withMax-Age=0, would stay in the header.mergeCookieswould still count it as the caller's and drop the store's new value, so the next request would carry the stale session. Tests pin these cases.prefs={"a":"x;y"}orA=1;B=2is not rewritten."; ".CallerCookies.of, and fix a stale comment inRedirect30xInterceptor.Compatibility
There is no public API change. On redirects and authentication retries, a caller-set
Cookieheader now reaches the next hop as written, minus the pairs the response replaced. Before, it could be reordered, lose pairs, or fail the hop.AI disclosure
OpenAI Codex and Claude Code on behalf of Matthias Kurz. The change was written in two steps, the first with Codex and the second with Claude Code, and squashed into one commit, which carries both
Co-Authored-Bytrailers perAGENTS.md.Test plan
New tests in
RedirectCookieRotationTestandAuthRetryCookieTestcover:Cookieheaders, and a raw pair deleted withMax-Age=0;Runs:
./mvnw -B -ntp -Dgpg.skip=true clean verifyon JDK 11: 1,808 tests, 0 failures, 0 errors, 22 skipped; Revapi passed. No test-skipping flags were used.-Djvm): 245 tests each, 0 failures.Cookieheaders on retries,ResponseBodyControl.execute, suspend/resume order, demand-bounded decompression): they merge without conflicts, together and in every pair, and./mvnw -B -ntp -Dgpg.skip=true clean installon JDK 11 passes with 1,853 tests, 0 failures, 0 errors, 22 skipped; Revapi passed.Cookie: z=1; flash=stale; a=2; flagthrough a same-origin redirect that setsflash, with the cookie store enabled. The next hop receivesz=1; a=2; flag; flash=redirect-cookie. Against AHC without this change, the valuelessflagis dropped and the test fails.