Your repos, encrypted before they ever leave your browser. The server never sees your data.
The Problem
Most developers don’t back up their GitHub repos. They assume GitHub won’t go down, that their account won’t get suspended, that they won’t accidentally git push --force the wrong branch. Until one of those things happens.
Slerim solves this by letting you schedule encrypted backups of your GitHub repositories to cloud storage — with a zero-knowledge guarantee baked into the architecture, not bolted on afterward.
Core Idea
Slerim is a GitHub backup service where the encryption key is generated entirely in your browser, split using Shamir’s Secret Sharing, and never stored whole on any server. The server handles scheduling, cloning, and storage — but it only ever sees ciphertext. Not even the session key used to encrypt a backup is stored in plaintext.
How It Works
Onboarding
You fill out a signup form. The browser immediately generates a K2 keypair using Argon2id KDF, derived from your master password and email. The private key never leaves your device. The public key is sent to the server.
The K2 private key is then split into 3 Shamir shares with a threshold of 2:
- Share 1 — you save offline (downloaded to your device)
- Share 2 — sent to your verified backup email
- Share 3 — stored on the server
Any 2 of 3 shares reconstruct K2. The server alone, holding only share 3, cannot reconstruct anything.
During onboarding you also provide and verify a mandatory backup email. This is not optional. It serves two purposes: it receives share 2 of your Shamir split, and it is your fallback identity if you ever lose access to your primary account. If your GitHub gets suspended and your Google account gets compromised, you can still authenticate via this email and reach your backups.
Backup Job Pipeline
A backup is triggered on schedule or manually. The job enters a Postgres-backed queue (SELECT FOR UPDATE SKIP LOCKED — no Redis needed). The worker picks it up, fetches your encrypted GitHub OAuth token, and decrypts it in memory only.
The worker clones your repo from GitHub, generates a fresh random symmetric key K, encrypts the repo tarball with K using AES-256-GCM, then encrypts K itself with your stored K2 public key. Both the encrypted backup and the encrypted K go to Cloudflare R2. The server never holds K or K2 in persistent storage.
Once the backup lands in R2, a single-use download token is generated (slerim.com/download?t=xyz) and Resend delivers it to your backup email as a clean URL.
Download and Restore
You click the link from your email. The /api/download endpoint validates the token — checking it hasn’t expired and hasn’t already been used. If either check fails, you get a 403 or 410. If valid, the token is immediately marked used (single-use enforced), a presigned R2 URL with a 1-hour expiry is generated, and you are 302-redirected to it. R2 serves the bytes directly to your browser. The server is out of the data path from this point forward.
Your browser downloads the zip containing enc_backup and enc_K. You provide your K2 private key (offline, from share 1 or reconstructed via any 2 Shamir shares). The browser decrypts enc_K using K2, then decrypts the backup using K. You get a bare git clone. From there: git push --all --tags to your new account.
Key Properties
Zero-knowledge — K2 is generated client-side via Argon2id. The private key never leaves the browser. The server stores only the public key, share 3, and ciphertext.
Auth independence — Google SSO means a GitHub lockout does not lock you out of Slerim. The verified backup email goes further: it is a parallel identity that survives both a GitHub suspension and a Google account compromise. Neither fallback is a convenience feature — both are load-bearing parts of the recovery story.
Shamir’s Secret Sharing — K2 is split 3 ways, threshold 2. Any two shares reconstruct the key. The server holding share 3 alone cannot reconstruct K2 — a server breach does not expose your private key.
Envelope encryption — Each backup is encrypted with a fresh random K. K is then encrypted with your K2 public key. K2 rotation does not require re-encrypting every backup — only the stored enc_K values need updating.
Single-use download tokens — Download links expire and are marked used on first redemption. Replaying a link returns a 410.
No Redis — The job queue is Postgres-backed using SELECT FOR UPDATE SKIP LOCKED. One less moving part.
Idempotent jobs — If a backup job fails mid-run, the retry does not create duplicate records, re-clone unnecessarily, or double-charge. Job state is tracked explicitly in the backup_jobs table.
Architecture
Onboarding Flow

User Browser
└─→ Signup Form
└─→ Client-Side Key Gen (browser only)
└─→ Argon2id KDF (master password + email)
├─→ K2 Private Key (never leaves device)
│ └─→ Shamir Split (3 shares / threshold 2)
│ ├─→ Share 1 — user saves offline
│ ├─→ Share 2 — sent to backup email
│ └─→ Share 3 — stored on server
└─→ K2 Public Key (sent to server)
└─→ Server DB stores: K2 pub, share 3, enc GitHub token
Mandatory backup email verified at onboarding via time-limited signed token.
Backup Job Pipeline

Trigger (scheduled / manual)
└─→ Job Queue (Postgres: SELECT FOR UPDATE SKIP LOCKED)
└─→ Worker: fetch enc GitHub token, decrypt in memory
└─→ GitHub API: clone repo bytes
└─→ Encryption Step
├─→ Generate random symmetric key K
├─→ Encrypt repo tarball with K (AES-256-GCM)
└─→ Encrypt K with K2 public key
└─→ Cloudflare R2: store enc_backup + enc_K
└─→ Generate single-use download token
└─→ Resend → backup email inbox
KEY RECOVERY: any 2 of 3 Shamir shares reconstruct K2.
The server holding share 3 alone cannot reconstruct.
Download and Restore

