diff --git a/docs/report/2026-08-03-lan-http-randomuuid-blocks-websocket.md b/docs/report/2026-08-03-lan-http-randomuuid-blocks-websocket.md new file mode 100644 index 00000000..bc3c580f --- /dev/null +++ b/docs/report/2026-08-03-lan-http-randomuuid-blocks-websocket.md @@ -0,0 +1,69 @@ +# 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_events` の `request_id` + +いずれも暗号化や認証のためではなく、ローカル Map のキーまたはプロトコル上の相関 ID +である。secure-context-only API を要求する必要性はない。 + +## 切り分け結果 + +開発ホスト上から次の条件で `/api/w//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 はこの現象の直接原因ではない。 + +## 改善案 + +- UI 全体で使う ID 生成 helper を用意し、secure context に依存しない実装にする。 + `crypto.getRandomValues()` から UUID v4 相当を生成する方法で十分である。 +- `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 が利用できることを証明できない。