You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[Bug]: WSL backend binds 0.0.0.0 while "Network access" is off and the UI says "Limited to this machine" #9056
With WSL backend enabled and WSL only = ON, the app-managed backend listens on 0.0.0.0:3773 even though Network access is toggled off. The Connections panel states "Limited to this machine", which is not true of the actual bind.
What makes me think this is a bug rather than intended behaviour: on the same machine, a backend started via npx t3@latest service install (systemd user unit) bound 127.0.0.1:3773 correctly. Only the desktop-app-managed WSL backend binds all interfaces. (That systemd unit was stopped at the time of the checks below, so the process bound to 0.0.0.0 is definitively the app-managed backend, running out of ~/.t3/wsl-runtime/….)
Steps to reproduce
Windows desktop app → Settings → Connections
WSL backend → pick a distro → choose Use only WSL
Leave Network access OFF ("Limited to this machine")
"Limited to this machine" reads as a security property, and a user may reasonably rely on it when deciding whether to open a project containing sensitive material.
On a default WSL2 setup the exposure is narrower than it looks, since WSL's NAT address is not reachable from the LAN without a portproxy. But anyone running Tailscale inside WSL has the backend reachable from every device on their tailnet, with the toggle off and the UI saying otherwise. In my case the only thing preventing that was a restrictive tailnet ACL — containment came from the network, not from the app control that claims to provide it.
Environment
Windows 11 + WSL2, Ubuntu 24.04
Desktop app installed via winget install T3Tools.T3Code, 2026-09-01
CLI t3@0.0.37 (used for the contrasting systemd-service test)
WSL backend enabled, WSL only ON
Network access off; T3 Connect off; Publish agent activity off
Note on scope
I have not verified whether the UI/API layer on :3773 authenticates. /mcp correctly returns 401 without the bearer token, but I did not test the rest of the surface. If the HTTP layer is fully authenticated then this is a "the UI states something untrue" bug rather than an exposure bug — either way, the toggle and the bind should agree.
This reproduces on a plain macOS desktop install, which I think widens the scope beyond the WSL-managed backend. The contrast you drew (app-managed WSL backend binds 0.0.0.0, systemd-installed backend binds 127.0.0.1) suggested WSL was the trigger; on macOS there is no WSL backend at all and the bind is still 0.0.0.0 with Network access displaying off / "Limited to this machine".
Environment
macOS 15 (Darwin 25.6.0), Apple Silicon Mac mini
T3 Code (Nightly).app 0.0.46-nightly.20261010.2908
Desktop-app-managed backend (no t3 service install — t3 service status reports not installed)
T3 Connect off, Publish agent activity off, WSL not applicable
Observed
Settings → Connections shows Network access — Limited to this machine. Meanwhile:
Unlike WSL's NAT address, the macOS LAN address is directly reachable from every device on the local network, so there is no incidental containment here.
Not a CLI override
The backend was started by the desktop app with no host flag:
Note host and origin already disagree inside this one file.
Where the two values diverge
~/.t3/userdata/desktop-settings.json contains:
{"serverExposureMode":"network-accessible", ...}
I never touched the Network access toggle on this machine. The file was last written 2026-09-28; the persisted mode has been network-accessible since, and the UI has shown "Limited to this machine" throughout.
Reading the shipped app.asar, desktop bootstrap does:
if(settings.serverExposureMode!==environment.defaultDesktopSettings.serverExposureMode)yield*logBootstrapInfo("bootstrap restoring persisted server exposure mode",{mode: settings.serverExposureMode});constserverExposureState=yield*serverExposure.configureFromSettings({port: backendPort});constbackendConfig=yield*serverExposure.backendConfig;if(serverExposureState.endpointUrl)yield*logBootstrapInfo("bootstrap enabled network access",{endpointUrl: serverExposureState.endpointUrl});elseif(settings.serverExposureMode==="network-accessible"&&serverExposureState.mode==="local-only")yield*logBootstrapWarning("bootstrap fell back to local-only because no advertised network host was available");
That last branch is exactly the state this machine is in: persisted mode is network-accessible, but the resolved serverExposureState.mode is local-only. My reading — offered as a hypothesis, not a verified claim — is that the advertised endpoint falls back to local-only (so the UI renders "Limited to this machine"), while the bind host still comes from configureFromSettings on the persisted network-accessible value, leaving the socket on 0.0.0.0. If that is right, the UI is reporting serverExposureState.mode while the listener follows the persisted setting, and the two can disagree on any platform — WSL is incidental.
I could not confirm this from logs: server.trace.ndjson rotates at 10 MB and on this machine covers only the last ~3 hours, so the 06:17Z bootstrap lines are long gone. Someone who can reproduce with a fresh log should look for bootstrap fell back to local-only because no advertised network host was available.
A plausible trigger for "no advertised network host" at boot is Tailscale not yet being up when the app launches, which may make this adjacent to #17362.
Same scope caveat as the original report
Like you, I have not audited whether the full surface on :3773 authenticates. Either way the toggle, the persisted setting, and the actual bind should agree, and "Limited to this machine" should not be shown while the port answers on the LAN.
Possibly related
t3 connect status prints Publish agent activity: enabled while the Settings UI shows that toggle off — another CLI/UI disagreement in the same panel, which may share the "CLI reads persisted value, UI reads runtime state" shape. Happy to split that out if it is unrelated.
Correction to my previous comment — my proposed mechanism was wrong
Retracting the hypothesis in my comment above. The observations stand; the explanation does not. I read further into app.asar and the divergence I proposed is not possible in this code path.
Why the hypothesis fails
I suggested the UI mode could fall back to local-only while the bind host stayed on the persisted network-accessible. It cannot: both values are produced by the same object, in the same branch.
mode: "local-only" always implies bindHost: DESKTOP_LOOPBACK_HOST. Downstream, getState (toContractState) and backendConfig (toBackendConfig) both read the same stateRef, so the UI mode and the listener's bind host cannot disagree by this route.
The downgrade to local-only requires no usable LAN IPv4 and no Tailscale IPv4. This Mac has both a LAN address (192.168.1.x) and a Tailscale address (100.x.x.x). resolveLanAdvertisedHost skips Tailscale addresses but accepts 192.168.1.x, so endpointUrl is non-null, unavailable is false, and the resolved state should be network-accessible with endpointUrl: http://192.168.1.x:3773.
So the 0.0.0.0 bind is correct for the persisted setting. My earlier framing had it backwards.
What I think the actual defect is here
The listener is right; the Settings panel is wrong. It shows Network access — "Limited to this machine" while the resolved exposure state is network-accessible with a live advertised endpoint.
One candidate, offered as a lead rather than a conclusion: the service seeds its state with
and initialRuntimeState() is built from DEFAULT_DESKTOP_SETTINGS.serverExposureMode (local-only) with port: 0. If the panel renders that seed instead of the state written by configureFromSettings, it would show exactly "Limited to this machine" regardless of the real bind. I have not confirmed this; the bootstrap lines that would show it had already rotated out of server.trace.ndjson.
This may not belong on this issue
Given the above, my case is probably not the same root cause as the original report:
My case: Network access persisted on, backend correctly bound to 0.0.0.0, UI falsely reports "Limited to this machine" — the display contradicts the setting and the socket.
The user-visible hazard is identical, and "Limited to this machine" is untrue in both, which is why I posted here. But the fixes are likely in different places. Happy to move mine to its own issue if a maintainer prefers that — say the word and I will, and I'll link it back here.
Apologies for the noise of a wrong mechanism; I would rather correct it in place than leave it standing.
Activity
labeebbappu commentedon Oct 10, 2026
Same mismatch on native macOS — no WSL involved
This reproduces on a plain macOS desktop install, which I think widens the scope beyond the WSL-managed backend. The contrast you drew (app-managed WSL backend binds
0.0.0.0, systemd-installed backend binds127.0.0.1) suggested WSL was the trigger; on macOS there is no WSL backend at all and the bind is still0.0.0.0with Network access displaying off / "Limited to this machine".Environment
0.0.46-nightly.20261010.2908t3 service install—t3 service statusreports not installed)Observed
Settings → Connections shows Network access — Limited to this machine. Meanwhile:
Unlike WSL's NAT address, the macOS LAN address is directly reachable from every device on the local network, so there is no incidental containment here.
Not a CLI override
The backend was started by the desktop app with no host flag:
~/.t3/userdata/server-runtime.json:{"version":1,"pid":7301,"host":"0.0.0.0","port":3773,"origin":"http://127.0.0.1:3773","startedAt":"2026-10-10T06:17:10.659Z"}Note
hostandoriginalready disagree inside this one file.Where the two values diverge
~/.t3/userdata/desktop-settings.jsoncontains:{"serverExposureMode":"network-accessible", ...}I never touched the Network access toggle on this machine. The file was last written 2026-09-28; the persisted mode has been
network-accessiblesince, and the UI has shown "Limited to this machine" throughout.Reading the shipped
app.asar, desktop bootstrap does:That last branch is exactly the state this machine is in: persisted mode is
network-accessible, but the resolvedserverExposureState.modeislocal-only. My reading — offered as a hypothesis, not a verified claim — is that the advertised endpoint falls back to local-only (so the UI renders "Limited to this machine"), while the bind host still comes fromconfigureFromSettingson the persistednetwork-accessiblevalue, leaving the socket on0.0.0.0. If that is right, the UI is reportingserverExposureState.modewhile the listener follows the persisted setting, and the two can disagree on any platform — WSL is incidental.I could not confirm this from logs:
server.trace.ndjsonrotates at 10 MB and on this machine covers only the last ~3 hours, so the 06:17Z bootstrap lines are long gone. Someone who can reproduce with a fresh log should look forbootstrap fell back to local-only because no advertised network host was available.A plausible trigger for "no advertised network host" at boot is Tailscale not yet being up when the app launches, which may make this adjacent to #17362.
Same scope caveat as the original report
Like you, I have not audited whether the full surface on
:3773authenticates. Either way the toggle, the persisted setting, and the actual bind should agree, and "Limited to this machine" should not be shown while the port answers on the LAN.Possibly related
t3 connect statusprintsPublish agent activity: enabledwhile the Settings UI shows that toggle off — another CLI/UI disagreement in the same panel, which may share the "CLI reads persisted value, UI reads runtime state" shape. Happy to split that out if it is unrelated.labeebbappu commentedon Oct 10, 2026
Correction to my previous comment — my proposed mechanism was wrong
Retracting the hypothesis in my comment above. The observations stand; the explanation does not. I read further into
app.asarand the divergence I proposed is not possible in this code path.Why the hypothesis fails
I suggested the UI mode could fall back to
local-onlywhile the bind host stayed on the persistednetwork-accessible. It cannot: both values are produced by the same object, in the same branch.mode: "local-only"always impliesbindHost: DESKTOP_LOOPBACK_HOST. Downstream,getState(toContractState) andbackendConfig(toBackendConfig) both read the samestateRef, so the UI mode and the listener's bind host cannot disagree by this route.What the resolver actually does on this machine
The downgrade to
local-onlyrequires no usable LAN IPv4 and no Tailscale IPv4. This Mac has both a LAN address (192.168.1.x) and a Tailscale address (100.x.x.x).resolveLanAdvertisedHostskips Tailscale addresses but accepts192.168.1.x, soendpointUrlis non-null,unavailableisfalse, and the resolved state should benetwork-accessiblewithendpointUrl: http://192.168.1.x:3773.So the
0.0.0.0bind is correct for the persisted setting. My earlier framing had it backwards.What I think the actual defect is here
The listener is right; the Settings panel is wrong. It shows Network access — "Limited to this machine" while the resolved exposure state is
network-accessiblewith a live advertised endpoint.One candidate, offered as a lead rather than a conclusion: the service seeds its state with
and
initialRuntimeState()is built fromDEFAULT_DESKTOP_SETTINGS.serverExposureMode(local-only) withport: 0. If the panel renders that seed instead of the state written byconfigureFromSettings, it would show exactly "Limited to this machine" regardless of the real bind. I have not confirmed this; the bootstrap lines that would show it had already rotated out ofserver.trace.ndjson.This may not belong on this issue
Given the above, my case is probably not the same root cause as the original report:
0.0.0.0— the listener exceeds the setting.0.0.0.0, UI falsely reports "Limited to this machine" — the display contradicts the setting and the socket.The user-visible hazard is identical, and "Limited to this machine" is untrue in both, which is why I posted here. But the fixes are likely in different places. Happy to move mine to its own issue if a maintainer prefers that — say the word and I will, and I'll link it back here.
Apologies for the noise of a wrong mechanism; I would rather correct it in place than leave it standing.
Squashed '.repos/effect-smol/' changes from a43c7e244c..e17ecdbe90