Security Decisions, Explained
What we kept, what we skipped, and why.
Mature security work isn't about adding every control you can think of. It's about understanding which controls actually defend against your real threat model — and being honest about the ones that look impressive on a checklist but don't move the needle. This page documents both sides of that ledger.
Threat ModelWho We Defend Against
-
✓
Passive network observers
ISPs, public Wi-Fi, corporate proxies. Mitigated by TLS 1.3, App Transport Security on iOS, and Android's
network_security_config. - ✓ Malicious or compromised certificate authorities A rogue or mis-issued public-CA certificate. Mitigated by Certificate Transparency enforcement at the OS level — Apple since iOS 12.1.1, Chrome and Android in parallel. Mis-issued certs land in public CT logs and are rejected by the platform without app involvement.
- ✓ Server-side compromise An attacker who gains backend access. Mitigated architecturally: RAM-only data layer with no disk persistence, immediate post-ACK purge, OIDC pseudonymization (your sign-in identity is SHA-256-hashed before storage), and Cloudflare Tunnel for origin isolation. Even a full compromise would expose only in-flight data — there is nothing at rest to exfiltrate.
- ✓ Local device compromise Physical access to an unlocked device. Mitigated by AES-256 encrypted local storage (Hive) with keys stored in iOS Keychain or Android Keystore, plus app-switcher screen blur for over-the-shoulder protection.
Controls That Earn Their Complexity
The short list — TLS 1.3 with system trust anchors, OIDC bearer authentication, Cloudflare Tunnel for origin isolation, Silero VAD to filter silence before transcription, RAM-only Redis with no persistence, and SHA-256 user-hash pseudonymization. Each one ties to a specific threat in the model above. The longer engineering rationale lives in our DPIA — see the DPIA page for the risk register.
Considered, not adoptedControls We Evaluated and Skipped — And Why
1. Application-layer Certificate Pinning
What it is: Hard-coding the server's certificate fingerprint into the app, so the client refuses to talk to anything else.
Why we don't do it:
- TLS terminates at Cloudflare's edge, and Cloudflare rotates certificates autonomously. A pinned binary would either need every user to update before each rotation, or it would silently break their app — both are hard failure modes.
- Certificate Transparency at the OS level already defends against the primary pinning threat (rogue or mis-issued certs). Apple and Google enforce CT for public CAs without any app involvement.
- The user-visible trust signal we want isn't "this app pinned a cert" — it's "the server can't keep your data" (RAM-only architecture + ACK-then-404 deletion proof). That's the work that earns trust.
If we change our mind: The decision is reversible. If we later take on an enterprise customer that requires MASVS L2, we add pinning with a runtime kill-switch and a documented rotation runbook.
2. Memory locking to prevent swap-to-disk
What it is: Lock the worker process's memory pages into RAM so the kernel can't swap sensitive data to disk.
Why we don't do it: It requires elevated container privileges that complicate a rootless deployment. The marginal benefit is small: between the RAM-only data layer and explicit zeroization of sensitive buffers after use, sensitive data lives for microseconds. The window where the kernel could swap it is vanishingly thin.
3. Redis TLS and password authentication
What it is: Encrypted and authenticated connections to the in-memory data layer from internal services.
Why we don't do it: The data layer runs only on a private internal network that is not exposed to the public. Anyone with access to that network already has container-level access — TLS inside a single-node private bridge is pure overhead, and a password adds a rotation burden without raising the security floor.
4. Circuit breakers between services
What it is: Hystrix-style fail-fast breakers on Redis client calls.
Why we don't do it: YAGNI for a single-node deployment. If Redis goes down, the whole host is down — failing fast on one call doesn't help anything. We'll revisit this if we ever scale to a multi-node deployment.
5. Third-party crash and analytics SDKs
What it is: Mixpanel, Amplitude, Sentry, Firebase Analytics, advertising IDs.
Why we don't do it: The privacy claim falls apart the moment a behavioral SDK ships in the bundle. Crash reports go to our own backend, scrubbed of PII before transmission, and the user can disable them entirely. There are no analytics SDKs to disable, because there are none in the bundle.