Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Sol design review — Serving plane & router

Part of the site’s external review; reproduced verbatim below the header. sol’s positions are folded into the design frontier as External review input lines.

  • Date: 2026-07-18
  • Reviewer: gpt-5.6-sol (via codex exec, model_reasoning_effort=xhigh), run on lane-182
  • Scope: src/part2/serving-plane.md, src/part3/serving-plane.md, and the serving-plane marker block of src/part5/emerging-specs.md (Plane Origin Convention v1, Route Table schema v1, honest-error contract v1).
  • Method: All in-scope material was inlined verbatim into a single self-contained prompt (no filesystem context given to the reviewer); the reviewer was briefed on the one-loopback-origin / pinned-workerd-router / three-frozen-route-types architecture and asked seven structured questions (confused-deputy robustness, https-vs-http, port custody, freeze+versioning+no-502, prior art, top-5 proposals, missing agenda questions), ending with a terminal sentinel line.

Orchestrator-facing summary (5 lines)

  1. Verdict: the isolation core is real but the review “overstates the strength” — Host and Fetch Metadata are request-routing and browser-CSRF signals, not plane authentication; sol demands a normative Browser Request Admission clause (exact authority, exact Origin on unsafe/upgrade, coherent Fetch Metadata, defined navigation exception) and an explicit PNA/LNA preflight policy (Access-Control-Request-Private-Network gated on a grant).
  2. Strongest single criticism: the cross-origin arm runs only when Origin is present and the Fetch-Metadata floor refuses only cross-site unsafe/websocket requests — so a cross-site safe request without Origin (image, navigation) can reach a backend prefix, including a whole-origin backend-proxy; relatedly, /xe/api/index//catalog are served before the hostname/Origin/Fetch-Metadata rungs, exposing app inventory to anything that reaches the loopback socket, and sibling .localhost apps are same-site, which SameSite cookies and Sec-Fetch-Site do not isolate.
  3. On G3 (https-vs-http): choose plain HTTP for regular apps, strictly limited to *.localhost:5454, with a separate cryptographic plane-identity handshake for Prism — the local CA is rejected as unreliable (non-enforcing clients gain authority beyond .localhost; root-key custody; ACME inapplicable to .localhost); migration is not a config toggle because scheme is part of the origin: staged per-app export/import, service-worker unregistration, Secure-cookie + HSTS audit, CA removal only after all legacy origins retire.
  4. On G2 (port custody): option (a) is “not an adequate security disposition” — framing a squatter is an integrity failure, not DoS; best shape = boot service generates a per-boot secret, hands it to Prism-native and the router via inherited descriptors / credential-checked OS IPC (never routes.json/env/URL/browser storage); Prism challenges /.well-known/xe-plane-identity/v1 with a fresh nonce and verifies an HMAC binding authority + full IPv4/IPv6 listener set + boot id + workerd digest + route-table generation before creating any guest frame — combined with supervisor-held socket custody of both stacks (socket-activation style, retained across workerd restarts).
  5. On the freeze / no-502 / top-5: “frozen but considering a 4th type” is not a workable evolution strategy — keep v0 at three types forever and put container-origin in xe/origin-routes/v1 with an explicit rollout protocol (dual-read, generation digest, last-known-good, table-wide 503 Plane upgrade required); the 503 collapse is defensible but a telemetry hazard — add Xe-Plane-Error + Xe-Request-Id, reserve 502 for protocol-invalid upstream responses and 504 for timeouts (and fix the honest-error contract’s self-contradiction on the 400/405 rows). Top-5 ranked: (1) normative Browser Request Admission clause; (2) Prism verifies a boot-bound plane attestation; (3) supervisor reserves both listener sockets; (4) freeze v0 + explicit v1 rollout protocol; (5) machine-identifiable, provenance-aware errors.

  1. ROBUSTNESS OF A LOOPBACK PLANE AGAINST CONFUSED-DEPUTY TRAFFIC

The strict .localhost Host grammar is useful, but the review overstates the strength of the “isolation core.” DNS rebinding to 127.0.0.1 would retain the attacker’s public hostname in Host, so the current guard should reject it; a browser cannot set a different Host. However, a public page can request http://photos.localhost:5454 directly, while an untrusted local process can forge Host, Origin, and every Sec-Fetch-* header. Host and Fetch Metadata are request-routing and browser-CSRF signals, not plane authentication.

