Skip to content

Verified meetings (experimental): passkey-based host identity#1

Open
predatorray wants to merge 4 commits into
mainfrom
feature/verified-host-meetings
Open

Verified meetings (experimental): passkey-based host identity#1
predatorray wants to merge 4 commits into
mainfrom
feature/verified-host-meetings

Conversation

@predatorray
Copy link
Copy Markdown
Owner

Summary

Adds an experimental, off-by-default "Verified meeting" feature: a host proves their identity to guests with a passkey (WebAuthn), so guests can refuse to join an impostor. This closes the squatting hole inherent to the deterministic host peer-id (rendezvous-<code>) on the public broker.

Trust chain a guest checks before sending any data:

  1. the host identity key hashes to the fingerprint pinned in the invite URL,
  2. a WebAuthn assertion vouches for an ephemeral session key (one biometric prompt per meeting), and
  3. the session key signed this guest's fresh nonce.

Both sides can open a Host identity dialog to compare the SSH-style fingerprint out-of-band (catches a tampered link). See docs/verified-meetings.md for the full protocol, threat model, and limitations.

What's included

  • Verified-meeting crypto (src/crypto), passkey identity + handshake (src/peer/hostIdentity.ts, src/peer/verification.ts), wire messages, and the experimental toggle.
  • Re-hosting: the invite link is the guest link, so the waiting room offers "Host this meeting" — a passkey holder can host a meeting they created ahead of time or left.
  • Waiting room + auto-join for all meetings: opening a meeting before the host arrives shows "Waiting for the host" and auto-joins once a host is live (ordinary meetings no longer dead-end on "Couldn't join").
  • Out-of-band fingerprint comparison UI; i18n across all 5 locales.

Limitations (documented)

  • Provides host authentication, not yet a fully app-layer-authenticated channel — an active relay MITM is a deliberate follow-up.
  • Passkeys are pinned to the origin domain (WebAuthn RP id).
  • The public PeerJS broker doesn't release a departed host's peer id immediately, so immediate same-code re-hosting can briefly fail.

Testing

  • 132 unit tests (incl. full guest-side verification trust chain with real WebCrypto).
  • 15 e2e (Playwright + CDP virtual authenticator for passkeys), covering verified/ordinary re-hosting and all existing flows.
  • Typecheck + CI=true build green.

🤖 Generated with Claude Code

predatorray and others added 4 commits May 22, 2026 21:03
Let a host prove their identity to guests with a passkey, defeating the
peer-id squatting that's otherwise possible since the host's PeerJS id is
derived from the meeting code.

- Trust chain: invite URL pins SHA-256 of the host identity key; the passkey
  signs an ephemeral session key once per meeting (one biometric prompt); the
  session key signs each guest's fresh nonce.
- Off by default behind a "Verified meeting" experimental toggle; the
  non-verified path is unchanged.
- Guests get a waiting room + auto-join when the host isn't present yet.
- Host and guests can open a Host identity dialog to compare the SSH-style
  fingerprint out-of-band (catches a tampered invite link).
- One reusable passkey identity, synced across the host's devices.

Limitation (documented in docs/verified-meetings.md): provides host
authentication, not yet a fully app-layer-authenticated channel, so an active
relay MITM is out of scope for this experimental cut.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The shared invite link is the guest link (no host=1), so a verified host who
created a link ahead of time — or left and came back — was stranded in the
waiting room as a guest with no way to host.

- Waiting room now offers "Host this meeting"; claiming runs the passkey
  ceremony against the identity pinned in the URL and switches into hosting.
- Verified guests fall back to the waiting room (and retry) on a connect
  timeout, instead of hanging on "Joining…" when the broker holds a stale
  registration for a host that just left.
- Re-hosting retries the peer-id claim for a short grace window while the
  broker releases the previous registration.
- e2e: drive a real passkey via the CDP virtual authenticator and cover the
  create-link-ahead / host-it-later flow.
- README + docs updated.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Ordinary (non-verified) meetings dead-ended at "Meeting not found" when the
host had left, so a host couldn't re-host using the invite link — unlike
verified meetings. The unhosted error screen now offers a "Host this meeting"
button that remounts as host and claims the code (no passkey needed).

- e2e: a guest can host an ordinary meeting that has no host.
- README: note re-hosting from the invite link.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Ordinary (non-verified) meetings now match verified ones: a guest who opens a
meeting before the host is present sees "Waiting for the host" and auto-joins
once a host goes live, instead of dead-ending on "Couldn't join the meeting".

- MeetingClient: all guests (not just verified) route to the waiting room +
  retry on peer-unavailable / connect timeout; emit a `joined` event on connect.
- useMeeting: guests go live on joined/verified rather than when the broker
  connects, avoiding a flash of empty room before the waiting room.
- MeetingPage: the waiting room's "Host this meeting" now works for ordinary
  meetings too; dropped the dead error-screen button and unused i18n key.
- Tests: peer-unavailable now asserts waiting+retry, added an auto-join test,
  switched MeetingClient tests to fake timers; non-verified e2e expects the
  waiting room. README updated.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
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.

1 participant