DEV Community

Cover image for Designing a Zero-Trust Personal Information Manager with Client-Side Encryption
Pranav Kishan
Pranav Kishan

Posted on • Edited on

Designing a Zero-Trust Personal Information Manager with Client-Side Encryption

I am a B.Tech Computer Science undergraduate at Amrita Vishwa Vidyapeetham who enjoys building privacy-focused systems and learning by deploying real software end-to-end.

The Motivation

The inception of InfoStuffs was not driven by a desire to build just another productivity application. It started with a very specific user requirement from my sister. She needed a digital space to organize personal documents and sensitive notes but refused to use standard cloud services such as Google Keep or Notion.

Her constraint was simple but technically demanding: she wanted the convenience of the cloud without trusting the cloud provider with her plaintext data.

This challenge became the foundation of InfoStuffs. My goal shifted from building a simple web application to architecting a Zero-Trust Information Management System that prioritizes privacy by default.

The Problem Statement

Modern productivity tools generally fall into two categories:

  1. Convenient SaaS: (e.g., Notion, Keep) store user data in plaintext or use server-managed keys, leaving data vulnerable to internal leaks or database breaches.
  2. Self-Hosted: (e.g., Obsidian, Nextcloud) offer strong privacy but are difficult to access and maintain across multiple devices.

InfoStuffs bridges this gap. The system had to be secure enough that a complete server-side compromise would yield nothing but garbage data, yet accessible via a standard web browser on any device.

High-Level Architecture

To satisfy these constraints, InfoStuffs uses a decoupled, cloud-native architecture with clearly separated responsibilities.

System/Infrastructure Architecture

  • Frontend: React (Vite) with Material UI. Responsibilities include UI rendering and client-side cryptographic operations (encryption/decryption).
  • Backend: Node.js and Express, following a Hybrid Architecture

    • Local Development: Runs as a fully dockerized monolithic container, ensuring a consistent development environment that mirrors production dependencies.
    • Production Deployment: Deployed to Vercel as stateless Serverless Functions, allowing the API to scale to zero when idle (cost-efficient) while maintaining a single Express codebase.
  • Database: MongoDB Atlas for storing encrypted metadata and ciphertext.

  • Authentication: Clerk. Delegating identity management reduced the attack surface for auth flows (MFA, session management).

  • Storage: Supabase Storage, used strictly for isolating binary objects (images and PDFs) via signed URLs.

Security by Design: The Zero-Trust Vault

Security was not an optional feature; it was the primary architectural constraint.

Zero-Trust Architecture

1. The Problem with Static Keys

In my initial design, I used a static encryption key stored in the server's environment variables (VITE_SECRET_KEY). I quickly realized this was a critical flaw. If an attacker or a compromised hosting environment were to expose environment variables, they could decrypt everyone's data. The key was visible, which violated the core concept of Zero-Trust.

2. The Solution: User-Derived Cryptography

To fix this, I removed the static key entirely. I implemented PBKDF2 (Password-Based Key Derivation Function 2) on the client side.

  1. When a user logs in, they enter a Vault Password. This password is never transmitted or stored and exists only transiently in the client’s memory.
  2. The browser runs PBKDF2 to derive a temporary 256-bit AES key in memory.
  3. This key is used to encrypt notes, titles, and file paths before the network request is even formed.

The server only ever sees (and stores) ciphertext. If the database administrator (me) were to look at the data, I would see nothing but unreadable strings.

The PBKDF2 parameters were chosen to balance resistance to brute-force attacks with acceptable latency on low-powered client devices.

3. Ephemeral Access to Media

For file storage, I avoided public buckets entirely.

  • Encrypted Paths: The database stores an encrypted string pointing to the file path (e.g., "user/123/image.jpg" is encrypted).
  • On-Demand Access: When a user unlocks their vault, the client decrypts the path and requests a Signed URL from Supabase.
  • Time-Limited: This URL allows access for exactly 60 seconds before expiring. This prevents "link sharing" leaks and ensures that even if a URL is intercepted, it becomes useless almost immediately.

