Security
NeyLivo Security
This page describes what actually protects your data in NeyLivo, and what does not. Where a protection is optional, off by default, or unproven, that is written here rather than left out.
Last updated: August 12, 2026 · NeyLivo 1.563.0
Security philosophy
Three rules the project holds itself to.
- No claim without code behind it. Everything on this page corresponds to something in the public source. If a protection is not implemented, it is not described as if it were.
- No silent downgrade. When encryption cannot be applied, the message is not sent at all and you are told why. A protection that quietly turns itself off is worse than no protection, because people keep trusting it.
- Name the limits. An honest limitation is more useful than a confident slogan. The limitations section below is not an afterthought — it is the point of the page.
Message protection
Direct messages between two people can be encrypted end to end. When it is on, the server stores ciphertext it has no key for.
- Key agreement
- ECDH on the P-256 curve. X25519 would be preferable, but it is not available in the WebCrypto implementation the app must run on.
- Key derivation
- HKDF-SHA256, with the purpose and the pair of devices bound into the derivation, so a key from one context is useless in another.
- Encryption
- AES-256-GCM. GCM also authenticates: ciphertext altered on the server fails to decrypt rather than decrypting into something else.
- Keys belong to devices
- Each device has its own key pair. The private part is stored in IndexedDB as a CryptoKey and is never sent anywhere in the clear.
- Length hiding
- Message text is padded into fixed-size blocks before encryption, so ciphertext size no longer reveals whether you wrote “yes” or a page of text.
- Verification
- Each key has a fingerprint — five groups of five digits — that two people can read out to each other to confirm nobody is in the middle.
- Multiple recipients
- A per-message content key is wrapped separately for the other person’s devices and for your own, so your own second device can read your history.
This is a setting, and it is off until you turn it on (Settings → Privacy). It applies to one-to-one direct messages only.
Not end-to-end encrypted: group conversations and server channels. Those messages are stored so that the server can read them, and are protected by database access rules — the same model as most group chat products. If you need a conversation the server cannot read, use a one-to-one direct message with encryption on.
If your correspondent has no published key — for example they have never opened a version that supports encryption — the message is not sent, and the app says so. It never falls back to plaintext behind your back.
Transport security
All traffic between the app and its backend goes over HTTPS/TLS, including the realtime connection (secure WebSocket) and file uploads. Plugins may only reach the network over https: and wss:; plain HTTP is refused.
The Windows app is downloaded over HTTPS and updates itself over HTTPS, checking the SHA-512 checksum published with the release. That checksum proves the file arrived intact — it is not a code-signing signature, and the installer is not code-signed.
Account security
Accounts are handled by Supabase Auth. Sessions are JWT-based with refresh tokens, stored locally on your device (and additionally in native storage on Android so that an app update does not log you out).
- Trusted devices. Signing in from an unknown device marks it as new, and it stays limited until you confirm it. Your other devices are notified.
- Recovery code. Stored on the server only as a SHA-256 hash, never in readable form — a database leak does not hand out master keys to accounts.
- QR sign-in. You can log in on a new device by scanning a code with one that is already signed in.
- Username or email. Login by username goes through a dedicated endpoint that answers identically for “no such user” and “wrong password”, so it cannot be used to discover who exists.
- Account deletion. You can delete your account and its data from inside the app.
There is no two-factor authentication with an authenticator app yet. Trusted devices and the recovery code are a partial substitute, not a replacement.
Password storage
NeyLivo does not store passwords. They are handled by Supabase Auth, which stores them as bcrypt hashes; the project writes no password storage code of its own.
The minimum password length is six characters, which is the platform default. That is short, and raising it is on the list of things to fix.
One consequence worth understanding. Your encryption key can be backed up to the server, locked with your account password (PBKDF2-SHA256, 250 000 iterations, AES-256-GCM). This exists so that logging out does not destroy your message history. The cost: the same password is also what you type into the login form, so a malicious or compromised authentication server could capture it and unlock that backup. NeyLivo protects your messages from outsiders and from a database leak; it does not protect them from whoever controls the server itself.
Attachments
Attachments are the weakest part of NeyLivo today, and it is being fixed. Files you send are uploaded to cloud storage that is configured as public: anyone who has, or can construct, the link to a file can download it without logging in. Access rules protect the database, not this storage bucket.
There is a switch that fixes this for direct messages: encrypt attachments. With it on, each file is encrypted in your browser with its own AES-256-GCM key before upload, stored under a neutral name with no file type, and the key travels inside the encrypted message — so it can only be obtained by decrypting the conversation. Like the other encryption switches, it is off by default.
Until the storage layer is closed, treat anything you send without attachment encryption as a file that is technically public. This limitation is listed below and in the project’s own audit.
Voice and video
Calls run over LiveKit. Permission to call someone is decided on the server, not in the app: blocking and your “who can call me” setting are enforced by the service that issues the call token, so the check cannot be bypassed by a modified client.
Calls between two people can be end-to-end encrypted, as a separate setting. The key is generated in your browser and delivered to the other person inside an encrypted message, so the call server never sees it.
Marked experimental. The encryption path is covered by tests in code, but it has never been verified in a real call between two devices. Until it has, it is not presented as proven. If the key cannot be delivered, the call proceeds unencrypted and says so on screen.
Without call encryption, media is decrypted at the media server, as in any ordinary group call. Server voice channels are not end-to-end encrypted.
Plugin isolation
Plugin code does not run in the app. It runs in a Web Worker, which the browser gives no DOM, no cookies and no access to the page — so a plugin cannot read your session, cannot draw a fake login window, and cannot reach into the interface.
Plugins that provide a full page (for games, visualisers, 3D scenes) get an <iframe sandbox="allow-scripts"> without allow-same-origin. That gives the page a unique opaque origin: it has its own DOM and can use WebGL, WebAssembly and audio, but it is a different origin from NeyLivo and can touch neither the app’s document nor its storage.
These are browser boundaries, not checks in our code that a clever plugin could talk its way around. Everything a plugin can do arrives through one dispatcher, and every call is checked against the permissions the plugin declared. There is a dedicated test suite that attacks these boundaries deliberately.
Plugin permissions
Before installing anything, you see the plugin’s name, author, version and the exact list of what it is asking for, in plain language — for example “Send messages on your behalf” or “Reach the internet (only the listed sites)”. There are 23 such permissions.
Two things to know. A plugin that declares * — or that declares nothing at all — asks for every permission, and the install screen shows this in red. And there are no quantitative limits: an installed plugin may send messages, make requests and run in the background as much as it likes. The app states this before you install, because the meaningful protection here is your decision, not a quota.
A plugin can be switched off or removed at any time.
Network permissions
A plugin can only reach the internet if it has the network permission, and only the domains it declared in its header. The rules are enforced in one place for every way out — ordinary requests, streaming responses and persistent connections — so a second code path cannot drift out of sync with the first:
- Only
https: and wss:; plain HTTP is refused
- Only the declared domains, or every domain if the plugin asked for that openly and you accepted it
- Never to NeyLivo itself or to its backend — a plugin cannot use your session against the app
- Headers from an allowlist only;
Cookie is never allowed
Infrastructure
NeyLivo is not self-hosted infrastructure. It runs on:
- Supabase. Database, accounts, file storage and the realtime connection. Access is governed by row-level security rules, which are tested against a real Postgres on every change.
- LiveKit. Voice, video and screen sharing. Handles media only.
- GitHub. Source code, automated builds and the release files this site links to.
The privacy page lists every third-party service the app talks to, including the ones that only see an IP address.
Threat model
What NeyLivo protects against
- Someone else reading your one-to-one messages, with encryption on — including the operator of the database
- Another user reading data that is not theirs: access rules are enforced in the database, not in the app
- A malicious plugin stealing your session, faking the interface or calling home to an undeclared server
- Passive collection: there is no analytics, no crash reporting and no advertising code to leak anything
- Someone with your database dump reading your recovery code, or your key backup without your password
What NeyLivo does not protect against
- A malicious operator of the authentication server, who could capture your password as you log in and unlock your key backup
- Anyone reading group conversations or server channels at the database — those are not end-to-end encrypted
- Anyone downloading unencrypted attachments, which are in public storage today
- A compromised device: malware or someone with your unlocked computer sees what you see
- Traffic analysis: who talks to whom and when is visible to the server
- A photograph of your screen taken with another camera, whatever the capture protection setting says
Known limitations
The list the project keeps of its own weak spots, in the order it intends to fix them:
- Attachments are in public storage. Files sent without attachment encryption can be downloaded by anyone with the link. The fix — closing the bucket and moving to signed links without breaking the millions of links already inside old messages — is planned in stages.
- Encryption is off by default. Privacy that only applies when found in the settings is privacy most people never get.
- Group and channel messages are not end-to-end encrypted. Group end-to-end encryption is a separate, larger design task and is not going to be improvised.
- Call encryption is unverified in the field. It works in tests; it has not been proven in a real call.
- No Content-Security-Policy. There is no known injection path today — messages are rendered as React nodes and there is no raw HTML insertion anywhere in the app — but CSP is the second line that catches tomorrow’s mistake.
- Emoji images load from a public CDN. That means a third party sees your IP address simply because the interface contains emoji. Bundling them is straightforward and planned.
- Password minimum is six characters. Too short.
- No external security audit. Nobody outside the project has reviewed any of this.
A full technical write-up of the same findings, with file references, is published in the repository as SECURITY_ARCHITECTURE_AUDIT.md. Open the audit →
Reporting vulnerabilities
If you find a vulnerability, please report it privately first, and give it a chance to be fixed before it is public. That protects the people using NeyLivo, not the project’s reputation.
- Use GitHub’s private vulnerability reporting on the repository, or open a minimal issue asking for a private channel — without the details.
- Include what you did, what happened, and why you believe it is a problem. A short reproduction is worth more than a long description.
- Please do not post working exploits, other people’s data or a public write-up before a fix exists.
There is no bug bounty. The project has no money to pay one, and promising one it cannot honour would be worse than saying this plainly.
Reporting policy in the repository →
· Privacy page →