Harden invitations and fix device invites - #28
Draft
adrastaea wants to merge 27 commits into
Draft
Conversation
- Add role assignment for team members in tests - Refactor admission link checks in connection logic - Improve error messages for missing admission links - Introduce new tests for invitation admission scenarios
…ecryption - Added tests for decrypting only new graph links during incremental sync. - Refactored invitation acceptance processing to improve validation logic. - Introduced new methods for managing authenticated team graphs. - Enhanced team graph decryption to reuse trusted plaintext and prevent tampering. - Updated store creation to support reusable machine results, ensuring state integrity. - Added comprehensive tests for machine result validation and rejection scenarios.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Secure invitation admission with transcript-bound proofs and authenticated graph validation
PR description
Summary
This PR hardens member and device invitation admission end to end. It replaces invitation proofs that were reusable independently of the claimed identity with a versioned, domain-separated protocol that binds the proof to the exact member/device claim and to a single connection handshake.
It also moves the invitation acceptance data behind authenticated encryption, validates the returned graph and the accepting peer before using team keys, requires exactly one effective admission for the signed identity, and delays
joineduntil session-key negotiation has authenticated the connection.Because those guarantees depend on trustworthy graph state, this PR also closes several underlying CRDX and team-state validation gaps: graph links retain authenticated sender metadata, claimed authors are matched to current member/server keys, incoming graphs are re-decrypted from authenticated ciphertext, invalid dispatches and merges are atomic, identity/key metadata must be globally unambiguous, and historical team keys are retained across synchronized rotations.
Why this is needed
The original device-linking failure came from a member-oriented admission check being applied to a first-use device claim. Fixing that check alone would restore the happy path, but it would leave the admission boundary vulnerable in several related ways:
ACCEPT_INVITATIONexposed the graph and team keyring before session encryption was established.ADMIT_*action consumed this invitation for this exact identity.joinedcould be emitted before session-key negotiation completed.This PR treats invitation admission as one security boundary instead of addressing only the device-claim crash.
What changed
1. Versioned, transcript-bound invitation proofs
packages/auth/src/invitation/types.ts:20-34,packages/auth/src/invitation/create.ts:21-37).packages/auth/src/invitation/types.ts:48-63,packages/auth/src/connection/Connection.ts:218-240).packages/auth/src/invitation/invitationProofPayload.ts:10-36,packages/auth/src/invitation/generateProof.ts:8-30).packages/auth/src/invitation/validate.ts:20-54,packages/auth/src/invitation/validate.ts:60-100).INVITE_MEMBERorINVITE_DEVICEgraph action and is enforced again forADMIT_MEMBERandADMIT_DEVICE(packages/auth/src/team/reducer.ts:159-170,packages/auth/src/team/validate.ts:346-382).2. Encrypted and authenticated invitation acceptance
REQUEST_IDENTITYnow carries the acceptor nonce, andACCEPT_INVITATIONcarries only versioned sender metadata plus an encrypted acceptance bundle (packages/auth/src/connection/message.ts:6-11,packages/auth/src/connection/message.ts:60-83).packages/auth/src/connection/invitationAcceptance.ts:28-60).packages/auth/src/connection/invitationAcceptance.ts:70-108,packages/auth/src/connection/invitationAcceptance.ts:128-191).packages/auth/src/connection/invitationAcceptance.ts:111-125).3. Exact effective-admission validation
packages/auth/src/connection/validateInvitationAcceptance.ts:43-60).packages/auth/src/connection/validateInvitationAcceptance.ts:62-109).packages/auth/src/connection/validateInvitationAcceptance.ts:113-141).packages/auth/src/connection/validateInvitationAcceptance.ts:143-173).packages/auth/src/connection/Connection.ts:331-384).4. Authenticated graph authorship and fail-closed identity state
senderPublicKey(packages/crdx/src/graph/decrypt.ts:19-41,packages/crdx/src/graph/types.ts:91-124).packages/auth/src/team/validate.ts:39-84).links(packages/auth/src/team/serialize.ts:29-42,packages/auth/src/team/Team.ts:225-236).packages/crdx/src/store/makeMachine.ts:12-28,packages/crdx/src/store/Store.ts:154-191,packages/crdx/src/validator/validators.ts:6-87).packages/auth/src/team/validate.ts:99-267).packages/auth/src/team/selectors/device.ts:7-37,packages/auth/src/team/selectors/member.ts:3-18,packages/auth/src/team/selectors/server.ts:4-18).packages/auth/src/team/transforms/addMember.ts:4-19,packages/auth/src/team/transforms/addDevice.ts:11-30).packages/auth/src/team/isAdminOnlyAction.ts:4-26).5. Connection lifecycle, key retention, and errors
joinedis emitted only after the invitation has been accepted and validated, the local team has been constructed, and session-key negotiation has succeeded (packages/auth/src/connection/Connection.ts:531-563).packages/crdx/src/store/Store.ts:102-105,packages/auth/src/team/Team.ts:977-980,packages/auth/src/team/decryptTeamGraph.ts:54-80).ENCRYPTION_FAILURE, and peer-reported failures emitremoteError(packages/auth/src/connection/Connection.ts:540-598).packages/auth/src/connection/errors.ts:36-43).6. Quiet sandbox migration
The Quiet sandbox now constructs the complete signed member claim, generates both handshake nonces, and carries the expected acceptor nonce through validation and admission (
demos/quiet-sandbox/src/auth/services/members/userService.ts:36-59,demos/quiet-sandbox/src/auth/services/invites/inviteService.ts:47-114).Security behavior: before and after
ADMIT_*for this invitation and identity, plus exact final stateCompatibility and rollout
This is intentionally a breaking invitation protocol change.
Legacy invitations are disabled and must be reissued(packages/auth/src/invitation/types.ts:20-34,packages/auth/src/invitation/validate.ts:20-30). Outstanding invitations must be revoked/reissued after deployment.packages/auth/src/team/reducer.ts:159-170).packages/auth/src/connection/message.ts:6-11,packages/auth/src/connection/message.ts:73-83). Invitation-capable peers should be upgraded together.generateProof,Team.validateInvitation,Team.admitMember, andTeam.admitDevicehave breaking source signatures because callers must supply the signed claim and handshake context (packages/auth/src/invitation/generateProof.ts:8-30,packages/auth/src/team/Team.ts:653-742).Linknow requiressenderPublicKey. Serialized encrypted links already carried it, but hand-built graph fixtures and direct consumers of the plaintext type must be updated (packages/crdx/src/graph/types.ts:91-124).packages/auth/src/invitation/validate.ts:32-43,packages/auth/src/connection/invitationAcceptance.ts:128-191).Test coverage
New and expanded coverage includes:
packages/auth/src/invitation/test/invitation.test.ts:15-119).packages/auth/src/connection/test/invitationAcceptance.test.ts:14-108).joined, failed session negotiation, and incomplete returned graphs (packages/auth/src/connection/test/invitationAdmission.test.ts:14-161).packages/auth/src/connection/test/validateInvitationAcceptance.test.ts:20-183).packages/auth/src/team/test/authorAuthentication.test.ts:7-48).packages/auth/src/team/test/invitationKind.test.ts:10-68).packages/auth/src/team/test/identityInvariants.test.ts:10-142).packages/auth/src/connection/test/concurrentAdmitMember.test.ts:21-94,packages/auth/src/connection/test/concurrentDeviceAdmission.test.ts:23-99,packages/auth/src/connection/test/concurrentMemberAdmission.test.ts:24-64).packages/crdx/src/graph/test/decrypt.test.ts:77-102,packages/crdx/src/store/test/createStore.test.ts:19-79,packages/crdx/src/validator/test/validate.test.ts:16-230).Verification performed
pnpm exec vitest run packages/auth packages/crdx --reporter=dotpnpm buildpnpm lintThe unit run passed when allowed to bind the sync-server test port. The sandboxed run's only failures were all seven sync-server cases failing with
EPERMwhile binding0.0.0.0:3100; the same command passed outside that restriction.Risks and follow-up
packages/auth/src/connection/test/concurrentAdmitMember.test.ts:31-94,packages/auth/src/connection/test/concurrentDeviceAdmission.test.ts:33-99).packages/auth/src/connection/invitationAcceptance.ts:38-60,packages/auth/src/team/Team.ts:977-980).Suggested review order
packages/auth/src/invitation/invitationProofPayload.ts:10-52,packages/auth/src/invitation/validate.ts:20-100.packages/auth/src/connection/invitationAcceptance.ts:28-191.packages/auth/src/connection/validateInvitationAcceptance.ts:43-183,packages/auth/src/connection/Connection.ts:262-384.packages/auth/src/team/validate.ts:39-382.packages/crdx/src/store/Store.ts:154-218,packages/crdx/src/store/makeMachine.ts:12-28,packages/auth/src/connection/test/validateInvitationAcceptance.test.ts:20-183.