Skip to content

feat(terminal): host wraps the session key per link guest - #98

Merged
kipavy merged 1 commit into
mainfrom
feat/link-session-key-wrapping
Oct 7, 2026
Merged

kipavy merged 1 commit into
mainfrom
feat/link-session-key-wrapping

Conversation

@kipavy

@kipavy kipavy commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

Closes #96.

Invite-link sessions stored the raw 32-byte session key and my-key handed it back as raw_key to anyone holding the link token or a redeemed short code. The link now grants admission only. The host's client wraps the key for each admitted guest through POST /invitees, the path People invites already use.

Flow

  1. Guest calls my-key with a valid grant and has no wrapped key yet: 202 {"pending": true}.
  2. Guest is admitted on the WebSocket through a join grant. The server records the guest in terminal_session_link_guests, in Postgres rather than in process (CLAUDE.md), and sends the host's socket {"type":"key_request","user_id":…}.
  3. Host POSTs the wrapped key to /invitees. grant_invitee sees a live link grant, stores the key and sends that guest's socket {"type":"key_ready"}. It skips the stranger checks, the knock limit, the invitee row and the push, because the guest asked to join. An invitee row would also keep admitting them after their grant was revoked.
  4. Guest fetches my-key again and gets wrapped_key + host_public_key.

Users without a live link grant go through /invitees exactly as before.

Compatibility

  • Sessions whose host still sends session_key_bytes keep getting raw_key for one release. That case used to 500 when no bytes were stored.
  • An old guest on a new host's session gets the 202 and fails on the client side, as the issue accepts.
  • Ending a session (end_session, host disconnect, failed create) clears session_key_bytes and the link-guest rows. Migration 054 clears the key on sessions that already ended.
  • Next release: drop the column and the raw_key branch.

Note for the client (VoltiusApp/voltius#521)

The frames are key_request / key_ready, matching control_update, session_ended and the others, not the hyphenated names in the issues.

Frames for a single socket travel over a new per-user channel in TerminalManager. That struct is already on the single-instance list, so this adds no item to it.

Tests

  • my-key returns pending, not raw_key, for a new-style link session (with 202 status)
  • /invitees for a link-grant holder creates the key row and the guest then gets wrapped_key, even with stranger invites switched off, and no invitee row is written
  • /invitees for a teammate with no grant, or whose grant was revoked, still creates an invitee row as before
  • Ending a session by either path clears the stored key; the 054 backfill clears ended sessions and leaves live ones alone
  • The legacy raw_key tests still pass
  • End-to-end WebSocket test: the guest joins with a token, the host gets key_request, the host wraps, and the guest gets key_ready; the guest never sees the request

cargo clippy --all-targets -- -D warnings and cargo test --all-targets (678) pass locally against Postgres.

🤖 Generated with Claude Code

Invite-link sessions stored the raw session key and handed it to anyone
holding the link or a redeemed short code, so the database or the server
process could decrypt every link-shared terminal. The link now grants
admission only; the host's client wraps the key for each admitted guest,
through the same /invitees path People invites use.

- WS admission through a join grant records the guest in
  terminal_session_link_guests and sends the host's socket
  {"type":"key_request","user_id":...}.
- grant_invitee, for a user whose admitting grant is still live, stores
  the wrapped key and sends that guest's socket {"type":"key_ready"}. No
  stranger checks, no invitee row and no push: the guest asked to join,
  and an invitee row would outlive a revoked grant.
- my-key answers 202 {"pending": true} to a grant holder with no wrapped
  key yet, instead of raw_key. Sessions whose host still sends
  session_key_bytes keep getting raw_key for one release.
- Ending a session (end_session, host disconnect, failed create) clears
  session_key_bytes and the link-guest rows; migration 054 clears the key
  on sessions that already ended.

Frames are key_request/key_ready, matching the other socket frames,
rather than the hyphenated names in the issue.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kipavy
kipavy merged commit 9208738 into main Oct 7, 2026
1 check passed
@kipavy
kipavy deleted the feat/link-session-key-wrapping branch October 7, 2026 09:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Invite-link terminal sessions: stop storing the session key, host wraps it per guest

1 participant