What Nectenda's server can and cannot see

Nectenda is end-to-end encrypted: the server stores an append-only log of ciphertext it has no key for. This document says exactly what that does and does not cover, because a privacy claim you cannot check is worth very little.

Every claim below is checkable against the client, which ships as readable JavaScript — see Checking this yourself. If a claim and the code ever disagree, the code is right and this document is a bug.

The cryptographic parameters are stated exactly, because a security document that hides them is asking to be trusted rather than checked. What is left out is our server's internal shape — table and column names, internal function names — which tells you nothing about whether the encryption holds and rather more than it should about where to push on a system nobody has audited yet.

Scope. This describes the cryptography and what the server holds. It is not a promise about a particular deployment's logging, retention or jurisdiction; those belong in a privacy policy and are not verifiable from code by anyone.


The short version

Server sees
Note content No — AES-256-GCM ciphertext only
Note and folder paths No — document ids are HMACs of the path
Attachment content No — sealed in a chunked AEAD envelope
Attachment filenames No — they live inside the encrypted listing
Your passphrase No — on the hosted service, never sent in any form; a self-hosted server receives an independent derivation that cannot yield the key
Your private key No — stored wrapped, unwrappable only by your password
Folder display names No — sealed under the folder's content key
Who shares a folder with whom Yes
Sizes, timing, device and account records Yes

Why you do not have to trust the server

The keys never reach it.

Your passphrase never leaves the device. It is stretched with PBKDF2-SHA256 at 600,000 iterations into a master key. That key is never sent and never stored. On the hosted service nothing derived from it is sent either: you sign in by a code to your email, a passkey, or a provider account, and the passphrase is used only to unwrap your keys locally — see docs/identity.md. A self-hosted server uses the passphrase as its login password too, by split derivation: it receives a second, independent derivation, which cannot be used to derive the encryption key, and bcrypts even that before storing it, because it is still password-equivalent for logging in.

Your identity key is wrapped before it is uploaded. A P-256 ECDH keypair is generated on device; the private half is encrypted under a key derived from the master key, and only then stored server-side. The server holds a blob it cannot open.

Folder keys are wrapped to each member individually. Sharing a folder means sealing its content key to the recipient's public key using ECIES over P-256, with a fresh ephemeral keypair for every wrap. The server stores ciphertext, one record per member, and never handles an unwrapped folder key.

Folder names are sealed too. A folder called "Redundancy consultation — legal" says as much as the notes inside it, so the name is stored as ciphertext under the folder's content key, alongside a note of which key generation sealed it. It cannot be null and the create route refuses an unsealed name: there is no such thing as a folder whose name the server can read.

Document ids are HMACs, not paths. A document is addressed as its folder id followed by HMAC-SHA256(nameKey, relativePath) truncated to 16 bytes. The server cannot reverse it and therefore never learns your folder structure or note titles. The name key is wrapped to members like any other folder secret, and is deliberately separate from the content key so that rotating content keys does not rewrite every id in the log.

Content is encrypted before it is pushed. Each update is sealed with AES-256-GCM, IV prepended, 28 bytes of overhead. Attachments use a chunked envelope whose additional authenticated data binds the blob id, the chunk index, the total chunk count and the algorithm, so a reordered, truncated or substituted chunk is rejected rather than decrypted.

The attack this does not stop by itself

Encrypting content protects you from a server that reads. It does not, on its own, protect you from a server that actively interferes.

When you add a collaborator, your client asks the server for their public key and wraps the folder key to it. A malicious server could return its own public key instead. You would be sharing with the operator, and everything would look normal.

The defence is fingerprint comparison, and it requires you to act. The folder members screen shows a fingerprint for every member and for you. Compare them with your collaborators through any channel that is not this server — in person, a phone call, a different messenger. If they match, no key was substituted.

This is the same mechanism as Signal's safety numbers and Threema's QR verification, and it carries the same caveat: it only works if somebody checks. Until then the guarantee holds against an operator who reads, not one who interferes.

What is not encrypted

Stated plainly, because a security document that only lists strengths is marketing.

Forgetting your password

There is a way back, and it is the recovery key you were shown once at registration. Nothing else works: your password is not stored, and it is not merely checked but used — it derives the key that unwraps everything — so no administrator, and no amount of access to the server, can reset it for you.

Asking to recover discloses nothing until you prove you hold the key. Requesting recovery parameters for a username returns only derivation parameters, and returns plausible ones even for accounts that do not exist, so it cannot be used to find out who has an account here. The wrapped key itself is released only after the server has verified a proof it cannot forge, and failed attempts are counted against the account rather than the network address they arrive from.

Recovering does not change your identity key, so every folder shared with you stays readable afterwards. It does issue a new recovery key, because the old one no longer opens anything — save it as you saved the first.

Traffic analysis is real. Sizes and timing alone can reveal that a document is being actively edited, roughly how large it is, and who was connected at the time. End-to-end encryption does not hide that, here or anywhere else, and no amount of auditing the client will tell you what a server chooses to retain.

If that metadata is what you need to protect, the answer is not cryptography — it is running the server yourself.

Unsharing a folder

An owner can unshare a folder, which deletes the server's copy: the ciphertext, the edit history, the attachments. It does not reach into anyone's vault. Every member still holds the notes as plain files and still holds the keys that opened them, so unsharing is not a revocation of anything already delivered — the same limit as removing a member, and for the same reason: what someone has read, they have.

What we do not claim

Checking this yourself

The client is the whole trust anchor, and unusually for an encrypted product, you can read the exact code that runs. Obsidian plugins ship as JavaScript: main.js is on your disk, in your vault, at .obsidian/plugins/nectenda/main.js. There is no compiled binary and no reproducible-build problem to reason about — the file that executes is the file you can inspect.

This is also why there is no Nectenda web app, and will not be one. A web page is delivered fresh by a server on every visit, and can be delivered differently to different people, so "read the code that runs" is not a promise a browser-based product can keep. Everything that touches your password, your master key, your identity key or a folder key happens in the plugin on your disk. Anything of ours you reach in a browser — a marketing page, a checkout, an invoice — never sees any of them, and cannot create or recover an account.

That boundary is a deliberate limit on the product, not an accident of what has been built yet. It is the reason the claim above holds.

Worth checking, in rough order of value:

  1. That plaintext never leaves. Every push goes through encrypt before it reaches the socket — see packages/plugin/src/multiplexed-provider.ts.
  2. That paths are not sent. Document names are derived by deriveDocId; grep the traffic for a note title and you will not find one.
  3. That the server's copy is unreadable. If you self-host, attach to your own server's database and read the stored update payloads. They are opaque without a folder key.
  4. That fingerprints match what your collaborator sees.

That the file you installed is the source you read

The above assumes the main.js in your vault corresponds to the published source. You do not have to assume it. Every release carries the SHA-256 of its main.js, and the source it was built from is the repository the release lives in, at that tag:

git clone https://github.com/Nectenda/nectenda-plugin && cd nectenda-plugin
git checkout <the release tag>
node scripts/verify-build.mjs --built ~/vault/.obsidian/plugins/nectenda/main.js

It rebuilds and compares byte for byte. On a difference it reports the offset and prints both sides around it, because "they do not match" does not distinguish a differing esbuild version from a differing cipher — and those deserve very different reactions.

The licence does not restrict any of this. The plugin is source-available under PolyForm Shield 1.0.0: reading, auditing, building and comparing are expressly permitted. What is withheld is the right to build a competing product, which is not something verification requires. If anything the check matters more now than it did under MIT — having narrowed what you may do with the code, the claim rests entirely on what you can check.