Skip to content

fix(desktop): the WSL backend binds loopback when network access is off - #31221

Open
THEtheChad wants to merge 3 commits into
pingdotgg:mainfrom
THEtheChad:fix/wsl-backend-loopback-bind
Open

THEtheChad wants to merge 3 commits into
pingdotgg:mainfrom
THEtheChad:fix/wsl-backend-loopback-bind

Conversation

@THEtheChad

Copy link
Copy Markdown

Problem

With the desktop app's WSL backend and Network access off, Settings → Connections says "Limited to this machine", but the backend listens on 0.0.0.0 and answers on every interface the distro has (tailnet, LAN, Docker bridge). Reported in #9056. resolveWslStartConfig hard-coded 0.0.0.0 and never read the Network access setting.

Change

The WSL bind host now follows the Network access setting whenever Windows reaches the backend over loopback:

 resolveWslStartConfig                       apps/desktop/src/backend/DesktopBackendConfiguration.ts
   distroIp = getDistroIp(distro)
   usesSharedNetworkStack = distroIp is one of this host's addresses     # mirrored networking
   rendererHost = usesSharedNetworkStack ? 127.0.0.1 : distroIp ?? 127.0.0.1
-  bind = 0.0.0.0
+  windowsUsesLoopback = usesSharedNetworkStack || distroIp is none
+  bind = windowsUsesLoopback ? networkAccess.bindHost : 0.0.0.0        # 127.0.0.1 when off
WSL networking Windows reaches the backend at Network access off Network access on
mirrored 127.0.0.1 127.0.0.1 (was 0.0.0.0) 0.0.0.0
NAT, distro IP lookup failed 127.0.0.1 (wslhost forwarding) 127.0.0.1 (was 0.0.0.0) 0.0.0.0
NAT (the default) distro IP 0.0.0.0 (unchanged) 0.0.0.0

This doesn't fix NAT, the reporter's setup. In NAT the desktop connects to the distro IP, which a loopback bind can't serve. #13745 makes NAT try loopback first, but it keeps 0.0.0.0 for its distro-IP fallback, so NAT stays on 0.0.0.0 even after #13745 unless that fallback is dropped. I left that trade-off to maintainers, which is why this PR says "Part of" and doesn't close the issue.

Interaction with open PRs: the decision uses the underlying facts (usesSharedNetworkStack, missing distro IP), not the chosen URL. With #13745's always-loopback primary URL, NAT still keeps 0.0.0.0, so its fallback isn't broken. The two PRs touch the same block, so whichever lands second needs a small textual rebase. With #23030, Network access on still binds 0.0.0.0 in every mode, so the WSL Tailscale address it advertises stays reachable.

Merge danger: a two-way door. The blast radius is WSL-backend users with Network access off on mirrored networking or a failed distro-IP lookup. Their backend stops answering on non-loopback interfaces, and the desktop never used those. Changing the setting already relaunches the app, which re-resolves the bind host.

Scope and approval

This is a small, focused fix for an obvious bug, filed under the exception in CONTRIBUTING. The UI states a property the bind doesn't have, and the fix makes the bind follow the existing setting, with no new behavior or settings. Bug report: #9056. Out of scope: NAT (above), and the macOS label mismatch in the issue's comments (a separate symptom).

Verification

Test: resolveWsl binds loopback only when network access is off and Windows uses it (DesktopBackendConfiguration.test.ts) is a table of four rows: failed lookup, mirrored, NAT, and Network access on. It asserts the renderer URL host together with the bind host.

  • Without the fix: network access bind 127.0.0.1, distro IP none: expected { url: '127.0.0.1', bind: '0.0.0.0' } to deeply equal { url: '127.0.0.1', bind: '127.0.0.1' }
  • With the fix: 18/18 in the file pass

vp run check:changed: format, lint, knip, typecheck (2 packages) and tests all pass.

Runtime transcript: Windows 11, WSL 3.0.3 in mirrored networking, Ubuntu 24.04. The server ran from source (t3 serve --mode desktop) on two spare ports, one per bind host. The status is HTTP from curl, and 000 means unreachable.

$ ss -tln
LISTEN 0 511   0.0.0.0:38951  0.0.0.0:*
LISTEN 0 511 127.0.0.1:38952  0.0.0.0:*

from                              0.0.0.0:38951   127.0.0.1:38952
Windows curl.exe -> 127.0.0.1           200             200
Windows curl.exe -> localhost           200             200
WSL -> tailnet IP (100.x)               200             000
WSL -> LAN IP (192.168.1.x)             200             000
WSL -> Docker bridge (172.17.0.1)       200             000
other LAN machine -> LAN IP             000             000

