What HermesBackup stores
- Ciphertext blobs, sizes, SHA-256metadata
- Labels and timestampsmetadata
- Device names, public key (display only)metadata
- Hashed device tokensauth
Security & Trust
Our database leaked tomorrow? Every backup is still unreadable. Zero-knowledge is not a marketing label here — it's a design constraint that makes the convenient path impossible.
We use age (X25519 asymmetric encryption), not passphrase-symmetric encryption. The distinction matters: naive zero-knowledge designs encrypt each backup with a passphrase — but cron can't type a passphrase, so the passphrase ends up stored in plaintext on disk, which defeats the point.
AGE-SECRET-KEY-1...) is shown to the user exactly once, stored locally at ~/.hermes/hermesbackup/age-identity.txt (chmod 600).Zero-knowledge does not mean zero metadata. Here is exactly what we can and cannot see.
Every link in the chain is designed so that a single compromise is survivable.
init requests rate-limited to 30/hour/device.u/{user_id}/{backup_id}.age — no user-supplied path components, no traversal surface.Blobs are opaque — we can't inspect content. Caps on count, size, and cadence are the enforcement surface. They're generous for real backup use while making generic file-hosting impractical.
| Limit | Free | Pro |
|---|---|---|
| Max object size | 2 GB | 2 GB |
| Uploads per day | 4 | 24 |
| Stored objects | 60 | 500 |
| Init rate limit | 30/hour/device | 30/hour/device |
Transparency is the brand. Below is the full set of threats we model and how each is mitigated.
D1 contains user metadata, device token hashes, backup metadata, and ciphertext SHA-256s — but no plaintext, no keys. A leaked D1 dump is useless for reading backups.
R2 stores ciphertext blobs. An attacker with direct R2 access gets opaque .age files they cannot decrypt. Private bucket, no public access.
The Worker never sees backup bytes (presigned URLs only) and has no decryption capability. Even a fully compromised Worker cannot decrypt existing blobs.
Tokens stored as SHA-256 hashes. User can revoke any device from the dashboard. Pairing codes expire in 10 minutes. Tokens are per-device, not global.
Recovery kit printed at setup, dashboard banner persists until confirmation. This is the cost of true zero-knowledge — we don't backdoor it.
TLS 1.3 everywhere. URLs expire in 15 min (PUT) / 5 min (GET). R2 verifies the signature.
POST /backups/{id}/complete makes the Worker HEAD the R2 object and verify size + SHA-256. Mismatch marks the backup as failed.
R2 object keys are server-generated: u/{user_id}/{backup_id}.age. No user-supplied path components are accepted.
init checks quota before issuing presigned URL. 507 returned if exceeded. Server never silently deletes.
Max object 2 GB, free 4 uploads/day + 60 objects. Count/size/cadence caps make generic file-hosting impractical.
Both /webhooks/stripe and /webhooks/emailit verify signatures before acting.
We never email the private key. Recovery kit is printed at setup. Reminder emails pointedly do not contain the key.
A full internal audit (July 2026) verified the core zero-knowledge invariants against the Worker and client implementations. 18 issues found (2 HIGH, 6 MEDIUM, 10 LOW), 5 remediation cards created. Core invariants hold: no plaintext/keys/passphrases reach the server. A third-party security audit is planned post-launch.
As of July 26, 2026, HermesBackup has received zero government requests for user data, backdoors, or key escrow. This statement will be updated weekly. Its absence is evidence.
Email [email protected] with details and reproduction steps. We respond within 72 hours and credit responsible disclosure.