fix(security): hosted multi-tenancy hardening — session REST owner-scoping + TOCTOU path revalidation (#704)#707
Conversation
…ace path at WS connect (#704) Hosted multi-tenancy hardening (follow-up to #655): 1. Owner-scope session REST endpoints. list/get/delete/messages queried without an owner filter, so an authed tenant could enumerate/read/modify/ end another tenant's sessions. Thread require_auth through all five handlers; scope repo.list() by user_id and gate per-id handlers via _get_owned_session (404 on missing-or-not-yours so IDs can't be probed). No-auth mode (user_id=None) skips scoping, unchanged. 2. TOCTOU symlink revalidation. The stored workspace_path cleared the allowlist at create time but terminal_ws/session_chat_ws used the raw string later as cwd/tool scope. A tenant could swap a dir for a symlink outside its root in between. Re-resolve + re-check (revalidate_workspace_path) at WS connect; close on reject; use the freshly resolved path. Known limitation: a sub-ms race remains between revalidation and shell spawn; true TOCTOU-proof isolation needs per-tenant container/chroot or openat2(RESOLVE_NO_SYMLINKS) — infra-level, deferred.
|
Warning Review limit reached
More reviews will be available in 22 minutes and 44 seconds. Learn how PR review limits work. Your organization has reached its usage spending cap. Adjust your spending cap in the billing tab. ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits. 🚦 How do rate limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (8)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
|
Code Review for fix/704 session owner-scoping and TOCTOU path revalidation. This is a well-scoped, surgical security fix. Both changes are independent and cleanly implemented. The test coverage is strong. Issues: (1) Typo in docstring ponytail: label in codeframe/ui/dependencies.py revalidate_workspace_path should be Note:. (2) Terminal WS test uses bare pytest.raises(Exception) missing code assertion for WebSocketDisconnect code 4008. (3) Inline imports inside function bodies style nit. (4) end_session race is acknowledged and fine. Summary: two things to address before merge - the ponytail: docstring typo and the weak pytest.raises(Exception) in the terminal symlink test. |
|
Full review (see below). Issues to fix before merge: (1) ponytail: docstring typo in revalidate_workspace_path should be Note:. (2) test_symlink_escape_rejected uses bare pytest.raises(Exception) instead of pytest.raises(WebSocketDisconnect) with code assertion == 4008. |
|
test comment - please ignore (will be replaced) |
Code Review — fix/704 session owner-scoping + TOCTOU path revalidationThis is a well-scoped, surgical security fix. Both changes (REST owner-scoping and WS TOCTOU revalidation) are independent and cleanly implemented. The test coverage is strong. What's good
Issues1. Typo in docstring —
2. Terminal WS test uses bare
Suggested fix: with pytest.raises(WebSocketDisconnect) as exc:
with client.websocket_connect("/ws/sessions/s1/terminal?token=x"):
pass
assert exc.value.code == 40083. Inline imports inside function bodies (style nit) Both WS handlers do 4. The concurrent-delete case is already handled by SummaryTwo things worth addressing before merge: the |
- Reword 'ponytail:' docstring label to 'Note:' in revalidate_workspace_path - Hoist revalidate_workspace_path imports to module top (no circular import) - Strengthen terminal symlink test to assert close code 4008
Code Review — fix/704 follow-up passPrior issues statusBoth issues raised in my earlier review have been addressed in the current diff:
One remaining nit
Suggested: remove the inline SummaryThe security logic is sound, owner-scoping is complete across all five REST endpoints, TOCTOU revalidation is correctly threaded through both WS handlers via |
Summary
Hosted multi-tenancy hardening for issue #704 (follow-up to #655). Two independent security fixes.
1. Owner-scope session REST endpoints
POST /api/v2/sessionsalready persisteduser_idand the terminal/chat WebSockets enforced ownership, but the other five REST endpoints queried without an owner filter — in an authenticated multi-user deployment one tenant could enumerate/read/modify/end another tenant's sessions.require_auththreaded throughlist,get,delete,POST/GET messages.repo.list()scoped byuser_id; per-id handlers gated via a new_get_owned_sessionhelper.user_id=None,CODEFRAME_AUTH_REQUIRED=false) skips scoping — behavior unchanged.2. TOCTOU symlink revalidation at WS connect
The stored
workspace_pathcleared the allowlist at create time, butterminal_ws/session_chat_wslater used the raw stored string as the shellcwd/ file-tool scope. A tenant could swap the dir (or an ancestor) for a symlink pointing outside its allowed root in between (TOCTOU).revalidate_workspace_path()re-runsenforce_workspace_allowlist(which.resolve()s symlinks) at WS connect; the socket is closed on rejection, and the freshly-resolved path is used downstream.Test plan
tests/ui/test_session_owner_scoping.py— cross-tenant 404 for each endpoint, same-owner 200, no-auth passthrough.tests/ui/test_terminal_ws.py::TestTerminalWsRevalidation+tests/api/test_session_chat_ws.py::test_chat_ws_symlink_escape_rejected— symlink-swap → WS closes; legit path connects with the resolved cwd.ruffclean.Known limitations
A sub-millisecond race remains between revalidation and the shell spawn. True TOCTOU-proof filesystem isolation needs a per-tenant container/chroot or
openat2(RESOLVE_NO_SYMLINKS)— infra-level, and gates a hosted launch that isn't deployed yet. Revalidation closes the practical window; container isolation is recommended for hosted ops.Closes #704