SECURITY

Don't take our word for it. Break it yourself.

Your keys are encrypted on your device before anything leaves it, so our server only ever holds data it cannot read. The demos below run the real cryptography in your browser, and nothing you type into them ever leaves your browser: flip a byte and watch it get rejected, put an attacker on the wire and watch the host key catch them. Encryption and sync are free on any storage you run, Juiced only buys the hosting.

Reflects ZestSSH v2.3.0 · Last reviewed September 2026

01
Account vault · key derivation

Your password becomes a key

Your password is not the key. It is too short and too guessable to be one. ZestSSH runs it through Argon2id, a deliberately slow, memory-hungry function that eats 128 MB of RAM per attempt and mixes in a random salt. Out comes a full 256-bit key. The slowness is the whole point: it costs you a fraction of a second once, and it costs an attacker a fortune per guess.

LIVE Real PBKDF2 runs in your browser here. A standard primitive, shown so you can watch the property hold. This is not ZestSSH's own code, and you should type a throwaway value, never a real password.
Type a password, watch it get stretched
What Argon2id's 128 MB would look like, illustrated. This demo runs PBKDF2, which uses almost none of it.
Derived key (256-bit, this is what locks your vault)
How long to crack it by brute force
If ZestSSH used a fast hash (SHA-256)
—
With Argon2id, 128 MB per guess
—
iThe salt is public and that is fine: it just guarantees two people with the same password get different keys, killing precomputed rainbow tables. The memory cost is what makes each guess expensive.

● What protects you

A slow, salted, one-way function. Even a huge GPU farm is throttled to a trickle of guesses per second because each one needs 128 MB it cannot share.

● What breaks it

A weak, common password. No key derivation saves password123. Length and unpredictability are the one part only you control.

Ships asArgon2id, 128 MB memory cost, per-vault random salt. Demo uses PBKDF2 (the closest thing browsers expose) for the byte-mixing; the memory grid and crack math illustrate Argon2id's real behaviour.
↳
Account vault · key derivation

Proving you know it, without storing it

How does ZestSSH know your password is right, if it never keeps your password or your key? It stores a small verification value, derived from the key through HKDF-SHA256. On unlock it derives the value again and compares. Right password, values match. Wrong password, they do not. The value is one-way: it cannot be turned back into your key or your password.

LIVE Real HKDF-SHA256 runs in your browser here, fed by a PBKDF2-derived key. The app derives that key with Argon2id, not PBKDF2. Standard primitives, not ZestSSH's own code, and use a throwaway password.
Real HKDF-SHA256, live
Stored on disk (this, and only this)
iStore a value, then try unlocking with the right and wrong password. The password itself is nowhere on disk.

● What protects you

Steal the device file and you get a one-way fingerprint, not the password or key. The comparison is constant-time, so it leaks nothing through timing either.

● What breaks it

Nothing here alone, this is a check, not the lock. Its strength rides entirely on your password: a weak password makes both the key and this value guessable.

Ships asHKDF-SHA256 verification value with constant-time comparison, derived from the Argon2id key. Real HKDF running above, fed by a PBKDF2 stand-in for the key.
02
Account vault · key hierarchy

The key that guards the keys

Here is the trick that makes ZestSSH both recoverable and zero-knowledge. Your data is locked by one random Data Key. That Data Key is never exposed, it is sealed inside two independent envelopes: one you open with your password, one you open with your recovery key. Either opens the same Data Key. Neither envelope reveals the other.

Click an envelope to open it
sealed
🔒
Password envelope
Opened by your Argon2id key (from gate 01)
sealed
🔒
Recovery envelope
Opened by your one-time recovery key
🔑 Data Key — sealed
Open either envelope to reveal it. This single key encrypts everything you store.
Simulate a bad day
iNotice the Data Key never changes. Change your password and only the small password envelope is re-sealed, your whole database is never re-encrypted.

● What protects you

Two independent ways in, so a forgotten password is not a lost account. And the server only ever stores sealed envelopes, it never sees the Data Key or your password.

● What breaks it

Losing both at once. That is not a bug, it is zero-knowledge: if neither you nor anyone else can open an envelope, the data is math, not recoverable. Keep the recovery key somewhere real.

Ships asA random Data Key (DEK) wrapped independently by a password key (MEK) and a recovery key (KEK), used for end-to-end encrypted Cloud Sync.
↳
Account vault · key hierarchy

Changing your password moves almost nothing

This is the one demo on the page that shows a ZestSSH design choice rather than a textbook fact about a cipher. Because the Data Key is wrapped, not derived from your password, changing your password only re-seals one small envelope. The Data Key never changes, so your entire encrypted database stays exactly where it is, byte for byte. That is why a password change is instant instead of a full re-encryption.