Infrastructure Evolution: Solving the Cost Problem

One of the most valuable learning experiences came from adapting the infrastructure to real-world cost constraints.

Phase 1: The "Enterprise" Trap (GCP)

My initial deployment used Google Cloud Platform with Cloud Run and Cloud Build. While this was an industry-standard "Enterprise" setup, it introduced significant problems for a personal project:

  • High Costs: Paying for load balancers, container registry storage, and compute time quickly added up.
  • Complexity: Managing IAM roles and build triggers for a simple app was overkill.

Phase 2: The Hybrid "Serverless Monolith" (Vercel)

To solve the deployment cost problem, I re-architected the stack to run for $0/month:

  • Local Development (Dockerized): I kept the convenience of a containerized environment. A single docker-compose up spins up the Frontend, Backend, and Database services. This ensures that the development environment is isolated and reproducible on any machine.
  • Production Deployment (Serverless): Instead of paying for a permanently running container (which costs money even when idle), I refactored the Express application to run on Vercel Serverless Functions.
  • The Result: I effectively have a "Serverless Monolith." I develop it like a standard monolithic app (easy to debug, easy to run locally in Docker) but deploy it as distributed functions. This gives me the best of both worlds: Zero infrastructure management and Zero cost for personal use.

I intentionally avoided microservices, as the domain does not yet justify multiple bounded contexts, and premature service decomposition would increase complexity and attack surface without tangible benefits.

Technical Challenges & Solutions

1. Environment Variable Visibility
As mentioned, relying on .env files for security was a mistake. The migration to user-derived keys solved this, but it required handling edge cases like "Lost Passwords." Since I no longer had the key, I had to implement a "Nuclear Reset" feature. This allows users to wipe their unrecoverable data and start fresh, prioritizing security over recovery.

2. Monorepo Build Contexts
Vercel initially failed to build the project because it couldn't locate vite.config.js within the monorepo structure. I resolved this by explicitly configuring the "Root Directory" in Vercel settings and rewriting the build command to ensure dependencies were installed from the correct path.

3. The Docker vs. Serverless Routing Mismatch
One of the most complex challenges was reconciling the difference between a running Docker container and Vercel's file-system routing.

  • The Problem: In my local Docker container, Express handled all routing internally. However, when deployed to Vercel, the platform treated the API as static files. Requests to sub-paths (like /api/info/nuke) were hitting Vercel's 404 handler before reaching my Express app, which the browser misinterpreted as a CORS failure.

  • The Solution: I implemented a Hybrid Routing Strategy. I created a Vercel-compatible entry point (api/info.js) and configured a vercel.json rewrite rule. This acts as a bridge, telling Vercel to pipe all sub-route traffic directly into the Express instance. This fixed the CORS issues and allowed the exact same code to run inside Docker (locally) and as a Function (in production).

4. Production OAuth Strictness (The "Blank Screen" Issue)A critical UI/UX bug emerged only in production: users completing Google Sign-In were dumped onto a blank white screen at /sso-callback before eventually being redirected.

  • The Problem: In development, Clerk’s "Developer Keys" allow for lenient, client-side redirection magic. However, in production, security protocols enforce strict OAuth callbacks. My Single Page Application (SPA) lacked a dedicated route to handle this specific server-side redirect, causing the browser to hit a "ghost" route while the client-side JavaScript frantically tried to recover the session in the background.

  • The Solution: I architected a dedicated "OAuth Landing Pad". I created a specific route for /sso-callback that renders a lightweight intermediary component (AuthenticateWithRedirectCallback). This component instantly intercepts the OAuth token, displays a branded loading state to maintain user trust, and seamlessly completes the handshake before forwarding the user to the dashboard. This eliminated the "hang" and standardized the login flow across all environments.

