· Paul Crossland
One Browser Run Can Take Three Network Routes
Proxy exclusions and dual-stack fallback can change a browser run before navigation. Audit effective routes per hop, not per job.
A browser worker can be labelled region: eu-west and proxy: enabled while important parts of the run take different network routes. The automation client may reach a remote browser directly because a proxy-exclusion entry matched unexpectedly. A browser download may repeatedly choose an IPv6 path that completes TCP but never completes TLS, even though IPv4 works. The page itself may then use a third route through the browser's configured proxy.
If the final record stores one region and one proxy flag, all three paths become one fictional provenance story.
Fresh automation changes expose both mechanisms. Selenium 4.47.0, released August 10, includes a Python fix for no_proxy matching. The merged August 7 change documents that an empty exclusion entry could disable the proxy for every host, a bare substring could match an unrelated hostname, and matching was case-sensitive. Separately, an August 11 Playwright bug report demonstrates browser-download retries repeatedly selecting IPv6 when that path accepted TCP but stalled during TLS, without reaching a working IPv4 endpoint. An open Playwright fix proposes committing to an address only after TLS completes and bounding each attempt with the remaining request budget.
The practical thesis is: network route is a property of each hop in a browser-backed fetch, not a static property of the job, so production systems should record the effective proxy decision, address-family attempts, and handshake winner separately for dependency, control, and content traffic.
This is not guidance for changing routes after a denial or finding a path around access controls. A challenge, login boundary, rate limit, contractual restriction, or explicit denial remains a stop or review condition. Route evidence is for proving where permitted traffic went and preventing accidental direct connections.
The repeated angle to avoid
Recent Better Fetch posts already cover resolver views, TLS groups, HTTP/2 stream evidence, runtime semantics, browser control-plane telemetry, release canaries, and browser-session provenance. The repeated article would say that network versions and proxy regions belong in logs.
The new surplus is a route graph. proxy_enabled: true does not prove that every hop used the proxy, and address_family: IPv6 does not prove that IPv6 completed the protocol needed by the operation. A browser-backed run can succeed while its installer, automation connection, API client, and page network stack made different decisions. Those distinctions affect reproducibility, regional assertions, data handling, and incident scope.
Source map
Fresh primary evidence from the last seven days:
| Source | Date | Contribution |
|---|---|---|
| Selenium 4.47.0 release | 2026-08-10 | Ships the Python no_proxy correction in a production automation release. |
| Selenium pull request 17884 | 2026-08-07 | Explains the empty-entry, substring, case, host, port, and IPv6 matching defects and adds regression tests. |
| Playwright issue 42193 | 2026-08-11 | Provides a controlled reproduction where IPv6 completes TCP but stalls at TLS, while browser-download retries never reach working IPv4. |
| Playwright pull request 42207 | 2026-08-11 | Shows the proposed mechanism: select a TLS route at secureConnect, apply per-attempt time budgets, and clean up losing transports. It is open, so it is evidence of current behavior and design work, not a released guarantee. |
Older background includes Happy Eyeballs, environment proxy conventions, split-horizon DNS, and browser remote-control protocols. The useful synthesis is not that either library controls every network request. It is that a fetch platform must identify which component made each route decision before assigning provenance to the resulting data.
Draw the route graph before debugging the page
A typical browser-backed job can contain at least these hops:
| Hop | Typical component | Route decision that matters |
|---|---|---|
| Dependency | browser or driver download, package bootstrap | DNS answers, address-family race, TCP and TLS completion, artifact mirror |
| Control | client to Selenium Grid, hosted browser, CDP, or WebDriver BiDi endpoint | system proxy, no_proxy, explicit remote URL, reverse proxy, WebSocket upgrade |
| Content | browser navigation and subresources | browser proxy profile, resolver path, origin connection, service worker, per-request policy outcome |
| Side-channel | API request context, upload, trace, screenshot, or telemetry service | library HTTP client, its own proxy rules, dual-stack fallback, destination policy |
These hops can share a process without sharing a network implementation. Selenium's fresh fix concerns Python client configuration for reaching a remote server; it does not prove which route the remote browser later uses to reach a target. The Playwright reproduction concerns browser installation, while the proposed code also touches its API request path; it does not establish the behavior of every page navigation in every bundled browser.
That scope is the point. Do not infer page egress from control-channel success. Do not infer the installer route from the browser's configured proxy. Do not infer a successful TLS path from a TCP connect event.
Why configured intent is insufficient evidence
Proxy exclusion lists look declarative, but matching semantics are executable behavior. A trailing comma can create an empty entry. A substring comparison can make foo.com match a hostname that merely contains those characters. Case, leading dots, ports, URLs, IPv6 brackets, and wildcard handling all create edge cases. The Selenium correction replaces loose matching with exact-host or dot-delimited subdomain semantics and explicitly ignores empty entries.
For operators, the failure is larger than a connection error. An accidental direct path can:
- violate an expected regional or customer network boundary;
- expose a control endpoint to a route that policy intended to proxy;
- make one worker pool appear healthier because it skipped a failing proxy;
- produce different DNS, latency, certificate, or access outcomes;
- leave the final dataset claiming a proxy route that was never used.
Dual-stack selection has a similar gap between intent and outcome. Resolving both address families does not mean both were usable. TCP completion proves that a transport connection opened; an HTTPS operation still needs a successful TLS handshake. If the client discards alternative candidates at TCP connect, a path that stalls one layer later can monopolize every retry. More retries then reproduce the same route-selection error rather than add resilience.
Record effective route per hop
Give every outbound connection a route_span_id and attach it to one named hop. A compact route record should include:
hop_class: dependency, control, content, side-channel, or unknown;- component and version that made the decision;
- destination class and approved host identifier, avoiding secrets in URLs;
- configured proxy mode and a hash of the normalized exclusion policy;
- effective decision: proxied, direct, tunnelled, browser-managed, or unknown;
- decision reason: explicit proxy, exact exclusion match, subdomain match, wildcard, no proxy configured, or library default;
- resolver profile, answer-family set, and candidate order;
- one attempt row per candidate with family, TCP outcome, TLS outcome, timeout phase, and cleanup result;
- winning family and the milestone that made it the winner: TCP, TLS, protocol upgrade, or application response;
- observed egress region or owned canary identity where policy allows verification;
- policy verdict: expected route, unexpected direct path, route mismatch, or incomplete evidence.
Never store proxy credentials, bearer tokens, cookie values, or full authenticated endpoint URLs in broad telemetry. Hash configuration snapshots, redact user information, and keep sensitive connection material in a restricted audit system.
An owned-fixture route test
Test route semantics without probing third-party defenses:
- Start an owned HTTP and HTTPS destination plus an owned recording proxy. Give each path a distinct harmless response marker.
- Run control-channel and side-channel clients with exclusions covering exact hosts, subdomains, unrelated lookalikes, uppercase names, ports, IPv4, bracketed IPv6, leading commas, trailing commas, doubled commas, and
*. - Assert both the response marker and the recorded route decision. A successful request is not enough if it took the wrong path.
- Resolve one fixture name to IPv6 and IPv4. Make IPv6 accept TCP but intentionally withhold TLS while IPv4 completes HTTPS. Verify that the operation reaches a usable candidate within its budget and closes losing sockets.
- Repeat with both families stalled. Confirm that every attempt is bounded, resources are released, and the final error names the handshake phase rather than generic navigation failure.
- Launch or attach to a test browser, then navigate to a separate egress canary. Prove that control-route evidence and page-route evidence remain separate.
- Run the matrix on every supported language binding and after automation, runtime, base-image, proxy-library, or browser-provider updates. Environment-variable semantics can differ even inside one product family.
Do not treat an open upstream fix as a deployment guarantee. Until behavior is released and your fixture passes against the exact build you operate, keep the route outcome unknown or apply a locally reviewed mitigation such as pre-provisioning dependencies through an approved path.
The operator decision rule
When two browser runs disagree, first compare route spans by hop. If dependency or control routes differ but content routes and evidence match, investigate infrastructure without relabelling the source data. If content egress differs, partition the observations by actual route before comparing region-sensitive fields. If a configured proxy was bypassed unexpectedly, quarantine the run and fix the client configuration; do not publish it under the intended region label. If TCP succeeded but TLS did not, classify a handshake-path failure rather than raising page retries.
Most importantly, do not turn a policy response into a route experiment. Once the content hop receives a denial, challenge, or limit, preserve it and stop according to source policy.
A browser run is a small distributed system. Its dependencies, control channel, page traffic, and artifacts can each resolve names, apply exclusions, race addresses, and negotiate security independently. Reliable web data starts by replacing the single proxy: true label with the routes that actually carried the run.