Secure shared-session access and harden P2P UX - #5
Conversation
Sharing now generates a secret code (only its hash is stored). The owner can join their own session from any device without a code, but any other viewer must present the matching code to get a viewer ticket, and non-owner attempts are rate limited per user and session to stop guessing. Unsharing clears the code and revokes outstanding access. The host prints the code and join command on share; `wrapper attach` gains a `--code` option.
P2P now defaults on so relay sessions get a direct low-latency data channel automatically, with the relay as the fallback. Set WRAPPER_P2P=0 (or false/off) to force the relay path. Documented the peer-IP exposure tradeoff in the env example.
… P2P default Explain the capability-based access model (owner plus share code), the shared behaviors (shared control, multi-viewer, consensus resize, single host), the --code join flow, and the on-by-default P2P transport across the READMEs and the docs site. No em dashes.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
| await backend.client | ||
| .mutation(sessionHeartbeatRef, { sessionId, shared: false, port: server.port }) | ||
| .catch(() => {}); | ||
| await backend.client.mutation(setShareCodeRef, { sessionId }).catch(() => {}); |
There was a problem hiding this comment.
Relay failure leaves orphan share hash
Medium Severity
When setShareCode succeeds but relay setup then fails (for reasons other than a Pro plan requirement), the backend's share code isn't cleared. This leaves the session in an inconsistent state: the host sees a local-only share and doesn't receive the code, but the backend believes it's shared. Recovery requires a manual unshare/share cycle.
Reviewed by Cursor Bugbot for commit afb203c. Configure here.
| } else { | ||
| for (const line of lines) log.info(line); | ||
| } | ||
| } |
There was a problem hiding this comment.
Heartbeat clears shared after setShareCode
High Severity
During relay share, setShareCode marks the session shared in Convex while the host’s local shared flag stays false until commitShared() runs. The periodic heartbeat still sends shared: false, which overwrites backend shared but leaves shareCodeHash set, so non-owners with a valid --code are rejected until the next heartbeat restores shared: true.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit afb203c. Configure here.
A shell-host minted one Convex JWT at startup and reused it forever, so sessions that outlived the token started failing every heartbeat, share, and close call with an expired-token error. The host now re-mints the JWT from the stored session token before it expires (and reactively on an auth-expiry error), and stops the refresh on shutdown.
The loopback WS server upgraded any connection with no auth, so on a shared machine another local user could attach to a shell by reaching 127.0.0.1. The host now generates a 256-bit token, stores it only in the 0600 registry, and requires it as ?token= on every local connection. attach reads it from the registry, and it is redacted from logs. The token is optional so headless tests are unaffected.
Keep role, session, and active transport visible in the terminal title without reserving a row that would break full-screen apps. Reveal context controls when the prefix is armed, repaint after shell title changes, and make P2P failures switch back to the relay promptly while preserving a live direct channel if the signaling socket closes.
Document the discoverable controls, transport state and hardened fallback path, add detailed non-rendered image TODOs where visual assets will help, and fix the docs dark-theme contrast so the accessibility check passes.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
There are 3 total unresolved issues (including 2 from previous reviews).
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit f4dab33. Configure here.
| .catch(() => {}); | ||
| // Clears both `shared` and the stored code hash, revoking any | ||
| // outstanding viewer access immediately. | ||
| void backend.client.mutation(setShareCodeRef, { sessionId }).catch(() => {}); |
There was a problem hiding this comment.
Unshare mutation not awaited
Medium Severity
Unshare clears local shared and stops the relay bridge but fires setShareCodeRef with void and swallows errors. Backend sharing and the code hash can stay active until a later heartbeat or not at all if the mutation fails.
Reviewed by Cursor Bugbot for commit f4dab33. Configure here.


Summary
Ctrl+\\is armed without reserving a row that would break full-screen apps.Security model
Test plan
bun run format:checkbun run check-typesbun run lintbunx turbo run test(76 tests, including real PTY e2e outside sandbox)mint validatemint broken-linksmint a11yrelaytop2p, then forceWRAPPER_P2P=0and confirm relay-only mode.Note
High Risk
Changes authentication, authorization, and default transport behavior for shared sessions; incorrect gating or P2P fallback could block joins or expose loopback attach without the token.
Overview
This PR tightens who can join a shared session and improves how relay sessions feel in the terminal.
Access control: Sharing now generates a human-readable share code; the backend stores only a hash via
session:setShareCode. Owners still join relay sessions without a code; everyone else must usewrapper attach --relay --id … --code …. Non-owner ticket requests are rate limited, and unshare clears the hash to revoke access. Local attach requires a per-session?token=on the loopback WebSocket (token lives in the owner’s registry). Long-running hosts auto-refresh Convex JWTs so heartbeats and share calls do not die after token expiry.Transport: WebRTC P2P is on by default (
WRAPPER_P2P=0opts out). Negotiation queues early ICE candidates, tolerates transient disconnects, falls back to relay when P2P fails, and can keep a session on P2P if signaling drops while the data channel stays up.UX: A terminal-title HUD shows role, short session id, and transport (
local/relay/p2p/ …), with armedCtrl+\controls—no reserved screen row. Docs and Mintlify pages are updated accordingly (plus image TODOs).Reviewed by Cursor Bugbot for commit f4dab33. Bugbot is set up for automated code reviews on this repo. Configure here.