User clicks email link
└─→ /api/download: validate token
├─→ expired or already used → 403 / 410
└─→ valid
├─→ mark token used (single-use enforced)
├─→ generate presigned R2 URL (1hr expiry)
└─→ 302 redirect to R2 URL
└─→ R2 serves zip directly to browser (server not in data path)
Client-side restore:
User provides K2 private key (offline / reconstructed from shares)
Browser: K2 decrypts enc_K → K decrypts enc_backup → bare git clone
└─→ git push --all --tags to new GitHub account
Tech Stack
| Layer | Choice |
|---|---|
| Frontend | Next.js (App Router) + Tailwind + shadcn/ui |
| Auth | Better Auth (Google SSO + backup email verification) |
| Database | PostgreSQL + Drizzle ORM (Neon) |
| Job Queue | Postgres (SELECT FOR UPDATE SKIP LOCKED) |
| Storage | Cloudflare R2 (presigned URLs, envelope encryption) |
| Runtime | Bun |
| Encryption | AES-256-GCM + Argon2id KDF |
| Key Management | Shamir’s Secret Sharing (3 shares, threshold 2) |
| Resend (backup delivery + email verification) | |
| Error Tracking | Sentry |
| Logging | Axiom (structured logs, queryable) |
Threat Model
The server is treated as compromised by default.
- K2 private key never touches the server — generated and held client-side only
- The server stores K2 public key, share 3, and ciphertext — none of which are useful alone
- GitHub OAuth token is encrypted at rest; decrypted in worker memory only, never logged
- Presigned R2 URLs are scoped and short-lived (1hr) — no persistent credential exposure
- Download tokens are single-use and time-limited — replay attacks return 410
- IDOR protection on all backup and restore endpoints
- Rate limiting on auth, job creation, and download routes
- Backup email verification uses time-limited signed tokens, not persistent reset links
- GDPR and DPDPA compliance considered from schema design, not patched in later
What Slerim does not protect against: a compromised browser environment, malware on the client machine, or a user who stores their offline share insecurely. Client-side encryption shifts the trust boundary — it does not eliminate it.
Observability
Structured logs on every backup job — who triggered it, which repo, duration, success or failure reason. Log level system (info / warn / error). Logs shipped to Axiom, queryable after the fact.
Error tracking via Sentry. When something breaks at 2am for a user, you need a stack trace with context, not a generic failure email. Free tier is sufficient through V1 and V2.
Minimum metrics tracked: backup job success rate, job queue depth, R2 storage per user, download token redemption rate. Without these you are flying blind when something degrades slowly rather than failing loudly.
Roadmap

V1 — MVP
Prove the core loop works.
- GitHub OAuth only (limitation accepted)
- Manual backup trigger only
- Clone repos, upload to R2 as plaintext zip (no encryption yet)
- Presigned URL emailed directly (spam risk accepted)
- PostgreSQL + Drizzle: users, repos, backup_jobs tables only
- Basic dashboard: repo list, manual backup button per repo
- Simple DB-backed async job queue
Not in V1: encryption, scheduled jobs, backup email, Shamir, rate limiting, Resend.
The only question V1 answers: does the backup-and-restore cycle work end-to-end?
V2 — Usable Product
Safe enough for real users.
- Add Google OAuth, mandate backup email at onboarding
- Encrypt GitHub OAuth token at rest (AES-256)
- Per-user envelope encryption: random K per backup, K encrypted with server-managed key
- Scheduled backups: daily and weekly via BullMQ or Inngest
- Download tokens:
slerim.com/download?t=xyz, Resend for delivery - IDOR protection on all endpoints, rate limiting on auth and download routes
- Guided restore UI with step-by-step instructions post-download
Not in V2: Shamir split key, billing, API access.
V2 is the version you show people and ask them to actually use.
V3 — SaaS Product
Monetisable, defensible, honest zero-knowledge claim.
- True zero-knowledge: client-side K2 generation, Shamir split, K2 never touches server
- Key recovery flow: 2-of-3 shares, share 1 offline, share 2 to backup email, share 3 on server
- Stripe billing: free tier and pro plan (storage, frequency, extra backup emails)
- Public API: programmatic backup trigger, API key auth (pro only)
- GitHub Org support: team accounts, multiple members
- Audit log: every backup, download, token use, key change — immutable
- Cloudflare WAF + DDoS protection, IP reputation filtering at edge
- Multiple backup emails and tertiary recovery account support (pro)
V3 is only built after V2 has real users who trust it. Do not build V3 features before V2 is validated.
Open Questions
- Key storage UX — store K2 private key in
localStoragefor convenience, or require re-upload each session for stricter security? The answer has real consequences for both UX and the threat model. - GitLab / Bitbucket — out of scope for V1 and V2. Reconsider after first paying users.
- Pricing tiers — flat storage-based subscriptions chosen over pay-as-you-go for revenue predictability. Exact tier boundaries TBD.
Links
- Repo: github.com/ufraaan/slerim
- Roadmap:
README.md - Dev and agent guide:
GUIDE.md