There are concrete gaps in the refusal ladder:

  • /xe/api/index and /xe/api/catalog are served before the ordinary hostname, Origin, and Fetch Metadata checks. The Plane Origin Convention must specify their exact numeric authority, admission policy, CORS policy, and authentication. App inventory must not be available merely because the request reached a loopback socket.

  • The cross-origin arm runs only when an Origin is present, while the later Fetch Metadata check refuses only cross-site unsafe methods and WebSockets. A cross-site image, navigation, or other safe request without Origin can therefore reach a backend prefix, including a whole-origin backend-proxy. “GET is safe” is not an admission control, and idempotent_get is described only as a cold-start wait-set.

  • Sibling <app>.localhost origins are cross-origin but may be same-site. SameSite cookies and a Sec-Fetch-Site: same-site value do not isolate one Xe app from another. The policy must treat every non-exact origin as untrusted for backend access, not merely requests classified as cross-site.

  • The internal x-xe-peer-request-origin header is accepted at the browser-facing listener and merely checked against the requested hostname. Any local process can satisfy that check. Browser ingress must always discard it; only an authenticated peer connector on a distinct internal channel may supply equivalent metadata.

  • Regular apps receive no frame-ancestors policy. A public page can frame an app even if it cannot read the response, enabling confused user interaction. A minimal CSP allowing only Prism’s exact origin, plus any explicitly declared embedders, is appropriate for regular apps and does not require IWA packaging.

The specs need a normative admission sequence:

  1. Confirm that the accepted socket is one of the launcher-owned loopback listeners.
  2. Parse exactly one HTTP/1.1 Host or HTTP/2 :authority; require the configured scheme, ASCII label, and exact :5454. Reject duplicate authorities, absolute-form target mismatches, userinfo, trailing dots, and other ports. If TLS remains, require matching SNI as well. Ignore Forwarded and all X-Forwarded-* headers.
  3. Authenticate reserved service paths and remove all internal x-xe-* headers from browser ingress.
  4. Load and validate the target route before answering a preflight.
  5. Parse exactly one non-null Origin and compare its normalized scheme, host, and port. Require exact Origin for unsafe methods and WebSocket handshakes; treat missing or contradictory Origin/Fetch Metadata as untrusted, apart from a narrowly specified top-level-navigation bootstrap.
  6. Refuse cross-origin backend traffic, including safe subresources, unless a separate browser-session capability is defined. Allow only explicit GET/HEAD document navigation for direct opening; Prism iframe navigation should carry a Prism bootstrap capability.
  7. Apply grant-scoped CORS last, echoing the exact allowed Origin and emitting the corresponding Vary fields.

Private Network Access/Local Network Access must be explicit. An OPTIONS request containing Access-Control-Request-Private-Network: true should fail unless a live grant separately authorizes private-network access. Only then may the response contain Access-Control-Allow-Private-Network: true, alongside exact Access-Control-Allow-Origin, methods, headers, credentials, and Vary: Origin, Access-Control-Request-Method, Access-Control-Request-Headers, Access-Control-Request-Private-Network. Declared static CORS alone should not grant private-network access.

Even this does not authenticate against an arbitrary local client, because that client can forge browser headers. If untrusted local processes are an integrity threat rather than merely a denial-of-service threat, unsafe backend requests need an unguessable browser/Prism session capability or application-level authentication. The specs must say this plainly.

  1. THE HTTPS-WITH-LOCAL-CA vs PLAIN-HTTP TENSION (decision d/101 / gap G3)

I would make plain HTTP the default for regular apps, strictly limited to *.localhost:5454, and use a separate cryptographic plane-identity handshake for Prism. TLS adds little confidentiality on a correctly bound loopback socket, while a locally trusted CA adds key custody, trust-store installation, rotation, removal, and platform-compatibility obligations.

RFC 6761 gives .localhost its special resolution behavior, and Secure Contexts treats loopback/localhost origins as potentially trustworthy. Consequently, conforming browsers can provide secure-context APIs and service workers to http://<app>.localhost. That treatment does not authenticate the process holding port 5454, so it does not solve G2.

