Skip to content

Draft. This text can change at any time, and there's no version history until the draft is frozen. Comments stay attached to the passage they were made on; if it changes, they move to its section, marked as on an earlier version.

Security claims and their evidence

This page answers one question: for each thing the Tieroute specification says an attacker can't do, what shows that it holds? It lists every claim in §17.1 and the "Can't" column of §17.2, the attack that tests it, and where that test lives. Section references (§) are to the Tieroute protocol specification.

A test shows that one implementation stops one attack. It doesn't prove that no other attack works. That's what the outside cryptographic review is for (§17.4), and this page marks the claims that only a review, or a formal model, can settle.

How to read the status

Status Meaning
Attacked The attack suite in attacks/ runs the attack against two real homeservers, from outside, and checks that it fails. Any implementation can run it.
Tested A unit test or test vector in rerouted (the reference server), the reference client, or spec/vectors/ checks it. These run inside one implementation, or check bytes rather than behavior.
By construction No test is needed: the data that would be required never reaches the party in question. The reason is given.
Not tested The feature is built and the reason it holds is given, but no test tries the attack yet.
Needs review It rests on reasoning that tests can't settle: the security of Messaging Layer Security (MLS) itself, or how Tieroute's pieces compose.

Every attack test was checked against a broken server: for each protection, the suite was run against a copy of rerouted with that protection switched off, and the matching test failed. A test that passes with the protection off proves nothing, and one did, until it was fixed (see the end of this page).

Your own homeserver, malicious or compromised

Claim Status Evidence
Can't read direct chats or group chats Tested; needs review Every message a device sends, commits included, is an MLS PrivateMessage with no plaintext in it (servers_only_ever_carry_ciphertext in the reference client). The homeserver never holds a group's keys. That MLS ciphertext can't be read without them is RFC 9420's guarantee, not something a test can show.
Can't read files or backups By construction Files and backups are encrypted on the client before upload (§7.5, §14), and rerouted stores only the bytes it's given. The reference client encrypts both before media/upload and backup/put.
Can't forge messages By construction; needs review Every app-message is signed by the sender's device inside MLS, which also authenticates senders. The server never has device keys.
Can't forge chain entries Attacked TestHomeserverCantForgeChainEntries: a homeserver pushes its user's chain with an entry signed by its own key, and another with an entry altered after signing. The receiving server refuses both and keeps its copy. Also TestVectorChainValidates and chain.json.
Can't forge profiles Attacked TestProfilesNeedADevice: a profile signed by a key that isn't a device of the identity is refused. Clients verify every profile they're given against the chain (§13.1). The reference client does that before showing one, and no client test tries a forged profile yet.
Can't keep you from moving Tested TestDeadOldHomeRecoveredByRecoveryCode: a recovery-signed move is accepted although the old home is gone. TestRecoveryForkPropagates.

A contact's homeserver

Claim Status Evidence
Can't read content By construction It carries the same ciphertext as your own homeserver (above).
Can't impersonate you to its user Attacked TestContactsServerCantImpersonate: a server that's home to one of Ravi's contacts delivers a message as Kat, who lives elsewhere, and gets 409 wrong-home; nothing reaches Ravi. TestRelayedContactRequestMustComeFromHome: that server replays a real contact request Kat sent to its own user, and it's refused, because Kat's chain names her real home. TestServerCantForgePresenceOrReports: it can't send presence for Kat, or a report in her name, either.

A group's hub

Claim Status Evidence
Can't read messages By construction The hub sees commits, which are PrivateMessages, and never application messages (§11.6).
Can't add members Needs review Adding a member takes an MLS commit from a member; the hub only orders commits it receives, and can't make one. This rests on MLS.
Can't accept junk commits from outsiders Attacked TestOutsiderCantFreezeGroup: an outsider who knows the group ID commits through the hub's own server and over server-to-server (S2S), signed with the wrong key, and is refused with bad-signature. A commit whose bytes don't match its request is refused too. The member then commits normally, so the group isn't frozen.
Can't give different members different commits without it showing Attacked; tested TestHubAcceptsOneCommitPerEpoch: an honest hub accepts one commit per epoch, and its receipt verifies with the hub's published key and names that commit's hash. A lying hub can sign two; a client that sees a second commit with a valid receipt for an epoch it applied stops following the group and warns (a_second_commit_for_an_applied_epoch_freezes_the_group in the reference client). Members only notice if one of them sees both.
A lost reply doesn't strand an accepted commit (§11.6) Attacked TestLostReceiptRetry: the hub accepts a commit and the reply is lost; the committer's homeserver retries the same request and gets the same receipt, and the commit's Welcome arrives. A different commit for that epoch still gets stale-epoch. Retrying an epoch-key registration that already took effect also succeeds (TestRegFallback).

