Dev.to Security ๐Ÿ” Cybersecurity ๐Ÿ‘ 0 ๐Ÿ“– 6 min read

Building SHALA: An AI Mental Health Companion with Zero-Knowledge Encryption

Tech How do you build an AI-powered mental health app that users can truly trust? The answer isn't just about a smart AI model; it's about Absolute Privacy and Data Security. Through SHALA (Supportive Help Agent and Li

Tech

How do you build an AI-powered mental health app that users can truly trust? The answer isn't just about a smart AI model; it's about Absolute Privacy and Data Security. Through SHALA (Supportive Help Agent and Lifeline Assistant), I implemented a Zero-Knowledge Encryption architecture, a Multi-Provider AI Gateway with 11 providers, and layered defenses to protect users' most sensitive data. This article breaks down the threat model, technical decisions, and lessons from real incidents.

1. Zero-Knowledge Encryption Architecture (AES-GCM + PBKDF2)

One of SHALA's core features is the Safe Space Diary. Users can lock their journals with a PIN. The key never touches the server.

  • KDF: The user's PIN is derived using PBKDF2 (100,000 iterations, SHA-256) directly in the browser via the WebCrypto API.
  • Encryption: Journal content is encrypted using AES-256-GCM (Galois/Counter Mode) with a unique Initialization Vector (IV) for every entry.
  • Zero-Knowledge: The server (Firestore) only receives ciphertext. Even database administrators cannot read the user's journal.

Conceptually, the flow is simple:

  • key = PBKDF2(PIN, salt, 100000) โ€” derive the key from the PIN
  • ciphertext, iv = AES-GCM-encrypt(plaintext, key) โ€” encrypt content
  • plaintext = AES-GCM-decrypt(ciphertext, key, iv) โ€” decrypt when opened
  • What reaches the server: {ciphertext, iv, salt} โ€” never the PIN, never the key, never the plaintext

The practical consequence: if the user forgets their PIN, the data is unrecoverable. This is a deliberate trade-off โ€” absolute privacy over recovery convenience.

2. WebCrypto Rationale & Web Worker

Why use the native WebCrypto API instead of third-party libraries (like CryptoJS)?

  1. Native & Fast: Runs at the browser engine level, utilizing hardware instructions (AES-NI).
  2. Secure: No risk of supply-chain attacks from npm libraries.
  3. Non-Blocking: The heavy encryption/decryption process (100,000 PBKDF2 iterations) executes inside a Web Worker, so the main thread (UI) never freezes or lags while the user types.

A Web Worker is not just an optimization โ€” it is a functional requirement. 100,000 PBKDF2 iterations on the main thread will freeze the UI for 300ฮ“ร‡รด800ms on mid-range mobile devices. During that freeze, the user may hit back or assume the app is hanging. Moving it to a Worker keeps the UI responsive; the password field can show a spinner while derivation runs in the background.

3. Threat Model: What Is Protected, What Is Not

A security system without an explicit threat model tends to overclaim. SHALA distinguishes honestly:

What is protected:

  • Journal contents from database administrators, cloud providers, and Firestore breaches
  • The user's PIN โ€” never sent to the server, never stored
  • AI provider API keys โ€” only exist on the backend proxy, never in the client JS bundle

What is not protected (transparent):

  • Metadata: timestamps, entry size, write frequency, and login status remain visible to Firestore
  • AI prompts: when a user requests AI module help, prompt content is sent plain-text to the backend proxy and forwarded to the AI provider. The gateway does not store prompts, but third-party providers may have their own retention policies
  • Registration: user email for Firebase Authentication is stored plain-text (by design โ€” Firebase Auth needs it for login)

Conclusion: encryption protects the content of the journal, not its existence or usage patterns. Users who need metadata privacy must understand this boundary.

4. Multi-Provider AI Gateway & Circuit Breaker

SHALA does not rely on a single AI provider. We built a backend Gateway (Express.js) that routes requests through a fallback chain with 11 providers: Gemini ฮ“รฅร† Groq ฮ“รฅร† GitHub Models ฮ“รฅร† Mistral ฮ“รฅร† Z.ai ฮ“รฅร† DeepSeek ฮ“รฅร† Cerebras ฮ“รฅร† SambaNova ฮ“รฅร† Cloudflare ฮ“รฅร† OpenRouter ฮ“รฅร† Pollinations.

If the primary provider times out or hits a rate limit (HTTP 429), the system automatically switches to the next provider in milliseconds.

The circuit breaker concept here:

  • Each provider has a state: closed (normal), open (skip, because it already failed N times consecutively), half-open (try once for recovery)
  • When the failure threshold is reached, the provider is marked open for a window โ€” not every request tries a provider that is clearly down
  • Requests move to the next provider in the fallback chain
  • If all providers are open, the system shows a friendly error โ€” not a stack trace

Why 11 providers, not 3? Because during peak hours, popular providers (Gemini, Groq) often hit rate limits. A longer chain increases availability at the cost of higher operational overhead.

5. Crisis Detection

The Gateway is also equipped with Crisis Detection โ€” high-risk keyword detection that immediately surfaces emergency helpline numbers when necessary. This is not an optional feature; it is a mandatory safety layer for a mental health app.

The flow:

  • Every user prompt is scanned against a keyword list (phrases like "want to end it", "no hope left", etc.) โ€” at minimum bilingual (ID + EN)
  • On match, the response is augmented with a grounding prompt + local hotline number
  • A dedicated rate limit prevents accidental repeated triggering
  • False positives can happen โ€” if a user writes "I want to end this movie", the system should not panic. Solution: keyword matching + sentence context + soft warning (not a hard block)

This is an area that must ethically fail-safe: better a false positive (excess warning) than a false negative (missed detection).

6. Firestore Security Rules

Firestore is a database accessed directly from the client via SDK. Without strict rules, anyone with a Firebase API key (which exists in the JS bundle โ€” by design) can read collections they should not.

Our principles:

  • Default deny: root catch-all allow read, write: if false
  • Owner-based: all user subcollections (/users/{uid}/diaryEntries/{id}) are accessible only by users with the same uid
  • Admin-based: system collections (system_errors, admin_logs, admin_metrics) can be read only by admins via custom claim

A common mistake: allowing isSignedIn() alone on collections like replies (Paper Plane). That means any signed-in user can write to anyone's collection. We had this bug once โ€” and found it during audit. Now every write on replies requires request.resource.data.userId == request.auth.uid.

7. Sentry PII Redaction (GDPR & UU PDP)

Observability without scrubbing = privacy violation. Sentry receives error stacks, breadcrumbs, and session replays โ€” all of which can contain PII.

SHALA does automatic redaction before data leaves the application, both client-side (beforeSend & beforeBreadcrumb) and server-side (beforeSend in Sentry Node):

  • Email: regex ฮ“รฅร† [EMAIL_REDACTED]
  • Phone: regex ฮ“รฅร† [PHONE_REDACTED]
  • API Key / Bearer Token: regex ฮ“รฅร† [API_KEY_REDACTED]
  • Sensitive fields (password, token, secret, creditCard): stripped from extra context

Session Replay is also masked for input fields โ€” PII must not appear in replay video.

Compliance: GDPR (EU) and UU PDP (Indonesia) require data minimization and purpose limitation. Scrubbing at the source is safer than at the destination โ€” if data has been sent, it's too late.

8. i18n: 13 Languages, 2 RTL

SHALA supports 13 languages: English, Indonesian, Spanish, French, German, Japanese, Korean, Chinese (Simplified), Arabic, Urdu, Hindi, Portuguese, Russian.

Two of them โ€” Arabic and Urdu โ€” are RTL (Right-to-Left), requiring full layout mirroring: sidebar moves to the right, arrow icons flip, padding swaps.

An honest reality: not all languages have 100% coverage. English and Indonesian = 100%. European languages = 70ฮ“ร‡รด78%. East Asian languages and Urdu = 11ฮ“ร‡รด12% (still in translation). This is a deliberate choice โ€” prioritize clinical translation quality (CBT terms, grounding) in primary languages, not speed of completing every language.

9. Incident & Challenge: Firestore Multi-Tab Crash

One of the most challenging incidents was when Firestore crashed with the message e.Vu.get is not a function. It turned out that the multi-tab sync feature (persistentMultipleTabManager()) on Firestore failed when the app was run inside a sandboxed iframe (like in AI Studio preview).

Solution:

  • Switched to persistentLocalCache({}) (single-tab mode) โ€” dropped multi-tab sync, but eliminated the crash
  • Vite configuration (optimizeDeps.exclude) for the entire firebase package โ€” prevented esbuild from breaking Firebase's ESM iterability

Lesson: default Firestore multi-tab is very useful for normal apps, but incompatible with sandboxed environments that have IndexedDB constraints. Always test in the target deployment environment, not just the dev browser.

10. Lessons Learned

Three key lessons from building SHALA:

  1. A Web Worker is not an optimization, it's a functional requirement. 100k-iteration encryption on the main thread = UI freeze. Move it to a Worker or users will think the app crashed.

  2. The threat model must be explicit and honest. Better to write "metadata is not protected" in the docs than to claim "100% private" and lose trust when users understand the limits.

  3. Firestore rules that look "secure enough" are often security holes. Default deny + owner check + periodic audit. The Paper Plane permission bug was found during audit, not at runtime.

SHALA is a project built with React 19, TypeScript, Express, Firebase Authentication & Firestore, and the WebCrypto API. The 11-provider AI gateway architecture with fallback chain and crisis detection is core to the app's resilience.

๐Ÿ“ฐ Read the original article on Dev.to Security

Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes โ€” full credit and traffic to the original publisher.