Security & privacy

Transport and encryption

How data travels between your devices and our servers, and how it is stored at rest.

Every byte that leaves your phone, browser or Twoody speaker travels over authenticated, forward-secret TLS. Once it reaches our database, anything sensitive (API keys, third-party credentials, session digests, voice profiles) is encrypted again with keys stored outside the database. A stolen backup is cryptographic noise; a captured packet is cryptographic noise; compromising either on its own yields nothing.

HTTPS everywhere

All traffic between your Twoody device, the mobile/web app and our servers goes exclusively over TLS 1.2 or higher. Plain HTTP is refused at the server level, not merely redirected.

HSTS + preload

Browsers are instructed to always use HTTPS for twoody.com and its sub-domains for one year, with includeSubDomains and preload. Our domain is submitted to the HSTS preload list so downgrade is impossible even on a first visit.

TLS 1.3 + forward secrecy

Connections negotiate TLS 1.3 when supported and fall back to TLS 1.2. Forward secrecy (ECDHE) is mandatory on every suite we accept, so a future leak of our private key cannot be used to decrypt recorded traffic.

Hardened CDN

Our Cloudflare edge blocks malicious bots (Bot Fight Mode), monitors certificate issuance (Certificate Transparency), enforces our WAF rules and absorbs DDoS traffic upstream of our origin.

DNSSEC enabled

Our DNS zone is signed, blocking cache-poisoning at resolvers that honor DNSSEC. Mail and subdomains pointed at <code>twoody.com</code> can't be silently hijacked at the DNS layer.

Encryption at rest

Sensitive columns (user API keys, third-party credentials, session digests) are encrypted with keys distinct from the main database key, via ActiveRecord Encryption. A database dump does not expose your secrets.

Encrypted backups

Production database backups are encrypted at rest and in transit, retained for a bounded window, and access is limited to the primary operator with per-action audit logs.

Principle of least storage

The strongest protection for a secret is to not hold it. We never store card numbers (Stripe does), we don't persist raw wake-word audio beyond what's strictly needed for matching, and we don't keep verbose logs of successful requests.

Deep dive

How the TLS handshake is pinned down

Our load balancer presents a modern cipher suite list: TLS 1.3 AEAD suites (AES-GCM, ChaCha20-Poly1305) first, then a short TLS 1.2 fallback that is ECDHE-only. Weak primitives (RC4, 3DES, export-grade, static RSA key exchange) are refused at the handshake — a client that can't negotiate forward secrecy gets a clean connection failure rather than a downgraded session. OCSP stapling is enabled so browsers can check certificate revocation without an extra round-trip to our CA.

The certificate chain uses Let's Encrypt ECDSA intermediates; we watch the Certificate Transparency logs and alert on any certificate issued for our domain that we didn't request.

Key distinction at rest

We maintain three distinct encryption keys, stored in environment variables and never in the database: a primary key for non-deterministic columns (different ciphertext every write), a deterministic key for columns that need to be queried by equality, and a key-derivation salt. Our boot process refuses to start in production if any of the three is missing or still holds the dev placeholder — you can't accidentally ship a build with weak encryption.

Key rotation is a first-class operation. The primary key is versioned, so a rotated key encrypts new rows while existing rows stay readable under the previous generation; a background job can re-encrypt on demand.

What we deliberately don't encrypt

Non-sensitive metadata — user IDs, timestamps, row counts, product SKUs — is stored in clear. Encrypting everything indiscriminately would break indexing, break analytics, and provide no real benefit; the attack model for that data is "they already have your database," in which case encrypted identifiers are no safer than plaintext ones.

We encrypt what matters (tokens, secrets, biometrics, third-party credentials), we document what we encrypt, and we rely on ordinary Postgres access controls for the rest.

Every cross-network hop — your phone to us, your speaker to us, our server to OpenAI — is covered by one of these layers. We do not run any unencrypted internal service on a network that a user request can touch.

Related topics