Files
yoi/docs/report/2026-08-03-lan-http-randomuuid-blocks-websocket.md
T

4.2 KiB

LAN の HTTP アクセスで crypto.randomUUID() が WebSocket 接続開始を阻害する

概要

Workspace WebUI の Vite dev server を --host 付きで LAN に公開し、別端末から http://192.168.1.32:5173 にアクセスすると、Worker Console の protocol 状態が connecting のまま進まなかった。

原因は WebSocket や Vite proxy ではなく、WebSocket multiplexing の相関 ID 生成に crypto.randomUUID() を直接使っていることである。http://localhost はブラウザから potentially trustworthy origin として扱われる一方、LAN IP 上の平文 HTTP は secure context ではない。crypto.randomUUID() は secure context 限定なので、LAN アクセスでは 接続処理が WebSocket の生成前に例外終了する。

発生経路

Worker Console の connectProtocolTransport は、最初に protocolState"connecting" に設定してから WorkspaceMultiplexer.subscribe() を呼ぶ。

  • web/workspace/src/routes/w/[workspaceId]/runtimes/[runtimeId]/workers/[workerId]/console/+page.svelte
    • protocolState = "connecting"
    • 直後に workspaceMultiplexer(...).subscribe(...)
  • web/workspace/src/lib/workspace/multiplexer.ts
    • subscribe() の先頭で const clientId = crypto.randomUUID()
    • その後の #ensureConnected() で初めて new WebSocket(url) を実行する

このため crypto.randomUUID() が利用できないブラウザでは、画面状態だけが connecting に更新された後に同期例外が発生し、WebSocket request 自体が送信されない。 接続失敗を示す closed や diagnostic にも遷移しないため、見た目からは WebSocket の 接続待ちに見える。

同じ multiplexer では crypto.randomUUID() を次の3用途に使っている。

  1. ブラウザ内で subscription を区別する clientId
  2. subscribe_events request と response を対応付ける request_id
  3. unsubscribe_eventsrequest_id

いずれも暗号化や認証のためではなく、ローカル Map のキーまたはプロトコル上の相関 ID である。secure-context-only API を要求する必要性はない。

切り分け結果

開発ホスト上から次の条件で /api/w/<workspace-id>/protocol/ws に WebSocket upgrade を 送ると、backend への直接接続と Vite の 5173 proxy 経由の両方で 101 Switching Protocols が返った。

  • Host: 192.168.1.32:5173
  • Origin: http://192.168.1.32:5173

したがって、現在の Vite 設定にある proxy["/api"].ws = true と、loopback に bind した backend への proxy はこの現象の直接原因ではない。

改善案

  • WorkspaceMultiplexer の client/request correlation ID は、module-local の単調増加 sequence から生成する。これらは1ページ・1接続内で重複しなければよく、暗号学的乱数や 永続的なglobal uniquenessは不要なので、cryptoMath.random()、時刻依存のfallbackを 使わない。
  • WorkspaceMultiplexer.subscribe() の同期初期化失敗を Console の diagnostic/state に 反映し、初期値の connecting に留まらないようにする。
  • LAN 上の平文 HTTP を開発時の対応経路とするなら、insecure context から subscription 初期化できることを regression test として固定する。
  • 本番相当の LAN 公開では HTTPS を使う。Passkey/WebAuthn も secure context を要求し、 現在の開発設定は rp_id = localhost なので、LAN origin を正式対応する場合は auth の origin/RP ID 設計も別途必要になる。

補足

--host は Vite の listener を LAN に bind するだけであり、配信 origin を secure context に変えるものではない。localhost で正常に動くことだけでは、LAN IP の HTTP アクセスでも同じブラウザ API が利用できることを証明できない。

Resolution (2026-08-06)

WorkspaceMultiplexer の client/request ID をmodule-localの単調増加sequenceへ変更した。 これによりWebSocket接続開始経路からsecure-context限定の crypto.randomUUID() 依存を除去した。 IDはlocal correlationにのみ使われ、認証・認可・capability authorityには使われない。