Repository navigation
fix(deploy): keep Synapse's admin API off the internet - #160
Merged
Merged
Conversation
argiad
added a commit
that referenced
this pull request
Nov 8, 2026
The all-in-one try-it image's nginx sent every path it does not route elsewhere to Synapse, so /_synapse/admin/* was reachable on the published port 8008. /_synapse/admin/v1/server_version answered without a token. nginx now returns 404 for /_synapse/admin, as Synapse's reverse-proxy guidance asks and as PR #160 does for the Traefik stack. Nothing in the image needs it through nginx: mm-core's homeserver URL and the init's SYNAPSE_URL are both http://127.0.0.1:8009. The image smoke test now asserts /_matrix/client/versions is 200 and /_synapse/admin/v1/server_version (plain and percent-encoded) is 404 through :8008. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
argiad
force-pushed
the
fix/block-public-synapse-admin
branch
from
November 8, 2026 22:09
afb19b0 to
c37259c
Compare
The Synapse catch-all router sent every path on matrix.<domain> that MatrixMedia does not serve to Synapse, /_synapse/admin included. Synapse's reverse-proxy guide says not to expose it; /_synapse/admin/v1/server_version even answers without a token. - Traefik: the catch-all excludes /_synapse/admin in BOTH routers that carry it, the file router and the docker label on the synapse service. Traefik runs both providers, so excluding it in one alone changes nothing. No router matches, so Traefik answers 404. /_matrix and /_synapse/client are routed as before. - Helm: an Ingress cannot exclude a prefix, so the Synapse ingress now allow-lists /_matrix, /_synapse/client and /.well-known/matrix instead of routing /. - self_smoke fails when /_synapse/admin/v1/server_version answers publicly. mm-core reaches the admin API at MM_MATRIX_HOMESERVER_URL (http://synapse:8008, compose and Helm) and the installer through docker exec, so neither goes through Traefik. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
argiad
added a commit
that referenced
this pull request
Nov 8, 2026
The all-in-one try-it image's nginx sent every path it does not route elsewhere to Synapse, so /_synapse/admin/* was reachable on the published port 8008. /_synapse/admin/v1/server_version answered without a token. nginx now returns 404 for /_synapse/admin, as Synapse's reverse-proxy guidance asks and as PR #160 does for the Traefik stack. Nothing in the image needs it through nginx: mm-core's homeserver URL and the init's SYNAPSE_URL are both http://127.0.0.1:8009. The image smoke test now asserts /_matrix/client/versions is 200 and /_synapse/admin/v1/server_version (plain and percent-encoded) is 404 through :8008. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
argiad
force-pushed
the
fix/block-public-synapse-admin
branch
from
November 8, 2026 22:13
c37259c to
7584e9e
Compare
argiad
enabled auto-merge (rebase)
November 8, 2026 22:54
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The
matrix-synapsecatch-all sent every path onmatrix.<domain>that MatrixMedia does not serve to Synapse, so/_synapse/admin/*was reachable from the internet. Synapse's reverse-proxy guide says to proxy/_matrixand/_synapse/clientand not to expose/_synapse/admin. The admin endpoints still needed an admin token or the registration shared secret, so this is defence in depth, not an open hole. The one exception was/_synapse/admin/v1/server_version, which answers without a token. (The version is also public through/_matrix/federation/v1/version, so that leak is not new.)Found while fixing the leftover registration sessions in #159.
Changes
Traefik (compose stack): the catch-all now also has
!PathPrefix(/_synapse/admin), in both routers that carry it:deploy/templates/traefik-dynamic.tmpl.yaml;matrix-synapsedocker label on thesynapseservice indeploy/docker-compose.tmpl.yml.Traefik runs both providers, so
matrix-synapse@fileandmatrix-synapse@dockershare one priority pool. Excluding the path in only one of them changes nothing; the TDD run below shows this. With no router matching, Traefik answers 404.Helm:
deploy/helm/synapse/templates/ingress.yamlrouted/. An Ingress cannot say "everything except this prefix". A deny path pointed at a dummy backend behaves differently per controller (Traefik skips a path whose backend is missing, so the request would fall through to/). The ingress therefore now allow-lists/_matrix,/_synapse/clientand/.well-known/matrix./.well-known/matrixstays because the dev overlay'sserverNameis the ingress host. ArgoCD syncs frommain, so no cluster changes until a release.Self-check:
self_smoke(install.sh,mmctl check,mmctl upgrade) now runsprobe_gatedon/_synapse/admin/v1/server_version, next to the mm-switch gates. It fails on a 200.CHANGELOG
[Unreleased] → Securityentry.What I checked for legitimate public use of
/_synapse/adminregister, bot invite login)cfg.matrix.homeserver_url, set asMM_MATRIX_HOMESERVER_URL: http://synapse:8008in compose andhomeserverUrl: "http://synapse:8008"in the Helm mm-core valuesweb/)/_synapse/*; it goes through mm-core's/_mm/admin/.../synapse/usersproxy ("the browser never sees Synapse credentials")deploy/)/_matrix/client/v3/{login,logout,account/whoami}; registration goes throughdocker execintomatrixmedia-synapse-1athttp://localhost:8008(#159)Production/ios,android,fluffychat)/_synapse/adminreferences. The vendored matrix-js-sdk inProduction/webhasisSynapseAdministrator/whoisSynapseUser/deactivateSynapseUser, but nothing calls themdocs/user-management.md, which usesHS="http://localhost:8008"(local dev); no guide tells operators to curl the public hostTo call the admin API by hand, do it on the server:
docker exec -it matrixmedia-synapse-1 sh, thencurl http://localhost:8008/_synapse/admin/…(CHANGELOG says so).Verification
TDD: the new tests failed first for the right reasons. With only the file router fixed, the routing test still failed with
/_synapse/admin -> matrix-synapse matrix-synapse@docker, which proves it guards both providers. A planted- path: /_synapse/adminin the Helm template makes the Helm guard fail.New tests in
deploy/test/traefik.bats:/_synapse/admin*reaches no router, and that/_matrix/client, federation, media, key,/_synapse/client/*and/.well-known/matrix/clientstill reach Synapse. Rule syntax it does not know fails the test instead of guessing.helm template(skips without helm).self_smokewiring.bats deploy/test/*.batsunder macOS bash 3.2: 353/353 ok (one existing skip: chown needs root). shellcheck run as indeploy-lint.yml: clean.The CI
render-validatesteps run locally: all templates render clean,docker compose config -qOK.The
helm-validatesteps run locally: dependency build, lint, dev and prod overlays all OK. The dev Synapse ingress renders['/_matrix', '/_synapse/client', '/.well-known/matrix'].Real Traefik v3.5.6 + Synapse v1.163.0 (throwaway local containers, removed afterwards):
/_synapse/admin/v1/server_version{"server_version":"1.163.0"}/_synapse/admin/v2/users//,/./,/../and%61dminvariants/_synapse%2Fadmin/…,/_SYNAPSE/admin/…,/_synapse/Admin/…/_matrix/client/versions,/_matrix/federation/v1/version,/_synapse/client/…probe_gatedflags the old config as PUBLIC and passes the new one.Existing installs:
render_onerewritestraefik-dynamic.yamlin place, and Traefik v3.5 watches the file itself (add watcher on: /etc/traefik/dynamic.yaml), not only its directory. Aninstall.shre-run therefore applies the file change without a Traefik restart; I confirmed this live. The label change recreatessynapsethroughcompose up -d(Synapse restarts once). Plainmmctl upgradedoes not re-render templates, so this ships with the next release's installer (mmctl upgrade --to).Not touched (code-only PR): the dev/prod servers.
🤖 Generated with Claude Code