The local CA is not a reliable substitute. Name constraints are useful on enforcing clients, but the material already identifies clients that do not enforce them. On such clients, trusting the issuing CA can create authority beyond .localhost; short-lived leaves reduce leaf-key exposure but do not cure compromise of the CA signing key. Public ACME cannot issue certificates for reserved .localhost names, while private ACME would automate issuance without removing the local-root trust problem.

Plain HTTP still needs browser release gates. Prism’s IWA/controlled-frame implementation must be tested for HTTP-localhost embedding, WebSockets, mixed-content handling, secure-context APIs, cookies, and service-worker registration. Although localhost is potentially trustworthy, controlled frames or individual clients may impose stricter scheme rules. Any future non-loopback exposure must use a different HTTPS origin convention rather than weakening the loopback-only premise.

The migration is not merely a configuration toggle because scheme is part of the origin:

  • Amend Plane Origin Convention v1 to fix regular apps at http://<label>.localhost:5454; change generated catalogs, Prism navigation, tests, and the launcher default together. Keep Prism itself under its IWA security model.

  • If the current HTTPS origins have not shipped with durable user data, make the breaking change now. If they have, provide a staged, per-app export/import flow while the old HTTPS origin still runs; IndexedDB, Cache Storage, service workers, cookies, and other origin state cannot be moved generically by the router.

  • Before each cutover, app-owned migration code should export supported state and unregister its HTTPS service worker. After stopping TLS and starting HTTP on 5454, the app imports that state under the new origin. Apps without a defined migration must disclose that the cutover resets browser-local state.

  • Audit for Secure cookies and HSTS. Secure cookies will not transfer automatically; if HSTS was ever emitted, the legacy HTTPS endpoint must clear it before the switch.

  • Remove generated CA keys and trust anchors only after all legacy origins have been retired, including separate browser/NSS and OS trust stores.

  1. PORT CUSTODY (gap G2)

Option (a) is not an adequate security disposition. Accepting same-UID denial of service may be a reasonable v1 scope, but framing a squatter is an integrity failure, not merely denial of service. Binding both address families and improving xe doctor narrows failure modes without giving Prism evidence about the process it is framing.

A pinned serving leaf can work if HTTPS is retained and Prism’s native embedding layer can enforce an SPKI pin. Trusting the local CA normally is insufficient: it proves only possession of some acceptable certificate, not that this is the launcher-supervised instance. Pin rotation and recovery would also need a defined boot-service trust path.

mTLS is a poor default for ordinary framed apps. It complicates browser certificate selection and app access, and authenticating Prism as a client does not by itself resolve how ordinary browser navigation should work.

A handshake secret or signed instance attestation is the best scheme-independent option, but it must not live in routes.json, an environment variable, a URL, browser storage, or the ordinary per-user data root. The trusted boot service should generate a per-boot secret and hand it separately to the router and Prism’s native process through inherited descriptors, a sealed pipe, or credential-checked OS IPC. Prism—not guest JavaScript—should challenge /.well-known/xe-plane-identity/v1 with a fresh nonce and verify an HMAC or signature before creating any guest frame.

The attestation should bind at least:

  • Protocol version and fresh Prism nonce.
  • Boot/session identifier and expiry.
  • Scheme, port, and the complete IPv4/IPv6 listener set.
  • Requested app authority.
  • Pinned workerd binary digest.
  • Route-table generation or digest.

This must be combined with socket custody. The boot service should acquire both 127.0.0.1:5454 and [::1]:5454, fail the plane if either cannot be acquired, and pass the open descriptors to workerd. It should retain custody across workerd restarts, preferably through systemd/launchd-style socket activation. That closes the verification-to-framing race and the IPv4/IPv6 split-bind hazard.

If “same UID” includes unrestricted filesystem inspection or process memory access, no user-space shared secret is a complete boundary. The threat model must distinguish “can bind/connect to loopback” from “can inspect or instrument Prism and the launcher”; the latter requires OS process isolation or a more privileged service.

  1. ROUTE-TABLE FREEZE + VERSIONING + THE NO-502 ERROR CONTRACT

Failing closed on an unknown route type is correct. Calling a three-value discriminator permanently frozen while simultaneously considering container-origin, however, is not a workable evolution strategy. The shipped empty-digest plus prefix:"/" overload is already evidence that the freeze is pushing new semantics into old shapes.

