- Shipped
- August 24, 2026 at 11:05 PM UTC
- Author
- kamo
- Commit
- 1b1c236
46 client modules read the *** id out of document.cookie and sent it as X-***-Token. sessionStorage is per-TAB; a cookie is per-ORIGIN. While every organization had a hostname of its own those were the same boundary, so it did not matter. Serving organizations without a domain from one shared host ends that: the cookie becomes a single slot any tab can overwrite, so a tab can send a different organization's session than the one it is displaying. Mostly this was already masked. installSessionLayer() patches window.fetch to set X-***-Token from sessionStorage on same-origin /api/* calls, and headers.set() overwrites, so an explicit cookie-sourced header is normally replaced -- every one of these call sites is same-origin /api/*, and none touches a WebSocket or another origin. Normally is not always: the patch only fires when sessionStorage HAS a token, so a freshly-opened tab with an empty store sends whatever the cookie holds. That is the case this closes. app/logout/page.tsx keeps its cookie writes -- clearing them is how a session is torn down -- and useSmsWebSocket.ts was already migrated; its document.cookie is in a comment explaining why it must not be used. check-session-token-source.mjs holds the line, with those two allowlisted and their reasons recorded. It strips comments before matching, so an explanation of why the cookie is avoided does not read as a use of it. Two files were CRLF and a naive rewrite reflowed them entirely; they were redone byte-wise, so the diff is the substitution and nothing else. 2882 tests and all 8 guards pass. The production build could not be run locally -- other sessions hold the Next build lock continuously -- but tsc is clean and an earlier full build of this same code succeeded; only line endings changed after it.