Security
How MoxiVue devices join, sign and talk to each other
The protocol, written down so that it can be checked: what each exchange holds, what is signed and with which key, what is sealed to whom, and what the Dispatcher and the witnesses can see. Nothing here needs to be secret for the protection to hold.
Describes the current version. This description is free to read, quote and implement. It covers the protocol; the apps themselves are not open source. Found a flaw? Tell us.
Keys and the log
Every device makes its own keys, a separate key for each purpose and a fresh set for every account it joins. Every signing key below has two halves, and a signature counts only when both verify. A public key that has ever been in an account's log is refused if it appears again.
| Key | What it does | Where it lives |
|---|---|---|
| Log signing key | Signs log entries, proposals, confirmations, cancels and root requests, each under its own label | Every device; made there, the private key never leaves it |
| Channel key | Proves the device in TLS (as a raw public key) and signs the connection's binding and the login to the Dispatcher | Every device |
| Receipt key | Receives keys sealed to it: generation keys for consoles, settings document keys for machines | Every device |
| Record key | Signs what a machine records: readings, status, its audit records, recordings | Every machine |
| Executor key | Signs the root executor's results and audit records | The machine's root executor only; the agent never holds it |
| Generation key | 256 random bits per place (the whole account, a site, a group) and generation; derives the readers keys and the settings key | Only on the consoles that reach the place, sealed to each in the log; never on a machine, never in clear on disk |
| Readers keys | Eight key pairs per generation, one for each 24-hour period: machines seal to the public half | Public halves in the log; private halves derived on use by the place's consoles |
| Recovery key | Can only ask for a recovery | On paper, with one person. Nothing is ever sealed to it |
| Install-token key | Signs one scripted machine join | Derived from the token on the machine; public half in the log; used once |
| Witness keys | Cosign checkpoints and sign answers | The two witnesses; their public keys are built into the apps |
The account log. One log per account holds every join, removal, role, reach, place, person, generation, settings version, rotation, proposal and confirmation, install token and recovery request. Every structure that is signed or hashed is written in one canonical encoding (a strict subset of deterministic CBOR, integers only), and every signature carries a label of the form moxivue/<purpose>/v1, one per purpose, so a signature made for one purpose can never be used for another. Each entry names the cosigned checkpoint it was made on. The entries form an RFC 6962 Merkle tree, the one whose root the witnesses cosign. Every device holds the whole log and verifies it from the first entry, with the same rules for every device and for the Dispatcher's early refusal of rule breaks, which no device relies on.
The account fingerprint is the SHA-256 of the first entry, 64 hexadecimal digits. A scripted machine join pins it in full on the command line.
Algorithm tags. Every key, signature, sealed item and checkpoint starts with a tag naming its algorithm, and every entry a format version; a device refuses a tag it does not expect. A change to new algorithms is a signed, confirmed switch-over in the log, with no way back to the old ones. Every algorithm is a classical/post-quantum hybrid — X25519MLKEM768 and X-Wing (X25519 with ML-KEM-768) for key agreement, Ed25519 with ML-DSA-65 for signatures, X-Wing (HPKE) for sealing, AES-256-GCM for contents — so a future quantum computer breaks none of it. Post-quantum, in detail.
1. A device connects to the Dispatcher
Every device keeps one connection to the Dispatcher: TLS 1.3 and nothing older. It proves which device it is with its own channel key, bound to that one connection. No password and no account key is involved.
Because the login names the Dispatcher and carries the exporter value of this one connection, a party in the middle cannot pass it on to another connection, another Dispatcher or another account. The login only lets the Dispatcher relay and store for the device: every device still checks everything against the log itself, so a Dispatcher that skipped this check would gain nothing over the network it already is.
2. The witnesses: how a device knows its view is current
A signed log proves who made each entry, but not that you are shown the newest log. Two witnesses answer that. We run both, on servers of their own; they hold no key to your data.
A device seals data, serves a session, runs a root action or completes a recovery only at a checkpoint that both witnesses cosigned within the last hour and that equals its own log. A device's wall clock is used only to refuse times more than 5 minutes ahead: the Dispatcher is the network and could set it. An entry counts as done only once it is in a cosigned checkpoint. One witness that is down or hostile can stall an account; it cannot make a device accept a false view on its own.
3. A console joins with a code and three words
A new console proves it is talking to the inviting console, not to the Dispatcher, with a password-authenticated key exchange (CPace) over an 8-digit code. Three words from that exchange are compared by people.
The Dispatcher never sees the code or the words. A wrong code or wrong words end the run with nothing joined; after three failed runs in an hour the inviting console waits before it makes a new code, doubling up to an hour. The very first console to join a new account needs no second confirmation (there is no one else yet); every later console join does. CPace itself is classical: the three words, compared by people, are what a stronger attacker would also have to beat.
4. A machine joins with a number and three words
A machine joins with root's consent on the machine, a number typed into the console and three words typed on the machine, so both ends check each other.
Because the machine commits before the console reveals, a Dispatcher playing either side cannot pick values that make the number match. For scripted installs an owner or admin issues install tokens instead: 256 random bits each, for machines only, used once, valid 1 to 24 hours, refused while the account is in set-up. The machine joins with sudo moxivue machine join --token --fingerprint <account fingerprint>, reads the token from the terminal and never sends it: the token derives a one-time signing key whose public half is in the log's token entry, and the full account fingerprint on the command line pins the account. A wrong fingerprint ends the join with exit status 4.
5. A connection between two of your devices
A terminal, a forward, an SSH session or a request to a machine runs in a connection between the two devices: directly where the network allows, otherwise carried through the Dispatcher. Either way it passes bytes it cannot read.
No 0-RTT data and no resumption across a change of the log. A peer whose key is not in the log, was removed, frozen or rotated away, or that presents another device's key, is refused; so is a TLS 1.2 hello or a connection that sends data before the binding. State-changing actions carry a durable action id, so a replay is refused also after a restart. A session ends at the latest an hour after the peer's removal, demotion or move is cosigned.
Direct links. A direct link is QUIC between the two devices, found through the Dispatcher's address reflector, with the same TLS 1.3 handshake, raw keys and binding; the Dispatcher only tells each side its public address. A link that hears nothing for 15 seconds ends, and its sessions go on through the Dispatcher.
What the machine allows. A terminal opens only where the account switched terminals on for the machine's place (off as installed; switching on is an owner's or admin's sensitive change, confirmed by a second console), for a console on the machine's list of allowed consoles, and, where the machine asks each time, after the person at it says yes within a minute. A session ends after 15 minutes without typing or 8 hours in all, and the machine shows a banner with End while it is open. Forwards follow the same rules under their own switch: to a port of the machine itself, or to a device on a network the machine is directly connected to by cable or Wi-Fi, never through another router or into a VPN or container network; the machine resolves a name once and connects only to that address. SSH to a device behind a machine is a forward to its port 22.
6. Readings, status and settings: sealed per place
What a machine records leaves it sealed to the consoles that may read its place. The Dispatcher stores and forwards it by a header it can read, around a body it cannot.
Status is sealed and signed the same way, every 2 minutes, and names the machine's newest record sequence, so records the Dispatcher holds back show as missing, not as false. A removal, demotion, move or freeze starts new generations whose recipients are exactly the remaining consoles; machines stop sealing to the old one within an hour of the change being cosigned. A console reaching one site is never a recipient of another site's generations. Machines hold no generation key at all: they only seal. After the account's history period (400 days, or less if an owner shortens it) the Dispatcher deletes the sealed records of that time and every device erases the generation and readers keys they were sealed under, so nobody can open them afterwards. A settings document is encrypted under a document key, which is encrypted for the place's consoles under a key derived from G and sealed to each machine's receipt key; its version and hash are fixed by a signed settings entry, so a changed, older or swapped document is refused. If the witnesses cannot be reached for more than an hour, machines stop sealing and keep records in a buffer of up to 128 MiB per account.
7. A recorded session
A machine records every terminal session it hosts, and every SSH session with a key from the account, unless an owner switched recording off for its place. Only consoles that may play the recording can open it.
The recording holds what the terminal showed, with its timing (asciicast v2); what was typed is never recorded. The Dispatcher keeps recordings 90 days, or 30 to 400 days as an owner sets, up to 2 GB per account; while it cannot take them, they wait sealed on the machine for up to 30 days or 1 GB. A reader added later gets an older recording only through an owner, who re-seals that session's key to it and signs the copy under moxivue/recording-reseal/v1. The session starts with a line saying it is recorded, and a session whose recording cannot be started does not start.
8. Root actions and sudo: the root executor
The program that talks to the network never runs anything as root. A separate small root program, the root executor, acts only on a signed request it checks itself.
The kinds are fixed, and a request can name nothing else: starting a session's shell (operators and up), one sudo command in a session, and a packet capture (owners and admins covering the machine); an update of MoxiVue on the machine and a restart are defined the same way (owners and admins; for an update the machine upgrades its own MoxiVue package from the package source already on it, which checks the signature). Root on the machine decides which kinds are on (sudo moxivue machine kinds): after a join, updates, sessions and their sudo are on, restart and capture off. In a session's shell, every sudo asks the executor, which builds the request from what it knows itself (the session, a fresh nonce, the hash of the command) and waits 30 seconds for the console that opened the session to sign it: owners and admins only, the console unlocked with its computer's password. sudo outside a MoxiVue session is refused. Without a fresh witnessed head the executor runs nothing.
9. Recovery with the paper
The recovery paper is a signing key of its own, kept on paper by one person. It holds no key to data: all it can do is ask for a recovery, which every owner and admin sees for a week.
A cancel from a whole-account device that the request does not name ends it; a cancel from a named device, or one with a narrower reach, extends it by 14 days; two named devices of the same person end it. An ended request suspends the paper until it is replaced. On completion the new console becomes an owner, the named devices are removed, every place gets a new generation, the new console receives only keys made after it, and a new paper is shown once on a separate new console. The Dispatcher can withhold a cancel from the log; the witnesses' cancel lists still count.
What the Dispatcher sees
The account log is signed. Its structure is in clear, so that the Dispatcher can order it and the witnesses can cosign it; the names in it are sealed. Everything else a device stores there is sealed.
| Readable to the Dispatcher | Not readable to it |
|---|---|
| Sign-in email addresses and linked sign-in ids; the email address of a person invited to an account | Readings, history, processes, networks, events |
| The account log's structure: device, place and person numbers, roles and reach, device kinds (console or machine), public keys, which settings are switched on (terminals, forwards, recording, SSH, sudo, per-person accounts), SSH key grants, recovery requests | The names of the account, its sites and groups, and people (sealed in the log); the content of settings documents |
| Record, status and recording headers: which machine, which place, when, how large, which consoles may open a recording | What a record, a status or a recording holds; machines' host names (they travel only sealed) |
| Which device is connected, from which IP address, who talks to whom, when and how much | What is asked and answered; what is typed, shown or carried in a terminal, forward or SSH session |
| The messages of a code or machine join and the rendezvous text | The code, the three words and the number |
Devices have no names in the log at all; the names of the account, its sites and groups, and people are sealed there for the account's consoles, so the Dispatcher and the witnesses see only their numbers.
Who can do what
| Someone who controls | Can | Cannot |
|---|---|---|
| The Dispatcher, or a copy of its database | See everything in the left column above; refuse service; drop, delay, reorder or replay messages; hold back log entries or records, which then show as stale or missing | Read sealed data; forge or change an entry; add a device; make a machine act; make a stale checkpoint fresh; hide a removal for more than an hour past its cosignature |
| One witness | Stall the accounts it serves | Make a device accept a false view: freshness needs both witnesses |
| The network between a device and the Dispatcher | See that MoxiVue is in use, and how much | Anything more: the connection is TLS 1.3, and the content is protected again inside it |
| Your sign-in | Start a join as you, or create accounts in your name | Join a console without the code and the three words, confirmed by a second owner or admin console |
| One console of your account | What its role and reach allow; its sensitive changes wait for another owner or admin console | Act beyond its role; confirm its own change; stay after removal: it receives no key of a later generation |
| The machine agent (the program on the network) | Carry requests to the root executor | Make the root executor act: it checks every request itself and signs its own results |
| Your recovery paper | Ask for a recovery of its holder's own devices | Do so unseen or at once: every owner and admin console shows the notice for 7 days with Cancel |
Known limits
- Malware on a console running with the user's rights can read that console's keys and act as it, within its role and reach, until it is removed. The console lock (the computer's password for sensitive actions and sessions, 15 minutes at a time, locked with the screen) and the rule that an owner's or admin's computer hosts no sessions for others narrow this; they do not remove it.
- Metadata. The Dispatcher sees the account log and who talks to whom, when and how much, as listed above. Every member console sees the whole account's log too, because it verifies it.
- One hour. A machine may still serve a removed console, or seal to a replaced generation, for at most an hour after the removal is cosigned.
- Stalling. A Dispatcher or one witness can stop an account working; past an hour without fresh checkpoints, machines end sessions and buffer what they record.
- Recovery is slow on purpose: at least 7 days, 21 when a named device objects.
- A person can be talked into it. The words prove the joining console's key, not that the join is wanted. Compare them in person, or by voice with someone you know.
- CPace is classical. The code exchange rests on the discrete-logarithm problem; the three words compared by people are the backstop.
- Removal needs a confirmer. Out of set-up an account always has three whole-account owner or admin consoles, two of them owners, so any one can be removed by the others. When two of them fail at once, or before the third has joined, a stolen console may only be removable by its own person's recovery, or not at all, until the machines are moved to a new account.
- A removed machine's record key could, with the Dispatcher's help, add records that claim periods from before its removal; consoles mark such a record where they can see that it first appeared afterwards.
- The apps are not open source, so that the program you run does what this page says rests on our tests and on what you can observe from outside (below).
Checking it yourself
An exported audit trail can be checked with verify-audit5.py, a script in plain Python that is not ours to trust: it applies the same log rules as the apps, from the first entry. moxivue log verify does the same check with the app, and moxivue witness status shows both witnesses' last answers and whether your view is fresh. The audit export page describes the files.