Keep remote-initiated opens off this Mac's local network

Closed
AU
6d ago
+822-50

Summary

Tightens what a remote machine can make this Mac do without a click.

  • Remote url-open. A url-open from a cloud or SSH machine can no longer hand a loopback, private, link-local, multicast or local-name URL (localhost, *.local, *.internal, single-label names, legacy IPv4 spellings such as 0x7f.1, and mapped IPv6) to the default browser.
    • SSH loopback routes still open in the cmux browser, where they reach the remote machine. For a remote request they never fall back to the system browser, because there they would reach this Mac's own port.
    • A refusal returns false, so the remote side prints the URL instead.
    • Public https:// opens are unchanged, including OAuth and device-login pages.
    • Clicked links are unchanged.
  • Private-address routing. When several SSH machines share 127.0.0.1, a browser URL now goes only to the machine that owns the browser, instead of the first machine the catalog returns.
    • This applies to both BrowserPanel.navigate and rebindCloudRouteIfNeeded.
    • The owner is the browser's cloud route, then the machine that owns its workspace.
  • Cloud notification subtitle. A producer-supplied subtitle still names the machine, as "detail on machine". Line breaks, control characters and bidi overrides are removed, and the detail and machine name are length-capped. reply_shape and click actions were already clamped for cloud rows, so they are unchanged.

The decisions live in CmuxCore and CmuxNotifications value types, which the app calls:

  • PrivateNetworkHostPolicy
  • RemoteLinkOpenPolicy
  • PrivateAddressRouteSelector
  • RemoteMachineNotificationSubtitle

Related: #12373, #10827 and #7397 (related, no overlap).

Follow-ups (not in this PR)

  • Remote file:// and path links that fall through to local files. This covers:
    • canResolveTerminalPathsAgainstLocalFilesystem should treat isLocal != false as remote;
    • the dock terminalLinkIsRemoteTerminal gap.
  • Resolving public DNS names before a remote-initiated open. PrivateNetworkHostPolicy.requiresAddressLookup marks which names need it. DNS rebinding remains after that.
  • Carrying the source machine to dock and split browsers, so a dock browser can route an SSH loopback URL. With no owner, loopback URLs are no longer routed to an arbitrary machine.
  • The notification title is still shown as the producer sent it.
  • App-level regression tests for the coordinator and notification wiring.

Testing

Red commit 99a24db852 has the tests and stubs that keep the old behavior. Green commit 70da666651 has the fix. The same focused commands were run on both:

  • swift test --package-path Packages/macOS/CmuxCore --filter 'PrivateNetworkHostPolicyTests|RemoteLinkOpenPolicyTests|PrivateAddressRouteSelectorTests'
    • Red: 20 tests in 3 suites failed with 59 issues.
    • Green: 20 tests in 3 suites passed.
  • swift test --package-path Packages/macOS/CmuxNotifications --filter RemoteMachineNotificationSubtitleTests
    • Red: 6 tests failed with 6 issues.
    • Green: 6 tests passed.
  • python3 scripts/verify-local.py --affected origin/main --swift-changed origin/main passed 4/4 checks: swift-syntax, wire-app-sources, package-groups and feature-flags.

Not run locally, because the machine's disk was nearly full:

  • the native app build;
  • cmuxTests;
  • dogfooding.

The app-side wiring in these files has not been compiled locally; CI is the first compile:

  • TerminalLinkOpenCoordinator
  • CloudGuestURLService
  • CloudNotificationLocalDelivery
  • BrowserPanel

Localization audit: no new user-facing strings. The subtitle reuses cloudNotification.subtitle.machine.

Changelog

  • Changed: Links a remote machine opens without a click stay off this Mac's local network, and remote notification subtitles always name their machine.

🤖 Generated with Claude Code


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.


Summary by cubic

Keeps links a remote machine opens without a click off this Mac's loopback and local network, routes private-address browser URLs to the machine that owns the browser, and stops remote terminal content from opening files on this Mac.

  • A remote-initiated url-open no longer hands a loopback, private, link-local, multicast, or local-name URL (localhost, *.local, *.internal, single-label names, legacy IPv4 spellings) to the default browser.
  • SSH loopback routes still open in the cmux browser, where they reach the remote machine, and never fall back to the system browser for a remote request. A refusal returns false, so the remote side prints the URL instead; public https:// opens and clicked links are unchanged.
  • When several SSH machines share 127.0.0.1, a browser URL now goes only to the machine that owns the browser, in both BrowserPanel.navigate and rebindCloudRouteIfNeeded.
  • Only a click in a terminal known to run on this Mac opens a local file. SSH, cloud, and unplaced terminals, plus any open a remote machine asks for, never fall back to this Mac's filesystem, so apps and scripts on this Mac don't launch from remote content. Scrollback exports Ghostty wrote on this Mac still open locally.
  • A remote notification's subtitle always names its machine; line breaks, control characters, and bidi overrides are removed, and the detail and machine name are length-capped.