Compare commits

..

21 Commits

Author SHA1 Message Date
nexy7574 0a1b284407 feat(event_auth): Add additional logging 2025-07-24 20:15:35 +01:00
Jade Ellis 1bc663e1c8 docs: Fix spacing at the top 2025-07-24 13:37:52 +01:00
Jade Ellis 68b0140c42 docs: Add vias to matrix.to links 2025-07-24 13:31:58 +01:00
nexy7574 f32f60d056 fix(policy-server): Return the correct result when an event is marked as spam 2025-07-23 18:01:46 +01:00
nexy7574 fe06d78c8e fix(policy-server): Update ask_policy_server docstring 2025-07-23 17:58:33 +01:00
nexy7574 99ebe022ed fix(policy-server): Correctly default to 10 second timeout 2025-07-23 17:56:45 +01:00
nexy7574 f335f45017 feat(policy-server): Add configurable timeout 2025-07-23 17:49:08 +01:00
nexy7574 1726633c0f fix(policy-server): Fixup refactor 2025-07-23 17:49:08 +01:00
nexy7574 dfda27fadc feat(policy-server): Don't fail-closed & refactor references 2025-07-23 17:49:08 +01:00
Jade Ellis 9465c5df1f style: Improve logging and comments 2025-07-23 17:49:07 +01:00
nexy7574 2d475b1220 style(policy-server): Run clippy 2025-07-23 17:49:07 +01:00
nexy7574 d7fa624fd2 feat(policy-server): Optimise policy server lookups 2025-07-23 17:49:07 +01:00
nexy7574 cc9202b0c4 feat(policy-server): Limit policy server request timeout to 10 seconds 2025-07-23 17:49:07 +01:00
nexy7574 a3d62ed0d9 feat(policy-server): Prevent local events that fail the policy check 2025-07-23 17:49:07 +01:00
nexy7574 78b7175677 feat(policy-server): Soft-fail redactions for failed events 2025-07-23 17:49:07 +01:00
nexy7574 74d60f256b style(policy-server): Restructure logging 2025-07-23 17:49:07 +01:00
nexy7574 732c69f5ca fix(policy-server): Avoid unnecessary database lookup 2025-07-23 17:49:07 +01:00
nexy7574 8e7801f323 chore: Update ruwuma & fix lints 2025-07-23 17:49:06 +01:00
nexy7574 9017efe45b feat(policy-server): Policy server following 2025-07-23 17:49:06 +01:00
Jade Ellis 7e2f04a78a chore: Check all features in CI and docs 2025-07-20 21:25:27 +01:00
Jade Ellis d74514f305 ci: Fix inverted latest tag 2025-07-20 20:59:29 +01:00
18 changed files with 153 additions and 91 deletions
+1 -1
View File
@@ -262,7 +262,7 @@ jobs:
type=ref,event=branch,prefix=${{ format('refs/heads/{0}', github.event.repository.default_branch) != github.ref && 'branch-' || '' }}
type=ref,event=pr
type=sha,format=long
type=raw,value=latest,enable=${{ !startsWith(github.ref, 'refs/tags/v') }}
type=raw,value=latest,enable=${{ startsWith(github.ref, 'refs/tags/v') }}
images: ${{needs.define-variables.outputs.images}}
# default labels & annotations: https://github.com/docker/metadata-action/blob/master/src/meta.ts#L509
env:
+2
View File
@@ -73,6 +73,7 @@ jobs:
run: |
cargo clippy \
--workspace \
--all-features \
--locked \
--no-deps \
--profile test \
@@ -132,6 +133,7 @@ jobs:
run: |
cargo test \
--workspace \
--all-features \
--locked \
--profile test \
--all-targets \
+1 -1
View File
@@ -59,7 +59,7 @@ representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement over Matrix at [#continuwuity:continuwuity.org](https://matrix.to/#/#continuwuity:continuwuity.org) or email at <tom@tcpip.uk>, <jade@continuwuity.org> and <nex@continuwuity.org> respectively.
reported to the community leaders responsible for enforcement over Matrix at [#continuwuity:continuwuity.org](https://matrix.to/#/#continuwuity:continuwuity.org?via=continuwuity.org&via=ellis.link&via=explodie.org&via=matrix.org) or email at <tom@tcpip.uk>, <jade@continuwuity.org> and <nex@continuwuity.org> respectively.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
+4 -4
View File
@@ -65,11 +65,11 @@ Tests, compilation, and linting can be run with standard Cargo commands:
cargo test
# Check compilation
cargo check --workspace
cargo check --workspace --all-features
# Run lints
cargo clippy --workspace
# Auto-fix: cargo clippy --workspace --fix --allow-staged;
cargo clippy --workspace --all-features
# Auto-fix: cargo clippy --workspace --all-features --fix --allow-staged;
# Format code (must use nightly)
cargo +nightly fmt
@@ -166,7 +166,7 @@ their contributions accepted. This includes users who have been banned from
continuwuity Matrix rooms for Code of Conduct violations.
[issues]: https://forgejo.ellis.link/continuwuation/continuwuity/issues
[continuwuity-matrix]: https://matrix.to/#/#continuwuity:continuwuity.org
[continuwuity-matrix]: https://matrix.to/#/#continuwuity:continuwuity.org?via=continuwuity.org&via=ellis.link&via=explodie.org&via=matrix.org
[complement]: https://github.com/matrix-org/complement/
[sytest]: https://github.com/matrix-org/sytest/
[mdbook]: https://rust-lang.github.io/mdBook/
+1 -1
View File
@@ -115,7 +115,7 @@ When incorporating code from other forks:
#### Contact
Join our [Matrix room](https://matrix.to/#/#continuwuity:continuwuity.org) and [space](https://matrix.to/#/#space:continuwuity.org) to chat with us about the project!
Join our [Matrix room](https://matrix.to/#/#continuwuity:continuwuity.org?via=continuwuity.org&via=ellis.link&via=explodie.org&via=matrix.org) and [space](https://matrix.to/#/#space:continuwuity.org?via=continuwuity.org&via=ellis.link&via=explodie.org&via=matrix.org) to chat with us about the project!
<!-- ANCHOR_END: footer -->
+16
View File
@@ -340,6 +340,22 @@
#
#federation_timeout = 300
# MSC4284 Policy server request timeout (seconds). Generally policy
# servers should respond near instantly, however may slow down under
# load. If a policy server doesn't respond in a short amount of time, the
# room it is configured in may become unusable if this limit is set too
# high. 10 seconds is a good default, however dropping this to 3-5 seconds
# can be acceptable.
#
# Please be aware that policy requests are *NOT* currently re-tried, so if
# a spam check request fails, the event will be assumed to be not spam,
# which in some cases may result in spam being sent to or received from
# the room that would typically be prevented.
#
# About policy servers: https://matrix.org/blog/2025/04/introducing-policy-servers/
#
#policy_server_request_timeout = 10
# Federation client idle connection pool timeout (seconds).
#
#federation_idle_timeout = 25
+1 -1
View File
@@ -3,7 +3,7 @@
## Getting help
If you run into any problems while setting up an Appservice: ask us in
[#continuwuity:continuwuity.org](https://matrix.to/#/#continuwuity:continuwuity.org) or
[#continuwuity:continuwuity.org](https://matrix.to/#/#continuwuity:continuwuity.org?via=continuwuity.org&via=ellis.link&via=explodie.org&via=matrix.org) or
[open an issue on Forgejo](https://forgejo.ellis.link/continuwuation/continuwuity/issues/new).
## Set up the appservice - general instructions
+4 -4
View File
@@ -75,9 +75,9 @@ subject to enforcement action.
## Matrix Community
These Community Guidelines apply to the entire
[Continuwuity Matrix Space](https://matrix.to/#/#space:continuwuity.org) and its rooms, including:
[Continuwuity Matrix Space](https://matrix.to/#/#space:continuwuity.org?via=continuwuity.org&via=ellis.link&via=explodie.org&via=matrix.org) and its rooms, including:
### [#continuwuity:continuwuity.org](https://matrix.to/#/#continuwuity:continuwuity.org)
### [#continuwuity:continuwuity.org](https://matrix.to/#/#continuwuity:continuwuity.org?via=continuwuity.org&via=ellis.link&via=explodie.org&via=matrix.org)
This room is for support and discussions about Continuwuity. Ask questions, share insights, and help
each other out while adhering to these guidelines.
@@ -85,7 +85,7 @@ each other out while adhering to these guidelines.
We ask that this room remain focused on the Continuwuity software specifically: the team are
typically happy to engage in conversations about related subjects in the off-topic room.
### [#offtopic:continuwuity.org](https://matrix.to/#/#offtopic:continuwuity.org)
### [#offtopic:continuwuity.org](https://matrix.to/#/#offtopic:continuwuity.org?via=continuwuity.org&via=ellis.link&via=explodie.org&via=matrix.org)
For off-topic community conversations about any subject. While this room allows for a wide range of
topics, the same guidelines apply. Please keep discussions respectful and inclusive, and avoid
@@ -95,7 +95,7 @@ care and respect for diverse viewpoints.
General topics, such as world events, are welcome as long as they follow the guidelines. If a member
of the team asks for the conversation to end, please respect their decision.
### [#dev:continuwuity.org](https://matrix.to/#/#dev:continuwuity.org)
### [#dev:continuwuity.org](https://matrix.to/#/#dev:continuwuity.org?via=continuwuity.org&via=ellis.link&via=explodie.org&via=matrix.org)
This room is dedicated to discussing active development of Continuwuity, including ongoing issues or
code development. Collaboration here must follow these guidelines, and please consider raising
+1 -1
View File
@@ -196,5 +196,5 @@ The initial implementation PR is available [here][1].
[4]: https://github.com/rust-lang/rust/issues/28794#issuecomment-368693049
[5]: https://github.com/rust-lang/cargo/issues/12746
[6]: https://crates.io/crates/hot-lib-reloader/
[7]: https://matrix.to/#/#continuwuity:continuwuity.org
[7]: https://matrix.to/#/#continuwuity:continuwuity.org?via=continuwuity.org&via=ellis.link&via=explodie.org&via=matrix.org
[8]: https://crates.io/crates/libloading
+1
View File
@@ -96,6 +96,7 @@ script = """
direnv exec . \
cargo clippy \
--workspace \
--all-features \
--locked \
--profile test \
--color=always \
+19
View File
@@ -431,6 +431,23 @@ pub struct Config {
#[serde(default = "default_federation_timeout")]
pub federation_timeout: u64,
/// MSC4284 Policy server request timeout (seconds). Generally policy
/// servers should respond near instantly, however may slow down under
/// load. If a policy server doesn't respond in a short amount of time, the
/// room it is configured in may become unusable if this limit is set too
/// high. 10 seconds is a good default, however dropping this to 3-5 seconds
/// can be acceptable.
///
/// Please be aware that policy requests are *NOT* currently re-tried, so if
/// a spam check request fails, the event will be assumed to be not spam,
/// which in some cases may result in spam being sent to or received from
/// the room that would typically be prevented.
///
/// About policy servers: https://matrix.org/blog/2025/04/introducing-policy-servers/
/// default: 10
#[serde(default = "default_policy_server_request_timeout")]
pub policy_server_request_timeout: u64,
/// Federation client idle connection pool timeout (seconds).
///
/// default: 25
@@ -2208,6 +2225,8 @@ fn default_federation_conn_timeout() -> u64 { 10 }
fn default_federation_timeout() -> u64 { 25 }
fn default_policy_server_request_timeout() -> u64 { 10 }
fn default_federation_idle_timeout() -> u64 { 25 }
fn default_federation_idle_per_host() -> u16 { 1 }
+10 -2
View File
@@ -149,7 +149,6 @@ where
for<'a> &'a E: Event + Send,
{
debug!(
event_id = %incoming_event.event_id(),
event_type = ?incoming_event.event_type(),
"auth_check beginning"
);
@@ -377,6 +376,11 @@ where
&user_for_join_auth_membership,
&room_create_event,
)? {
warn!(
target_user = ?target_user,
sender = ?sender,
"m.room.member event was not allowed",
);
return Ok(false);
}
@@ -412,7 +416,10 @@ where
let membership_state = membership_state.deserialize()?;
if !matches!(membership_state, MembershipState::Join) {
warn!("sender's membership is not join");
warn!(
membership = %membership_state,
"sender's membership is not join"
);
return Ok(false);
}
@@ -511,6 +518,7 @@ where
};
if !check_redaction(room_version, incoming_event, sender_power_level, redact_level)? {
warn!("redaction event was not allowed");
return Ok(false);
}
}
+1 -1
View File
@@ -1,5 +1,4 @@
mod acl_check;
mod call_policyserv;
mod fetch_and_handle_outliers;
mod fetch_prev;
mod fetch_state;
@@ -7,6 +6,7 @@ mod handle_incoming_pdu;
mod handle_outlier_pdu;
mod handle_prev_pdu;
mod parse_incoming_pdu;
mod policy_server;
mod resolve_state;
mod state_at_incoming;
mod upgrade_outlier_pdu;
@@ -12,17 +12,27 @@ use ruma::{
events::{StateEventType, room::policy::RoomPolicyEventContent},
};
/// Returns Ok if the policy server allows the event
/// Asks a remote policy server if the event is allowed.
///
/// If the event is the `org.matrix.msc4284.policy` configuration state event,
/// this check is skipped. Similarly, if there is no policy server configured in
/// the PDU's room, or the configured server is not present in the room, the
/// check is also skipped.
///
/// If the policy server marks the event as spam, Ok(false) is returned,
/// otherwise Ok(true) allows the event. If the policy server cannot be
/// contacted for whatever reason, Err(e) is returned, which generally is a
/// fail-open operation.
#[implement(super::Service)]
#[tracing::instrument(skip_all, level = "debug")]
pub async fn policyserv_check(&self, pdu: &PduEvent, room_id: &RoomId) -> Result {
pub async fn ask_policy_server(&self, pdu: &PduEvent, room_id: &RoomId) -> Result<bool> {
if *pdu.event_type() == StateEventType::RoomPolicy.into() {
debug!(
room_id = %room_id,
event_type = ?pdu.event_type(),
"Skipping spam check for policy server meta-event"
);
return Ok(());
return Ok(true);
}
let Ok(policyserver) = self
.services
@@ -31,19 +41,19 @@ pub async fn policyserv_check(&self, pdu: &PduEvent, room_id: &RoomId) -> Result
.await
.map(|c: RoomPolicyEventContent| c)
else {
return Ok(());
return Ok(true);
};
let via = match policyserver.via {
| Some(ref via) => ServerName::parse(via)?,
| None => {
debug!("No policy server configured for room {room_id}");
return Ok(());
return Ok(true);
},
};
if via.is_empty() {
debug!("Policy server is empty for room {room_id}, skipping spam check");
return Ok(());
return Ok(true);
}
if !self.services.state_cache.server_in_room(via, room_id).await {
debug!(
@@ -51,7 +61,7 @@ pub async fn policyserv_check(&self, pdu: &PduEvent, room_id: &RoomId) -> Result
via = %via,
"Policy server is not in the room, skipping spam check"
);
return Ok(());
return Ok(true);
}
let outgoing = self
.services
@@ -65,7 +75,7 @@ pub async fn policyserv_check(&self, pdu: &PduEvent, room_id: &RoomId) -> Result
"Checking event for spam with policy server"
);
let response = tokio::time::timeout(
Duration::from_secs(10),
Duration::from_secs(self.services.server.config.policy_server_request_timeout),
self.services
.sending
.send_federation_request(via, PolicyRequest {
@@ -85,7 +95,7 @@ pub async fn policyserv_check(&self, pdu: &PduEvent, room_id: &RoomId) -> Result
);
// Network or policy server errors are treated as non-fatal: event is allowed by
// default.
return Ok(());
return Err(e);
},
| Err(_) => {
warn!(
@@ -94,7 +104,7 @@ pub async fn policyserv_check(&self, pdu: &PduEvent, room_id: &RoomId) -> Result
room_id = %room_id,
"Policy server request timed out after 10 seconds"
);
return Ok(());
return Err!("Request to policy server timed out");
},
};
if response.recommendation == "spam" {
@@ -104,8 +114,8 @@ pub async fn policyserv_check(&self, pdu: &PduEvent, room_id: &RoomId) -> Result
room_id = %room_id,
"Event was marked as spam by policy server",
);
return Err!(Request(Forbidden("Event was marked as spam by policy server")));
return Ok(false);
}
Ok(())
Ok(true)
}
@@ -17,6 +17,13 @@ use crate::rooms::{
};
#[implement(super::Service)]
#[tracing::instrument(
level = "debug",
skip_all,
fields(
event_id = incoming_pdu.event_id().as_str(),
)
)]
pub(super) async fn upgrade_outlier_to_timeline_pdu<Pdu>(
&self,
incoming_pdu: PduEvent,
@@ -47,10 +54,7 @@ where
return Err!(Request(InvalidParam("Event has been soft failed")));
}
debug!(
event_id = %incoming_pdu.event_id,
"Upgrading PDU from outlier to timeline"
);
trace!("Upgrading PDU from outlier to timeline");
let timer = Instant::now();
let room_version_id = get_room_version_id(create_event)?;
@@ -58,10 +62,7 @@ where
// backwards extremities doing all the checks in this list starting at 1.
// These are not timeline events.
debug!(
event_id = %incoming_pdu.event_id,
"Resolving state at event"
);
trace!("Resolving state at event");
let mut state_at_incoming_event = if incoming_pdu.prev_events().count() == 1 {
self.state_at_incoming_degree_one(&incoming_pdu).await?
} else {
@@ -80,10 +81,7 @@ where
let room_version = to_room_version(&room_version_id);
debug!(
event_id = %incoming_pdu.event_id,
"Performing auth check to upgrade"
);
trace!("Performing auth check to upgrade");
// 11. Check the auth of the event passes based on the state of the event
let state_fetch_state = &state_at_incoming_event;
let state_fetch = |k: StateEventType, s: StateKey| async move {
@@ -93,10 +91,7 @@ where
self.services.timeline.get_pdu(event_id).await.ok()
};
debug!(
event_id = %incoming_pdu.event_id,
"Running initial auth check"
);
debug!("Running initial auth check");
let auth_check = state_res::event_auth::auth_check(
&room_version,
&incoming_pdu,
@@ -110,10 +105,7 @@ where
return Err!(Request(Forbidden("Event has failed auth check with state at the event.")));
}
debug!(
event_id = %incoming_pdu.event_id,
"Gathering auth events"
);
trace!("Gathering auth events");
let auth_events = self
.services
.state
@@ -131,10 +123,7 @@ where
ready(auth_events.get(&key).map(ToOwned::to_owned))
};
debug!(
event_id = %incoming_pdu.event_id,
"Running auth check with claimed state auth"
);
debug!("Running auth check with claimed state auth");
let auth_check = state_res::event_auth::auth_check(
&room_version,
&incoming_pdu,
@@ -245,45 +234,49 @@ where
.await?;
}
// 14-pre. If the event is not a state event, ask the policy server about it
if incoming_pdu.state_key.is_none() {
debug!(
event_id = %incoming_pdu.event_id,"Checking policy server for event");
let policy = self.policyserv_check(&incoming_pdu, room_id);
if let Err(e) = policy.await {
warn!(
event_id = %incoming_pdu.event_id,
error = ?e,
"Policy server check failed for event"
);
if !soft_fail {
soft_fail = true;
if !soft_fail {
// Don't call the below checks on events that have already soft-failed, there's
// no reason to re-calculate that.
// 14-pre. If the event is not a state event, ask the policy server about it
if incoming_pdu.state_key.is_none() {
debug!("Checking policy server for event");
match self.ask_policy_server(&incoming_pdu, room_id).await {
| Ok(false) => {
warn!("Event has been marked as spam by policy server");
soft_fail = true;
},
| _ => {
debug!(
"Event has passed policy server check or the policy server was \
unavailable."
);
},
}
}
debug!(
event_id = %incoming_pdu.event_id,
"Policy server check passed for event"
);
}
// Additionally, if this is a redaction for a soft-failed event, we soft-fail it
// also
if let Some(redact_id) = incoming_pdu.redacts_id(&room_version_id) {
debug!(
redact_id = %redact_id,
"Checking if redaction is for a soft-failed event"
);
if self
.services
.pdu_metadata
.is_event_soft_failed(&redact_id)
.await
{
warn!(
// Additionally, if this is a redaction for a soft-failed event, we soft-fail it
// also.
// TODO: this is supposed to hide redactions from policy servers, however, for
// full efficacy it also needs to hide redactions for unknown events. This
// needs to be investigated at a later time.
if let Some(redact_id) = incoming_pdu.redacts_id(&room_version_id) {
debug!(
redact_id = %redact_id,
"Redaction is for a soft-failed event, soft failing the redaction"
"Checking if redaction is for a soft-failed event"
);
soft_fail = true;
if self
.services
.pdu_metadata
.is_event_soft_failed(&redact_id)
.await
{
warn!(
redact_id = %redact_id,
"Redaction is for a soft-failed event, soft failing the redaction"
);
soft_fail = true;
}
}
}
+12 -4
View File
@@ -166,14 +166,22 @@ pub async fn create_hash_and_sign_event(
}
// Check with the policy server
if self
match self
.services
.event_handler
.policyserv_check(&pdu, room_id)
.ask_policy_server(&pdu, room_id)
.await
.is_err()
{
return Err!(Request(Forbidden(debug_warn!("Policy server marked this event as spam"))));
| Ok(true) => {},
| Ok(false) => {
return Err!(Request(Forbidden(debug_warn!(
"Policy server marked this event as spam"
))));
},
| Err(e) => {
// fail open
warn!("Failed to check event with policy server: {e}");
},
}
// Hash and sign
+1 -1
View File
@@ -7,7 +7,7 @@
<p>To get started, you can:</p>
<ul>
<li>Read the <a href="https://continuwuity.org/introduction">documentation</a></li>
<li>Join the <a href="https://matrix.to/#/#continuwuity:continuwuity.org">Continuwuity Matrix room</a> or <a href="https://matrix.to/#/#space:continuwuity.org">space</a></li>
<li>Join the <a href="https://matrix.to/#/#continuwuity:continuwuity.org?via=continuwuity.org&via=ellis.link&via=explodie.org&via=matrix.org">Continuwuity Matrix room</a> or <a href="https://matrix.to/#/#space:continuwuity.org?via=continuwuity.org&via=ellis.link&via=explodie.org&via=matrix.org">space</a></li>
<li>Log in with a <a href="https://matrix.org/ecosystem/clients/">client</a></li>
<li>Ensure <a href="https://federationtester.matrix.org/#{{ server_name }}">federation</a> works</li>
</ul>
+5
View File
@@ -605,3 +605,8 @@ ul#searchresults span.teaser em {
margin-inline-start: -14px;
width: 14px;
}
/* HACK: Stop the keyboard shortcuts from adding a giant space at the top */
#mdbook-help-container {
display: none;
}