Skip to content

Security & privacy

What's protected, what isn't, and where the sharp edges are. Written to be useful rather than reassuring.

The one that matters most

Channels are end-to-end encrypted by default — every channel created on an instance whose deployment can support it, Megolm-encrypted from the moment it exists, with no switch to find. Keys live on members' devices; the instance and its homeserver store ciphertext they have no key for, and file contents are encrypted in the browser before upload.

Against an operator who reads the database, or whose backups leak, or who is handed a subpoena, that works. Setting up encryption gives you a recovery key, backs your message keys up to the server encrypted to a key the instance never sees, and has your devices verify each other by comparing emoji.

"Can support it" is about the deployment, not the software. It takes a homeserver clients can actually reach and an instance that records what its homeserver pushes. Without both there is nowhere for your keys to live except the server — so those instances get plaintext channels and an interface that says so, rather than a lock icon over text the operator can read. The default LXC install is like this, and so is any channel created before encryption became the default.

Three limits, stated here rather than discovered later:

Nothing here should be described as private in the way Signal is private. That sentence surviving this feature is deliberate.

What is protected

AreaHow
Passwords scrypt hashes. Never stored or logged in plaintext.
Sessions Signed JWT in an httpOnly, SameSite=Lax cookie. JavaScript on the page can't read it. Stateless — see the gaps below.
Desktop credentials Stored in the operating system's keychain, not in browser storage.
Identity tokens (if you enable SSO) Ed25519, valid five minutes, and bound to a single instance — a token minted for one instance is refused by every other. Verified against a cached key, so the identity service going down blocks new sign-ins but logs nobody out.
Backups Written 0600, with a checksum per component verified before any restore — and encrypted at rest when you set a backup passphrase.
Brute force Login attempts rate-limited per IP and email — ten per fifteen minutes.
Matrix tokens Device-scoped and minted per client. In the browser they live in memory only and are never persisted; sessions are listable and individually revocable.
Encrypted channels Megolm. Keys on members' devices, ciphertext on the instance, and room keys shared only with cross-signed devices — an unverified device receives none.
Every read and write Membership and role checked server-side. The client's opinion about what it's allowed to do is never trusted.
File downloads Streamed through the app with membership checks, not from a public IPFS gateway. Content identifiers don't leak outside the channel.
Homeserver registration Disabled outright. Accounts are created by the app using a shared secret that never leaves the server process, so nobody who finds your homeserver directly can sign up on it.
Traffic in transit HTTPS whenever you use a public address. Between services, traffic stays on loopback or a private container network.

What listens where

Only two ports are reachable from outside the machine. Everything else is bound to loopback deliberately. Publishing the homeserver for direct client connections — which encrypted channels require — routes through the same HTTPS front door as the app rather than opening anything new.

PortReachable fromWhat
3000your networkthe app
4001the internetIPFS peering
6167 / 8008loopback onlyMatrix homeserver
5001loopback onlyIPFS admin API
5432loopback onlyPostgreSQL
Why 5001 matters The IPFS API is an unauthenticated admin socket — anyone who can reach it controls the node completely. It was briefly published to the host in an earlier version of the compose file. It isn't now, and it shouldn't be in any deployment you build.

Known gaps

Stated together rather than scattered, because a gap you have to hunt for is a gap that gets missed.

The full analysis — sixteen threats with attacker capabilities, mitigations, and residual risk each — is in the threat model. Mechanism-level detail is in the security architecture.

Federation is off by default

Your homeserver talks to no other Matrix server until you set MATRIX_ALLOW_FEDERATION=true. Turning it on is a real decision: it means other homeservers can reach yours, your users can join rooms elsewhere, and your server name becomes publicly meaningful.

Federation is genuinely valuable — it's much of why Matrix is worth building on. It just shouldn't happen by accident.

If you use a sovrgnnet.cc account

Signing in with a sovrgnnet.cc account is optional — every server also issues its own local accounts, and an operator can refuse the shared ones entirely. If you do use one, this is what it means:

The desktop app signs in with a device code you approve in your own browser, rather than a redirect back into the app — a redirect would hand a sign-in token to whichever application had registered that link type, which isn't something any operating system verifies.

What you're responsible for

Self-hosting means the security boundary is yours. Concretely:

Reporting a vulnerability

Please don't open a public issue for a security bug. Email [email protected] with what you found and how to reproduce it. You'll get an acknowledgement, and credit in the changelog if you want it.

This is alpha software that has not been independently audited. It's written carefully and reviewed honestly, but those aren't the same thing as an audit, and it would be wrong to imply otherwise.