A thief with one of your devices

Claim Status Evidence
Can't rotate your recovery code Attacked TestThiefCantRotateRecoveryCode: a device-signed set-recovery is refused with invalid-chain.
Your other devices are alerted at once Attacked TestThiefsChangesAlertTheOwner: a new device raises a notice, and removing a device raises the alarm. The removed device's session ends immediately.
Changes don't stick if you act within 72 hours Attacked; tested TestRecoveryUndoesThiefsMove: the thief moves the identity away through its real homeserver; a device-signed fork is refused; the recovery fork is accepted, and the contact's server switches to it. The 72-hour boundary itself can't be reached in a live test, since homeservers refuse entries dated more than 10 minutes back: TestVectorFork and fork.json cover both sides of it, and both orders of arrival: a late recovery entry gives way to the final device branch whichever arrived first.
Can't move your identity without your homeserver seeing it Attacked; tested TestThiefCantMoveIdentityBehindHomeserversBack: the thief signs a move to their own server, which pushes it to a contact's server. The move isn't accepted, and the thief's server can't then deliver as the victim. vouch.json has 11 cases, checked by rerouted and independently by check.mjs, including a first fetch while the previous home can't be reached, which is refused rather than taken on trust.
Working with your homeserver, can't keep your identity (§17.2) Needs review A colluding homeserver can let a thief backdate entries past the 72-hour fork window. What still holds is that the recovery code outranks every device and can move the identity anywhere: TestDeadOldHomeRecoveredByRecoveryCode shows a recovery-signed move accepted without the old home's help.

A spammer with free identities

Claim Status Evidence
Can't reach anyone who hasn't published or given them a code Attacked TestNoCodeNoRequest: requests with no code or a wrong code, from the same server, another server, and over S2S, all get the same 202 as a real one, and none arrive. TestCodeGuessingIsLimited: after 20 wrong codes in an hour from one server, even the right code from it is dropped.
Can't message anyone who hasn't accepted them Attacked, with a known gap TestStrangerCantMessageOrAddToGroup: deliveries from strangers are answered like any other and dropped. Known gap: group IDs aren't secret, so a stranger who learns one can put junk deliveries into that group's members' inboxes. Clients drop them, since they aren't valid MLS, and servers rate-limit them (§10.3). Open question: whether to close this fully with a per-epoch delivery key that members sign deliveries with.
Can't add anyone to a group Attacked TestStrangerCantMessageOrAddToGroup: a Welcome from someone who isn't a contact is dropped.
Can't drain key packages Attacked TestStrangerCantClaimKeyPackages: claims from strangers on the same server, another server, and over S2S are refused, and the target's package is still there. Claims by contacts are rate-limited too (§11.4; rerouted: 50 a day per claimant and target), which no attack test tries yet.
Can't multiply per-origin limits with subdomains of one domain (§9.5) Tested Per-origin limits count the registrable domain (TestOriginKey): spam1.evil.example and chat.evil.example share them, while sites on a shared host such as github.io don't.
Can't register without an invite Attacked TestRegistrationNeedsInvite.
Can't reach someone who blocked them, whatever code they carry (§9.6) Attacked TestBlockedIdentityCantReach: answered 202 like any request, and dropped.
Can't ask again for 30 days after a decline, even with a new code (§9.3) Attacked TestDeclinedRequesterWaits.

Privacy: presence and status messages

Claim Status Evidence
Presence and status messages go only to mutual contacts (§13.2, §18.2) Attacked TestStrangersDontSeePresence: strangers on the same server and on another server never get them. Once a contact removes the user, they stop getting them too, though the user still lists them: checked on the user's own server, and across servers, where the receiving server checks again.

Verification codes and clients' own chain copies

Claim Status Evidence
A matching code means nobody is in the middle, and both sides see the same chains (§6.7) Tested; needs review The reference client reproduces codes.json (verification_code_vector), and a code changes with either chain head (code_ignores_argument_order). A real round between two homeservers gives both sides the same code. That an attacker gets one guess per round rests on the commit-then-reveal design, which is for the review.
A homeserver can't roll back or swap a contact's chain without the client noticing (§6.5) Tested The reference client applies the fork rule to every chain it fetches, against its own copy, and passes every case in fork.json (fork_vectors).
A verified contact's new device is flagged Tested A real round, then the contact links a device: the verifier is told "@ravi has a device you didn't verify."

Group membership

