fix(device-pair): mobile pairing rejects local IPv6 cleartext URLs#101008
Conversation
|
Codex review: needs maintainer review before merge. Reviewed July 6, 2026, 3:11 PM ET / 19:11 UTC. Summary PR surface: Source +13, Tests +48. Total +61 across 2 files. Reproducibility: yes. source-reproducible: current main routes /pair setup-code issuance through validateMobilePairingUrl, and the device-pair cleartext host guard lacks an IPv6 ULA/link-local branch, so ws://[fc00::1] is rejected before setup token issuance. Review metrics: 1 noteworthy metric.
Merge readiness Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch. Rank-up moves:
Next step before merge
Security Review detailsBest possible solution: Land the focused plugin-side IPv6 local cleartext allowance with its allow/reject regression coverage, while keeping public IPv6, Tailscale CGNAT IPv4, and public hostnames on the secure-URL path. Do we have a high-confidence way to reproduce the issue? Yes, source-reproducible: current main routes /pair setup-code issuance through validateMobilePairingUrl, and the device-pair cleartext host guard lacks an IPv6 ULA/link-local branch, so ws://[fc00::1] is rejected before setup token issuance. Is this the best way to solve the issue? Yes. The PR is the best narrow fix for this plugin because it updates the existing device-pair guard to match sibling setup-code and Android local-cleartext policy without crossing the extensions plugin boundary; a future shared SDK helper could reduce drift but is not required for this repair. AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against 2f89de81655a. Label changesLabel justifications:
Evidence reviewedPR surface: Source +13, Tests +48. Total +61 across 2 files. View PR surface stats
What I checked:
Likely related people:
What the crustacean ranks mean
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics. How this review workflow works
Review history (1 earlier review cycle)
|
|
Merged via squash.
|
…penclaw#101008) * fix(device-pair): allow local IPv6 pairing URLs * test(device-pair): cover IPv6 cleartext lower bound
…penclaw#101008) * fix(device-pair): allow local IPv6 pairing URLs * test(device-pair): cover IPv6 cleartext lower bound
What Problem This Solves
Fixes an issue where users pairing a mobile device with a local IPv6 gateway URL would be blocked when the
device-pairplugin produced cleartextws://[fc00::1],ws://[fd7a:115c:a1e0::1], orws://[fe80::1]setup URLs. The plugin already allowed.local, RFC1918 IPv4, and IPv4 link-local addresses, but treated IPv6 ULA and IPv6 link-local literals as non-local cleartext endpoints and stopped/pairbefore issuing a setup code.Why This Change Was Made
The cleartext pairing guard now recognizes normalized IPv6 literals in the local ULA
fc00::/7and link-localfe80::/10ranges. Public IPv6, deprecated site-local-stylefec0::/10addresses, Tailscale CGNAT cleartext URLs, and public hostnames still fail closed with the existing secure URL guidance.User Impact
Operators on IPv6-capable local networks can use local IPv6 LAN/link-local gateway addresses in mobile setup codes without switching to TLS or advertising a different host. Non-local cleartext pairing URLs remain rejected, so public mobile pairing still requires
wss://or Tailscale Serve/Funnel.Evidence
pnpm exec vitest run extensions/device-pair/index.test.ts— passed, 59 tests.pnpm exec vitest run extensions/device-pair/index.test.ts -t 'non-LAN IPv6 cleartext'— passed, 3 tests covering public IPv6,fe7f::1just belowfe80::/10, and deprecated site-localfec0::/10rejection.pnpm exec oxfmt --check --threads=1 extensions/device-pair/index.test.ts— passed.pnpm exec oxlint extensions/device-pair/index.test.ts— passed.Runtime command proof using the real bundled plugin loader and
/paircommand:Isolated
pnpm gateway:devstartup proof loaded the patcheddevice-pairplugin and reached ready: