Security

What we can read, and what we cannot

Ubimate is built so that the parts of it we run cannot see the content you keep in it. This page says how that works, and where the limits of it are.

Open beta No third-party audit yet security@ubimate.com

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

security@ubimate.com

Everything else

hello@ubimate.com

The server, in full

ubimate/ubimate-server