fix(sidebar): explicit current-enclave header for sidebar re-renders + membership gate (LC-415) #431
Loading…
Reference in a new issue
No description provided.
Delete branch "fix/sidebar-enclave-context-hardening"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Follow-up hardening to LC-415 (#430), covering the whole-sidebar re-render routes that have no authoritative enclave in their own path: star toggle / reorder, mark-all-read, mark-room-read, mark-unread.
Why
The LC-415 fix used the path
enclave_idfor the category mutations. The routes above can't: a starred or read room may live in a different enclave than the one on screen, so the room's enclave is the wrong context. What the re-render actually needs is the viewer's CURRENT enclave (the one whose sidebar they are looking at), andHX-Current-URLwas the only signal for it - fragile when absent, stripped by a proxy, or raced by a sidebar OOB swap. When it failed to resolve, the re-render collapsed to the DM-only sidebar and the just-changed row vanished.Change
live.jssends the viewer's current enclave on every htmx request via a newX-LC-Current-Enclaveheader, read live from the rendered#sidebar-nav-{id}element id (the element the user is looking at, so always present and correct).routes::current_enclave_for_sidebarprefers that header, falling back to the existingHX-Current-URL/RefererURL parse for non-htmx requests. The two duplicatedcurrent_enclave_from_headerscopies now delegate to it.HX-Current-URLalways did), soload_sidebargates the enclave context on membership. A crafted header naming an enclave the viewer is not in (and is not a site admin of) falls back to the DM-only sidebar instead of leaking that enclave's room + category names. The membership lookup is resolved once and reused for the existing manage check, so it adds no query.Tests
read_all_preserves_enclave_via_explicit_header: the header preserves enclave context through/read-all; the no-header control falls back to DM-only.spoofed_current_enclave_header_does_not_leak_categories: a non-member pointing the header at a foreign enclave cannot enumerate its category names.just check,just test,just test-saas,node --check live.jsall green.Relates to LC-415.
🤖 Generated with Claude Code
Follow-up hardening to LC-415. The whole-sidebar re-render routes that have NO authoritative enclave in their own path - star toggle (`/rooms/{id}/star`), star reorder (`/sidebar/stars/positions`), mark-all-read (`/read-all`), mark-room-read (`/room/{id}/read`), mark-unread (`/messages/{id}/unread`) - still re-derived the viewer's current enclave from `HX-Current-URL`. Unlike the category mutations (fixed in LC-415 by using their path `enclave_id`), these can act on a room in a different enclave than the one on screen, so the room's enclave is the wrong context; the viewer's CURRENT enclave is what the re-render needs, and the header was the only signal for it - fragile when absent, stripped, or raced by a sidebar OOB swap. live.js now sends the viewer's current enclave on every htmx request via an `X-LC-Current-Enclave` header read live from the rendered `#sidebar-nav-{id}` element id (the very element the user is looking at, so always present and always correct). A new shared resolver `routes::current_enclave_for_sidebar` prefers that header and falls back to the existing `HX-Current-URL` / `Referer` URL parse for non-htmx requests. The two duplicated `current_enclave_from_headers` copies (sidebar_categories.rs, starred_rooms.rs) now delegate to it. Defense-in-depth: because the enclave now comes from a client-controlled header (as `HX-Current-URL` always was), `load_sidebar` gates the enclave context on membership - a crafted header naming an enclave the viewer is not in (and is not a site admin of) falls back to the DM-only sidebar instead of leaking that enclave's room + category names. The membership lookup is resolved once and reused for the existing manage check, so this adds no query. Tests: `read_all_preserves_enclave_via_explicit_header` (header preserves context; no-header control falls back) and `spoofed_current_enclave_header_does_not_leak_categories` (non-member cannot enumerate a foreign enclave's categories). `just check`, `just test`, `just test-saas`, `node --check live.js` all green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>