Claim Status Evidence
Someone who can add you to a group can't slip a lookalike into one of your conversations by naming it in replaces (§11.11) Tested demo/groups.sh: someone who isn't in a group creates one that claims to replace it. The reference client keeps it a separate chat, with no offer, and the real group keeps working. A member's honest re-creation is only offered, with who's missing from it; the old chat keeps sending to the old group until the user switches.
Replacing a member's only device doesn't take them out of the group, or cost them their admin role (§11.9) Tested demo/groups.sh: an admin with one device replaces it, a member who isn't an admin commits the swap in one commit, and she's still a member and an admin, and can send.
Members agree on a commit whatever the age of their copies of chains (§7.1) Attacked Every member judges a commit's devices as of its hub receipt time, on a copy of the chain fetched after it, and doesn't decide when it can't get one. demo/adversarial.sh, across three servers with real MLS: members whose copies of a chain differ agree on a Remove by a member who isn't an admin; an approval signed by a revoked admin device doesn't authorize an Add; a revoked device key can't be added as a leaf; a forged Welcome can't pass as the user's own re-creation. A copy days behind, and the 24-hour wait before offering re-create, aren't tested yet.

Moving between servers

Claim Status Evidence
Someone who has seen a move can't register it on the new server first, with allowlists of their choosing (§6.6) Attacked TestMoveNeedsTheOwnersSignature: the move's chain is registered with no signed move, and with one signed by a stranger, putting the observer on the user's contact list. Both are refused; the user's own signed move then goes through, without the observer.
A home you moved away from can't act in your name, beyond delivering what it had queued (§6.6) Attacked TestOldHomeOnlyDeliversDuringGrace: within the 72-hour grace window, the old home's claim of a contact's key packages as the user is refused with 403, the new home's is accepted, and the old home's deliveries still go through.

Server-to-server requests

Claim Status Evidence
Requests can't be replayed, redirected, or reused for another endpoint Attacked TestS2SRequestsAreBound: a replay, a request for another server, one posted to another endpoint, a stale one, one signed with a key the server doesn't publish, and one claiming to come from another server are all refused with 401. s2s.json has the signing input.
Signatures verify the same way everywhere Tested ed25519.json: small-order and non-canonical points, and out-of-range scalars, rejected by rerouted, the reference client, and check.mjs alike.

Built, not yet attacked

These features are built, and each claim has a reason to hold, but no attack test tries to break it yet.

Attacker Claim Status Evidence
A group member who isn't an admin Can't add anyone without an admin's signed approval, or remove other members Not tested The reference client rejects a commit that adds someone without a valid admin approval, and ignores answers to add requests from members who aren't admins (§11.8).
Someone with your recovery code Can't read messages from before they added a device, unless they also restore your backup Needs review Rests on MLS forward secrecy (removed_device_loses_access covers the related case of a removed device). The backup is the exception: the recovery code opens it (§7.4, §17.3).
A network observer Can't read content, or see devices' IP addresses during calls Not tested TLS plus end-to-end encryption. Calls are relay-only by default (§15.2), and demo/calls.sh with RELAY=1 checks that each homeserver's relay carried the call. No test watches for leaked IP addresses.
Calls DTLS-SRTP (Datagram TLS, keying the Secure Real-time Transport Protocol) plus SFrame (Secure Frame) keyed from MLS Tested; needs review sframe_vectors checks the SFrame keys derived from the MLS group against sframe.json, and epochs_change_mid_call checks that a call keeps working across a group change.

Needs outside review

These can't be settled by tests, and are the reason for the review in §17.4:

  • The composition: identity chains, vouching, epoch keys, the reg fallback, receipts, and SFrame keyed from MLS, taken together. Each test above checks one attack; the review asks whether there's an attack nobody wrote a test for.
  • A future quantum computer: key exchange is hybrid ML-KEM-768 (Module-Lattice-Based Key-Encapsulation Mechanism) + X25519 (uses_the_pinned_post_quantum_suite checks the suite), but what that buys rests on the ML-KEM and X-Wing analyses.
  • MLS itself, which Tieroute uses and doesn't change (RFC 9420).

What building this found

  • rerouted answered 200 with an empty list when asked for a group's missed commits by a member whose homeserver isn't the group's hub. An empty list tells a client there's nothing to catch up on. It answered 501 not-implemented until S2S commit/fetch was built, and now fetches the commits from the hub (TestCommitFetchThroughRemoteHub).
  • The first version of the presence test only checked strangers, who were never in the user's contact list anyway, so it passed with the "mutual contacts" check switched off. It now also checks a contact who removed the user, on the same server, where that check is the only defense.
  • The first version of the thief test backdated the move, as a real thief would. The chain then broke the rule that timestamps never go backwards, so every server rejected it for that reason, and the test passed even with vouching switched off. It now uses a current-time move, which only vouching stops.

Run it

./conformance/run.sh

It builds rerouted, starts two homeservers in dev mode with the suite's two rogue servers mapped as peers, and runs every attack. It takes under a minute.

© 2026 Reroute Holdings LLC

A public draft of Tieroute v1, open for comments.