The clean rule is: xe/origin-routes/v0 remains permanently limited to its three existing types. A fourth type requires xe/origin-routes/v1, not an “additive” change to v0. The candidate itself is confusingly named “Route Table schema v1” while documenting a v0 wire identifier; that should be resolved before ratification.

The migration protocol should be explicit:

  1. Deploy a reader that accepts both v0 and v1.
  2. Have the supervisor advertise the reader versions/features it supports.
  3. Only then allow the generator to atomically publish v1 with a generation number and digest.
  4. Retain the ability to regenerate v0 during a rollback window.
  5. Reject an unsupported schema table-wide with 503 Plane upgrade required, rather than returning a misleading per-route 404.

Because this table is generated, regeneration from canonical state is preferable to an in-place mutation journal. Grants and peer-route state still need their own compatibility and atomic-write rules. The supervisor should validate a complete candidate before swapping it in and retain a last-known-good table rather than letting per-request reads observe a partial or incompatible generation.

The absence of 502 can be defensible, but the current contract makes it a telemetry hazard. 503 is reasonable for an intentionally unavailable peer, a cold backend, a missing connector, or an unavailable plane dependency. 502 should mean that the gateway reached an upstream path but received no valid HTTP response or a protocol-invalid response; a timeout is more precisely 504. A genuine, valid upstream 500 should continue to pass through unchanged, not be relabeled 502.

Whatever status policy is selected, add stable machine-readable headers such as Xe-Plane-Error: peer_offline|backend_unreachable|table_missing|upstream_protocol_error and Xe-Request-Id, plus structured local logs identifying whether the status was synthesized or passed through. Otherwise automation must parse mutable English bodies to distinguish several unrelated 503 conditions. All generated errors should be Cache-Control: no-store, not only the peer and index cases.

The honest-error candidate also contradicts itself: it says every body names a next action and ends with “then retry,” while its 400 and 405 rows show only 400 Bad Request and 405 Method Not Allowed. Either provide actionable copy for every row or narrow the invariant.

  1. PRIOR ART & STANDARDS ALIGNMENT
  • Private Network Access / Local Network Access: The design is behind the relevant browser work because its preflight clause does not mention private-network request headers or permission. Its default refusal is directionally safe, but legitimate grants may fail in supporting browsers, while a future generic CORS change could accidentally over-authorize them. PNA/LNA is defense in depth and cannot replace server authorization or protect against non-browser local processes.

  • Fetch Metadata: Using Sec-Fetch-Site as a CSRF floor aligns with established resource-isolation policy. The implementation is incomplete because it permits cross-site safe requests to reach backends and does not define handling for missing headers, sibling same-site apps, document navigation, or Sec-Fetch-Mode/Sec-Fetch-Dest. Fetch Metadata is deliberately forgeable by non-browser clients and must not be described as authentication.

  • RFC 6761 and Secure Contexts: Using separate *.localhost hosts is a sound way to obtain distinct browser origins without DNS configuration, and it makes classic public-hostname DNS rebinding fail the Host check. Secure-context treatment supports the plain-HTTP recommendation. Neither standard proves that the process holding port 5454 is Xe, and separate origins do not imply separate sites for SameSite-cookie purposes.

  • Local development proxies: Tools serving *.localhost or locally resolved *.test commonly validate Host, terminate a wildcard or generated local certificate, and explicitly install a development CA. Xe is ahead of ordinary development tooling in its fail-closed route table and per-chunk grant revocation, but it is claiming a stronger production trust boundary. That requires stricter port identity, cookie isolation, frame policy, and request admission than a developer proxy normally supplies.

  • Tailscale Serve/Funnel: Tailscale Serve is relevant as a daemon-owned front door that proxies local services behind a named, authenticated identity; Funnel additionally makes exposure an explicit operator action. Neither is a direct browser-loopback CSRF solution. The useful precedent is that listener custody, service identity, and exposure policy are owned by the fronting daemon rather than inferred from a port number.

  • Cloudflare Workers/workerd: A pinned runtime, declarative bindings, and a small fail-closed ingress worker fit the Workers/workerd model well. The Xe route discriminator and grants are Xe-specific policy, not protections supplied by workerd. Mature deployment systems also carry explicit configuration versions, compatibility declarations, atomic rollout, and rollback; the unnegotiated v0 JSON table is behind that practice.

  • Local TLS and CAs: A name-constrained local CA is better than an unconstrained development root on clients that enforce constraints. mkcert-style practice demonstrates that local trust can be made usable, but also exposes the costs of root-key custody and separate browser/OS trust stores. Public ACME is not applicable to .localhost; a private ACME service could automate leaves but would not solve root trust, client enforcement, or plane-instance identity.

