The code that encrypts your data is published — in full, under a free licence, and with a checksum you can recompute yourself. You do not have to believe us. You can look.
This is where the source lives, where the tests run and where every release is built. If you want to know what the core does, you read it here — file by file, line by line.
OpenThe same state of the code, in the place most developers look first. A mirror, so that verifiability does not fail on habit.
OpenThe finished package, exactly as it is built into our vault. You can install it yourself and hold it against the source.
OpenBriefly — and why it genuinely makes the encryption better.
Open source means the source code is published: anyone may read it, check it, reuse it and name the mistakes in it. For encryption that is not a nice extra but the norm — for some hundred and fifty years cryptography has held that a method must stay secure even though everyone knows how it works. Security that rests on keeping the method secret is not security, it is a head start.
A flaw in encryption does not show up in use — the vault opens, everything looks right. It is found by reading. So whoever wants to read has to be able to.
We write that your password never leaves your device. The open code says whether that is true. The difference between a promise and a verifiable statement is exactly this publication.
The core runs without us. Should finilog one day cease to exist, the method for decrypting your data remains published — under a licence nobody can withdraw.
This excerpt is not a re-enactment. It is generated from the published package and checked against it in our pipeline — the line numbers are the real ones, and you can look them up in the repository.
It shows the moment of encryption. Red is what goes in and never leaves the device; teal is what comes out and is stored with us. Neither input appears in the `return`.
Check it yourself — with instructions@finilog/crypto/src/crypto.ts431–453const encryptBytesWithPassword = async (
plaintext: BufferSource,
password: string,
saltBase64?: string | null,
aad?: BufferSource
) => {
const salt = saltBase64 || toBase64(randomBytes(16))
const iv = randomBytes(12)
const wrappingKey = await deriveKeyCached(password, salt)
const ciphertext = await crypto.subtle.encrypt(
gcmParams(iv, aad),
wrappingKey,
plaintext
)
return {
salt,
iv: toBase64(iv),
ciphertext: toBase64(ciphertext)
}
}
plaintext · passwordPlaintext and vault password — they go in and never leave the device
salt · iv · ciphertextSalt, initialisation vector, ciphertext — the only things that leave the browser
Neither input appears in the `return`. What our server receives is exactly these three values: a random salt, an initialisation vector, and the ciphertext.
The whole core is a handful of files with not a single runtime dependency — small enough to read in an afternoon. The checksum below ties this page to exactly the state you can download.
119f6001a4a051114a0727f09470486e69ef5d8ab2159297a65b8296621098a4Computed over every .ts file in @finilog/crypto/src, sorted by name, each as "name, length, content", line endings normalised to LF. Recompute it with `npm run checksum` in the package — the script ships with it.
This number is not maintained by hand: if the core changes without it being updated, the check in our pipeline fails.
The published package's own tests — key derivation, formats, wraps, recovery code and the passkey factor. They run in the core repository, not in this application.
Unlike your vault entries, personal messages are not readable by you alone. They are delivered later as an email — at a moment when you can no longer take part. That requires the key to sit on our server.
So technically we could read these messages. We do not, but "we cannot" would be a lie here. For everything else in the vault — credentials, documents, files — it still holds without qualification.