Skip to content

fix(matrix-communication): don't report verify success on MAC mismatch; fetch keys in --listen#48

Merged
CybotTM merged 2 commits into
mainfrom
fix/e2ee-verify-mac-failure-and-listen-keys
Jun 27, 2026
Merged

fix(matrix-communication): don't report verify success on MAC mismatch; fetch keys in --listen#48
CybotTM merged 2 commits into
mainfrom
fix/e2ee-verify-mac-failure-and-listen-keys

Conversation

@CybotTM

@CybotTM CybotTM commented Jun 27, 2026

Copy link
Copy Markdown
Member

Follow-up to #47 (already merged), addressing two issues found in review of that PR.

1. matrix-e2ee-verify.py reported verification success on a genuine MAC mismatch

nio's Sas.receive_mac_event never raises on a bad MAC — on a key mismatch (or invalid/unexpected MAC event) it silently sets the SAS state to SasState.canceled and returns (crypto/sas.py). The merged code caught only exceptions, so a genuine mismatch (a possible MITM) fell through to self.verified = True, sent m.key.verification.done, and printed ✅ VERIFICATION COMPLETE / 🎉 Device verified and keys synced!.

#47 correctly gated the trust-persisting verify_device() call on sas.verified, so a bad device was not trusted in the local store — but the script still told the user it succeeded, which is false assurance for a security-critical step.

This change distinguishes the benign state-machine race (sas.verified can be False even after a successful exchange) from a real failure using sas.verified_devices — a device id lands there only once its MAC has been cryptographically validated, and a later cancel does not clear it. On a real mismatch it now sends a cancellation (sas.get_cancellation()) and marks the attempt cancelled instead of claiming success.

2. --listen mode skipped the post-verification room-key fetch

The --listen branch returned immediately after verification, so it never ran the room-key fetch that the request path performs (the documented purpose: "complete verification and fetch room keys"). A user verifying via --listen saw Device verified! but had to run matrix-fetch-keys.py manually before encrypted history would decrypt.

The fetch is extracted into a shared fetch_room_keys() helper and now runs in both the request and --listen flows.

Notes

  • Verified the nio behavior against the installed matrix-nio source (crypto/sas.py: silent canceled on mismatch; verified_devices populated only on validated MAC; get_cancellation() available).
  • ruff check and ruff format --check pass; py_compile clean.

Test plan

  • matrix-e2ee-verify.py with matching emojis → VERIFICATION SUCCESSFUL, device trusted, keys fetched.
  • matrix-e2ee-verify.py with mismatched emojis (decline in Element) → VERIFICATION FAILED, no success message, device not trusted.
  • matrix-e2ee-verify.py --listen → after Element-initiated verification, room keys are fetched (not just Device verified!).

🤖 Generated with Claude Code

…h; fetch keys in --listen

Follow-up to #47.

- MAC handler: nio's receive_mac_event silently sets the SAS state to
  canceled on a key mismatch (it never raises), so the previous code sent
  m.key.verification.done and reported success even on a genuine mismatch
  (possible MITM). Distinguish the benign state-machine race from a real
  failure via sas.verified_devices; on a real mismatch, send a cancellation
  and mark the attempt cancelled instead of claiming success.
- --listen mode returned right after verification, skipping the
  post-verification room-key fetch that the request path performs. Extract
  the fetch into a shared fetch_room_keys() helper and run it in both flows.

Signed-off-by: Sebastian Mendel <info@sebastianmendel.de>
Copilot AI review requested due to automatic review settings June 27, 2026 06:21
@github-actions

Copy link
Copy Markdown
Contributor

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Scanned Files

None

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request refactors the Matrix device verification script to handle nio's SAS state-machine race conditions more robustly, ensuring genuine key mismatches trigger cancellation messages. It also extracts the post-verification room key fetching logic into a reusable fetch_room_keys function. The review feedback suggests two important improvements: filtering for encrypted rooms before slicing the room list to guarantee up to 10 encrypted rooms are checked, and wrapping the post-verification client.sync calls in a try-except block to prevent transient network errors from crashing the script after successful verification.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread skills/matrix-communication/scripts/matrix-e2ee-verify.py Outdated
Comment thread skills/matrix-communication/scripts/matrix-e2ee-verify.py

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

… and sync errors

Addresses review feedback on PR #48:
- Filter rooms to encrypted ones before applying the [:10] limit, so up to
  10 encrypted rooms are always checked instead of 10 arbitrary rooms that
  could all be unencrypted.
- Guard the post-fetch sync loop so a transient sync error cannot crash the
  script (and report failure) after verification has already succeeded.

Signed-off-by: Sebastian Mendel <info@sebastianmendel.de>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@CybotTM CybotTM merged commit 600fb7f into main Jun 27, 2026
17 of 18 checks passed
@CybotTM CybotTM deleted the fix/e2ee-verify-mac-failure-and-listen-keys branch June 27, 2026 06:44
@sonarqubecloud

Copy link
Copy Markdown

CybotTM added a commit that referenced this pull request Jun 27, 2026
)

From a cross-session retrospective (2026-06-27). Adds one learning to
the E2EE guide (`skills/matrix-communication/references/e2ee-guide.md`)
— references-only, no script behavior change.

## What changed

Two related items added to `references/e2ee-guide.md`:

1. **Gate device trust on the verification result.** Documents the
security rule that `client.verify_device(...)` must be gated on the
SAS/emoji outcome (`sas.verified` / `sas.verified_devices`) — an
unconditional `verify_device` trusts a device even on a MAC mismatch (a
MITM bypass). Captures the matrix-nio subtleties (`receive_mac_event`
never raises on a bad MAC; `sas.verified` can be `False` after a
successful exchange due to a state-machine race, so `verified_devices`
is the authoritative signal). This bypass was caught by the automated
commit security review and fixed in #48.

2. **matrix-nio API notes (0.25.2)** — maintainer notes verified against
the installed nio 0.25.2:
- `nio.__version__` does not exist (raises `AttributeError`) — use
`importlib.metadata.version("matrix-nio")`.
- `DeviceStore` is **not** importable from `nio.store` (`ImportError`);
the real path is `from nio.crypto import DeviceStore`.
- `device.id` is a read-only property aliasing `device_id` and does
**not** raise — a reviewer claim that it errors is incorrect.

All three nio facts were re-verified against matrix-nio 0.25.2 in this
environment before documenting.

## Version

No version bump — references-only change. The `check-version-parity`
hook only triggers on `plugin.json` / `SKILL.md` / `composer.json`, none
of which were touched (hook skipped cleanly in pre-commit).

## Validation

`pre-commit run --files` on the changed file: trailing-whitespace,
end-of-file-fixer, markdownlint-cli2 all **Passed**; version-parity and
other hooks correctly skipped (no relevant files).
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.

2 participants