Security Policy
Herkos handles private keys and encrypted messages, so we take security reports seriously and would rather hear about a problem early than late.
Reporting a vulnerability
Please do not open a public issue for security problems.
Report it privately by email to security@herkos.email.
Useful things to include: what the problem is, how to reproduce it, which platform and version you tested, and what an attacker could achieve. A proof of concept helps a lot.
Never include your own nsec, secret key or bunker secret in a report.
We aim to acknowledge reports within a few days. Since Herkos is a small project, please allow reasonable time for a fix before disclosing publicly. We are happy to credit you in the advisory unless you prefer otherwise.
What is in scope
The Herkos application itself: key handling and storage, encryption and
decryption of messages (including the NIP-59 sender checks and Bcc isolation
in local_packages/nostr_mail), the app lock, relay and bridge communication,
attachment handling, and the build and release pipeline. For Herkos Drive,
also its protocol package (packages/herkos_drive_protocol) and the page
that opens its links (herkos.app/f/); see its own section below.
What is out of scope
- The Nostr protocol and its NIPs themselves — report those upstream.
- Relays, bridges and Blossom servers operated by third parties. Herkos ships with defaults but does not run them; report issues to their operators.
- The upstream project Nostr Mail Client, unless the issue is specific to changes made in Herkos.
- Missing hardening that we already document as a known limitation (see below).
Known limitations, stated plainly
These are design trade-offs, not vulnerabilities. We would rather write them down than have someone discover them the hard way:
- Mail to traditional addresses is not end-to-end encrypted. Messages between Nostr users use NIP-17 gift-wrap and are E2E encrypted. When you write to a regular email address, a bridge converts the message and can read it.
- The app lock (PIN and biometrics) is a convenience barrier, not encryption at rest. It stops someone who picks up an unlocked device; it does not protect against an attacker with full access to the device or its storage.
- On Windows, biometric-only authentication cannot be enforced. Windows Hello does not allow selecting the method, so the system PIN always remains available as a fallback.
- Losing your key means losing the account. There is no recovery, by design.
- Metadata is not fully hidden. Relays can observe connection patterns, and every relay sees your IP address, even when they cannot read message contents. What Herkos does not give away (since 30-09-2026) is who is writing to whom when a relay asks for NIP-42 authentication:
- Looking up where to deliver. A recipient's relay and Blossom lists are
asked for on a separate connection that never identifies you, first on
relays that do not require authentication (
purplepag.es,user.kindpag.es,nos.lol,offchain.pub). - Profiles and the scheduler. The name and picture shown for anyone else (kind 0), and the relay list of the scheduler DVM when you schedule or cancel an email, are asked for the same way: anonymously first and, if a relay insists, with the temporary key described below, never with yours (since 01-10-2026; before, a relay that asked for authentication got your key, and could tell whose profile you were looking at). Only your own profile is still read as you.
- Delivering. Gift wraps go to the recipient's DM relays, on that same separate connection. If one of them requires authentication, Herkos answers with a key made for the purpose and kept only in memory, never with yours; each account on the device has its own. The relay can still see that the same device (same IP, same temporary key) sent several envelopes during a session.
- Large emails. The encrypted copy of an email over 32 KB is uploaded to Blossom servers, including the recipients'. Only your own servers (your Blossom list) get the upload authorised with your key. Any other server gets it anonymously and, if it insists on an authorisation, one signed with the same temporary key as above (since 01-10-2026; before, every server got your key). A server that only accepts known keys refuses it, and the copy stays on the other servers.
- Your own data. Syncing your mailbox, labels, settings and drafts authenticates, where a relay asks, as the account the data belongs to, even during an account switch. On your own relays that tells them nothing they did not know: the requests name your key anyway. Your own relay and Blossom lists are public and are asked for without identifying you, and so are relays named in an email link.
- Read state and folders are public metadata (for now). Marking an email as read, starred, archived or moved publishes an unencrypted NIP-32 label event tied to your public key, including custom folder names. A sender who knows the id of an email they sent you can tell when you acted on it. Encrypting these labels is planned; until then, treat folder names as public.
- Push notifications and scheduled sending use third-party services. The
push server, if you choose one (none by default since 11 November 2026;
api.nmail.liwithrelay.nmail.lion request), learns your public key, a push token and when you receive mail; the scheduler DVM learns your public key and the already-encrypted messages it must publish. Both are opt-in and documented inPRIVACY.md. The text of a push notification is chosen by the push server, and Herkos only trusts it when UnifiedPush delivers it encrypted (RFC 8291); an unencrypted payload only wakes the app up to fetch the mail itself. - The web version keeps your key in the browser, which is the weakest place for it. The installed apps hand your secret key to the operating system's secure storage (Android Keystore, DPAPI on Windows, libsecret, the iOS/macOS Keychain). A browser has none of that: the key sits in the origin's storage and passes through JavaScript on every signature, within reach of any code that runs in the page — a browser extension with access to every site, an XSS bug in the app or in a dependency, a tampered deployment — and of any infostealer that copies the browser profile. There is also no moment where you verify the code once: a signed binary is checked at install time, while a web page ships fresh code on every visit. Log in to the web version with a remote signer (NIP-46 bunker, Amber), so the key never reaches the browser, or treat it as a throwaway identity and keep your real key in an installed app.
- Bridged mail is only as trustworthy as the bridge. Herkos verifies that a
message marked as coming through a bridge really was signed by a bridge you
have configured (or one of the defaults). It cannot verify anything about the
legacy
From:address the bridge relays. - Attachments are opened by the operating system. Herkos never launches executable or script file types and asks before handing any other non-previewable attachment to the system default app, but the app that opens the file is outside our control.
Herkos Local Relay: what it protects and what it does not
Herkos Local Relay keeps your apps' events on your device, private ones too (drafts, private lists, the envelopes you receive). This is what protects them and where the limit is, platform by platform. Report the relay's security issues the same way as any other (above).
The whole database is encrypted, its journal included (SQLite3 Multiple Ciphers, AEGIS-256). The key is 32 random bytes made on the device and kept in the system's secure storage: the Keystore on Android, DPAPI on Windows, the keyring (libsecret) on Linux. It has no password of yours, because the relay has to start on its own. Without the key the relay does not start and says so: it never opens or creates an unencrypted database, and it never deletes one it cannot open.
| If… | Android | Windows | Linux |
|---|---|---|---|
| someone copies the database file (a backup, a synced folder, a disk image) | Protected | Protected | Protected |
| the device or the disk is stolen while off | Protected: the key is in the Keystore | Protected, unless they know your Windows sign-in password: DPAPI protects the key with it | Protected, unless they know your keyring's password (nearly always your session's) |
| another user account on the same device | Protected | Protected: DPAPI is per user | Protected: the keyring is per user |
| another app or program running as your user | Protected: Android keeps each app apart and the Keystore gives the key to the relay only | No: DPAPI encrypts per user, not per app; a program running as you can ask for the key just as the relay does | No: your session's keyring does not keep apps apart |
| a rooted phone, or a tool that runs code as the app | No | — | — |
| someone holds the device unlocked | Only the relay's PIN or fingerprint, if you set them | Same | Same |
| someone reads the relay's memory while it runs | No | No | No |
On a desktop, the limit of DPAPI and of the keyring is that of any program: they protect against other users and against whoever takes the disk, not against a malicious program already running as you. If that case worries you, what protects you is encrypting the system disk (BitLocker, LUKS) and not running programs you do not trust. On a Linux without a keyring (no Secret Service) the relay does not start and explains why: it never keeps the key in the clear.
Who gets in, on the device itself. The relay listens on 127.0.0.1 only,
and any app on the device, or a web page in the browser, can reach that
address. So:
- An app has to pair: it shows up on the relay's screen and you approve it (with the PIN or fingerprint, if you set them). Any app can give itself any name: approve only if you have just asked for it from that app.
- A web page always pairs, and a pairing works only from where it was made: an app of the device's pairing opens nothing to a web page, even one that signs with the same key.
- A paired app only reads and writes the kinds of event you gave it, and only deletes the ones it may write. Revoking it disconnects it at once.
- An unpaired app only reads your public events (which are public anyway) and writes nothing, unless you turn on the setting that lets the device's unpaired apps in.
- Private events go only to whoever proves with NIP-42 that they hold the key they belong to, and an envelope only to its recipient.
What it does not protect, here:
- A malicious app on the device can slow the relay down or make it use memory. It cannot read anything private that way.
- While the relay sleeps or is not running, its port is free. On Android it sleeps after 10 minutes without connections; on a desktop the port is free while you have not started it. Another app could listen there and pass itself off as the relay. Herkos apps will check the relay's key before sending it anything private; they do not yet, and until then this is a real limit.
- On a desktop, other users of the computer can connect as an unpaired app: read your public events and ask to pair.
The local network, if you turn it on (it is off by default):
wss://only, with the relay's own certificate; never unencryptedws://outside the device.- Nothing before authenticating (NIP-42) with a key on your list. A connection that does not do so within 30 seconds is closed, and the local network has its own number of connections: it never takes those of the device's apps.
- A key on the list reads your public events and its own private ones, and writes only events it signed. Taking it off the list disconnects it.
- The certificate is only valid for names at home (
localhost,.local, private addresses): even if someone stole its key, it would be of no use to sign an Internet domain on the devices that installed it.
What it does not protect, here: anyone on the same network sees that the device has an open port and how much traffic goes through it, though not what; and if the device has a public address (no router in between), that port can be seen from the Internet, though it still asks for a key on your list.
Copies. The encrypted copy has a password (scrypt and XChaCha20-Poly1305): without it nobody can open it, us included. The JSONL export is not encrypted and carries your private events in the clear; the app warns you.
The lock (a PIN, plus fingerprint or Windows Hello) is asked for when the app opens, after 30 seconds away, and to approve a pairing. It is a barrier of convenience, as in Mail: it encrypts nothing. On Android, with a PIN set there are no screenshots and no thumbnail among the recent apps, and since Android 12 no other app's window can sit on top of the relay's while you use it.
Herkos Drive: what it protects and what it does not
Herkos Drive keeps your files on Blossom servers and the list of your files
and folders on Nostr relays, both encrypted on your device. This is what
protects them and where the limits are. The full detail is in
spec-drive.md and the threat model amenazas-drive.md, in
packages/herkos_drive_protocol/; what each relay and server sees is in the
privacy policy. Report Drive's security issues
the same way as any other (above).
What relays and servers cannot do:
- Read your files or their names. Each block of a file is encrypted with AES-256-GCM under a key of its own revision; names, folders, sizes, dates and keys go sealed with XChaCha20-Poly1305 under the key of the folder they are in. Nothing is derived from the content: the same file uploaded twice gives different blocks.
- Change, swap or move anything unnoticed. Every block is checked against the manifest sealed in its file's entry, and every entry is signed by the volume key and sealed for its own place (volume, identifier and folder): a relay cannot pass one entry off as another, or a block as another.
- Bring back an old version. The app never takes an entry older than the one it already has for the same identifier.
- Write as you with the volume key alone. What your devices write carries the owner's mark, an HMAC with a key no link gives; an entry or a deletion without it is not taken, and the app warns you.
What it does not protect:
- The pattern of your Drive. Relays can count your files and folders, see the shape of the tree and when each entry changes; servers see roughly how large each file is (blocks are padded to 64 KiB, entries to a power of two). Every relay and server sees your IP address. Use Tor or Orbot at the system level if that matters to you.
- What someone already downloaded. Removing a link, or a person, gives what they could open new keys at once, so they do not open what you add later; it cannot take back what they already saved.
- A leaked link. A link without a password is the key. The browser may keep it in its history before the page takes it out of the address bar. For anything sensitive, give the link a password, sent another way, or share with the person's account. A weak password can be broken with time: whoever has the link can try offline.
- The page
herkos.app/f/runs in the browser of whoever opens a link. It is served with a strict Content-Security-Policy, Subresource Integrity and no third-party code, but whoever controls the web host could serve a different page. That is the limit of every web page that decrypts in the browser. - Whoever had the volume key (yours, or given with a link to a folder) can still delete the blocks that were uploaded with it, until they are uploaded again. You can change the volume key (Settings → Volume key): your other devices follow on their own.
- Your device. The list of files is cached encrypted, with its key in the system's secure storage; a file you download or open is written in clear where you choose. Drive's PIN is a barrier of convenience, as in Mail.
- A quantum computer, later. The blocks and seals are symmetric (AES-256, XChaCha20), but your locator is encrypted with NIP-44 on secp256k1: someone who keeps it today and breaks secp256k1 tomorrow could open the whole Drive.
How it is checked. Two internal reviews against the threat model (2 November 2026) and their fixes; tests of what each event and block shows; fuzzing of every reader of what comes from outside (events, seals, manifests, links, backups). No external audit yet: one is planned before the public beta, and its report will be published in full. Drive is still in development.
Supported versions
Security fixes are applied to the latest released version. Given the size of the project, older versions are not maintained — please update before reporting.