Juiced
Juiced
Section titled “Juiced”Juiced keeps your SSH connections, identities, and configuration synchronized across all your devices with end-to-end encryption. Whatever storage you point it at only ever holds an unreadable encrypted blob — it never sees your plaintext data.
Bring Your Own Backend
Section titled “Bring Your Own Backend”ZestSSH sync is multi-backend. Think of the encryption as a sealed safe that ZestSSH packs on your device: the storage you choose is just the shelf the safe sits on, and it never has the combination. You pick where the encrypted data lives, and the client does all the cryptography before anything leaves the device. There are two flavours:
- Hosted ZestSSH Cloud — the zero-setup, paid option. Requires the Juiced entitlement. (Holding both the Squeezed and Juiced entitlements simply puts you on the top “Juiced” tier.)
- Bring-your-own stores — point sync at storage you already own. These are free; ZestSSH charges nothing because it never touches the data.
| Backend | Setup | Cost | Auth | Notes |
|---|---|---|---|---|
| ZestSSH Cloud | Zero — sign in and go | Juiced | Google / Apple sign-in | Hosted at api.zestssh.com; the default for every install |
| WebDAV | Server URL + credentials | Free | Basic auth (app password) | Nextcloud, ownCloud, Synology, mailbox.org, Koofr, a NAS… |
| S3-compatible | Endpoint, bucket, keys | Free | Access key + secret | Cloudflare R2, Backblaze B2, Wasabi, self-hosted MinIO… |
| Google Drive | One OAuth sign-in | Free | Google sign-in (drive.appdata) | A single file in a hidden app-data folder ZestSSH cannot see past |
| Custom ZestSSH server | Base URL (+ optional token) | Free | Account sign-in or static bearer token | A self-hosted server that speaks the ZestSSH sync protocol |
You choose and configure the backend under Settings > Sync > Sync storage. Switching backends disconnects this device from the old store (nothing is deleted remotely) and prompts you to set sync up again on the new one. Whichever you pick, the encryption below is identical.
Sync vs. Backup. Sync keeps a single, continuously-updated encrypted manifest that all your devices merge into. That is different from a backup destination, which writes dated
.zestsnapshot files you restore from manually. WebDAV and Google Drive can serve in both roles, but the two use separate folders and separate files — they do not interfere with each other.
Zero-Knowledge Architecture
Section titled “Zero-Knowledge Architecture”ZestSSH sync uses a zero-knowledge encryption design regardless of which backend you choose. See Zero-Knowledge Encryption for the full technical details. The key points:
- Your sync password is used to derive a Master Encryption Key (MEK) via Argon2id (128 MB, 3 iterations, 4 parallelism). This is the current parameter set (“v2”); accounts created before v1.6 used a 64 MB “v1” profile and keep working — the parameters travel with the blob so any device can re-derive the key.
- A random Data Encryption Key (DEK) encrypts the actual data with AES-256-GCM.
- The DEK is wrapped by the MEK and stored encrypted alongside the blob.
- A separate recovery key wraps the DEK independently for account recovery.
- The store only ever holds encrypted blobs, salts, wrapped keys, and verification hashes.
- On bring-your-own stores there is no server to check your password, so verification happens client-side: ZestSSH tries to unwrap the DEK, and a wrong password fails the AES-GCM authentication tag. The DEK, MEK, sync password, and recovery key never leave the device on any backend.
What Syncs
Section titled “What Syncs”| Data | Synced | Notes |
|---|---|---|
| Connections | Yes | Host, port, label, group, protocol, keep-alive, startup command, WoL settings, environment variables, agent forwarding, home-card size/pin/note, sort order |
| Identities | Yes | Username, auth method, key type, public key fingerprint/data, default flag |
| Identity secrets | Yes | Passwords and private keys (encrypted in the sync blob, also written to secure storage on restore) |
| Connection groups | Yes | Name, color, sort order, expanded state |
| Snippets | Yes | Label, command, category, confirm-before-run flag, default flag, sort order |
| Port forward rules | Yes | Connection ID, type, bind address/port, remote host/port, label (auto-start and non-loopback binds are reset on restore — see note) |
| Networks | Yes | Name, color, icon, triggers (subnet/SSID/cellular), fallback flag, VPN bring-up, sort order |
| Host network overrides | Yes | Per-host, per-network address/port/username/identity/jump-host overrides |
| Port-forward network overrides | Yes | Per-rule, per-network on / off / inherit state |
| Deletion tombstones | Yes | Propagate deletes for connections, groups, identities, snippets, snippet categories, networks, and port-forward rules/overrides (see Conflict Resolution) |
Networks and their overrides sync as of v1.6. A change made only to a network profile or an override is enough to trigger a push, so context-aware connection behavior now follows you between devices.
Restored port forwards are re-armed safely. On restore, every synced rule has
auto-startforced off and any non-loopback bind address rewritten to127.0.0.1. This prevents a synced rule from silently opening a LAN-reachable listener on a new device before you have re-approved it in the UI.
Schema Version
Section titled “Schema Version”The sync blob includes a schema_version field (currently 11) and a synced_at timestamp. This allows schema migrations without breaking existing synced data. An older build that does not understand a newer blob refuses to restore it (rather than silently dropping fields and pushing truncated data back) and asks you to update the app.
What Doesn’t Sync
Section titled “What Doesn’t Sync”| Data | Reason |
|---|---|
| Host-key local-verification status | Whether THIS device has locally confirmed a host key is device-local and never travels. The host keys themselves DO sync as of blob v11 — with per-record provenance, and a conflicting key (same host, different fingerprint) is never silently overwritten — but each device still confirms trust on its own first local connect. |
| Terminal transcripts / session recordings | Too large; device-specific |
| Automation history / audit logs | Device-specific execution records |
| App settings and preferences | Theme choices, font sizes, UI preferences are personal to each device |
| Terminal theme customizations | Stored locally in SharedPreferences |
| Auto-backup passwords | Device-specific secure storage |
Sync Flow
Section titled “Sync Flow”The steps below describe the ZestSSH Cloud / custom-server protocol. Bring-your-own stores follow the same contract over a single encrypted manifest object — see Backend Differences for where they diverge.
Initial Setup
Section titled “Initial Setup”- Choose your backend under Settings > Sync > Sync storage. For ZestSSH Cloud, sign in with Google or Apple; for a bring-your-own store, enter its credentials and run Test connection.
- ZestSSH verifies your Juiced entitlement (ZestSSH Cloud only — bring-your-own stores skip this).
- Choose a sync password (minimum length enforced).
- A random DEK is generated.
- The MEK is derived from the password + random salt.
- A recovery key is generated and displayed (save it securely).
- The DEK is wrapped with both the MEK and recovery KEK.
- All local data is encrypted and pushed.
Push (Upload)
Section titled “Push (Upload)”- Gather all local data (connections, identities, groups, snippets, port-forward rules, Networks, and their overrides) plus identity secrets from secure storage.
- Encrypt the data blob with the DEK.
- Include the salt, verification hash, wrapped DEKs, and Argon2id parameters.
- Store the blob with an
expected_versionfor conflict detection (aPOST /sync/pushon the ZestSSH server; a compare-and-swap write on bring-your-own stores). - On success, the new version number is returned.
Pull (Download)
Section titled “Pull (Download)”- Fetch the encrypted blob (
GET /sync/pullon the ZestSSH server; a read of the manifest object on bring-your-own stores). - The store returns the encrypted blob, salt, version, wrapped DEKs, and Argon2id parameters.
- Derive the MEK from the stored password + salt.
- Unwrap the DEK using the MEK.
- Decrypt the blob using the DEK.
- Restore data as an upsert plus tombstones: incoming records overwrite matching local records (or are inserted if new), and the blob’s deletion tombstones remove local records whose deletion they carry (see Conflict Resolution).
Conflict Resolution
Section titled “Conflict Resolution”Two mechanisms operate at different layers.
Push lock (server/store-side): optimistic concurrency with version numbers:
- Each push includes an
expected_version, the version the client last saw. - If the stored version matches, the push succeeds and the version increments.
- If the stored version has advanced (another device pushed), the write is rejected with a
SyncConflictException. On the ZestSSH server this is an HTTP409; on bring-your-own stores it is an ETag / revision precondition failure. - The client then pulls the latest data, merges local changes, and retries the push. The auto-sync cycle does this for you.
This serializes writes; it is not the merge itself.
Merge Strategy
Section titled “Merge Strategy”The restore uses an upsert-plus-tombstones strategy:
- Existing records are updated (overwritten wholesale) with the synced data.
- New records are inserted.
- Deletions propagate. Deleting a connection, group, identity, snippet, snippet category, network, or port-forward rule/override on one device writes a deletion tombstone carrying a
deletedAttimestamp. On the next sync, other devices apply that tombstone and remove their local copy too. - Edit-versus-delete conflicts are resolved by timestamp: a tombstoned record survives only if its own
updatedAtis newer than the tombstone’sdeletedAt(a genuine edit made after the delete wins); records with noupdatedAtare delete-wins. Ordinary edit-versus-edit conflicts are resolved by push order (the push lock above), not by comparing timestamps. - Tombstones are retained for two years before garbage collection. A device that has not synced in longer than that is warned and asked to reconcile rather than silently losing or resurrecting data.
Password Change
Section titled “Password Change”Because of DEK indirection, changing your sync password does not require re-encrypting all data:
- Derive a new MEK from the new password + new salt.
- Re-wrap the existing DEK with the new MEK.
- Push the updated wrapped DEK to the store.
- The encrypted data blob remains untouched.
This works identically on every backend, because only the small wrapped-key metadata changes — the large data blob is never re-encrypted.
Recovery
Section titled “Recovery”If you forget your sync password:
- Enter your recovery key.
- ZestSSH derives the recovery KEK from the key + stored recovery salt.
- The recovery hash is verified (server-side on ZestSSH Cloud; client-side by unwrapping the DEK on bring-your-own stores).
- The DEK is unwrapped using the recovery KEK.
- Data is decrypted using the DEK.
- You can then set a new sync password (re-wraps the DEK with a new MEK).
Data Purge
Section titled “Data Purge”To delete all sync data from the store:
- Go to sync settings and select Purge Data.
- Enter your sync password for verification.
- On the ZestSSH server the verification hash is sent to
/sync/purge; on a bring-your-own store the encrypted manifest object is deleted directly (possession of the store credentials is the authorization). - All encrypted data is permanently deleted from the store.
On server-backed backends the verification-hash requirement prevents data destruction by an attacker who only holds the account auth token.
Account Deletion
Section titled “Account Deletion”Deleting your ZestSSH server account:
- Navigate to account settings.
- Select Delete Account.
- Enter your sync password for verification.
- The server deletes the account and all associated data via
/account/delete.
Bring-your-own stores have no ZestSSH account, so “delete account” is equivalent to a data purge — the encrypted object is removed and the store itself remains yours to manage.
Backend Differences
Section titled “Backend Differences”Every backend implements the same encrypted-sync contract, so the crypto, merge strategy, and conflict handling above are identical. Where they differ:
| Concern | ZestSSH Cloud | Custom ZestSSH server | WebDAV | S3-compatible | Google Drive |
|---|---|---|---|---|---|
| Authentication | Firebase (Google / Apple), Authorization: Bearer <ID token> | Account sign-in or a static bearer token you set | HTTP Basic (app password recommended) | Access key + secret (SigV4) | Google OAuth, drive.appdata scope |
| Entitlement | Requires Juiced | None (free) | None (free) | None (free) | None (free) |
| Storage layout | Server-managed rows | Server-managed rows | One manifest file under /ZestSSH/sync/ | One object under your bucket (optional key prefix) | One file in the hidden app-data folder |
| Optimistic lock | HTTP 409 version check | HTTP 409 version check | ETag precondition (If-Match / If-None-Match) | ETag precondition | Revision precondition |
| Password check | Server-verified | Server-verified | Client-side (unwrap DEK) | Client-side (unwrap DEK) | Client-side (unwrap DEK) |
All backends require a valid TLS certificate — ZestSSH refuses to sync secrets over an unverified or plaintext connection.
Common Mistakes
Section titled “Common Mistakes”| Mistake | What happens | Fix |
|---|---|---|
| Losing both the sync password and the recovery key | Nobody — including ZestSSH — can decrypt the blob | Store the recovery key somewhere safe at setup |
| Pointing sync at a WebDAV/HTTP URL without TLS | ZestSSH refuses to save the backend | Use an https:// endpoint with a valid certificate |
Reusing the /ZestSSH/backups/ folder for sync | Sync and backups fight over the same location | Leave the defaults — sync uses /ZestSSH/sync/, backups use /ZestSSH/backups/ |
| Assuming host-key trust carries over | New devices re-prompt to trust each host | Expected — known-hosts are deliberately not synced (see What Doesn’t Sync) |
| Not syncing a device for a very long time | That device’s tombstone set may be past the two-year retention window, so it can no longer safely suppress records deleted while it was away | ZestSSH detects the over-window condition and asks you to merge, adopt the cloud state, or export first |
Next Steps
Section titled “Next Steps”- WebDAV Backup Destination — use a WebDAV server for dated
.zestsnapshots as well as live sync - Nextcloud Setup — step-by-step Nextcloud configuration
- Local Backups — on-device
.zestexports and auto-backup - Zero-Knowledge Encryption — the full cryptographic design
- Encrypted Backups — the
.zestfile format