Case Study · Cross-domain · Desktop App
Kaizei
Personal Finance OS — TW/US stocks · smart accounting · double-entry ledger
Pain points
Two structural problems with personal-finance software: (1) multi-device sync inevitably needs cloud, but handing API keys and transaction records to a third-party server is handing over the keys; (2) mainstream Electron apps treat security carelessly — missing CSP, Node.js integration enabled, API keys stored in plaintext config files.
Kaizei's design goal: deliver everything (TW/US stock auto-update, double-entry ledger, reports, Gemini analysis), but make it so the server is physically incapable of reading user data, and apply OS- and sandbox-level defenses on the desktop side.
System architecture
Three-layer separation: desktop + edge + object storage. The server only sees ciphertext; auth and decryption happen on the user's device.
- 1 · User master password
Entered locally, never leaves the device. Web Crypto derives two independent keys.
- 2 · Dual-key derivation (PBKDF2 310,000 iterations)
authKey (sent to server for identity verification) and encKey (stays on device, encrypts/decrypts the vault). Both derived from the same password but with different salts — neither can be derived from the other.
- 3 · Local AES-256-GCM encryption
Vault data (API keys, settings, sensitive fields) is encrypted with
encKeyinto ciphertext before being sent to the cloud. The server receives an encrypted binary blob. - 4 · Cloudflare Worker (Hono + custom JWT)
The Worker compares identity via SHA-256(authKey + server pepper) and issues a 30-day HS256 JWT. Even if the JWT is stolen, the attacker still gets ciphertext that the server itself can't decrypt.
- 5 · Supabase (PostgreSQL + RLS)
RLS is set to no public policies — only the Worker's service_role can write. GitHub Actions pings daily at 02:00 UTC to prevent free-tier pause.
Design highlight 1 — Zero-knowledge vault
The most critical design: the server is physically incapable of decrypting user data. Even if the server is fully breached, the DB is dumped, and the attacker gets admin access, what they pull out is still AES-GCM ciphertext — without the user's master password, there's no path back to plaintext.
Concrete implementation details:
- PBKDF2 310,000 iterations (OWASP 2023 recommended floor) derives authKey + encKey from the master password + two independent salts
- Server pepper is added when hashing authKey, so even if DB salts leak, offline brute-forcing is infeasible
- JWT 30-day expiry; even a leaked JWT only sees ciphertext
- Web Crypto API is used end-to-end via browser/Electron native crypto — no third-party crypto library trust issues
Trade-off
- Lost master password = unrecoverable data. No backdoor = no "forgot password" option. User education must be strong
- Each unlock takes ~ 200-400ms (PBKDF2 by design). Acceptable cost for security
- Sync conflict resolution is limited: the server can't see the contents, so no field-level merge — only whole-payload overwrite (last-write-wins)
Design highlight 2 — Desktop-side defense in depth
Zero-knowledge protects the "server can't see" line. The Electron desktop side needs a separate defense in depth against local attacks:
- OS keychain integration
Windows DPAPI / macOS Keychain provide a second-layer OS-level encryption for locally stored API keys (even though they're already part of the vault), bound to the user account / hardware.
- Electron sandbox fully on
sandbox: true+nodeIntegration: false+contextIsolation: true. The renderer has no direct Node.js access; the main process exposes a strict IPC whitelist. - Enforced CSP + DOMPurify
Production builds automatically strip
'unsafe-inline'. All potentially HTML-bearing content (changelog from GitHub Releases, user-input notes) is forced through DOMPurify.
Design highlight 3 — Data pipeline & supply-chain defense
- Excel-import ReDoS protection
Hard 20 MB file-size limit (blocks ReDoS attack vector) + auto-snapshot before import (up to 5 local backups) + one-click restore without password.
- TypeScript strict mode, zero tolerance
v2.4.17 fully enables
strict: true; fixed 32 pre-existing type errors to reach a 0-errors baseline. Type safety = compile-time elimination of an entire bug class. - Dependabot weekly scans
npm package vulnerabilities scanned weekly with auto-PRs. Secret Scanning + Push Protection prevent accidental secret pushes.
- Public release mirror (auditable binaries)
The app's main repo is private, but releases sync to the public yu8812/kaizei-releases, where users can verify binary hashes and release notes.
Tech stack
Reflections / Future directions
- The tension between no-backdoor design and "forgot password" — losing the master password = unrecoverable data is the necessary cost of zero-knowledge architecture. Optional middle-ground solutions (Shamir Secret Sharing 2-of-3 recovery, social recovery) increase implementation complexity and trust assumptions. A worthwhile design problem.
- Real attack-surface evaluation for Electron desktop apps — multi-layer defense (sandbox + CSP + OS keychain + AES) is in place, but whether the corresponding threat model is over- or under-engineered requires empirical attack testing (e.g., sandbox escape research).
- Cross-machine vault sync conflict resolution — currently last-write-wins. Can field-level CRDT merge be done without breaking zero-knowledge? An interesting cryptography + distributed-systems crossover.
Further reading
- Kaizei official site — full security-architecture deep-dive (PBKDF2 → AES-256-GCM diagram)
- ExClinCalc Case Study (multi-role security on the medical side: RLS + TOTP + audit)
- ClinCalc Case Study (consumer-side local-first + structured injection)