mx.client 0.2.0
Security
- HIGH: homeserver-supplied keys are now verified
before use.
mx_crypto_known_devices() checks each device’s
Ed25519 self-signature with
mx.crypto::mxc_verify_device_keys(), and
mx_crypto_claim_otks() checks each claimed one-time key
with mx.crypto::mxc_verify_one_time_key() against that
verified Ed25519. Both previously read the keys straight out of the
response, so a malicious or compromised homeserver could substitute its
own Curve25519 key for any device and read everything sent to that user.
A device that fails verification is dropped with a warning and the
remaining devices still receive the message.
- HIGH: Olm to-device payloads now carry the
sender, recipient,
recipient_keys, and keys fields the spec
requires. mx_crypto_room_key_payload() emitted only
type and content, leaving the receiver nothing
to authenticate against; other clients reject such payloads.
- HIGH:
mx_crypto_process_sync() and
mx_crypto_handle_to_device() now confirm a decrypted Olm
payload names this device as recipient before acting on it. Decrypting
only proves the message was encrypted to our key, not that it was meant
for us here, so a server could previously replay a captured
payload.
mx_crypto_process_sync() records the sender the Olm
payload claimed when it shared each Megolm session, and drops any
decrypted event whose cleartext envelope disagrees with that claim: the
server was previously free to set sender to anything.
mx_crypto_process_sync() and
mx_crypto_handle_to_device() gain a devices
argument taking the verified list from
mx_crypto_known_devices(). A decrypted event reports
sender_verified = TRUE only when the payload’s claimed
(sender, ed25519, curve25519) matches one of those devices
as a triple. Agreement between the payload and the envelope is not
enough on its own: a hostile homeserver writes both, so it can make them
corroborate each other. Binding to a self-signed
device_keys object is the part it cannot forge. Callers
that pass no devices still decrypt but always get
sender_verified = FALSE.
Breaking changes
mx_crypto_room_key_payload() requires four further
arguments: sender_user_id, sender_ed25519,
recipient_user_id, and recipient_ed25519. They
are what the payload’s authentication block is built from, so they have
no defaults; a five-argument call now errors rather than silently
emitting an unauthenticatable payload.
mx_crypto_encrypt_for_devices() gains
sender_user_id and now requires each recipient to carry a
verified ed25519. Recipients from
mx_crypto_known_devices() already do.
mx_crypto_handle_to_device() gains
self_id, self_ed25519, and
devices; its result carries sender_bound.
New
mx_table_html() and mx_send_table() render
a data frame, matrix, or list as the conservative Matrix table HTML that
clients such as FluffyChat 2.6.0+ accept: a bare
<table> of <tr>,
<th>, and <td> nodes, with no CSS,
colspan, rowspan, or custom attributes. A plain-text body
is generated alongside for clients that ignore
formatted_body.
mx_markdown_to_html() converts GitHub-style pipe tables
to the same table HTML, honouring the
:---/:---:/---: alignment
row.
mx_set_displayname(client, name): client-level wrapper
over mx.api::mx_set_displayname() that builds the session
from the client config and retries once through
mx_with_relogin() on a rejected token, so long-running bots
can rename themselves without hand-rolling session or relogin
plumbing.
print() method for mx_client_config masks
token and password as
<hidden> (or <unset> when empty)
instead of falling through to the default list print, which showed
credentials verbatim. sync_token and every other field stay
visible; unclass() still exposes the raw values.
Other
mx_extract_text_events() keeps the event’s
origin_server_ts as a ts field (milliseconds
since the epoch, NULL when the server omits it). Records previously
carried no time at all, leaving consumers to stamp messages with their
own poll clock.
- Session stores written before this release still load: a bare
megolm_in pickle is read as a session with no attested
sender rather than discarded, so a running client keeps its history.
Events decrypted with such a session report
sender_verified = FALSE.
Suggests: mx.crypto (>= 0.2.0), the first version
providing the verification helpers.
- New
inst/skills/mx.client/matrix-messaging/SKILL.md.
- New
inst/tinytest/test_transport.R covers the
key-verification paths against tampered fixture responses.
R/transport.R previously had no test file at all.
mx.client 0.1.1
- First release. Stateful client layer over ‘mx.api’: configuration
persistence
(
mx_client_load()/mx_client_save()/mx_client_configure()),
session construction (mx_client_session()), room resolution
(mx_resolve_room(), mx_room_lookup_by_name()),
sync cursor handling (mx_sync_update()), sync-event
extraction (mx_extract_text_events(),
mx_extract_invites(),
mx_extract_reaction_verdict()), invite acceptance
(mx_accept_invites()), text sending
(mx_send_text(), with a mentions argument that
sets m.mentions and rewrites textual
@localpart into matrix.to pills via
mx_pill_mentions()), and a conservative Markdown-to-HTML
converter (mx_markdown_to_html()).
- End-to-end encryption orchestration over the optional ‘mx.crypto’
package: a pickled crypto store and account lifecycle
(
mx_crypto_store_dir(), mx_crypto_account()),
signed device-key construction for upload
(mx_crypto_device_keys()), Olm room-key sharing as
to-device payloads (mx_crypto_room_key_payload(),
mx_crypto_handle_to_device(),
mx_crypto_inbound_session()), and Megolm event
encrypt/decrypt (mx_crypto_encrypt_event(),
mx_crypto_decrypt_event()). A loopback test exercises a
full encrypted round-trip in-process.
mx_room_encrypted() reads a room’s
m.room.encryption state (with the usual name/id/default
resolution), so callers can pick the encrypted or plaintext send path
before building one.
- Client-layer media sending:
mx_send_media() resolves
the room by name (or default), builds the session from the stored
config, and uploads + posts in one call via
mx.api::mx_send_media().
- Token-rotation recovery:
mx_client_relogin() refreshes
the access token with the stored password while preserving the device id
(so an E2EE device identity survives), and
mx_with_relogin() wraps any client operation with a
one-shot catch-and-retry on M_UNKNOWN_TOKEN, using mx.api’s
classed error conditions. Requires mx.api >= 0.3.0.
- All exported functions carry examples.