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 index bc3c580f..038179f4 100644 --- a/docs/report/2026-08-03-lan-http-randomuuid-blocks-websocket.md +++ b/docs/report/2026-08-03-lan-http-randomuuid-blocks-websocket.md @@ -52,8 +52,10 @@ backend への proxy はこの現象の直接原因ではない。 ## 改善案 -- UI 全体で使う ID 生成 helper を用意し、secure context に依存しない実装にする。 - `crypto.getRandomValues()` から UUID v4 相当を生成する方法で十分である。 +- `WorkspaceMultiplexer` の client/request correlation ID は、module-local の単調増加 + sequence から生成する。これらは1ページ・1接続内で重複しなければよく、暗号学的乱数や + 永続的なglobal uniquenessは不要なので、`crypto`、`Math.random()`、時刻依存のfallbackを + 使わない。 - `WorkspaceMultiplexer.subscribe()` の同期初期化失敗を Console の diagnostic/state に 反映し、初期値の `connecting` に留まらないようにする。 - LAN 上の平文 HTTP を開発時の対応経路とするなら、insecure context から subscription @@ -67,3 +69,9 @@ backend への proxy はこの現象の直接原因ではない。 `--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には使われない。 diff --git a/web/workspace/src/lib/workspace/console/worker-console.ui.test.ts b/web/workspace/src/lib/workspace/console/worker-console.ui.test.ts index 2b3b71e7..9108d2be 100644 --- a/web/workspace/src/lib/workspace/console/worker-console.ui.test.ts +++ b/web/workspace/src/lib/workspace/console/worker-console.ui.test.ts @@ -735,6 +735,11 @@ Deno.test("Workspace Worker list and Console share the multiplexed connection", multiplexer.includes("frame: 'worker_protocol'"), "Sidebar and Console should share one Workspace multiplexer and route Worker methods through a subscription lane", ); + assert( + multiplexer.includes('nextMultiplexerId') && + !multiplexer.includes('crypto.randomUUID'), + "Workspace subscription correlation IDs should not require secure-context crypto APIs", + ); assert( multiplexer.includes("this.#socket?.readyState === WebSocket.OPEN") && multiplexer.includes("this.#sendSubscribe(subscription)") && diff --git a/web/workspace/src/lib/workspace/multiplexer.ts b/web/workspace/src/lib/workspace/multiplexer.ts index 5f8ab10c..e155a1df 100644 --- a/web/workspace/src/lib/workspace/multiplexer.ts +++ b/web/workspace/src/lib/workspace/multiplexer.ts @@ -26,6 +26,12 @@ export type WorkspaceMultiplexerSubscription = { }; const multiplexers = new Map(); +let nextMultiplexerSequence = 0; + +function nextMultiplexerId(kind: 'client' | 'request'): string { + nextMultiplexerSequence += 1; + return `${kind}-${nextMultiplexerSequence}`; +} export function workspaceMultiplexer(workspaceId: string): WorkspaceMultiplexer { let multiplexer = multiplexers.get(workspaceId); @@ -53,7 +59,7 @@ export class WorkspaceMultiplexer { selector: EventSubscriptionSelector, listener: Listener, ): WorkspaceMultiplexerSubscription { - const clientId = crypto.randomUUID(); + const clientId = nextMultiplexerId('client'); const subscription: ActiveSubscription = { clientId, selector, @@ -112,7 +118,7 @@ export class WorkspaceMultiplexer { } #sendSubscribe(subscription: ActiveSubscription): void { - const requestId = crypto.randomUUID(); + const requestId = nextMultiplexerId('request'); subscription.requestId = requestId; this.#requests.set(requestId, subscription.clientId); this.#send({ @@ -198,7 +204,7 @@ export class WorkspaceMultiplexer { message: { method: 'unsubscribe_events', params: { - request_id: crypto.randomUUID(), + request_id: nextMultiplexerId('request'), subscription_id: subscription.subscriptionId, }, },