Skip to content
283 changes: 283 additions & 0 deletions proposals/4293-redact-on-ban.md
Copy link
Member Author

@turt2live turt2live Jun 9, 2025

Choose a reason for hiding this comment

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

Original file line number Diff line number Diff line change
@@ -0,0 +1,283 @@
# MSC4293: Redact on kick/ban

[MSC2244 (accepted)](https://github.com/matrix-org/matrix-spec-proposals/blob/main/proposals/2244-mass-redactions.md)-style
mass redactions are incredibly helpful for cleaning up large volumes of spam, especially because they
reduce the total number of events a server needs to process in order to clean up a room. However, they
have a few issues:

1. To populate the target events, the sender needs to know which event IDs to target. Events may be
[soft failed](https://spec.matrix.org/v1.15/server-server-api/#soft-failure) by the moderator's
local homeserver, which may prevent the sender's client from seeing them. Further, there may be
timeline gaps which limit the sender's visibility on the events to target.

This may be fixed by adding a "sugar API" like `/room/:roomId/redact/:userId` which causes the
server to calculate event ID targets and send out one or more mass redactions, though this would
only be effective if the server had all of the user's events at the time of the call. If an event
came in late for any reason, the sent redactions might not target it, allowing the likely spam
through to the room.

Proposals such as [MSC4194](https://github.com/matrix-org/matrix-doc/pull/4194) explore this
solution.

2. Dedicated events must still be sent in order to perform the redaction, many of which might not be
small if there's lots of events being targeted. This can impact bandwidth and data storage, though
not as badly as trying to redact large volumes of spam without mass redactions.

3. MSC2244 mass redactions are breaking for clients which perform redaction locally due to changing
the type of `redacts` on `m.room.redaction` events from a string (single event ID) to an array.
This is in part mitigated by MSC2244 through a new room version, though clients are currently
unable to opt out of incompatible room versions.

It's also possible to mitigate this concern by using a new event type for mass redactions instead.
This alternative is explored in [MSC4343](https://github.com/matrix-org/matrix-spec-proposals/pull/4343).

4. Due to changes in event authorization, MSC2244 requires a new room version in order to function.
This limits the feature to new rooms or those which upgrade to a new enough version. Theoretically,
it's possible to allow mass redactions in existing room versions anyway, though the redactions may
get rejected due to the very authorization rules MSC2244 changes. This may cause senders to target
a small number of events per mass redaction to avoid the possibility of spam being left visible
due to a single event target not being present on the receiving homeserver.

To work around these issues, this proposal suggests adding a new flag to membership events to indicate
to clients and servers that all of that user's events should be redacted in addition to being kicked
or banned. There are still drawbacks for existing room versions with this approach, namely that the
new flag isn't protected from redaction itself and may cause problems, but it does allow moderators
to take an action they were going to anyway: ban & redact everything the user ever sent, without
needing to wait for a new room version to roll out.

By applying the flag to a user ID instead of an event ID (or set of event IDs), the consumer's local
view is considered instead of the sender's: a redaction for "all of `@alice:example.org`'s events"
when applied by a client removes all events visible to that client. Similarly, that redaction applied
by a server to their local database will redact whatever events the server has already seen, or will
see in the future.

Though this MSC isn't breaking for clients in the same way that MSC2244 is, clients (and to a degree,
servers) may not support the MSC right away. This means that senders may still have to send "fallback
redactions" to ensure the room is cleaned up, though those senders can begin to treat those redactions
as best effort (currently, moderation bots in particular work incredibly hard to ensure they get *every*
event redacted, but can run into delivery, reliability, and completeness issues with 1:1 redactions).

It's also important to note that this proposal intends to compliment mass redactions and coexist in
Copy link
Member

Choose a reason for hiding this comment

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

Nitpick:

Suggested change
It's also important to note that this proposal intends to compliment mass redactions and coexist in
It's also important to note that this proposal intends to complement mass redactions and coexist in

whatever room version mass redactions land in. This proposal has narrow scope and is fairly blunt as
a measure, which may not be desirable in all situations. For example, only a user's last dozen or so
messages may be worth redacting rather than the hundreds they sent prior to the ones which got them
banned. This proposal does not support such a use case, but mass redactions do.

## Proposal

A new flag is added to [`m.room.member`](https://spec.matrix.org/v1.14/client-server-api/#mroommember)
events where the target user is kicked or banned: `redact_events`. Discussed later, this new flag has
no effect when applied to self-leaves or other membership states. This flag is a boolean and has two
effects when `true`:

1. Clients apply the redaction algorithm to events sent by that user which are cached locally, up to
the point of the user's previous membership event. No [`m.room.redaction`](https://spec.matrix.org/v1.14/client-server-api/#mroomredaction)
events are sent by the client, but the user's events are redacted as though there was such an event.

2. Servers perform the same function as clients, also not actually sending any `m.room.redaction`
events. Instead, the user's events are redacted locally by the server. When serving events redacted
in this way, the [`redacted_because`](https://spec.matrix.org/v1.14/client-server-api/#redactions)
field is populated using the membership event, like so:

```jsonc
{
// irrelevant fields not shown

"type": "m.room.message",
"sender": "@banned:example.org",
"content": {}, // because the event is redacted, `content` is empty
"unsigned": {
"redacted_because": {
// irrelevant fields also not shown here

"type": "m.room.member",
"sender": "@moderator:example.org",
"state_key": "@banned:example.org",
"content": {
"membership": "ban",
"reason": "spam",
"redact_events": true
}
}
}
}
```

Note that because `m.room.redaction` supports a `reason` field in the same place as `m.room.member`,
clients which look up that reason by going `event["unsigned"]["content"]["reason"]` will still get
Copy link
Member

Choose a reason for hiding this comment

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

Do you mean event["unsigned"]["redacted_because"]["content"]["reason"]?

a renderable, human-readable, string for their UI regardless of how the event was actually redacted.

When `redact_events` is `false`, it acts as though it was not specified: no redactions are applied,
exactly like how bans work prior to this proposal.

Prior to applying the effects of redaction above, clients and servers MUST ensure that the sender of
the kick or ban has power level to redact the target user's events. This means having a power level
higher than or equal to `redact` *and* `events["m.room.redaction"]` (if set). We maintain the `events`
check despite not actually sending events of that type to keep the same expectations within rooms. If
the sender doesn't have permission to redact an event normally, no the `redact_events` flag is ignored
(and therefore no redaction effect is applied).

Events which are delivered after the kick or ban are likely [soft failed](https://spec.matrix.org/v1.14/server-server-api/#soft-failure)
and are still redacted by servers if the user's *current* membership event has `redact_events: true`.

The `redact_events` flag has no effect when present on any other membership state, such as joins,
knocks, invites, and voluntary leaves (non-kicks).

**Note**: Due to this proposal being enabled in existing room versions, the `redact_events` flag may
become redacted when a user's `m.room.member` event is redacted too. When this happens, the redactions
already applied up to that point are *not* undone, though clients/servers which purge the events and
re-fetch them might receive unredacted copies if they originate from a server which didn't apply this
proposal's effects. Future proposals like [MSC4298](https://github.com/matrix-org/matrix-spec-proposals/pull/4298)
protect the flag from redaction, avoiding the awkward state where events might be redacted for no
discernible reason after the membership event itself is redacted.

`redact_events` is also added to the [`/kick`](https://spec.matrix.org/v1.14/client-server-api/#post_matrixclientv3roomsroomidkick)
and [`/ban`](https://spec.matrix.org/v1.14/client-server-api/#post_matrixclientv3roomsroomidban)
endpoints and is proxied to the resulting event just like `reason` is. It is optional on these endpoints.

An example ban event is:

```jsonc
{
// Irrelevant fields excluded

"type": "m.room.member",
"state_key": "@spam:example.org",
"sender": "@mod:example.org",
"content": {
"membership": "ban",
"reason": "flooding", // this is copied to `redacted_because`, leading to clients showing it
"redact_events": true
}
}
```

An example sccenario would be:

1. Alice joins the room.
2. Alice sends events A, B, and C.
3. Alice leaves the room.
4. For whatever reason, Alice rejoins the room.
5. Alice sends events D, E, and F.
6. Bob bans Alice with `redact_events: true`.
7. Clients and servers apply redactions to events D, E, and F, but *not* A, B, or C. No actual
`m.room.redaction` events are sent in this example.
8. Alice's event F arrive's late to Bob's server.
9. Bob's server soft fails F because it fails current auth state (Alice is banned), and redacts it
because `redact_events: true` is set on Alice's ban. There are still no actual `m.room.redaction`
events sent here.

Events A, B, and C are not redacted because Alice's leave event at step 3 has an implied `redact_events: false`.

Moderation bots and similar MAY still wish to issue (mass) redactions upon kick/ban to protect users
on servers or clients which don't have this feature.


## Fallback behaviour

Servers which don't support this feature may be served redacted events over federation when attempting
to fill gaps or backfill. This is considered expected behaviour.

Clients which don't support this feature may see events remain unredacted until they clear their local
cache. Upon clearing or invalidating their cache, they will either receive redacted events if their
server supports the feature, or unredacted events otherwise. This is also considered expected behaviour.

Though this proposal makes it clear that `m.room.redaction` events aren't actually sent, senders of
kicks/bans MAY still send actual redactions in addition to `redact_events: true` to ensure that older
clients and servers have the best possible chance of redacting the event. These redactions SHOULD be
considered "best effort" by the sender as they may encounter delivery issues, especially when using
1:1 redactions instead of mass redactions. "Best effort" might mean using endpoints like
[MSC4194](https://github.com/matrix-org/matrix-spec-proposals/pull/4194)'s batch redaction, or using
the less reliable `/messages` endpoint to locate target event IDs to redact. Senders SHOULD note that
this fallback behaviour will only target events they can see (or be made to see via MSC4194) and might
not be the same events that a receiving client or server sees.

Senders are encouraged to evaluate when they can cease sending fallback redactions like those described
above to minimize the event traffic involved in a ban. For moderation bots this may mean waiting for
sufficiently high *client* implementations existing in their communities.
Comment on lines +176 to +197
Copy link
Contributor

Choose a reason for hiding this comment

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

How does a moderation bot provide fallback if the server it is using implements this MSC?

Copy link
Member Author

Choose a reason for hiding this comment

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

The server's support is a lot less important than the client support I think. The general fallback behaviour is for the bot to do what it normally does alongside setting the flag, though individual projects can (and should) make their own decisions.


## Potential issues

1. It's a little annoying that the flag is redacted when the membership event is redacted, however it's
extremely rare for a moderator/admin to redact a kick or ban event. This can be fixed in a future
room version, like what is proposed by [MSC4298](https://github.com/matrix-org/matrix-spec-proposals/pull/4298).

2. Though extremely rare, if an existing server in the room didn't apply the redactions *and* a sender's
ban was redacted, a new server to the room may backfill through that existing server and see unredacted
events without knowing it's supposed to redact them due to the ban having lost the `redact_events`
field. This is fixed for future room versions by implementing something like [MSC4298](https://github.com/matrix-org/matrix-spec-proposals/pull/4298).

3. Clients may miss the membership event if they are using lazy loading, though servers should already
be tracking which membership events the client has received and needs to render events in the timeline.
This should mean that those clients will still receive the event.

Servers which miss the event will eventually receive or retrieve it, just like they would with any
other event.

4. Moderation bots/clients which attempt to reduce the amount of duplicate work they do may need to
inspect `redacted_because`'s `type` instead of checking for its presence to determine which kind of redaction
was applied to a given event. This is especially true if the moderation bot/client is providing the
fallback support described above.

5. If a user is banned using `redact_events: true`, unbanned, rejoins, sends more events, and is banned
again using `redact_events: true`, the user's events between bans will be subsequently redacted. The
events redacted by the first ban may also be re-redacted by servers/clients depending on implementation.
This is considered expected behaviour, and implementations can internally track which events they've
already auto-redacted to avoid duplicate work.

6. With respect to the fallback behaviour, it's not great that implementations, and in particular
moderation bots, need to maintain their "find all events sent by this user and redact them" behaviour.
[MSC4194](https://github.com/matrix-org/matrix-spec-proposals/pull/4194) should help with this, though
has the limitations discussed throughout this MSC when applied to this proposal's use case.

7. If a user is banned without the flag then banned again with the flag, their events sent before the
first ban won't be redacted. This is already the case with moderation bots which support autoredaction
when certain ban reasons are used: if there's a typo/problem with the reason, the bot's operator
may need to issue more commands/requests to reach the intended result.

Clients and moderation bots are encouraged to implement UX which reduces the chances of this sort
of thing happening. Moderation teams are also encouraged to develop operating procedures which
limit the opportunity for accidentally encountering this case.

8. A spammer may attempt to work around this MSC's effects by joining and leaving the room during
their spam. This has relatively high cost (the impact of spam is lesser when they aren't joined
to the room, and re-joining will hit a more restrictive rate limit on most servers).

Moderation bots and similar community safety tools are encouraged to add restrictions to the number
of join+leave cycles a user may perform in a short window. This will further reduce the effectiveness
of such an attack. Issue 7 above also applies here.
Comment on lines +242 to +248
Copy link
Member

Choose a reason for hiding this comment

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

How does one effectively limit the number of joins a given user can perform? If the spammer is on their own homeserver, then there are no CS API rate limits.

What's to stop a user joining a room, waiting 24hrs to clear any join rate-limits, then spamming and sending another join? Relying on banning a user before they finish spamming and sending a state event seems very hopeful, especially when spam is typically reported via bystanders.

Why not just redact all of a user's event history since the creation of the room?

Copy link
Member

Choose a reason for hiding this comment

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

@mscbot concern The proposal doesn't prevent users from spamming and sending an m.room.member event to protect the spam event.

Copy link
Member Author

Choose a reason for hiding this comment

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

Originally the MSC shied away from doing so for performance concerns, but it feels increasingly valuable to say "anything before the ban", especially to simplify the implementations.

Thoughts from more folks would be helpful for steering the MSC in any particular direction.

Copy link
Member

Choose a reason for hiding this comment

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

My client implementation is at least just "if current state says redact_events, hide all events sent by user". I'm assuming that if a spammer is unbanned, the events have either been redacted explicitly or weren't actually spam and can be shown again.

Copy link
Member

Choose a reason for hiding this comment

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

Couldn't a malicious user also get around the redactions by constantly sending displayname changes or no-op m.room.membership events? I'd be in favour of changing it to "anything before the ban". It seems to me like the most common case is that if someone gets banned and their messages redacted, it's likely that we'd prefer to err on the side of redacting too much, rather than redacting too little.


## Alternatives

Alternatives are discussed inline on this proposal and in the introduction.

## Future considerations

It may be desirable to place this behaviour on self-leaves too, allowing for faster removal of one's
own messages/events. This proposal doesn't suggest adding this functionality here to maintain narrow
scope on T&S functionality. A future proposal may introduce this, or rely on regular mass redactions
instead.

## Security considerations

As the room moderator/administrator would already send redactions, and may still for full protection,
it's not deemed any more risk than today. This may change if self-leaves are permitted to also carry
the field.

There may also be implementation or reliability bugs which inhibit the "stop redacting now" point
from working as intended. Server implementations in particular should ensure that an event received
after a membership event which asks for redaction is *really* affected by that redaction. ie: whether
it's just a late delivery, or if there's a join waiting for state res to make a determination.

## Unstable prefix

While this proposal is not considered stable, implementations should use `org.matrix.msc4293.redact_events`
instead of `redact_events`.

## Dependencies

This MSC has no direct dependencies.

## Credits

Credit goes to Erik of the Spec Core Team for the suggestion to look into this.