The threat model
Ubimate assumes the machine in front of you is yours and the server is not. Everything follows from that.
What the design defends against: a hosting provider reading your documents, an operator of the sync service being compelled to hand them over, a stolen backup of the server's database, and a compromise of the server process itself. In each of those cases the attacker holds ciphertext and wrapped keys, and the material that would open them was never there.
What it does not defend against: someone with access to your unlocked device, malware running as you, or a password weak enough to guess. Local data is deliberately stored unencrypted so that it stays readable without our software — the protection there is the operating system's, so use full-disk encryption on anything that travels.
Local mode
The desktop app has no server in it. Documents, Yjs history and media live in a SQLite database and a folder on your disk, and every feature — editing, search, tables, panels, the HTML mirror — runs against that copy.
No account exists, nothing is transmitted, and there is no cryptography in the way: the local database is plain, on purpose. That is what lets you open it with any SQLite tool, back it up as a file, and read your content the day you stop using Ubimate.
If you never turn on sync, nothing in the rest of this page applies to you. There is no server holding anything of yours to protect.
Keys and authentication
Your keys are derived from your credentials on your own device, and derived the same way every time, so there is no keypair to store, sync or recover.
username (permanent, chosen at registration) │ ▼ UUID(SHA-256(normalise(username))) user_id → used as the Argon2id salt │ password ──┤ ▼ Argon2id (client-side, high cost) 32-byte seed ├─▶ Ed25519 signing keypair (who you are) └─▶ X25519 agreement keypair (what you can open)
Logging in is a signature, not a secret: the server issues a single-use nonce with a short lifetime, your device signs it with the Ed25519 private key, and the server checks that against the public key it holds. It stores your username, your two public keys and a contact email — no password, no hash, nothing that could be cracked offline.
The username is permanent because it is an input to the derivation: change it and you change the keys, and everything sealed under the old ones stops opening. The contact email is separate and can be changed whenever you like.
The cost of having no back door
A forgotten password cannot be reset into readable data. Your local workspace is unaffected and can be re-synced under new credentials, but content that exists only in the cloud, under a password nobody has, stays closed. There is no recovery path on our side, because a recovery path we could use is a recovery path we could be compelled to use.
Content encryption
Each workspace has its own random symmetric content key. Everything bound for the server — document properties, Yjs updates, attachments — is encrypted with it on your device before it is sent.
The content key itself never travels in the clear. It is sealed to each
member's X25519 public key with crypto_box_seal and stored in
that wrapped form, so a member unwraps their own copy locally and the server
holds envelopes it has no key for.
Collaboration without a trusted middle
Live editing uses CRDTs (Yjs), which converge without anything in the middle understanding the content. The relay moves encrypted update blobs between clients, stores them as it received them, and merges nothing it can read. Compaction — folding many updates into one snapshot — is CRDT arithmetic on opaque state, not a read.
Inviting someone means sealing the workspace key to their public key, which the sender signs so the recipient can verify where the invitation came from. Removing a member removes their access; rotating a workspace key after a revocation is on the roadmap rather than automatic today.
What the server sees
Encryption hides content, not the existence of things. These are the fields the sync service necessarily observes, and why each one is there.
| Visible | Why it has to be |
|---|---|
| Document tree: ids, parent, type, position | Routing and ordering. The type is the one you chose when you made the block, not something inferred from what is in it. |
| Timestamps | Sync ordering and conflict resolution. Sorting and filtering by them happens on your device. |
| Document count and blob sizes | Storage accounting and upload progress. Sizes leak rough complexity — a short note and a video attachment do not look alike. |
| Workspace and user ids, and your username | Access control, and how collaborators find the public key to seal a workspace key to. |
| Membership and permission level | Authorisation. That two accounts share a workspace is visible; what is in it, and which document either is editing, is not. |
What is never visible: page content, titles, icons, tags and other properties, datatable rows and schemas, attachment contents, your search index, your password, your seed, and your private keys.
The trade is deliberate. Encrypting the structure as well would mean a server that cannot route a request, enforce a permission or tell one document from another — and the features that depend on the server knowing that much are the ones people install sync for.
Server hardening
Zero knowledge is not a substitute for the ordinary work, since a server that cannot read your documents can still be abused.
- Challenge-response authentication with single-use, short-lived nonces and replay protection.
- Rate limiting on the authentication endpoints, per IP.
- HttpOnly, SameSite=strict session cookies for browsers; short-lived bearer tokens for the desktop app, and 60-second tokens for the sync socket.
- A JWT secret that must be set: the server refuses to start in production without one rather than falling back to a default.
- Prepared statements throughout, and a separate SQLite database per user.
- Uploads served behind authentication, with a path-traversal guard.
- Link previews fetched behind an SSRF guard that re-checks every redirect hop against private and loopback ranges.
- HTTP security headers via helmet, and dependencies pinned and audited.
Known gaps
The list a security page usually leaves out. These are real, and none of them is a reason not to say so.
- Metadata inference. Someone with long-term access to the server could correlate timestamps, update frequency and blob sizes into a picture of when you work and roughly on what. There is no padding or cover traffic today.
- No external audit. The design is documented and the server is open source, but no third party has reviewed it. We would like one, and we will publish the report when there is one to publish.
- Passphrase change and key rotation. Changing a password means re-wrapping every workspace key under the new public key, and revoking a collaborator should rotate the workspace key. Both are planned rather than shipped.
- Guests in cloud workspaces. Server-side workspace access for guests is not yet fully zero-knowledge; where it applies, it is called out in the product rather than glossed over here.
- Local data is not encrypted. By design, and stated again because it is the trade-off most worth understanding: sovereignty on your disk, protected by your operating system rather than by us.
Self-hosting
The sync and collaboration backend — Express, the Yjs relay and SQLite
— is open source at
github.com/ubimate/ubimate-server
and runs with a single docker compose up.
The point is not only privacy, which the encryption already covers. It is that the arrangement survives us: if this company stops existing tomorrow, everything you rely on keeps running on hardware you control, from a repository you already have.
Our hosted service runs on servers in the EU, reachable only by key-based SSH, with continuous encrypted backups of each node's database.
Reporting a vulnerability
If you have found something, please tell us before you tell everyone else. Send it to security@ubimate.com with what it is, what it lets an attacker do, and enough detail to reproduce it.
We aim to acknowledge a report within two business days and to give you a remediation timeline within seven, and we ask for 90 days before public disclosure.
Scope
- The desktop app, on macOS and Windows
- The mobile app
- The web app and the hosted API and sync server
- This website
Out of scope: denial of service against our infrastructure, already-public vulnerabilities in third-party dependencies, and social engineering of the people who work here.
Good faith
We will not pursue researchers who act in good faith, give us enough to reproduce and fix the issue, and do not access or modify anyone's data beyond what it takes to demonstrate the problem.
Vulnerabilities
Everything else
The server, in full