Skip to content

macOS guests 5/6: guest-port forwarding - #514

Open
chruffins wants to merge 1 commit into
feat/macos-snapshotsfrom
feat/macos-guest-ports
Open

chruffins wants to merge 1 commit into
feat/macos-snapshotsfrom
feat/macos-guest-ports

Conversation

@chruffins

@chruffins chruffins commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

Summary

PR 5 of 6. Guest-port forwarding for macOS guests. Base: #506. Replaces #501: GitHub marked #501 merged into the builds branch when builds moved to the top of the stack. Nothing from it reached main.

  • GET|HEAD|POST|PUT|PATCH|DELETE /instances/{id}/ports/{port}/* forwards HTTP and WebSocket requests to 127.0.0.1:{port} inside a running macOS guest. It requires instance:write, the same scope as exec.
  • Path and query pass through unchanged. Host is rewritten to 127.0.0.1:{port}, because Chrome DevTools rejects other hosts. Authorization is removed. Responses come back unchanged; rewriting URLs such as webSocketDebuggerUrl is the caller's job (metro-api, as for Linux).
  • Guest side: the guest agent listens on vsock 2223, reads a two-byte port, connects to loopback, answers with one status byte and copies both ways. At most 64 concurrent forwards per guest; handshakes have a five-second deadline.
  • Errors: 501 for Linux guests or images without the agent, 400 for a bad port, 409 when not Running, 502 when nothing listens, 503 when the forwarder is unavailable or full. There is no response timeout, so long polls work.
  • Hypeman does not launch or supervise a browser; the guest image starts its own services.

Linux code touched

  • cmd/api/main.go: exec, cp and port forwarding now share one middleware chain (previously exec and cp each repeated it).
  • lib/scopes: the route and its methods, defined once.
  • lib/system/guest_agent/main.go: starts the forwarder on Darwin only.

Testing

  • Unit tests with -race cover the handshake, the agent forwarder (saturation, slot reuse, transient accept errors) and the API handler: path and escaping, Host and Authorization handling, a WebSocket round trip, and each error status.
  • Native QA ran on Apple silicon (macOS 27.0.1 host and guests) across six runs plus a VZ restore spike. The simplification pass after run 6 has not been re-run natively yet. This PR's paths covered there: forwarding through the agent, the 64-slot limit, a 45-second slow response, client disconnect, and the 502/400 errors.

Review focus

The security boundary: an instance:write token can reach any loopback port in the guest. That's deliberate, since the same token can already exec as root.

Stack

Each PR is one commit on the one below it. macOS logic lives in its own files (macos*.go, *_darwin.go, exec_lifecycle.go, port_forward.go); shared Linux files get small hooks such as if stored.MacOS != nil.

  1. macOS guests 1/6: VZ runtime, machine images and instance lifecycle #498 VZ runtime, machine images and instance lifecycle
  2. macOS guests 2/6: OCI machine-image integration #499 OCI machine images
  3. macOS guests 3/6: Darwin guest service, readiness and exec #500 Darwin guest agent, readiness and exec
  4. macOS guests 4/6: snapshots, schedules and stopped forks #506 snapshots, standby and stopped clones
  5. macOS guests 5/6: guest-port forwarding #514 guest-port forwarding
  6. macOS guests 6/6: experimental machine-image builds #503 machine-image builds (not needed for v0; can land later)

Note

Medium Risk
Introduces a new authenticated tunnel to arbitrary guest loopback ports (same privilege model as exec); misconfiguration or compromised instance:write tokens could expose internal guest services.

Overview
Adds /instances/{id}/ports/{port}/* so clients with instance:write can reach HTTP and WebSocket services on a running macOS guest’s 127.0.0.1:{port} (ingress replacement for vmnet NAT guests). The handler is an httputil.ReverseProxy over guest.DialPort (vsock 2223): path/query pass through, Host becomes loopback, Authorization is stripped, and there is no response timeout for long polls.

On Darwin, the guest agent listens on vsock 2223, runs a port handshake, dials loopback, and copies bytes both ways with a 64-connection cap and bounded handshakes. lib/guest implements the host-side dial; GuestOpPortForward returns 501 for Linux or images without the agent. Exec, cp, and port forward share one middleware chain; route constants live in lib/scopes.

Reviewed by Cursor Bugbot for commit 23c6890. Configure here.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 23c6890. Configure here.

Comment thread lib/system/guest_agent/port_forward.go
@chruffins
chruffins marked this pull request as ready for review October 14, 2026 19:12
@chruffins
chruffins force-pushed the feat/macos-guest-ports branch from 23c6890 to 837ba07 Compare October 14, 2026 20:10
@chruffins
chruffins force-pushed the feat/macos-guest-ports branch 2 times, most recently from 8e6d7c8 to 8385500 Compare October 15, 2026 16:40
@chruffins
chruffins force-pushed the feat/macos-guest-ports branch from 8385500 to 62cae75 Compare October 15, 2026 19:26
@chruffins
chruffins force-pushed the feat/macos-guest-ports branch from 62cae75 to ca64689 Compare October 15, 2026 22:56
@chruffins
chruffins force-pushed the feat/macos-guest-ports branch 3 times, most recently from a18ab32 to 6e48e3e Compare October 19, 2026 17:24
@chruffins
chruffins force-pushed the feat/macos-guest-ports branch from 6e48e3e to 802a888 Compare October 19, 2026 18:01
@chruffins
chruffins force-pushed the feat/macos-guest-ports branch 2 times, most recently from 4861a6c to 8a799f6 Compare October 20, 2026 19:33
@chruffins
chruffins force-pushed the feat/macos-guest-ports branch from 8a799f6 to 22b13e3 Compare October 20, 2026 20:29
Forward HTTP and WebSocket requests from /instances/{id}/ports/{port}/* to a TCP
port on a macOS guest's loopback interface, through the guest agent on vsock 2223.
Hypeman does not start or supervise services in the guest; the image does.
@chruffins
chruffins force-pushed the feat/macos-guest-ports branch from 22b13e3 to 82a8e04 Compare October 20, 2026 20:32

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

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant