Commit Graph
25943 Commits
Author SHA1 Message Date
dependabot[bot]andGitHub bfabb0ae8d Bump actions/setup-python from 6.3.0 to 7.0.0
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 6.3.0 to 7.0.0.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](https://github.com/actions/setup-python/compare/ece7cb06caefa5fff74198d8649806c4678c61a1...5fda3b95a4ea91299a34e894583c3862153e4b97)

---
updated-dependencies:
- dependency-name: actions/setup-python
  dependency-version: 7.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-06 10:12:08 +00:00
dependabot[bot]andGitHub 701baedbea Bump actions/checkout from 7.0.0 to 7.0.1 in the minor-and-patches group (#20043)
Signed-off-by: dependabot[bot] <support@github.com>
2026-08-06 10:09:50 +00:00
dependabot[bot]andGitHub 98a0cae3c3 Bump the patches group with 6 updates (#20042)
Signed-off-by: dependabot[bot] <support@github.com>
2026-08-06 10:09:27 +00:00
Olivier 'reivilibreandGitHub 320265f09b Remove broken DROP statements for SQLite in make_full_schema script. (#20028)
Broken out of #20027 because I'd like to have it land first.

> It seems a lot of time in our trial tests goes towards setting up the
database. (The same is probably true of Complement too)
> 
> We haven't done a full schema for about 20 schema versions, so no
surprise!
> 
> As a result, I want to produce a full schema soon.

When running `make_full_schema.sh`, these drop statements now cause the
error:

```
Parse error near line 2: table event_search_content may not be dropped
Parse error near line 3: table event_search_segments may not be dropped
Parse error near line 4: table event_search_segdir may not be dropped
Parse error near line 5: table event_search_docsize may not be dropped
Parse error near line 6: table event_search_stat may not be dropped
Parse error near line 7: table user_directory_search_content may not be dropped
Parse error near line 8: table user_directory_search_segments may not be dropped
Parse error near line 9: table user_directory_search_segdir may not be dropped
Parse error near line 10: table user_directory_search_docsize may not be dropped
Parse error near line 11: table user_directory_search_stat may not be dropped
```

It seems SQLite has cracked down on code that edits the internal tables.

Because SQLite dumps the schema with `CREATE TABLE IF NOT EXISTS` for
these virtual tables, it's harmless to leave them in the schema dump.

---------

Signed-off-by: Olivier 'reivilibre' <oliverw@matrix.org>
2026-08-06 10:15:21 +01:00
Eric EastwoodandGitHub 3cb6c3484d Document how to capture a JSON snapshot of a Grafana dashboard (#20048)
To make it easy to share to people and aid in debugging when they run into
performance issues.

Unfortunately, the Grafana UI doesn't make the export or import steps
easy so it involves some manual Grafana API calls.

As first explored in
https://github.com/element-hq/synapse-rust-apps/pull/397
2026-08-05 11:11:30 -05:00
Eric Eastwood c9da03e576 Merge branch 'master' into develop 2026-08-04 14:12:18 -05:00
Eric Eastwood 7a3e98b6f7 1.158.0 v1.158.0 2026-08-04 12:53:51 -05:00
Eric EastwoodandEric Eastwood 5b29f8160e Drop support for Ubuntu 25.10 'Questing Quokka', add support for Ubuntu 26.04 'Resolute Raccoon' (#20039)
Fix https://github.com/element-hq/synapse/issues/20031

### Dev notes

 - https://endoflife.date/ubuntu
 - https://endoflife.date/debian
 - https://endoflife.date/python

Finding Python version on distros:

 - https://packages.debian.org/trixie/python3
 - https://packages.ubuntu.com/resolute/python3
2026-08-04 09:12:10 -05:00
Eric EastwoodandGitHub 2c8bf9a3ab Drop support for Ubuntu 25.10 'Questing Quokka', add support for Ubuntu 26.04 'Resolute Raccoon' (#20039)
Fix https://github.com/element-hq/synapse/issues/20031

### Dev notes

 - https://endoflife.date/ubuntu
 - https://endoflife.date/debian
 - https://endoflife.date/python

Finding Python version on distros:

 - https://packages.debian.org/trixie/python3
 - https://packages.ubuntu.com/resolute/python3
2026-08-04 09:08:19 -05:00
Jason LittleandGitHub 415a869f1f fix RemoteJoinHelper to handle v12 rooms (#20021)
While preparing for the default room version change to "12", the new
tests for
[FederationJoinUpgradedRoomTestCase](https://github.com/element-hq/synapse/blob/36664cac4fdec38993c68078cd9a82452b56dca7/tests/federation/test_federation_join_upgraded_room.py#L44)
were unable to handle creation events inside of the manufactured
`auth_events` sections. All of these tests fail.

Related to https://github.com/element-hq/synapse/pull/20015 as another
recent fix to `RemoteJoinHelper`


### Testing strategy

To test, I recommend changing the `DEFAULT_ROOM_VERSION` in [the
`server.py` config
file](https://github.com/element-hq/synapse/blob/36664cac4fdec38993c68078cd9a82452b56dca7/synapse/config/server.py#L179)
to `"12"`, then running `poetry run trial
tests.federation.test_federation_join_upgraded_room.FederationJoinUpgradedRoomTestCase`
for these selective tests. Alternatively, creating the
`default_config()` for the `TestCase` in question to set the default
room version, but that is more work.
2026-08-03 13:47:26 -05:00
Eric EastwoodandGitHub f61950d54d Fix release announcement to link to correct release branch of changelog (#20023)
Without this change, it was linking to `develop` as we were on the
`develop` branch because of the previous `merge-back` step at this point
in the release process.

Follow-up to https://github.com/element-hq/synapse/pull/19984 as this
was an oversight and I assumed we would still be on the `release-v1.158`
branch by that point.
2026-07-31 13:58:20 -05:00
Eric Eastwood e4644ae2b9 Remove duplicated punctuation v1.158.0rc1 2026-07-30 11:03:39 -05:00
Eric Eastwood 305e590bea Link and use canonical API name 2026-07-30 10:55:30 -05:00
Eric Eastwood 446bc852de Split #19898 changelog across two points 2026-07-30 10:52:13 -05:00
Eric Eastwood ade9176934 1.158.0rc1 2026-07-30 09:54:20 -05:00
Olivier 'reivilibreandGitHub 36664cac4f Add Module API hooks that notify modules when events are delivered over federation. (#20019)
Closes: https://github.com/element-hq/synapse/issues/19904

---------

Signed-off-by: Olivier 'reivilibre <oliverw@matrix.org>
2026-07-30 12:32:27 +01:00
Eric EastwoodandGitHub 897f68b3c2 Fix assertIncludes printing None at the end of the message (#20020) 2026-07-29 13:30:39 -05:00
Eric EastwoodandGitHub fba14358a7 Fix RemoteJoinHelper signing events with the default room version (room version mismatch) (#20015)
Fix `RemoteJoinHelper` signing events with mismatched room version
compared to the `room_version` arg. The default room version on
`develop` is `11` but the `RemoteJoinHelper` `room_version` arg defaults
to `10` (room version mismatch). This mismatch wasn't present where this
fix was developed
(https://github.com/element-hq/synapse-private/pull/136) as the default
room version was only recently bumped to `11` via
https://github.com/element-hq/synapse/pull/18680 (not even in a release
yet).

Fixes the CI being broken on `develop` :x::

```
[ERROR]
Traceback (most recent call last):
  File "/home/runner/work/synapse/synapse/tests/federation/test_federation_join_upgraded_room.py", line 298, in test_no_transfer_when_tombstone_does_not_match
    join_helper.join(local_user_id, local_user_tok)
  File "/home/runner/work/synapse/synapse/tests/federation/_remote_join.py", line 350, in join
    self._test_case.helper.join(remote_room_id, local_user_id, tok=local_user_tok)
  File "/home/runner/work/synapse/synapse/tests/rest/client/utils.py", line 195, in join
    return self.change_membership(
  File "/home/runner/work/synapse/synapse/tests/rest/client/utils.py", line 333, in change_membership
    assert channel.code == expect_code, (
builtins.AssertionError: Expected: 200, got: 400, PUT /_matrix/client/r0/rooms/!remote-room:other.example.com/state/m.room.member/@user1:test?access_token=syt_dXNlcjE_JSEarRndiAGqtydnrdLn_33qlLD -> resp: b'{"errcode":"M_UNKNOWN","error":"No create event in state"}'

tests.federation.test_federation_join_upgraded_room.FederationJoinUpgradedRoomTestCase.test_no_transfer_when_tombstone_does_not_match
```

These tests were originally introduced
https://github.com/element-hq/synapse-private/pull/136 (developed
private as this was part of the Synapse security release) and introduced
into the public codebase via
https://github.com/element-hq/synapse/commit/cbc6934821aab314506c5957223c60c74eeda091
2026-07-29 10:32:41 -05:00
dependabot[bot]andGitHub e3715b24fb Bump the patches group with 2 updates (#20012)
Signed-off-by: dependabot[bot] <support@github.com>
2026-07-29 09:30:21 +00:00
dependabot[bot]andGitHub 41c41d2c12 Bump quinn-proto from 0.11.14 to 0.11.16 (#20010)
Signed-off-by: dependabot[bot] <support@github.com>
2026-07-29 09:30:01 +00:00
Eric EastwoodandGitHub 1a1af7b622 Link to changelog instead of duplicating content in the tag/release (#19984)
This means we can point to a single source of truth instead of
duplicating the content to the tag and GitHub release. Less to manage
and worry about when you make some updates to the changelog (maintenance
burden). We also get to avoid the content sitting in the vendor lock-in
GitHub releases.

We point to
`https://github.com/element-hq/synapse/blob/{repo.active_branch.name}/CHANGES.md`
as it will have the relevant changelog entry at the top and won't change
as we archive releases on `develop`. Even for RC releases after the main
release goes out, the entry will still be towards the top. We could try
to get the heading anchor for the specific section but I thought that it
wasn't necessary (nice but more complex).
2026-07-28 15:44:26 -05:00
dependabot[bot]andGitHub 5d55a05f8e Bump regex from 1.12.4 to 1.13.0 (#20013)
Signed-off-by: dependabot[bot] <support@github.com>
2026-07-28 14:02:43 +00:00
Olivier 'reivilibre 9761f9bc42 Merge branch 'master' into develop 2026-07-28 14:39:05 +01:00
Olivier 'reivilibre 0c709bbb78 1.157.2 v1.157.2 2026-07-28 14:02:12 +01:00
Olivier 'reivilibreandOlivier 'reivilibre 0a2456fef3 Validate room name, avatar and heroes' profile fields before sending down sliding sync as trusted fields
Fixes: https://github.com/element-hq/synapse/security/advisories/GHSA-jhcg-5392-5mjw
Fixes: https://github.com/matrix-org/internal-config/issues/1751

I introduce some stricter JSON types that don't break down to `Any` — it seems these have become possible since our last attempt.
(I'm pretty sure mypy wouldn't let you do this a few years ago.)
Our `dict[str, Any]` type is such a footgun. I'd like to spread this out further, but will do so after the security release.

I then use these stricter JSON types on everything the sliding sync handler pulls out of `event.content` and therefore get forced into a bare minimum level of validation, by the type checker.

-----

Reviewed-on: https://github.com/element-hq/synapse-private/pull/151
2026-07-28 13:58:06 +01:00
Olivier 'reivilibreandOlivier 'reivilibre c3adee3509 Restrict what errors we forward to clients from federation and policy servers
Fixes: https://github.com/element-hq/synapse/security/advisories/GHSA-95fh-hv8c-chvq
Fixes: https://github.com/matrix-org/internal-config/issues/1721

https://github.com/element-hq/synapse-private/pull/158 is the end goal, I think

-----

Reviewed-on: https://github.com/element-hq/synapse-private/pull/159
2026-07-28 13:58:06 +01:00
Olivier 'reivilibreandOlivier 'reivilibre 44216bf2b6 Limit the size of push rules
Fixes: https://github.com/element-hq/synapse/security/advisories/GHSA-fp53-rw9v-hcf9
Fixes: https://github.com/matrix-org/internal-config/issues/1071

Because this is an out-of-spec limit and could break someone's workflow on release day, I've opted to make it configurable.

-----

Reviewed-on: https://github.com/element-hq/synapse-private/pull/149
2026-07-28 13:58:06 +01:00
Olivier 'reivilibreandOlivier 'reivilibre 4f8037ab59 Anchor the ends of our path patterns to prevent path traversal vulnerabilities
Fixes: https://github.com/element-hq/synapse/security/advisories/GHSA-hgcg-p9gx-fq5f
Fixes: https://github.com/element-hq/backend-internal/issues/217 -> https://github.com/matrix-org/internal-config/issues/1760

-----

Reviewed-on: https://github.com/element-hq/synapse-private/pull/146
2026-07-28 13:58:06 +01:00
cbc6934821 Prevent theft of room aliases via remote join to room with illegitimate predecessor
Fixes: https://github.com/element-hq/synapse/security/advisories/GHSA-cjh7-rcpx-xpf8
Fixes: https://github.com/matrix-org/internal-config/issues/1729

---------

Co-authored-by: Eric Eastwood <erice@element.io>
Reviewed-on: https://github.com/element-hq/synapse-private/pull/136
2026-07-28 13:58:06 +01:00
Olivier 'reivilibreandOlivier 'reivilibre 68139e5234 Add missing 'event in expected room' check on /event_auth
Fixes: https://github.com/element-hq/synapse/security/advisories/GHSA-qcjr-46gf-7f4r
Fixes: https://github.com/matrix-org/internal-config/issues/1714

The `/event_auth` endpoint could be tricked to give you the auth chain for an event in a foreign room,
because it trusted the requester to provide the correct `room_id` for the event.

Now we pass the `room_id` through all the way to `get_event`'s `check_room_id`, which (correctly IMO) treats mismatches as unknown events (seems correct as it prevents divulging what events we know about).

The `test_event_auth_wrong_room_returns_404` test failed before the fix.

-----

Reviewed-on: https://github.com/element-hq/synapse-private/pull/147
2026-07-28 13:58:06 +01:00
Olivier 'reivilibreandOlivier 'reivilibre 3772859fbc Add missing same-room check on /get_missing_events handler
Fixes: https://github.com/element-hq/synapse/security/advisories/GHSA-27p5-4f45-gx76
Fixes: https://github.com/matrix-org/internal-config/issues/1717

I've tried my best to make the tests a good narrative for the thought process here,
but essentially the rationale is to make `/get_missing_events` not distinguish
between 'event is in wrong room' and 'event is unknown to me'.

-----

Reviewed-on: https://github.com/element-hq/synapse-private/pull/141
2026-07-28 13:58:06 +01:00
83672faf9c Add missing 'host in room' check for /timestamp_to_event/{roomId}
Fixes: https://github.com/element-hq/synapse/security/advisories/GHSA-r66v-qhwx-8rg4
Fixes: https://github.com/matrix-org/internal-config/issues/1718

---------

Co-authored-by: Eric Eastwood <erice@element.io>
Reviewed-on: https://github.com/element-hq/synapse-private/pull/144
2026-07-28 13:58:06 +01:00
Olivier 'reivilibreandOlivier 'reivilibre 51988c509f Fix spoofed to-device messages over federation not being dropped.
Fixes: https://github.com/element-hq/synapse/security/advisories/GHSA-rgv2-84w7-5j9p
Fixes: https://github.com/matrix-org/internal-config/issues/1520
Introduced in: d4a35ada28

The code was obviously intended to drop them,
but the if block only logged without actually taking any action.

-----

Reviewed-on: https://github.com/element-hq/synapse-private/pull/145
2026-07-28 13:58:06 +01:00
Olivier 'reivilibreandOlivier 'reivilibre e39303af40 Fix some paths being accessible at unintended locations with extra prefix components
Fixes: https://github.com/element-hq/synapse/security/advisories/GHSA-vh4c-pqh4-w3wq
Fixes: https://github.com/matrix-org/internal-config/issues/1703

The key thing to understand is that in `synapse/util/httpresourcetree.py`,
we create `UnrecognizedRequestResource` and then dangle children (with real resources) off them.

Since `UnrecognizedRequestResource` returns itself as a catch-all 'dynamic child',
this means any `UnrecognizedRequestResource`s with real children can have unlimited path components inserted between it and its child.

So `/_matrix/INSERTED/static/client/login/style.css` or `/_matrix/INSERTED/AS/MANY/AS/I/WANT/static/client/login/style.css` would unexpectedly resolve to the resource.

Client, Federation and Admin APIs wouldn't have been affected because you wouldn't get through the regex routing that they use.

-----

Reviewed-on: https://github.com/element-hq/synapse-private/pull/143
2026-07-28 13:58:06 +01:00
Olivier 'reivilibreandOlivier 'reivilibre e860184067 Fix multipart/form-data mitigation being case-sensitive.
Fixes: https://github.com/element-hq/synapse/security/advisories/GHSA-6wjm-9p2x-gvpm
Fixes: https://github.com/matrix-org/internal-config/issues/1740

Relevant library code to check against:

 - CPython `EmailMessage.get_content_type()`  (https://github.com/python/cpython/blob/v3.13.3/Lib/email/message.py#L608-L630)
 - Twisted `_parseContentType()` (https://github.com/twisted/twisted/blob/twisted-25.5.0/src/twisted/web/http.py#L306-L313)
 - Twisted `Request.requestReceived()` (https://github.com/twisted/twisted/blob/twisted-25.5.0/src/twisted/web/http.py#L1079-L1097)

-----

Signed-off-by: Olivier 'reivilibre <oliverw@matrix.org>
Reviewed-on: https://github.com/element-hq/synapse-private/pull/133
2026-07-28 13:58:06 +01:00
Eric EastwoodandGitHub c5e5abb822 Use weakref to Python DatabasePool to allow homeserver to cleanly shutdown (#20009)
Use weakref to Python `DatabasePool` to allow homeserver to cleanly
shutdown

Spawning from
[upgrading](https://github.com/element-hq/synapse-small-hosts/pull/420#discussion_r3661700252)
the Synapse version in Synapse Pro for small hosts and seeing our tenant
deprovision tests failing (end-to-end Complement tests). Specifically,
it was failing with a test in
[`TestLogging`](https://github.com/element-hq/synapse-small-hosts/blob/f24ee731ff419890680d1d80ef6eafe44f87f2c2/complement/tests/multi_synapse/logging_test.go#L328-L385)
in CI but was equally reproducible with any test where we deprovision
(`deployment.StopServer(...)`) like
[`TestProvisionHomeserverTenant/deprovision_homeserver_tenant`](https://github.com/element-hq/synapse-small-hosts/blob/f24ee731ff419890680d1d80ef6eafe44f87f2c2/complement/tests/multi_synapse/provision_test.go#L48-L58).

Clean homeserver shutdown specifically regressed with the changes from
https://github.com/element-hq/synapse/pull/19878. The
`PythonDatabasePoolWrapper` in Rust holds onto a reference to the Python
`DatabasePool` which references the Python `HomeServer` and keeps the
`HomeServer` from being garbage collected on the Python side.

This PR updates `PythonDatabasePoolWrapper` to use a weak reference.


### Dev notes

Previous PR where we had to deal with Rust <-> Python reference cycles,
https://github.com/element-hq/synapse/pull/19837#discussion_r3425358096

PyO3 garbage collector docs (`__traverse__`, `__clear__`):
https://pyo3.rs/v0.28.3/class/protocols#garbage-collector-integration


### Pull Request Checklist

<!-- Please read
https://element-hq.github.io/synapse/latest/development/contributing_guide.html
before submitting your pull request -->

* [x] Pull request is based on the develop branch
* [x] Pull request includes a [changelog
file](https://element-hq.github.io/synapse/latest/development/contributing_guide.html#changelog).
The entry should:
- Be a short description of your change which makes sense to users.
"Fixed a bug that prevented receiving messages from other servers."
instead of "Moved X method from `EventStore` to `EventWorkerStore`.".
  - Use markdown where necessary, mostly for `code blocks`.
  - End with either a period (.) or an exclamation mark (!).
  - Start with a capital letter.
- Feel free to credit yourself, by adding a sentence "Contributed by
@github_username." or "Contributed by [Your Name]." to the end of the
entry.
* [x] [Code
style](https://element-hq.github.io/synapse/latest/code_style.html) is
correct (run the
[linters](https://element-hq.github.io/synapse/latest/development/contributing_guide.html#run-the-linters))
2026-07-28 10:04:48 +01:00
Eric Eastwood 408b0936ac Revert "Add changelog"
This reverts commit befd2e2b7a.
2026-07-27 18:56:17 -05:00
Eric Eastwood befd2e2b7a Add changelog 2026-07-27 18:53:37 -05:00
Erik JohnstonandGitHub 51672809c7 Fix _get_server_keys_json invalidations over replication as JSON (#19966)
The cache key of `_get_server_keys_json` is a single argument which is
itself a `(server_name, key_id)` tuple, and `store_server_keys_response`
passed that nested tuple straight into the cache invalidation stream.
psycopg2 quietly serialises the inner tuple as a Postgres *record*, so
the `keys` column of `cache_invalidation_stream_by_instance` ended up
holding the record literal as a single string (e.g.
`{"(srv,ed25519:abc)"}`) — which never matches the real cache key on the
receiving side, i.e. the
invalidation has always been a silent no-op on workers. The native Rust
backend's stricter parameter conversion turns the same nested tuple into
a loud `TypeError: unsupported parameter type for postgres: tuple`.

Fix it the same way https://github.com/element-hq/synapse/pull/18899 did
for `_get_e2e_cross_signing_signatures_for_device`, which has the same
nested-tuple key shape: invalidate the local cache directly, JSON-encode
the key for the replication row, and decode it again in
`process_replication_rows`.

Found as part of the the effort to port the database pool to Rust.
2026-07-27 09:29:58 +01:00
Hugh Nimmo-SmithandGitHub 97fb38eca6 Return M_USER_LIMIT_EXCEEDED error code for media upload limits from MSC4335 (#18876)
This PR uses the new `M_USER_LIMIT_EXCEEDED` common error code instead
of the malformed `M_RESOURCE_LIMIT_EXCEEDED` error code (as reported by
#18749) (spec: MSC4335).

The error code is also changed from `400` to `403` as this matches what
is in the spec for the endpoints:
https://spec.matrix.org/v1.18/client-server-api/#post_matrixmediav3upload
and
https://spec.matrix.org/v1.18/client-server-api/#put_matrixmediav3uploadservernamemediaid
(albeit the latter says that `M_FORBIDDEN` would be returned)

By default a new built-in `media_upload_limit_exceeded.html` template
will be served for the `info_uri`. Administrators can specify an
external URI in config instead.

Compatibility is retained for any existing modules making use the
`MediaUploadLimit` (e.g. via the `get_media_upload_limits_for_user`
callback).
2026-07-24 16:39:04 -05:00
dependabot[bot]andGitHub a58ad44eb0 Bump the minor-and-patches group with 5 updates (#19982)
Signed-off-by: dependabot[bot] <support@github.com>
2026-07-24 09:23:18 +00:00
dependabot[bot]andGitHub eaba03b577 Bump pillow from 12.2.0 to 12.3.0 (#19983)
Signed-off-by: dependabot[bot] <support@github.com>
2026-07-24 09:21:30 +00:00
dependabot[bot]andGitHub c39c92e9c9 Bump setuptools from 82.0.0 to 83.0.0 (#19988)
Signed-off-by: dependabot[bot] <support@github.com>
2026-07-24 09:20:15 +00:00
dependabot[bot]andGitHub bdd53836ef Bump gitpython from 3.1.50 to 3.1.52 (#19990)
Signed-off-by: dependabot[bot] <support@github.com>
2026-07-24 09:19:25 +00:00
dependabot[bot]andGitHub 444564f0be Bump pyasn1 from 0.6.3 to 0.6.4 (#19989)
Signed-off-by: dependabot[bot] <support@github.com>
2026-07-24 09:10:04 +00:00
343581a1a0 fixes related to room v12 and sytest (#19898)
Co-authored-by: Andrew Morgan <1342360+anoadragon453@users.noreply.github.com>
2026-07-23 11:13:21 +00:00
Eric Eastwood 01ef46f95d Merge branch 'master' into develop 2026-07-22 13:57:25 -05:00
Eric Eastwood 2b5a0623ba 1.157.1 v1.157.1 2026-07-22 09:31:47 -05:00
Eric EastwoodandGitHub 7cb94ab266 Fix config regression around falsy experimental_features (None) (#19987)
Fix https://github.com/element-hq/synapse/issues/19986

Regressed in https://github.com/element-hq/synapse/pull/19539
2026-07-22 09:07:29 -05:00
Eric Eastwood 3622579e7b Merge branch 'master' into develop 2026-07-21 10:52:42 -05:00