Skip to content

0.50.0

Released 2026-10-31.

Wi-Fi access: the GUI on your phone over the local network

Section titled “Wi-Fi access: the GUI on your phone over the local network”

/boss gui lan lets a phone on the same Wi-Fi as the computer open the GUI without Tailscale. It is off by default and listens only on private IPv4 addresses of this computer, on port 8445.

  • The sb server makes its own certificate authority, after showing you the risk and recommending your phone’s hotspot or a direct Wi-Fi. Each phone installs the certificate once, so the page is HTTPS and the installed app works. The computer’s screen tells you how to check what the phone installed (exactly one certificate, with the SHA-256 shown).
  • Pairing is by QR code or a typed code, as with remote access, plus approval on the computer: you type the number the phone shows.
  • A typed Wi-Fi code is shown as ABCD EFGH and entered in the installed app’s entry as eight one-character boxes, the same as the Tailscale entry since 0.49.1: the boxes join into one code, and case, full-width letters, spaces, any dash, and invisible characters are forgiven.
  • Fixed with it: the Wi-Fi entry’s pages send Referrer-Policy: same-origin. Under the pages’ no-referrer a form post carries Origin: null, which the Wi-Fi redeem refuses, so neither the QR’s Pair button nor a typed code could pair (reproduced in headless Chrome).
  • A Wi-Fi device may do what a paired tailnet device may do.
  • Not measured yet: whether a phone limits this certificate to this computer’s name and private addresses. Every platform gets the strong warning in the certificate page, the Same Wi-Fi tab, and the /boss gui lan overlay (see the release gate below).
  • New: /boss gui lan, /boss gui lan off, /boss gui lan reset; the Phone tile gets a Tailscale tab and a Same Wi-Fi tab; cli.mjs status has lan; cli.mjs up can warn lan_off.
  • New files in the state directory: lan.json, lan-ca.json, lan-ca-files/.
  • Firewall notes: macOS and Windows are read without elevation and the note says what to change; the page words them in English and Traditional Chinese, keeps the commands verbatim, and marks only the notes that block access as errors.
  • The Same Wi-Fi tab offers one download link for each certificate file, and a request that waits shows a notice in the page’s header while the tab is closed.
  • /boss gui rotate also unpairs Wi-Fi devices and keeps the certificate.
  • Straw Link, Tailscale access, and desk mode are unchanged; the Phone tile still does not show in a desk window.

Details: how it works, design: the spec.

The name-constraint table and the live phone checks were not run before release, by the boss’s decision on 2026-10-31, answering “Wi-Fi 存取只差手機關卡…要現在做嗎?”:

「先發版,未來再來找人幫忙測試」

They stay open as follow-up, to be done later with helpers who have the phones. The Windows VM rows keep the results they have. After the release, the Windows Edge and Chrome rows of the name-constraint table were measured on the VM (both enforce); the shipped warning still treats every platform as unmeasured until the table is complete.

Spec: “Release gate: name constraints”. Not run before release. When it is run: an iPhone (Safari and a home-screen app), an Android phone (Chrome and an installed app), macOS (Safari and Chrome), and Windows (Edge and Chrome, in the VM). Leaves (a), (b), (c), (d1), (d2), (e), (f), (f2), and (g) are listed in the spec. A client that cannot run a leaf counts as failed for it, so every client counts as failed for now: each gets the strong warning (“a copy of the key could pose as any website”), worded as not verified yet, not as a measured failure. A failed (a), (b), (d1) or (d2), or (f) does not block a release; a failed (c) or (f2) does.

Client Version (a) (b) (c) (d1) (d2) (e) (f) (f2) (g)
iPhone, Safari not measured not measured not measured not measured not measured not measured not measured not measured not measured
iPhone, home-screen app not measured not measured not measured not measured not measured not measured not measured not measured not measured
Android, Chrome not measured not measured not measured not measured not measured not measured not measured not measured not measured
Android, installed app not measured not measured not measured not measured not measured not measured not measured not measured not measured
macOS, Safari not measured not measured not measured not measured not measured not measured not measured not measured not measured
macOS, Chrome not measured not measured not measured not measured not measured not measured not measured not measured not measured
Windows, Edge 152.0.4191.66, Windows 11 build 26300 ARM64 (VM) refused refused accepted refused not run (d1 refused) refused refused accepted refused by CryptoAPI (not a browser use)
Windows, Chrome 156.0.8078.80, Windows 11 build 26300 ARM64 (VM) refused refused accepted refused not run (d1 refused) refused refused accepted refused by CryptoAPI (not a browser use)

