feat(trust): auto-clear wedged Olm sessions on olm_wedging_index increment #9
Labels
No labels
agent:felinus
agent:human
agent:maximus
priority:p0
priority:p1
priority:p2
priority:p3
status:blocked
status:needs-review
status:wip
type:chore
type:docs
type:feat
type:fix
type:perf
type:refactor
type:test
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
jmz/matrix-bridge#9
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Context
Today's UTD asymmetry between rpi5 (
@claudius-maximus) and Tabby's Mac (@claudius-felinus) was eventually root-caused to a wedged Olm session sitting under what looked like a complete server-side trust chain.After PR #3 (sync stability + auto-key-forward), PR #4 (bootstrap-cross-signing CLI), PR #7 (user-sign other users' master keys), and PR #8 (
pin()beforeverify()sopinned_master_keytracks current state), all server-side trust state was correct:e2e_cross_signing_signaturesadmin dump)pinned_master_key.signaturespost-PR-#8 contained the user-signing keyshared_with_setcorrectly listed the peer device withSharedstatusBut
inbound_group_sessioncount on the recipient never incremented —to-devicem.room_keyshares were silent-dropped.The smoking gun: peer device record had
olm_wedging_index: 1while the outbound Megolm session'sshared_with_setrecordedolm_wedging_index: 0. The Olm channel had been flagged as wedged after the recipient set was selected. matrix-sdk-crypto bumped the wedging index but left the wedged session row in place, so subsequent encrypts continued routing through it.Manual unblock (with explicit authorization):
After respawn →
/keys/claimfor fresh OTK → fresh Olm session → fresh Megolm with rebuiltshared_with_set→m.room_keydelivered → recipientinbound_group_sessioncount went22 → 25(3 messages caught up).Proposed change
Add a small
apply_trust_policy-side (or session-manager-side) check: when iterating devices, ifdevice.olm_wedging_index > current_olm_session.expected_wedging_index, drop the existingsessionrow(s) for that device'ssender_keyso the next encrypt forces a fresh/keys/claim+ Olm establishment.Pseudo-Rust in
trust.rs:Why this matters
apply_trust_policywhich is called from bootstrap + periodic-trust-refresh, so this gets invoked on every bridge startup (acceptable cadence).Open questions
inbound_group_sessionrows for that sender_key, or only the Olmsessionrows? Probably only Olm — Megolm history is precious.outbound_group_sessionproactively (forces re-share to all recipients) or wait for natural rotation (next message afterrotation_periodorrotation_period_msgs).Verification recipe
device.olm_wedging_index > 0against a known sender_key with no recent freshcreation_time.apply_trust_policy, observe the warn-level log + zero rows insessionfor that sender_key./keys/claimHTTP call (via mitmproxy or bridge debug log), confirm new Olm session row, confirmm.room_keydelivered to recipient (inbound_group_sessioncount delta).Related
!qmEZYWbxkxTUFqNGQC:matrix.ziefle.org(2026-05-02 ~17:20Z–17:59Z)Investigation update (2026-05-28 ~02:30 CET)
Started on this tonight, expecting to implement the
apply_trust_policy-side check from the issue body. Found that the situation is more nuanced than the issue described, so writing it up here for whoever picks this up next:What matrix-sdk-crypto already does (0.14.0, unchanged through 0.17.0)
The full wedge-recovery loop is implemented inside the library:
OlmError::SessionWedgedpropagated up.machine.rs:1543catches it → callssession_manager.mark_device_as_wedged(sender, curve_key).users_for_key_claim→ next outgoing-request cycle does/keys/claim→ fresh Olm session.check_if_unwedged()sends anm.dummyto-device event via the fresh session.device.olm_wedging_index(olm/account.rs:1393).ShareState::Shared { message_index, olm_wedging_index }, seeolm_wedging_index < d.olm_wedging_index, and re-share the room_key (session_manager/group_sessions/mod.rs:751-758).So in steady state the symptom this issue describes — wedge → no recovery — shouldn't happen. The library handles it.
The actual gap that bit us on 2026-05-02
session_manager/sessions.rs:80,118:If the wedged session was created less than 1 hour ago,
mark_device_as_wedgedis a no-op — recovery is BLOCKED for up to an hour. Almost certainly what bit us: rapid trust-policy churn during the 2026-05-02 debugging session put us inside that 1-hour window, and the library refused to unwedge until the interval elapsed.This is a deliberate throttle (avoids hammering
/keys/claimfor transient network blips) — sensible for normal Element clients, painful for bridges where wedges are rare + observability is poor.Why the issue body's pseudo-Rust can't be implemented as-is
The proposed
apply_trust_policy-side fix assumesdevice.olm_wedging_indexand a way to delete sessions for asender_keyare accessible. Both are out of reach from outside the crate:DeviceData.olm_wedging_indexispub(crate)(identities/device.rs:103) — not readable fromapply_trust_policy's perspective.SessionManager::mark_device_as_wedgedispubbutSessionManageritself isn't reached viaClient::encryption()— effectively crate-private to downstream users.delete_sessions(&curve25519)on any public API.Bumping doesn't help
Checked the source of
matrix-sdk-crypto-0.17.0against our pinned=0.14.0:UNWEDGING_INTERVALmark_device_as_wedgedvisibilityolm_wedging_indexvisibilitypub(crate)pub(crate)(unchanged)Changelog entries for 0.15 / 0.16 / 0.16.1 / 0.17 don't mention wedge handling.
Where this leaves us
The structural fix lives upstream —
matrix-org/matrix-rust-sdkneeds to either exposeforce_unwedge_device()(public method onClient::encryption()orOlmMachine) OR makeUNWEDGING_INTERVALconfigurable per-client.Until that lands, operators have two paths when wedge-within-1-hour hits:
sessionrow for the wedgedsender_key. Risky — bypasses matrix-sdk's in-memory caches; subsequent encrypts may produce inconsistent state until the next process restart. Use only when (1) isn't acceptable + operator can immediately restart the bridge afterward.Leaving this issue open
Not closing — the upstream PR is still wanted long-term. When someone has bandwidth to draft the upstream patch:
matrix-org/matrix-rust-sdkasking for either approach (public force-unwedge OR configurable interval). Reference this issue for the bridge-side motivation.apply_trust_policy(per the issue body's original design, but using the new public API).Re-titling this issue would be reasonable — something like
feat(trust): auto-clear wedged Olm sessions inside the matrix-sdk UNWEDGING_INTERVAL window— makes the actual ask clearer.