The strongest parts relative to prior art are the content-addressed static store, unknown-type fail-closed behavior, static-only grants, live revocation during streaming, and honest user-facing refusals. The weak parts are the missing browser admission matrix, absent plane attestation, and undeclared configuration-upgrade protocol.

  1. TOP-5 CONCRETE IMPROVEMENT PROPOSALS, RANKED

  2. Add a normative Browser Request Admission clause. Require exact Host/authority, exact Origin on unsafe and upgrade requests, coherent Fetch Metadata, and a narrowly defined top-level-navigation exception. Deny all cross-origin backend subresources—including GET/HEAD—and require an explicit Prism bootstrap capability for iframe navigation.

  3. Make Prism verify a boot-bound plane attestation. Have the boot service pass a per-boot secret to Prism native code and the router through inherited protected handles. Define a nonce-based HMAC handshake binding the authority, listener set, boot ID, workerd digest, and route-table generation; Prism must not frame before it verifies successfully.

  4. Reserve both listener sockets in the supervisor. Acquire IPv4 and IPv6 together, fail closed if either is unavailable, and pass the sockets into workerd so restarts never relinquish custody. Add PID diagnostics for failures, but do not treat diagnostics as authentication.

  5. Freeze v0 and introduce an explicit v1 rollout protocol. Keep the three v0 types forever and put container-origin, if adopted, in xe/origin-routes/v1. Add reader capability advertisement, atomic generation/digest fields, dual-read deployment order, last-known-good rollback, and a table-wide upgrade-required error.

  6. Make errors machine-identifiable and provenance-aware. Add Xe-Plane-Error, Xe-Request-Id, Cache-Control: no-store, and structured logs to every synthesized error. Retain 503 for expected unavailability, add 502 only for invalid upstream responses and 504 for upstream timeouts, and pass valid upstream 5xx responses unchanged.

  7. QUESTIONS MISSING FROM THE SESSION AGENDA

  • Is an untrusted same-UID process assumed only to bind/connect to sockets, or may it read files, inspect process memory, or instrument Prism?
  • What is the complete admission matrix for public cross-site pages, sibling Xe apps, direct top-level navigation, Prism frames, missing Fetch Metadata, and WebSockets?
  • Will PNA/LNA access require a new private_network field in grants, and which exact preflight headers will be emitted?
  • How does Prism authenticate itself for initial iframe navigation, and what exact frame-ancestors policy should regular apps receive?
  • Are /xe/api/index and /xe/api/catalog public to every local browser origin, Prism-only, or native-client-only?
  • Are ports 5198 and 5199 authenticated against arbitrary local processes, and on which channel may internal x-xe-* headers be trusted?
  • Must backend Set-Cookie responses be forced to host-only cookies so one app cannot set Domain=localhost cookies affecting sibling apps?
  • How are backend Location, CORS, CSP, cookie, hop-by-hop, and WebSocket headers rewritten or rejected at the proxy boundary?
  • Who may write route and grant files, and what are the required permissions, atomicity, integrity checks, and last-known-good behavior?
  • What happens to service workers, caches, IndexedDB, and cookies when an app is uninstalled, upgraded, tombstoned, or explicitly reclaimed?
  • Which browsers and controlled-frame versions are release-gated for .localhost, IPv6, Secure Contexts, PNA/LNA, Fetch Metadata, and HTTP framing?
  • What request-size, header-size, timeout, streaming, and malformed-upstream limits apply to backend and peer proxies?
  • How are opaque origins such as Origin: null, sandboxed frames, requested CORS headers, and credentialed grants handled?
  • What per-request audit data is retained, for how long, and how are app names, device names, and paths protected from unnecessary disclosure?

SOLREV-SERVING-DONE