Windows still reaches a loopback-bound backend, the path the desktop uses in mirrored mode, while the tailnet, LAN and Docker-bridge interfaces stop answering. On this host the Hyper-V firewall for WSL blocks inbound traffic by default (DefaultInboundAction: Block), so another LAN machine couldn't reach either port. Hosts with inbound rules, an in-distro Tailscale, or containers on the same box are where 0.0.0.0 leaks.

Not checked: a real desktop app running a WSL backend (the transcript runs the server directly with each bind host), and the failed-lookup route on a NAT host. That route relies on wslhost loopback forwarding, which the desktop already used before this change.

Review

Two review rounds by GPT-6 Astra (Codex) and Muse Wonder Full: 5 findings accepted, 2 declined, 0 deferred.

Declined
Round Reviewer Severity Finding Reason
1 Muse Wonder Full nit Map the bind host explicitly in case a future exposure mode emits a LAN IP. DesktopServerExposure only emits 127.0.0.1 or 0.0.0.0. A LAN-IP mode would need its own WSL design, so this is speculative.
2 Muse Wonder Full nit The test asserts the preflight-failure config rather than a launchable one. Both return paths share the same bootstrap, and the neighboring WSLENV test has the same shape. Driving the full launch path adds harness setup without extra coverage today.

Part of #9056

Implementation: Claude Opus 5.5 in T3 Code (Claude Code harness). Reviewed with GPT-6 Astra (Codex) and Muse Wonder Full.

🤖 Generated with Claude Code

THEtheChad and others added 3 commits November 4, 2026 18:33
With Network access off, the desktop-managed WSL backend still bound
0.0.0.0 while Settings said "Limited to this machine". It now uses the
Network access bind host whenever Windows reaches it over loopback
(mirrored networking, or no distro IP). NAT setups, where Windows
reaches the distro IP, keep 0.0.0.0 until loopback-first lands (pingdotgg#13745).

Part of pingdotgg#9056

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…istro

Base the loopback decision on the shared-network-stack and missing-distro-IP
facts instead of the chosen renderer URL, so it still holds once the
desktop prefers loopback with a distro-IP fallback (pingdotgg#13745). Cover mirrored
mode and assert the renderer URL alongside the bind host.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:M 30-99 changed lines (additions + deletions). labels Nov 5, 2026
@macroscopeapp

macroscopeapp Bot commented Nov 5, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs Human Approval

Macroscope's review found this PR requires human approval — The WSL backend now honors local-only exposure when Windows connects through loopback, while preserving wildcard binding for NAT connections. This changes effective default listener reachability on affected WSL configurations and requires confirmation of that behavior.

You can add or adjust custom eligibility rules. Learn more.

@coderabbitai

coderabbitai Bot commented Nov 5, 2026

Copy link
Copy Markdown
Contributor

Explain Change →

No actionable comments were generated in the recent review.

Walkthrough

The WSL backend now selects its bootstrap bind host using the server exposure configuration and distro network state. The resolver receives the exposure service at both call sites. Tests cover four combinations of exposure settings and distro IPs.

Changes: WSL bind host selection

Layer / File(s) Summary
Select and verify WSL bind host
apps/desktop/src/backend/DesktopBackendConfiguration.ts, apps/desktop/src/backend/DesktopBackendConfiguration.test.ts
The WSL resolver receives DesktopServerExposure. When the distro shares the Windows network stack or has no IP, the bootstrap bind host uses the configured exposure bind host; otherwise, it uses 0.0.0.0. Tests cover four exposure and IP combinations.

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix

Suggested labels: bugfix

Suggested reviewers: maria-rcks, juliusmarminge, macroscopeapp

Merge Risk: Minimal · up to 93efb

No merge-blocking risk was identified in the WSL bind-host change.

Pre-merge checks: Passed 4

Check name Status Explanation
Title check Passed The title clearly and concisely describes the main change: the WSL backend binds to loopback when network access is disabled.
Description check Passed The description includes the required Problem, Change, Scope and approval, and Verification sections. It explains the affected networking modes, limitations, tests, runtime validation, unchecked scena…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Full details: Description check

Explanation

The description includes the required Problem, Change, Scope and approval, and Verification sections. It explains the affected networking modes, limitations, tests, runtime validation, unchecked scenarios, and review context.

Finishing Touches

Generate unit tests (beta)

  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
Recent review info

Run configuration

Commits

Reviewing files that changed from the base of the PR and between 4b87a45 and 93efb70.

Files selected for processing: 2

  • apps/desktop/src/backend/DesktopBackendConfiguration.test.ts
  • apps/desktop/src/backend/DesktopBackendConfiguration.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 9 remain after this review.

Wladefant

This comment was marked as spam.

@t3dotgg
t3dotgg dismissed Wladefant’s stale review November 8, 2026 23:29

Dismissed: unsolicited automated review from a blocked account.

@pingdotgg pingdotgg deleted a comment from Wladefant Nov 8, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M 30-99 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants