Skip to content

ios: open loopback links on the computer, never the phone - #2018

Merged
ex3ndr merged 2 commits into
mainfrom
steve/ios-live-loopback-rewrite
Nov 9, 2026
Merged

ex3ndr merged 2 commits into
mainfrom
steve/ios-live-loopback-rewrite

Conversation

@ex3ndr

@ex3ndr ex3ndr commented Nov 9, 2026

Copy link
Copy Markdown
Contributor

WebKit ignores a page's ProxyConfiguration for the phone's own loopback. On an iPhone 12 (iOS 26.1), a live preview or chat link to http://127.0.0.1:PORT or http://[::1]:PORT connected straight to the phone, and localhost bypassed the proxy too. The page came from the phone, not from the agent's computer. This fix is client-only. The server and agent are unchanged: the tunnel still receives a literal loopback CONNECT target.

  • Click handler / open: a page start or main-frame navigation or redirect to 127.0.0.1, [::1] or localhost (normalized) opens on ipv4|ipv6|name.happy-loopback.invalid at the same port and scheme (BrowserPolicy.Loopback).
  • App proxy: BrowserProxy tunnels an alias as CONNECT <loopback>:<port>. BrowserRelay.loopback frames each request and rewrites Host, plus Origin/Referer on the alias, back to the loopback, so Vite/Next host checks pass. TLS, upgrades and responses pass through byte for byte.
  • Fail closed: every BrowserPage has a WKContentRuleList that blocks anything else it loads from the phone's loopback. The exact spellings are listed in docs/browser-proxy.md.
  • The address bar, errors and Share show the original address.

Tests:

  • Core BrowserTests: new alias mapping and request-rewrite tests, 17/17.
  • Hosted BrowserStates + Browser: 23/23 on the simulator, including the unchanged numeric-127 test testALiveArtifactWhoseComputerIsGoneSaysSoAndOpensWhenItIsBack and testComputerUnreachable….
  • New testTheLoopbackOpensOnTheComputerAndNothingReachesThePhones: listeners on the phone's 127.0.0.1/[::1] get 0 hits for 127.0.0.1, [::1] and localhost. No-cors fetches to 9 loopback spellings are blocked. 127.example.com still goes through the proxy. The dev server sees Host: <loopback>:<port>.
  • Physical iPhone 12: 5/5. As a negative control, removing the alias and the blocker makes the listeners record direct hits.

Reviewed by ufcvos (2 passes, nothing blocking).

Known limits, not fixed here:

  • A main-frame form POST to the loopback reloads on the alias as a GET.
  • Only the alias of the current CONNECT has its Origin rewritten, so a page on name. fetching ipv4. sends the alias Origin.
  • Content-blocker coverage of <link rel=preconnect> hasn't been verified.
  • WebRTC ICE/UDP bypasses the proxy, as it did before this change.
  • A subresource on the exact loopback (e.g. fetch('http://localhost:3000') from another origin) is blocked, not rewritten.

WebKit connects to the phone's own 127.0.0.1, [::1] and (on device)
localhost directly, whatever the proxy configuration says, so a live
preview or chat link to a dev server reached the phone, not the agent's
computer. A page or main-frame navigation to the loopback now opens on
ipv4/ipv6/name.happy-loopback.invalid; BrowserProxy tunnels that alias as
CONNECT to the loopback it stands for and puts the loopback back in each
request's Host, Origin and Referer. A content blocker refuses anything
else a page loads from the phone's loopback.
@ex3ndr

ex3ndr commented Nov 9, 2026

Copy link
Copy Markdown
Contributor Author

Hosted CI, run 42557795138: uikit and hosted3 passed. Every failure in hosted1 and hosted2 has failed before on runs without this change:

@ex3ndr
ex3ndr merged commit 1b9da58 into main Nov 9, 2026
2 of 4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant