Don't forward credentials when the verification fetch encounters a cross-origin redirect - #213
Conversation
The native redirect-verification fetch attached the WebView's cookies as a static Cookie header and issued the request with a redirect-following client. On a cross-origin redirect, OkHttp forwards that caller-set Cookie header to the destination (it drops Authorization on a host change, but not a caller-set Cookie), delivering first-party cookies to a different origin. The fetch only needs to detect a cross-origin redirect, not follow it with credentials. Disable redirect following on the verification client and resolve the destination from the Location header, comparing the full origin (scheme, host, and port) so a scheme downgrade or port change counts as cross-origin. No credential-bearing request is ever sent to a redirect destination. Session.visitRequestFailedWithNonHttpStatusCode no longer gates on response.isSuccessful (which only held when redirects were followed to a 2xx); detecting the cross-origin redirect response is sufficient to propose the cross-origin redirect visit. Adds HttpRepositoryTest (MockWebServer) covering the no-leak invariant, cross/same-origin detection, and relative Location resolution.
f7f5403 to
fcd94e8
Compare
Keep the reason the fetch doesn't follow redirects and drop the rest: the same-origin comparison reads off its own body, and the guarantee it supports is stated once where the client is built. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DyPGJuffiyfWW9113PNxHG
Both MockWebServer instances bind the same loopback host, so the existing cross-origin case differs from the first-party origin only by port and exercises just the port term of the origin comparison. Add a case with an absolute off-host Location, which needs no second server precisely because the redirect isn't followed. Also trim the test's comments, keeping the two that say why a request count is asserted. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DyPGJuffiyfWW9113PNxHG
|
🤖 Review pass on this branch, run by an agent on Rosa's account. Two commits pushed on top of yours; everything else below is left for you to decide. Base branch
Retargeting to Credentials on a redirect, every hopWith
Hostile Pushed
Worth deciding before merge1. Multi-hop is the regression to weigh. 2. 3. The origin comparison widens behaviour as well as tightening it. 4. Nits. An empty 5. The description and commit message describe the pre-#211 code ("attached the WebView's cookies as a static Cookie header and issued the request with a redirect-following client"). On this base that isn't what the fetch does any more. I left your commit message alone. 6. #215 overlaps. It adds One nice side effect nobody claimed: |
Summary
The native redirect-verification fetch in
HttpRepositoryattaches the WebView's cookies to the request as a staticCookieheader and then issues it with a client that follows redirects. When the requested URL responds with a cross-origin redirect, OkHttp forwards that manually-setCookieheader to the redirect destination — delivering first-party session cookies to a different origin. (OkHttp drops theAuthorizationheader when a redirect changes host, but it does not drop a caller-setCookieheader.)This fetch only needs to detect whether the response is a cross-origin redirect; it never needs to follow that redirect with credentials. This change stops following redirects on the verification fetch and inspects the
Locationheader directly, so no credential-bearing request is ever sent to a redirect destination.Changes
followRedirects(false)/followSslRedirects(false).Location. The redirect target is computed by resolving theLocationheader against the request URL, so relative, protocol-relative, and absolute locations are all handled correctly.Session.visitRequestFailedWithNonHttpStatusCodepreviously gated onresponse.isSuccessful, which only held when redirects were followed through to a 2xx. Since the verification fetch no longer follows redirects, the response is the unfollowed 3xx; detecting a cross-origin redirect is now sufficient to propose the cross-origin redirect visit.Only the first redirect hop is inspected — sufficient to detect a direct cross-origin redirect. Deeper same-origin chains that would eventually cross origin now fail closed (the visit fails) rather than being followed with credentials.
Testing
Adds
HttpRepositoryTest(MockWebServer):Cookieor other credential can reach it — while the first-party request still carries its cookies.Locationresolves against the request origin (same-origin).Scope run locally:
./gradlew :core:testDebugUnitTest(core module unit tests).