xmtp_mls: route Sender::NewMemberCommit through from_external_commit validator#3668
xmtp_mls: route Sender::NewMemberCommit through from_external_commit validator#3668tylerhawkes wants to merge 1 commit into
Conversation
|
Claude finished @tylerhawkes's task —— View job Code Review — 2026-05-27 18:35 UTCSummaryThis PR successfully implements the dispatch logic for routing Issues Found1. Unused variable The Suggestion: If this is genuinely unused in the current implementation, consider adding a comment explaining why it's sourced but not yet consumed, or removing it entirely until needed. Location: 2. Inconsistent error handling pattern between dispatch arms The Both the migrated and non-migrated paths in the Concern: This could result in double cursor updates if metadata extraction succeeds but Suggestion: Consider refactoring the Location: 3. Missing test coverage (acknowledged in PR description) The PR description notes that end-to-end integration tests are deferred to T-1. Codecov reports 10% patch coverage with 9 lines missing coverage in this file. The critical dispatch logic at lines 1117-1221 appears to be entirely untested in this PR. Recommendation: Before merging the T-1 integration PR, ensure coverage includes:
Positive Observations
Tasks
|
| return Err(CommitValidationError::ExternalCommitAppDataUpdateOutOfScope); | ||
| } | ||
|
|
||
| for mutation in &delta.mutations { |
There was a problem hiding this comment.
🟠 High groups/validated_commit.rs:2692
enforce_app_data_update_scope permits TlsMapMutation::Delete mutations when they target the joiner's own inbox id, but a delete-only delta leaves the joiner in the MLS group with no GROUP_MEMBERSHIP entry — violating the invariant that external joins must register the joiner. Since resync external commits are unsupported (ResyncExternalCommitNotSupported), the joiner has no pre-existing entry to delete or update; only Insert should be allowed.
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file crates/xmtp_mls/src/groups/validated_commit.rs around line 2692:
`enforce_app_data_update_scope` permits `TlsMapMutation::Delete` mutations when they target the joiner's own inbox id, but a delete-only delta leaves the joiner in the MLS group with no `GROUP_MEMBERSHIP` entry — violating the invariant that external joins must register the joiner. Since resync external commits are unsupported (`ResyncExternalCommitNotSupported`), the joiner has no pre-existing entry to delete or update; only `Insert` should be allowed.
Evidence trail:
crates/xmtp_mls/src/groups/validated_commit.rs lines 2692-2701 (REVIEWED_COMMIT): Loop permits Delete/Update mutations as long as key matches joiner_id.
crates/xmtp_mls/src/groups/validated_commit.rs lines 948-1032 (REVIEWED_COMMIT): `from_external_commit` has no downstream check that delta contains an Insert.
crates/xmtp_mls/src/groups/validated_commit.rs line 2582 (REVIEWED_COMMIT): ResyncExternalCommitNotSupported confirms joiner has no pre-existing entry.
crates/xmtp_mls/src/groups/validated_commit.rs lines 2641-2642 (REVIEWED_COMMIT): Docstring states intent is only Insert.
crates/xmtp_mls_common/src/tls_map.rs line 386 (REVIEWED_COMMIT): TlsMapMutation enum with Insert/Update/Delete variants.
| /// group to be migrated to AppData. The component's | ||
| /// `permissions.update_policy` (super-admin-only by default) gates | ||
| /// who can flip the bits. | ||
| pub async fn set_external_commit_policy( |
There was a problem hiding this comment.
🟠 High groups/mod.rs:2015
set_external_commit_policy allows external commit policies to be set on DM conversations, bypassing the restriction that other metadata update methods enforce. This could enable third parties to join a two-person DM via external commits. Consider adding the DM check that other methods use: if self.metadata().await?.conversation_type == ConversationType::Dm { return Err(MetadataPermissionsError::DmGroupMetadataForbidden.into()); }
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file crates/xmtp_mls/src/groups/mod.rs around line 2015:
`set_external_commit_policy` allows external commit policies to be set on DM conversations, bypassing the restriction that other metadata update methods enforce. This could enable third parties to join a two-person DM via external commits. Consider adding the DM check that other methods use: `if self.metadata().await?.conversation_type == ConversationType::Dm { return Err(MetadataPermissionsError::DmGroupMetadataForbidden.into()); }`
Evidence trail:
crates/xmtp_mls/src/groups/mod.rs lines 2015-2046 (set_external_commit_policy missing DM check), lines 1987-2004 (update_commit_log_signer with DM check at 1993-1995), lines 2051-2060 (set_allow_external_commit wrapper). git_grep for DmGroupMetadataForbidden shows the check at lines 1901, 1927, 1994, 2086, 2144, 2184, 2482 — all other metadata update paths.
ApprovabilityVerdict: Needs human review 2 blocking correctness issues found. This PR enables a new external commit/QR-invite join code path with significant runtime behavior changes. Two unresolved high-severity findings identify potential security issues: improper mutation handling in external commit validation and missing DM policy checks. These warrant human review. You can customize Macroscope's approvability policy. Learn more. |
84d77cd to
717fbcc
Compare
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## tyler/qr-invite-l7-validator #3668 +/- ##
===============================================================
Coverage ? 83.91%
===============================================================
Files ? 401
Lines ? 59586
Branches ? 0
===============================================================
Hits ? 50002
Misses ? 9584
Partials ? 0 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
e6b79bc to
684fa1b
Compare
684fa1b to
be09e92
Compare
f29a7c1 to
6d0ec42
Compare
Summary
Adds the dispatch arm in
mls_sync.rsthat routes incoming commits withSender::NewMemberCommitto L-7'sValidatedCommit::from_external_commitvalidator (the QR-invite flow). The existing member-commit path remains intact forSender::Member(idx).Dispatch flow:
processed_message.sender().Sender::Member(idx)— existingfrom_staged_commitpath.Sender::NewMemberCommit— sourceexternal_commit_allowedvia L-6'sis_external_commit_allowed(mls_group)helper. Sourceimmutable_metadata+mutable_metadatavia the capability-aware path (registry-aware for migrated groups). Callfrom_external_commit(..., external_commit_allowed). Skip committer-leaf-index extraction (joiner not in tree yet from existing members' POV). Converge tomerge_staged_commit_logged.NewMemberProposal,External) — explicit rejection withCommitValidationError::ActorNotMember, traced.Post-merge new-installation registration happens through the existing handler — the joiner's other installations (from the atomic external commit's by-value
Addproposals) get registered through the standard pipeline.Depends on #3667 (L-7 validator).
Test plan
cargo check -p xmtp_mls— clean.🤖 Generated with Claude Code
Note
Route
Sender::NewMemberCommitthrough external commit validation in MLS message processorStagedCommitMessagehandler in mls_sync.rs forSender::NewMemberCommitthat validates atomic external/join commits usingValidatedCommit::from_external_commit, with capability-aware policy and metadata resolution.ProtocolMessagetype acceptance to includePublicMessageframes (e.g., external commits) in addition toPrivateMessage; other frame types returnUnsupportedMessageType.extract_message_senderto handleSender::NewMemberCommitby deriving inbox id and installation id from the staged commit's update-path leaf credential.CommitValidationError::ActorNotMemberand advances the processing cursor before returning non-retryable errors on the new path.Macroscope summarized be09e92.