# Security and privacy

This page describes the security contract: what is protected, how, and what is not. It does not describe internal defensive heuristics, which would be counterproductive to publish.

## Accounts

Passwords are hashed with **bcrypt at 12 rounds** and compared in a timing-safe way. Only the hash is stored; the plaintext is never written anywhere.

Sessions use signed tokens with a bounded lifetime. Change your password or use **Sign out other devices** in Settings and every other session dies on the next request. They are still not used as machine credentials: `/api/mcp` accepts only an API token. See [authentication](/docs/authentication).

## Encryption

**A specific set of profile fields is encrypted at rest** with AES-256-GCM under a per-value random initialization vector, so identical inputs do not produce identical ciphertext. That set is your display name and seventeen profile fields: age, height, goal weight, goal, injuries, allergies, your stated commitment, what has worked and failed before, free-text notes, and the seven Mission Statement fields. Your WHOOP access and refresh tokens are encrypted the same way.

**Nothing else is.** In plain terms:

| Stored in the clear | |
| --- | --- |
| Lab results | Every marker and value from an uploaded bloodwork report |
| Methylation results | Your genotypes |
| Coach conversations | The full transcript of every chat |
| Progress pictures | The image data itself |
| Ledger data | Push-ups, meals, weights, habit checkboxes |
| Daily summaries and learnings | Generated prose about your patterns |
| Your username | |

The database itself is encrypted at rest by the provider, and access to it is limited. Application-layer encryption — the kind that survives a database compromise — covers the eighteen fields named above and your wearable tokens, and nothing more. If you are uploading a genome or a bloodwork panel, assume it is protected by database access control rather than by field-level cryptography.

Reads fail closed: a value that cannot be decrypted is withheld rather than served as raw ciphertext.

## Machine credentials

API tokens for the [MCP server](/docs/mcp) are stored only as SHA-256 hashes, carry explicit non-cumulative scopes, and are checked against the database on every call — so revocation takes effect on the very next request. Full detail in [authentication](/docs/authentication).

The MCP endpoint additionally refuses any request carrying a browser `Origin` header, which is what the MCP transport specification asks for to close the DNS-rebinding case.

## AI safety

**Prompt injection defense.** Input to the AI endpoints is screened before it reaches a model, and suspicious content is either sanitized or blocked. This matters more here than in a typical chat product because the coach reads text you did not necessarily write — imported CSV content, extracted lab-report text, meal notes.

**Length and history bounds.** Message length, conversation depth and total conversation size are all capped, so a single request cannot exhaust the context window.

**Your data is not training data.** Content sent to model providers is processed to answer your request. It is not used to train models, and it is not shared with anyone else.

## Third parties

| Service | Receives | Why |
| --- | --- | --- |
| MongoDB Atlas | Everything, as the system of record | Storage |
| xAI | Coaching context for the current request; lab and methylation *text* during extraction | Chat, voice, extraction |
| VoyageAI | Day and summary text | Embeddings and reranking |
| WHOOP | Nothing. Data flows inbound only | Wearable sync |
| Vercel | Request metadata and logs | Hosting |

Two things deliberately never leave your device or your account:

- **Lab and methylation PDFs.** Text extraction happens in your browser. Only the extracted text is sent for parsing, so the file — with its name, date of birth and provider header — stays local.
- **Progress pictures.** Stored against your account and returned only to you. Not sent to any model, not embedded, and not readable by any MCP tool. Stored unencrypted; see the table above.

## Infrastructure

HTTPS everywhere, enforced by the platform. Rate limits per user and per action. Circuit breakers that fail fast when an upstream service degrades rather than hanging your request. Schema validation on every API request, including sanitization that rejects MongoDB operators in field names. Structured logging with semantic event names, which is what makes an anomaly visible.

## What is not protected

Being straight about the limits is more useful than a longer list of controls.

**Rate limits are not a hard quota.** They are sized to bound abuse rather than to meter usage precisely, so treat a published limit as the intended ceiling rather than a guarantee.

**API tokens do not expire.** That is safe only because revocation is immediate. A token you have forgotten about is a token that still works — list them periodically and revoke what you no longer use.

**Health documents are not field-level encrypted.** Lab results, methylation results, progress pictures and chat transcripts sit in the clear inside the database. See the table above.

**Ledger data is readable by anyone with database access.** See the encryption section above. The set of people with that access is very small, but it is not zero.

**This is early-access software.** It is built carefully and tested, but it has not been through an external security audit.

## Your data

**Export.** CSV export of your full ledger over any range, at any time, without asking anyone.

**Deletion.** Delete the account from Settings (username + password). The record and every user-scoped collection go with it.

**Ownership.** It is your data. It is not sold, not shared with advertisers, and not used to train anything.

## Reporting a problem

If you find a security issue, report it privately rather than publicly, and give a reasonable window for a fix before disclosure. Write to `Austin@nLight.fit`.

## Related

- [Authentication](/docs/authentication) — token format, scopes, revocation
- [Data model](/docs/data-model) — what is stored and where