LIVE Real AES-256-GCM key wrapping runs in your browser here. Standard primitives, shown so you can watch the property hold. This is not ZestSSH's own code.
Change your password, watch what actually moves
Data Key (the one thing that must never change)
Password envelope: the wrapped Data Key the server holds
Your encrypted data on disk
iType a new password and hit re-seal. Watch the envelope turn over completely while the Data Key and your data hold, byte for byte.

● What this buys you

Instant password changes, and a recovery key that is a fully independent second envelope, not a copy of your password. Rotating one never touches your data or the other path.

● The catch

The envelopes are only as strong as what seals them, which loops straight back to gate 01: a weak password is a weak envelope, no matter how elegant the indirection.

Ships asDEK wrapped by an Argon2id-derived key (MEK); a password change re-wraps the DEK under a new MEK and re-uploads only that small envelope. The wrap above is real AES-256-GCM in your browser.
03
Account vault · authenticated encryption

Locking the actual data

Now the Data Key does its job with AES-256-GCM. GCM does two things at once: it scrambles the data, and it stamps a 16-byte authentication tag over every single byte. If anything changes later, even one bit, the tag no longer matches and decryption refuses to run. It never hands back half-right or attacker-tweaked data. This demo runs the real cipher, right here.

LIVE Real AES-256-GCM runs in your browser here. A standard cipher, shown so you can watch the property hold. This is not ZestSSH's own code.
Real AES-256-GCM, live
Nonce (fresh random every time)
Ciphertext · click any byte to tamper with it, then decrypt
ciphertext 16-byte auth tag you flipped this
Nothing tampered with yet. 0 bytes changed.
the last orange bytes are the authentication tag
click any byte to flip one bit · Enter works too
iEncrypt your secret, then click a ciphertext byte to flip it and hit decrypt. You are attacking real AES-GCM.

● What protects you

Confidentiality and integrity together. A tampered backup, a corrupted sync, a byte flipped by an attacker: all rejected outright, not silently trusted.

● What breaks it

Reusing a nonce with the same key would leak patterns. ZestSSH draws a fresh random nonce for every message, so it never happens.

Ships asAES-256-GCM for stored and synced data. This is the exact cipher and mode running in the box above.
04
This device · data at rest

Sitting on the disk

Everything on your device lives in a SQLCipher database, encrypted as a whole. The database key is itself wrapped behind your PIN, and you can bind that wrapping to the device's hardware security chip: Keystore or Secure Enclave on mobile, the TPM on Windows. Steal the file and you get noise. Steal the file off a hardware-bound device and it is noise that will not open anywhere else.

LIVE Real AES-256-GCM runs in your browser to encrypt the sample below. SQLCipher itself uses AES-256-CBC with per-page HMAC, a different authenticated construction, but the at-rest property you see here is the same.
Steal the laptop, see what you get
zest.dbencrypted
SQLCipher whole-database encryption
Every table, every row, encrypted on disk
always on
Database key wrapped behind your PIN
The key to the file only exists after you unlock
off
Wrapping bound to the hardware chip
Keystore / Secure Enclave, the blob will not open on another device
off
What the attacker ends up with
iToggle the layers and watch what an attacker holding the raw file actually ends up with.

● What protects you

A lost or stolen, locked device is a brick full of ciphertext. Hardware binding means even a byte-for-byte copy of the file is useless on the attacker's machine.

● What breaks it

An already-unlocked device in someone's hands: the key is live in memory. That is exactly why auto-lock exists and defaults to a short timeout.

Ships asSQLCipher at rest (AES-256-CBC with per-page HMAC-SHA512), PIN-wrapped database key (1.8 vault); opt-in hardware-backed encryption binds the key to Keystore or Secure Enclave on mobile and to the TPM on Windows, macOS and Linux are not hardware-bound yet. The demo above uses AES-256-GCM, a different authenticated construction, to show the same at-rest property.
05
The connection · SSH transport

Talking to the server

When you connect, three things happen before a single keystroke is sent. Both sides agree on a shared key without ever transmitting it (X25519). The server proves it is really itself by signing with its host key (Ed25519), and you check that signature against the fingerprint you trust. Then the whole channel is sealed with authenticated encryption. Run it, then let a man in the middle try to sit in between.

LIVE Real X25519 and Ed25519 run in your browser here (an equivalent curve where your browser lacks them). Standard primitives, not ZestSSH's own code.
Step 1 · Key exchange, run live

ZestSSH (you)

idle
⇄

Server

