pull/3277/merge
Matthew Hodgson 2 months ago committed by GitHub
commit 4d2f66ab76
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,225 @@
# MSC3277: Scheduled messages
## Introduction
It's sometimes useful to schedule messages so that recipients will read them
at a point in the future rather than immediately. Use cases include:
* Not wanting to disturb the recipient(s) out-of-hours
* Wanting to remind yourself or others of some information in future
* Releasing embargoed information at a given time.
This is a simplified proposal derived from Travis' [original draft](https://docs.google.com/document/d/1vFbQuCnCNURBRs4_zukxiKLW_p2Ka7Ogo42oahpXnz4),
which aims to incorporate all the feedback gathered there.
## Proposal
A new query parameter, `at`, is added to the [/send](https://matrix.org/docs/spec/client_server/r0.6.1#put-matrix-client-r0-rooms-roomid-send-eventtype-txnid)
and [PUT /state](https://matrix.org/docs/spec/client_server/r0.6.1#put-matrix-client-r0-rooms-roomid-state-eventtype-statekey)
endpoints to indicate when the origin server should aim to send the event
(milliseconds since the unix epoch). The message is added to the room DAG
when the sender creates the event, and is immediately relayed to the sender's
devices (as a remote echo) but not the other members in the room.
It is then effectively soft-failed until the scheduled `at` time (or as close
to it as the server can manage), when it is then re-authed and relayed as
normal to all room members (both local and remote, including the sender).
Scheduled events are sent out over over federation at this point rather than
creation time in order to minimise embargo leaks. If the event can't be
re-authed (e.g. due to the sender no longer being in the room, or no longer
has sufficient PL to send the event) then the event is soft-failed. As such,
the event effectively appears as if it had been received over federation
after a federation delay. This means all event types can be scheduled in
this manner, including state events. Normal event ID and redaction behaviour
applies.
The server must set the `origin_server_ts` of the message is set to be its
`at` time, given in practice this is the label used for clients to display
the intended delivery timestamp of a message. It must also set `scheduled:
true` on the event (not the contents) in order to let servers know to treat
it differently for history visibility purposes, and so the sender's clients
all know that it's scheduled. The recipients can then also see the message
is scheduled. (A future MSC may define how to suppress this field from
recipients if desired).
For rooms with non-shared history visibility, servers must special-case the
visibility of scheduled events based on the membership of the room at the
scheduled time, rather than based on its DAG position (which reflects the
creation time).
The sender's clients will want to track the unsent scheduled messages in its
various rooms, such that the sender can cancel them by redacting them, or
edit them (optionally redacting earlier versions <sup id="a1">[1]
(#f1)</sup>, providing private drafting). We propose using the same
mechanism as for tracking 'starred' (aka 'favourited' or 'flagged') messages,
to avoid sprouting two different APIs for almost identical functionality.
This is deferred to a future MSC.
## Encryption considerations
In an E2EE room, the sending client must distribute the megolm session used to
encrypt the scheduled message immediately to the devices currently in the
room. We then rely on the normal mechanisms used to share megolm keys with
new devices (i.e. keyshare requests; online key backup; MSC3061-style sharing
room keys for past messages) to avoid the messages being undecryptable by
future devices.
The megolm session must be a one-off used just for this scheduled message
(meaning that as it's shared for future participants in the room, it only exposes
the session used to encrypt that particular message rather than any surrounding
non-scheduled ones).
Sender's clients may optionally attempt to wake themselves up at the deadline
in order to re-share the megolm session to the current participants in the
room (and/or be available to respond to keyshare reqs), in order to reduce
the risk of UISIs.
MSC3061-style 'sharing room keys for past messages' will need to special-case
history visibility rules for scheduled messages. Therefore we need to know
which sessions are for scheduled messages, and include them when sharing keys
with new users/devices... but stop once the scheduled message has been
received. Therefore we add a `m.forward_until: timestamp` field to the
`m.room_key` to-device message to indicate the session's lifetime. The
timestamp should be that of the scheduled event; users who join the room
after that timestamp should not be able to read the message.
E2EE implementations must not discard 'unused' megolm sessions for scheduled
messages (i.e. megolm sessions with `m.forward_until` fields), given they may
be shared long in advance of the scheduled message.
## Possible issues
A risk is that E2EE key distribution mechanisms which rely on clients seeing
an undecryptable message (i.e. keyshare reqs) will not take effect until the
scheduled message is sent - by which point clients which could service the
keyshare reqs will be more likely to be absent due to the passage of time.
One mitigation to this could be to relax the rules on who can service the
keyshare request and let any device in the room share to the newcomer if they
believe the newcomer should be entitled to decrypt the message. This
introduces the attack that a malicious server admin could add a fake
user/device to their local copy of a room, and could use this to exfiltrate
keys from bystanders - but this may be an acceptable tradeoff to improve
the chances of the message being decryptable in the distant future.
Alternatively, MLS may solve this(??)
Another problem is that because we share E2EE to users at msg authoring time, if
users subsequently leave the room, they will still have the keys to read the
scheduled message when it's revealed. This is probably inevitable. Clients could
try to solve it by redacting the scheduled message and resending it with a new megolm
session whenever they spot that users (or devices?) have left the room.
## Example
```
PUT /_matrix/client/r0/rooms/!wherever:example.com/send/m.room.message/123?at=1636197454551
{
"body": "a note to my future self",
"msgtype": "m.text"
}
```
...would result in the origin server sending the following event to room
recipients at Sat Nov 6 10:17:34 2021 UTC (assuming the event still passed
auth checks):
```json
{
"content": {
"body": "a note to my future self",
"msgtype": "m.text"
},
"origin_server_ts": 1636197454551,
"sender": "@matthew:matrix.org",
"type": "m.room.message",
"unsigned": {
"age": 391
},
"event_id": "$FC9MwqLPBzRl1AzLi-qOJbdYeMJFzugORlF6yPJkyII",
"room_id": "!xYvNcQPhnkzdUmYczI:matrix.org"
}
```
N.B. there's nothing interesting in this event other than its
`origin_server_ts` being set to the `at` parameter, and obviously being
sent out at the scheduled time.
## Alternatives
The origin server could queue the message with a fake event ID and only
attempt to add it to the DAG when it's time to send. This two-phase commit
complicates the implementation significantly - although it does provide a
slightly simpler solution to the history visibility problem and the problem
of needing to be in a room to cancel your scheduled messages.
Alternatively, the scheduled messages could queue on the receiving client
(thus letting users choose to see them at their leisure). However, this
kills the "embargo" use case, and complicates the implementation as receiving
clients have to be able to filter for scheduled events and generally shifts
complexity to clients, which is always undesirable as there way more clients
than servers and we want clients to be as simple as possible.
Separately, there's a trade-off between whether you send the queued message
out over federation at the time it's created or at the scheduled time. If
you send at create-time, it means that the origin server doesn't need to
still be running at the scheduled time. On the other hand, it means that you
are much more likely to break embargos, as one-person homeservers will get
the message ahead of schedule. We've gone for the more privacy preserving
option (send over federation at the scheduled time). Plus it acts as an
incentive for people to keep the origin server running ;P. <sup id="a2">[2]
(#f2)</sup>
Another alternative would be to use an API shape where you put a field on the
event contents (e.g. `m.pts` for presentation timestamp) to tell servers and
clients alike that a given message is scheduled. However, this ends up with
two timestamps on the event (confusing), complicates E2EE further
(don't encrypt the `m.pts`), and makes it harder to hide from recipients
whether a message was scheduled. So we've stuck with `?at=`.
We could avoid all the E2EE and history visibility problems if the sending
user simply queued the message in their clients until it was ready to send.
However, this of course means they have to keep an always on client somewhere
to send the message at the right time, which feels fragile (or encourages a
longlived serverside client/bot to be run, which could put E2EE at risk).
## Security considerations
This MSC allows for spammers to potentially pre-plan their attacks for
off-peak hours when the moderators may not be present. It also potentially
allows a user to stack up a large number of pending events which all get sent
throughout the day as a disruption attempt. Servers should impose harsh
limits to the number of events a user may schedule, such as a maximum of N
pending events per room (where N < ~5), to reduce the potential for abuse
while still maintaining an amount of usability for common scenarios.
Users could also try to schedule many events at once or schedule such that
they all get sent at once - servers should apply rate limiting on both the
scheduling and sending sides to limit the users ability to spam a room.
There's a risk that an attacker could DoS a user with fake megolm sessions
for scheduled messages which never arrive. This could be mitigated in the
receiving client by enforcing similar limits to those on the server (e.g.
if you receive more than N megolm sessions for scheduled messages from
a given user in the room, the client could warn and discard them).
This proposal relies on absolute timestamps, and so for it to work sensibly
servers need to have an accurate (e.g. NTP-synced) clock.
## Unstable prefix
Implementations should use `org.matrix.msc3277.at` in place of the at query
parameter, and expose/look for `org.matrix.msc3277` in the
unstable_features` of `/versions` while this MSC is not in a released
version of the spec.
## Footnotes
<a id="f1"/>[1]: A smart server could stop earlier redacted drafts ever
being sent to the destination servers by placing new drafts as siblings
to the old drafts in the DAG rather than as children. This means that
recipients wouldn't be able to see that the scheduled message was
drafted. This is a bit of an overenthusiastic optimisation though.
<a id="f2"/>[2]: For P2P, it's likely that the origin server/client will not
be online at the designated time, so we'll want to special-case scheduled
messages for P2P such that they are queued on a privacy-preserving relay
server of some kind rather than queuing on the origin server/client.
Loading…
Cancel
Save