Fix page.route changing request headers on the wire - #675
Open
pratyush618 wants to merge 3 commits into
Open
Conversation
Playwright pairs every setRequestInterception with setCacheDisabled, and LOAD_BYPASS_CACHE makes necko attach Pragma: no-cache and Cache-Control: no-cache to every request. Firefox only sends those for a forced reload. INHIBIT_CACHING alone keeps responses out of the cache without saying so.
Resuming an intercepted request rebuilds the channel, and the header copy skips connection and cookie so the new channel re-appends them after the sec-fetch-* block. No real Firefox emits that order.
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.
Related Issue
Closes #428
Closes #271
Description
page.route("**/*", lambda r: r.continue_())changed what every request looked like on the wire, so anti-bot services blocked pages that load fine without the route. Both reports are the same root cause, and the block lands on the document request, before any page script runs — this is an HTTP-level tell, not a JS leak.Two independent causes:
1.
Pragma: no-cache+Cache-Control: no-cacheon every request. Playwright pairs everysetRequestInterceptionwithsetCacheDisabled(true)(ffNetworkManager.ts,ffBrowser.ts), and juggler implements that asLOAD_BYPASS_CACHE | INHIBIT_CACHING.LOAD_BYPASS_CACHEmakes necko attach both headers. Firefox only sends them for a forced reload, so every request announced itself as automated.Dropping
LOAD_BYPASS_CACHEand keepingINHIBIT_CACHINGgives identical cache behaviour (verified: 6 network hits for amax-age=99999subresource either way — nothing is stored, so nothing is read) with no wire footprint.Page.setCacheDisabledis only ever called as a side effect of interception, so no public API changes.2.
ConnectionandCookiedisplaced pastSec-Fetch-*. Resuming an intercepted request makes necko tear down the channel and build a new one (InterceptedHttpChannel::ResetInterception). The header copy skipsconnectionandcookie— the replacement channel is expected to regenerate them — which it does, appended at the end. Header order feeds JA4H/Akamai-style fingerprints, and the resulting layout is one no real Firefox emits.Both are fixable in
additions/juggler/— no Firefox C++ patch needed, since the headers are still mutable athttp-on-modify-requeston the resumed channel.Type of Change
Testing
Reproduced and verified against the prebuilt binary from the issue report (
official/135.0.1-beta.24), driven with a raw-socket server that logs the exact request bytes in arrival order, with juggler patched insideomni.ja.This is inherited from upstream juggler, not a Camoufox regression — stock Playwright Firefox 134 shows the identical diff. Worth reporting upstream too.
After the fix, a routed request is byte-identical to an unrouted one across the document, CSS and JS subresources, with and without cookies, and with
set_extra_http_headers.Playwright suite (
tests/) against the patched binary: 203 passed, 0 regressions. The 12 failures are pre-existing (HTTPS/service-worker/asset-path) and fail identically on the pristine binary.New
tests/async/test_route_request_fingerprint.py— 4 tests that compare a routed page against an unrouted one rather than hardcoding a header layout, so they survive Firefox upgrades. Confirmed they fail on the unfixed binary and pass on the fixed one:Reproduction script and the before/after captures are in the issue thread on request.
Fingerprint Report
I could not produce a meaningful build-tester report. The only binary I have is
135.0.1-beta.24; the current build-tester scores it 0/0, Grade F — and scores the pristine, unmodified binary identically, so the harness is simply incompatible with a binary that old (this branch targets 152). It needs a run on a real 152 build before merge.Fingerprint report
Notes for review
_onInternalRedirectReadyhunk is textually identical across both versions and the helper ports verbatim, but this deserves one confirmation run on a 152 build.route.continue_(headers=...)still reorders headers —clearRequestHeaderspinscookienear the front, and any explicitly-passed header list replaces the layout wholesale. Different code path, left alone here to keep this focused; happy to open a separate issue.Checklist
Service tests passtemporarily out of service