After the gate is run: fill the table in docs/how-it-works.md (Wi-Fi access), narrow the warning for the platforms that pass, and remove the test CAs from every device.

Spec: “Live: this Mac and phones”, in a scratch state, never the live server. Not run before release (the boss’s decision above); the Result column stays blank until each check is run with helpers.

# Check Result
1 Warning face, macOS firewall prompt, certificate face, served addresses
2 iPhone: AirDrop and page paths, Contains wording, trust switch, pairing with approval, home-screen app
3 Android: Quick Share and page paths, Chrome’s plain-HTTP download prompt, install, pairing, WebAPK or shortcut
4 The swap is detected in all three forms (lone attacker CA, real plus attacker, real plus DNS-settings payload)
5 Approval, two pending, timeout, documents with image, font, and answer, writes from a paired device refused
6 Name and address forms, an IPv6 Wi-Fi, the phone’s own hotspot
7 Restart and /boss gui rotate
8 Address change, sleep and wake, full-tunnel VPN note, name conflict note
9 Host rebinding checks (403 on TLS, 400 on plain)
10 Remote and Wi-Fi both on, one phone on both
11 Another account cannot take the bound address
12 Name-constraint gate above
13 Clean-up by PID and port; profiles and CAs removed from the phones

Spec: “Windows VM”. These include the items the build notes mark “verify on VM”.

# Check Result
1 Firewall prompt for the WMI-started server; the notes before and after the allow rule; block rules from cancelling the real prompt; a Public network. Also (L8): the filters’ InstanceID is the rule Name; Service, Package, Profile, Action, Enabled, NetworkCategory, and InterfaceType print the words the parser reads; the read finishes in seconds on a real rule set; PowerShell 5.1 parses the script; the path nvm-windows’ link stores in a rule matches the note (RVL8-F003) PASS at ca1d715 (recheck in vm.md): the filtered read works (21 s at host load 20), the notes before the allow rule are the block rule, no allow rule, Public; after the note’s own allow rule the no-allow note is gone. Not run: nvm-windows link path (not installed). First run at 262c787 failed: filtered read denied, no-allow note hidden by packaged-app rules
2 From the Mac: pairing and <vm name>.local resolution PASS (see vm.md)
3 Edge and Chrome trust the CA with exactly one Security Warning (SHA-1 matches); the swap forms (lone, PKCS #7 bundle, two PEMs) are caught by the written step PASS at 35a23dd (recheck in vm.md): the written step (Install Certificate, Current User, Trusted Root) gives exactly one Security Warning whose SHA-1 equals the tile’s, and Edge 152 and Chrome 156 then load the page by name and by address (both refuse it with the CA removed). The gate leaves behave as the spec requires in both browsers (a, b, d1, e, f refused; c, f2 accepted). Swap forms: a lone attacker CA shows another SHA-1 in its one warning; two PEMs in one .crt import only the first (the real CA); a PKCS #7 bundle named .crt is refused by Windows; none gave a second warning
4 lan-ca.json ACL read-back; a second account cannot read it PASS: ACL read-back (262c787); at 35a23dd a second standard account is refused the folder listing and lan-ca.json, lan.json, gui.json, server.json (see vm.md)
5 A second account binds the address with SO_REUSEADDR and SO_EXCLUSIVEADDRUSE PASS at 35a23dd: on the served address a plain bind and SO_EXCLUSIVEADDRUSE get 10048, SO_REUSEADDR gets 10013; none receives a connection. The wildcard 0.0.0.0:8445 can be bound (plain, SO_REUSEADDR) but connections to the served address still reach the server; a wildcard held first makes the server’s plain bind fail (the taken note), not be intercepted (see vm.md)
6 Which adapters are served (Hyper-V Default Switch, WireGuard, AnyConnect or Secure Client, OpenVPN TAP); decides open question O1 One VirtIO adapter only; O1 stays open (see vm.md)
7 status shows lan; restart keeps port, CA, and pairing; sleep and wake rebinds PASS: cli.mjs status shows lan (recheck at ca1d715); restart and resume PASS (see vm.md)
8 The connection gate on the VM’s subnet and a routed path On-link PASS; routed BLOCKED (see vm.md)
  • iOS Contains row (RVL7-F006): confirm the profile screen says 「包含」 (and the English word) as the pages and the written steps quote it, for one certificate and for the bundle forms. Result:

What ran for this release:

  • npm run check, npm run gui:build, and npm test (with SB_PACKAGED=1) pass on main, which holds the page and the not-verified wording.
  • The Windows VM rows above, with the results they show.

Not run: the name-constraint gate and the live phone checks (see the release gate).