Skip to content

25 Security and Privacy Model

Anubha Parashar edited this page Oct 10, 2026 · 1 revision

HealthBook AI (internal code name: BabyCare) holds a family's medical documents and daily care records, so its security model assumes the hosting platform, the database dashboard or a stored file could be exposed, and limits what each layer reveals. This page brings the controls the code implements into one model: transport and headers, authentication, profile ownership, application-layer encryption, database lock-down, private object storage, and the privacy rules for sharing, push, AI and the home camera. Each control names the module that implements it.

Responsibilities

  • Authenticate every account-scoped request and authorize it against the specific family profile.
  • Keep clinical content unreadable at rest without the application key, and keep the storage and database tiers closed to client-side keys.
  • Minimise disclosure: push payloads, logs, share access logs and the internal status endpoint carry no medical detail.
  • Make every disclosure outside the family explicit, scoped, time-limited and revocable: doctor links, export, external AI.
  • Leave an audit trail of writes, exports, shares and consents.

Security architecture

flowchart TD
    subgraph clients["Clients"]
        W["Web app<br/>cookie plus CSRF header"]
        A["Android app<br/>bearer token"]
        D["Doctor browser<br/>share token plus PIN"]
        K["Home camera worker<br/>worker key"]
    end
    W -->|"HTTPS"| GW["FastAPI guardrails<br/>origin, rate limit, size, CSP, HSTS"]
    A -->|"HTTPS"| GW
    D -->|"HTTPS"| GW
    K -->|"HTTPS"| GW
    GW --> AUTH["Authentication<br/>require_user, hashed session lookup"]
    GW --> SHARE["Share token check<br/>hash, expiry, revoke, PIN"]
    GW --> CAMK["Camera key check<br/>hash of worker key"]
    AUTH --> OWN["Profile ownership check<br/>own_patient and own_resource"]
    SHARE --> SCOPE["Package scope only<br/>selected record ids"]
    OWN --> APP["Handlers<br/>validation and audit"]
    SCOPE --> APP
    CAMK --> APP
    APP --> ENC["Application-layer encryption<br/>Fernet EncryptedText"]
    ENC --> PG[("PostgreSQL<br/>RLS on every table<br/>client roles revoked")]
    APP --> ENCF["Fernet encrypt originals"]
    ENCF --> OBJ[("Private Supabase object storage<br/>service-role key on server only")]
Loading

The API connects to PostgreSQL as the table owner, which RLS does not restrict. harden_database() enables row level security on every public table and revokes all table, sequence, function and schema privileges from the Supabase anon and authenticated roles. Anyone holding a client key for the hosted REST or GraphQL endpoints can therefore read nothing. In production, a hardening failure stops the server from starting. GET /api/internal/status reports any tables without RLS and any remaining grants.

Threat model

