Follow-up to #655 (which closed the core authenticated cross-tenant RCE: workspace path allowlist + per-user binding + WebSocket session ownership). Two further hardening items were identified by cross-family review but are broader than #655's scope and gate a HOSTED launch only (not deployed yet).
1. Scope session REST endpoints by owner
POST /api/v2/sessions now persists user_id, and the terminal/chat WebSockets enforce ownership. But the REST session endpoints still query without an owner filter, so in an authenticated multi-user deployment one tenant can enumerate/read/modify/end another tenant's sessions:
GET /api/v2/sessions (list)
GET /api/v2/sessions/{id}
DELETE /api/v2/sessions/{id}
POST /api/v2/sessions/{id}/messages
GET /api/v2/sessions/{id}/messages
Fix: thread the authenticated principal through these handlers and scope every InteractiveSessionRepository operation by user_id (skip only in no-auth mode).
2. TOCTOU symlink revalidation + filesystem isolation
create_session validates and stores the resolved workspace_path, but terminal_ws later uses the stored string directly as cwd. In HOSTED mode a tenant could create a session for a dir inside its allowed root, then replace that dir (or an ancestor) with a symlink to a host directory before opening the terminal — bypassing the allowlist.
Fix: re-validate the stored path against the allowlist at WebSocket connect time (before spawning the shell), and use filesystem isolation resistant to symlink/TOCTOU replacement (e.g. per-tenant chroot/container/bind-mount, or O_NOFOLLOW-style resolution).
Source: codex cross-family review on the #655 PR. Gate any hosted offering on both items.
Follow-up to #655 (which closed the core authenticated cross-tenant RCE: workspace path allowlist + per-user binding + WebSocket session ownership). Two further hardening items were identified by cross-family review but are broader than #655's scope and gate a HOSTED launch only (not deployed yet).
1. Scope session REST endpoints by owner
POST /api/v2/sessionsnow persistsuser_id, and the terminal/chat WebSockets enforce ownership. But the REST session endpoints still query without an owner filter, so in an authenticated multi-user deployment one tenant can enumerate/read/modify/end another tenant's sessions:GET /api/v2/sessions(list)GET /api/v2/sessions/{id}DELETE /api/v2/sessions/{id}POST /api/v2/sessions/{id}/messagesGET /api/v2/sessions/{id}/messagesFix: thread the authenticated principal through these handlers and scope every
InteractiveSessionRepositoryoperation byuser_id(skip only in no-auth mode).2. TOCTOU symlink revalidation + filesystem isolation
create_sessionvalidates and stores the resolvedworkspace_path, butterminal_wslater uses the stored string directly ascwd. In HOSTED mode a tenant could create a session for a dir inside its allowed root, then replace that dir (or an ancestor) with a symlink to a host directory before opening the terminal — bypassing the allowlist.Fix: re-validate the stored path against the allowlist at WebSocket connect time (before spawning the shell), and use filesystem isolation resistant to symlink/TOCTOU replacement (e.g. per-tenant chroot/container/bind-mount, or
O_NOFOLLOW-style resolution).Source: codex cross-family review on the #655 PR. Gate any hosted offering on both items.