Security review checklist¶
Use this checklist for every release candidate.
Authentication¶
- Password storage uses LibSodium Argon2id and never stores plaintext.
- Access tokens are CSPRNG-generated, high entropy, bearer-only, and stored as cryptographic hashes.
- Token comparisons avoid early-exit secret comparison.
- Public registration requires
security.registration.require_token=trueand an owner-onlysecurity.registration.token_file. - Admin bootstrap is explicit and cannot be claimed by enabling public registration.
Federation¶
- Request signatures are verified with Matrix canonical JSON.
- Event signatures are verified with Ed25519 for the expected origin.
- Key fetch, DNS, well-known, TLS, and private-address controls are covered by integration tests.
- Replay windows, backoff, quarantine, and audit logging are tested.
Federation worker IPC¶
- The main process and
merovingian-fed-workercomplete a mutually authenticatedcrypto_kxhandshake keyed from a hash of the operator master key (crypto::derive_ipc_auth_key) before any frame is trusted; a peer that cannot prove possession of the master key is rejected, fail-closed. - Frames are
crypto_secretstreamAEAD-encrypted after the handshake (crypto::IpcStreamCipher); a process that can read the socket pair without completing the handshake sees only ciphertext. - The Ed25519 signing key and inbound client credentials (
Authorization/X-Matrixheaders, access tokens) never cross the channel, and the worker cannot ask main to sign: there is nosign_requestframe, main answers one with an error and no signature without taking the runtime lock, and the worker's provider (crypto::RefusingEd25519Provider) refuses every sign and verify request (ADR-0078). - The worker refuses to start unsandboxed: on a platform with no seccomp equivalent, hardening failure is fail-closed (federation degrades to 503) rather than running unconfined while logging hardening as active.
- The worker is spawned with a minimal environment (
PATHonly), so no secret in main's environment leaks to the worker via inheritance.
Application Service API¶
as_token/hs_tokenare held inSecretBuffer, never logged, and compared withcrypto::constant_time_equal.- Namespace regexes are matched with
std::regex_matchagainst the whole identifier, neverstd::regex_search, so a namespace claim cannot swallow an unrelated identifier as a substring match. - Registration
idandas_tokenuniqueness and exclusive-namespace conflicts are enforced at boot (validate_registrations()); a conflicting set fails the whole set closed rather than resolving by load order. - Outbound transaction delivery (
appservice_client.cpp) is a deliberate, narrow exception to the outbound SSRF policy: it resolves the appserviceurldirectly and allows plainhttp://, because that URL comes from an operator-placed registration file, not network-reachable input — seedocs/threat-model.md, "Outbound Application Service API transaction delivery".
Identity Service client¶
- Every Identity Service host resolves to SSRF-safe pinned addresses via
federation::CachedServerDiscovery'sdeny_ip_rangesbefore any request is made; the client never performs its own DNS lookup and never accepts a client-supplied address, failing closed when resolution yields nothing usable. - Identity Service base URLs must be
https://; certificate and hostname verification apply and redirects are refused. - The homeserver's signing key and
Authorization: X-Matrixare never sent to an Identity Service; authenticated calls use a bearerid_access_tokeninstead, and the call site confirms the target is inserver.identity_server.trusted_serversfirst.
Push gateway delivery¶
- A pusher's gateway URL is attacker-influenced (any client can register one
pointing anywhere) and is independently re-validated as
https://with a path of exactly/_matrix/push/v1/notifybefore resolution is attempted. - The gateway host resolves through
federation::CachedServerDiscovery's pinned-address/deny_ip_rangespath, the same as federation and identity outbound calls. - Delivery, not registration, is bounded: at most
k_max_pushers_per_delivery(10) pushers are contacted per event, and at mostk_max_in_flight_push_deliveries(128) deliveries run concurrently process-wide; both caps live inroom_service.cpp. notify()and the delivery scheduler both re-checkserver.push.enabledbefore any DNS lookup, connection, or byte leaves the process.
Cross-signing over federation¶
- A stored key signature is merged into a published key only through
federation::merge_visible_key_signatures(),published_device_keys(), orpublished_cross_signing_key()(key_signatures.hpp) — never by hand elsewhere. - An uploaded signature is shown only to its uploader's own audience unless the uploader owns the key, so an upload cannot speak for another user (ADR-0060).
- Remote users' cross-signing keys are proxied from their own server at query
time and never cached locally;
cross_signing_keysholds local users only (ADR-0061).
Persistence¶
- Runtime data is durable in SQLite or PostgreSQL.
- All write paths use prepared statements and transactions.
- Schema upgrades, downgrades, and incompatible schemas fail safely.
- Secrets and event contents are redacted from logs, metrics, and SQL summaries.
Runtime hardening¶
- Linux packages use a locked-down systemd unit.
- BSD packages use an unprivileged service user and isolated data/log paths.
- Core dumps are disabled for secret-bearing processes.
- Production health fails if required hardening is disabled or unknown.
Media¶
- Upload size, MIME validation, content hashing, quarantine, and admin actions are integration-tested.
- Remote media fetches resolve the origin server via federation discovery,
reject resolved addresses that are private or loopback
(
media::remote_media_fetch_policy), and re-resolve and re-pin anyLocationredirect before following it (resolve_media_redirect_url()); fetched bytes are never marked scanner-clean and are quarantined by default (security.media.remote_fetch_media_policy). - Any decoder runs in a sandboxed worker with no network.
Release evidence¶
- Alpha tags matching
v*-alpha*publish hardened Linux and FreeBSD tarballs plus SHA-256 checksum files through.github/workflows/release.yml. - CI (
ci.yml, covering Linux and BSD build/test jobs), sanitizers (sanitizers.yml), static-analysis (static-analysis.yml), fuzz (fuzz.yml), and reproducible-build (reproducible-build.yml) workflows passed, andscripts/check-release-readiness.sh(run fromci.ymlandrelease.yml) passed. - CodeQL (
codeql.yml), secret-scanning (secret-scan.yml), dependency-triage (dependency-vulnerability-triage.yml), and SBOM (sbom.yml) workflows passed for the release candidate commit. - Dependency vulnerability triage artifacts were reviewed before tagging.
- SBOM and package checksums are attached.
- Release artifacts are signed with a detached GPG
.ascsignature by the maintainer key (.github/workflows/sign-artifacts.yml; verify withgpg --verify <file>.asc <file>).