Future Roadmap

While InfoStuffs is fully functional, I plan to explore:

  • Redis Caching: To reduce database reads for frequently accessed (encrypted) metadata.
  • React Native Mobile App: Wrapping the existing logic to allow biometric vault unlocking (FaceID) instead of typing the password.
  • Offline Mode: Using PWA capabilities to allow read-only access to cached encrypted notes.

Closing Thoughts

InfoStuffs is more than a note-taking application. It is a practical exploration of Zero-Trust Engineering.

By addressing the real-world problems of data visibility and cloud costs, I built a system where privacy is enforced by mathematics, not by policy. It satisfies a real user need while serving as a valuable learning experience in full-stack security and DevOps.

Repository: GitHub
Live Deployment: Link

Note: The live deployment requires authentication and a vault password. The core security properties are enforced client-side and are best understood via the architecture discussion above.

Top comments (10)

Collapse
 
caerlower profile image
Manav

This is genuinely solid work. You didn’t just build an app, you clearly thought through the security model end to end, and catching the static-key issue early shows good instincts. The user-derived key approach & short-lived signed URLs is exactly how zero-trust systems should be built.

What’s interesting is how naturally this could grow without breaking your design. If you ever move beyond purely personal use (shared vaults, delegated access, automation), confidential compute ideas, like running logic in TEEs on platforms such as Oasis ,could fit nicely while keeping the same “don’t trust the server” mindset.

Really nice balance of practicality, security, and cost awareness. This is the kind of project that actually teaches you how systems fail in the real world.

Collapse
 
pranav_kishan_ profile image
Pranav Kishan

Thanks, I appreciate that. The static-key issue was a key turning point, and extending the same threat model to shared access and confidential compute is definitely on my list.

Collapse
 
mehrzad_karami_5eac98d28b profile image
Mehrzad

Great example and solid work, thx for sharing this Pranav. Curious to know if you have got more feedback from users, and whether have seen more interest from others in using this.
And how do you deal with search? Does data encryption introduce a challenge in search, especially as the data grows more and more

Collapse
 
pranav_kishan_ profile image
Pranav Kishan

Thanks! Most feedback so far has been from family, friends, and privacy-focused users. Search is the main trade-off. Since the server only sees encrypted data, I handle search on the client by decrypting notes in memory and filtering locally. It works well for personal use, but it will need rethinking as the dataset grows.

Collapse
 
mehrzad_karami_5eac98d28b profile image
Mehrzad

Great to hear this . Yes, Search would be a challenge for big datasets, but also due to the whole encryption and privacy-preserving impl. How would you do it , keeping the encryption and privacy intact of course. Meta data? indexing?

Collapse
 
savvysid profile image
sid

This is a solid example of actual zero-trust design, not just “encrypted at rest” marketing. Client-side key derivation & server seeing only ciphertext is the right call, and your pivot away from env-stored secrets shows good threat modeling. The serverless-monolith approach is also very pragmatic for personal projects.

If you ever want to push this further, the next interesting step would be verifiable or confidential server-side logic e.g., TEEs for things like metadata processing or search, so even compute can be proven without exposing data. But as it stands, this is a clean, well reasoned privacy architecture with real-world tradeoffs handled thoughtfully.

Collapse
 
pranav_kishan_ profile image
Pranav Kishan

Thank you, I really appreciate that. The shift away from env-stored secrets was a key design turning point, and I agree that TEEs or verifiable compute would be a natural next step if server-side logic ever expands beyond simple coordination.

Collapse
 
elite_gamer_007_343e7859b profile image
Elite GAMER_007

Neat

Collapse
 
yash_bardia_b8734d8e4779f profile image
Yash Bardia

Amazing work!

Collapse
 
adiseshan_ramanan_8906f6e profile image
Adiseshan Ramanan

Nice@pranav_kishan_f81e2fc8327