Threat Control Module
Cross-site script injection or clickjacking CSP default-src 'self', script-src 'self', object-src 'none', frame-ancestors 'none'; X-Frame-Options: DENY; nosniff; Referrer-Policy: no-referrer api/guardrails.py
Downgrade or sniffing on the network HTTPS hosting; HSTS for one year in production; Secure session cookie in production api/guardrails.py, api/main.py
CSRF against the cookie session SameSite=Strict cookie; non-GET cookie requests need X-Requested-With: BabyCare; unsafe requests from foreign origins get 403; CORS limited to explicit origins api/security.py, api/guardrails.py
Credential stuffing or API flooding 30 auth requests per minute and 600 API requests per minute per IP; 429 with Retry-After: 60; database or memory store api/guardrails.py
Stolen database rows reveal sessions Session token token_urlsafe(36), only its SHA-256 stored; 14-day expiry; logout deletes the row api/security.py
Password database theft PBKDF2-HMAC-SHA256, 390,000 iterations, random 16-byte salt per user; minimum length 12; constant-time comparison; one error message for unknown email and wrong password api/security.py, api/main.py
Account enumeration or abuse through password reset Always 202 with the same message; email sent in the background; 3 requests per hour per email hash; token is 32 random bytes, stored only as SHA-256, valid 30 minutes, single use, newest link wins; carried in the URL fragment; confirmation signs out every session api/password_reset.py
Another family reading a profile own_patient and own_resource on every route; 404 for foreign ids; safe_doc rejects a source document from another profile api/security.py, api/main.py
A read-only caregiver changing records Writes need an editor grant; sharing, invitations, audit, export, camera keys and AI consent are primary-guardian only; revoking a caregiver cancels their pending notifications api/security.py, api/workflows.py
Database or bucket dump Fernet encryption of clinical text and originals; the key is never stored alongside the data; startup refuses to generate a new key when encrypted data exists api/crypto.py, api/storage.py
Supabase client keys used directly RLS on every table and grants revoked; the bucket must be private, or startup fails api/cloud.py, api/storage.py
Long-lived file URLs leaking Fernet-sealed tickets valid for 120 s and bound to a live session; no public object URLs api/workflows.py
Doctor link guessed, forwarded or kept 256-bit token, only its hash stored; optional 4 to 12 digit PIN with salted PBKDF2; 8 failed PINs in 15 minutes gives 429; expiry of 1 h, 24 h, 3 d or 7 d; revocation (410); view-only and download switches; noindex and no-store headers; documents addressed by index inside the package only api/share.py, api/share_models.py
Share access log leaking content The log stores the event, time, a SHA-256 of the IP, and a short label; never clinical content api/share.py
Camera credentials or video leaving home Credentials embedded in the stream URL are moved into encrypted columns; private-network hosts by default; worker key from token_urlsafe(32) is shown once and stored as a hash; only a key holder can fetch the stream config; the worker sends derived events only, never raw video or audio api/camera.py, api/camera_api.py, api/camera_worker.py
Lock screen exposing medical detail Expo and Web Push send one fixed sentence ("You have a confirmed care reminder. Open HealthBook AI to review it."); registration requires consent_generic_push: true api/worker.py, api/push_web.py, api/cloud.py
Silent transfer to an external AI Off unless configured; per-document consent flag plus a matching provider hostname; HTTPS only; guardian only; at most 10 pages; the consent is audited before transmission; the result stays needs_review api/providers.py
Scheduler endpoint abuse Returns 404 unless BABYCARE_SCHEDULER_KEY has at least 24 characters; header compared in constant time; returns counts only api/cloud.py
Spreadsheet formula injection in exports CSV cells starting with = + - @ or a tab or CR get a leading quote api/workflows.py
Logs containing PHI The middleware never logs bodies; the worker, mailer and storage log error classes only; email bodies and addresses are not logged api/guardrails.py, api/worker.py, api/mailer.py

Privacy controls

  • Data export. GET /api/patients/{pid}/export (guardian only) returns a ZIP with records.json, the decrypted originals, transcriptions and review payloads. The selective PDF and ZIP exports in api/export_api.py and api/share.py are built in memory and audited.
  • Deletion. POST /api/account/delete requires the password and the exact phrase DELETE MY ACCOUNT AND OWNED CHILD RECORDS. Owned profiles cascade, the encrypted blobs are deleted, and other families' profiles and records are untouched. The response reminds the operator that offline backups expire only under their own retention policy.
  • Audit trail. The audit table records user, profile, action, entity and id for every create, correction, confirmation, export, share, consent and password reset. Guardians read the latest 250 entries through GET /api/patients/{pid}/audit. Previous values are kept in record_revisions.

Clinical safety rules

A notification is never proof that a dose was given. OCR or AI output is extracted, not clinically verified, until a caregiver confirms it against the original. Stool-image interpretation is observational only. Share and export labels keep prescribed and administered information apart, and keep parent-reported entries apart from clinician-documented ones.

Known gaps

  • There is no multi-factor authentication or account lockout beyond the per-IP rate limit.
  • Dates, statuses and ids are not application-encrypted.
  • The camera worker key is compared with a plain hash equality, not hmac.compare_digest.
  • In-memory rate limiting is per process.
  • Key rotation is not implemented.

See Limitations.

Implementation

api/guardrails.py, api/security.py, api/crypto.py, api/storage.py, api/cloud.py, api/password_reset.py, api/share.py, api/providers.py, api/push_web.py, api/worker.py, api/camera.py, api/camera_api.py, api/workflows.py

Related pages

Authentication and Session Security · Family Profile Isolation · Encryption and Private Storage · Share With Doctor Architecture · Home Camera Architecture · Notifications and Reminder Architecture

Clone this wiki locally