idle
Shared session key: not yet computed
step 1 of 2
Step 2 · Host key check, then let an attacker interfere
iRun the handshake first. The server will sign a challenge with its Ed25519 host key.
step 2 of 2

● What protects you

Someone recording the whole exchange still cannot derive the session key. And an impostor server cannot fake the host key's signature, so ZestSSH catches it and warns you loudly.

● What breaks it

Clicking through a host-key-changed warning. That warning is the interception alarm. On a real network it is the one thing you must never wave past.

Ships asCurve25519 key exchange, Ed25519 / Ed448 host keys, ChaCha20-Poly1305 channel, via the open-source zest_ssh_core engine. Live demo uses your browser's real X25519 and Ed25519 where available, with an equivalent curve as fallback.
06
The whole picture · threat model

What actually breaks it

This is the part worth remembering. Pick an attack and see which locks stop it cold and which ones it walks straight past. Honest security is knowing where the wall is, and where the door you have to guard is.

Pick an attack
Pick an attack above to see how ZestSSH holds up.

The things a demo cannot show

Everything above you can watch happen. These you have to be told, so they are short. Anything deeper lives in the whitepaper, which is what a reviewer reads anyway.

No tracking. Period.

Zero analytics, zero crash reporting, zero telemetry. No Firebase Analytics, no Crashlytics, no usage tracking of any kind. We have no idea how you use the app, how often you open it, or what servers you connect to.

What does leave your device is limited, disclosed, and only when you use the feature that needs it: purchase verification, Firebase sign-in if you enable sync, an optional avatar upload, any automation webhook or monitoring endpoint you configure, an anonymous update check on desktop, a service status check while the Help panel is open, and a bug report or suggestion only when you send one yourself from the Help panel (what you typed, the app version and platform, and an optional screenshot that is redacted by default; it is posted to the public ZestSSH Discord, and if you are signed in your account id rides along for rate limiting only, never shown there; no install identity). None of it carries your vault data. Full egress inventory in the whitepaper and the Privacy Policy.

Recovery, backups, and what is synced

Sync setup issues a one-time recovery key, generated on your device, shown once, never sent to us, formatted ZEST-XXXX-XXXX-XXXX-XXXX-XXXX. It opens your vault if you forget your password. If you lose both your password and your recovery key, your data is permanently unrecoverable, that is the tradeoff of real end-to-end encryption: a server that cannot read your data also cannot help you recover it.

Local backups (.zest files) use the same primitives: AES-256-GCM with an Argon2id key from a password you choose. Because they are encrypted with your password and not a key we hold, you can safely store them in any cloud drive.

What is synced: SSH connections (including each host's last-used remote SFTP folder, which you can clear per host), identities, private keys, stored passwords, known_hosts, groups, snippets, and port-forwarding rules, serialized, compressed, and AES-256-GCM encrypted before anything leaves the device.

Moving files between two servers

The desktop SFTP browser can point each pane at a different host, so you can copy straight from one server to another. That copy passes through your own device, never our infrastructure: ZestSSH downloads the file to a temporary file on your disk, uploads it to the destination, and deletes it when the copy finishes. A move within a single server is a server-side rename, so those bytes never leave that server at all.

Paste-time safety

When a shell turns on bracketed paste, ZestSSH sends a multi-line paste as one block the shell will not auto-run, so a stray trailing newline cannot fire a half-pasted command. A raw shell, many full-screen prompts and device consoles leave that mode off, and there Dangerous Command Protection is the backstop: it holds a paste, a snippet run, or a broadcast that carries a flagged command (rm -rf, mkfs, dd, a fork bomb, curl piped to a shell, shutdown) and asks first. Commands you type by hand are never intercepted.

Automation, and what it cannot do

The Automation Engine is default OFF and must be enabled with a warning shown first. External apps authenticate with per-key API keys stored in OS-native secure storage (never plaintext), compared in constant time, and rate-limited.

What it cannot do: run arbitrary commands you did not pre-configure, read or exfiltrate your SSH keys and passwords, bypass host-key verification, or run at all while the engine is off. Full model in the whitepaper.

Hardened builds and account protection

Every public release is compiled with Dart obfuscation, symbols split out and kept private. Destructive account operations (deleting sync data or the account) require proof you hold your sync password, not just a valid login token, so a stolen session cannot wipe your data.

Report a vulnerability

ZestSSH is built by one person, and I take security reports seriously. Email [email protected]. I can't offer cash, but validated findings get a free Squeezed license or Juiced upgrade, public acknowledgment if you want it, and my genuine thanks. See the full disclosure policy.

Your data. Your keys. Your control.

Encryption and sync are free on storage you run. Juiced buys the hosting, not the privacy.