Every layer of the stack, explained.
VitalMesh handles protected health information, real-time biometric streams, and signed legal consent. The security model has to hold up to a hospital CISO reading this page and a red team probing the API. Below is every layer, in plain language, with the spec references a reviewer can cross-check.
Encryption in transit and at rest
Every byte between a clinician’s browser and our edge is TLS 1.2+ with HSTS preload. Server-to-database traffic is wrapped in TLS by the Neon managed-Postgres provider. Patient identifiers never appear in URLs, query strings, or unencrypted logs.
- TLS 1.2 minimum; HSTS preload-ready with `max-age=63072000; includeSubDomains; preload`
- AES-256 at rest in Neon (managed Postgres). Object storage (Zepp fixtures, audit exports) is encrypted with envelope keys per tenant
- MongoDB-style query params scrubbed at the edge middleware — `password`, `secret`, `token`, `apikey` and friends are rewritten away before any handler or log sees them
- Headers: CSP, X-Frame-Options DENY, X-Content-Type-Options nosniff, COOP, COEP-ready, Referrer-Policy strict-origin-when-cross-origin
Sessions, passwords, and MFA
Sessions are random 256-bit tokens stored as HMAC-signed cookies with HttpOnly + SameSite=Strict + Secure flags. Passwords use Argon2id with per-deployment parameters. Failed logins are rate-limited per IP and the audit log records every attempt.
- Per-IP rate limit on /api/auth/login: 10 attempts per 60s window, sliding
- Per-IP cap on /api/auth/signup: 5 / 60s — a hostile IP can’t burn the DB with junk accounts
- Magic-link sign-in uses 32-byte tokens, single-use, 15-minute TTL, dropped from the URL on landing
- 5-session cap per user — newer logins invalidate older devices, so a stolen cookie stops working as soon as the legitimate user signs in elsewhere
- Self-Sovereign Identity (SSID) sign-in available: VC-backed wallet login with verifier-side replay protection and on-chain anchored DIDs
Authorization is per-org, per-bridge
Role-based access control plus row-level org isolation. A doctor at Hospital A cannot read Hospital B’s rows — even if the row ids are leaked. Cross-hospital visibility only opens when the patient explicitly grants a bridge and a home-hospital admin signs it.
- Roles: `super_admin`, `admin`, `doctor`, `nurse`, `family`, `patient` — each has its own /api/audit visibility rules
- Org isolation enforced in every query: every read filters by `orgId === session.user.orgId` or `actorUserId === session.user.id`
- Cross-hospital bridges are HMAC-signed, time-bound (1–72h), auditable, and revocable from either side without escalation
- Open-redirect protection on `?next=` — only internal paths, no `//evil.com` or protocol-relative slip-throughs
- CSRF: every state-changing endpoint enforces Origin / Referer equality; `Authorization` Bearer tokens bypass the check (server-to-server API)
Append-only audit ledger with hash chain
Every grant, revoke, red-flag, login attempt, and bridge event is appended to a Postgres table linked by SHA-256 hash. Each row’s `prevHash` is the SHA-256 of the previous row’s transaction signature, making tampering detectable on every integrity check.
- Slots allocated under Serializable isolation with a retry loop on P2002 (unique-slot race) and P2034 (deadlock)
- Per-row payload includes `prevHash` + `entryHash` + the Ed25519 signature over the canonicalised payload
- Tamper detection: `GET /api/audit?integrity=1` (admin-only) walks the chain and flags any slot whose hash link or signature fails
- Tenant scoping: each audit row carries the actor’s `orgId`; super_admin sees everything, doctors see their own org + their own actor rows, patients see their own patient-pubkey rows
- Solana devnet publication is best-effort: the local hash chain is the source of truth, the on-chain proof is a verifier convenience
Self-training from resolved outcomes
The risk-score engine improves itself from real-world outcomes. When an emergency request resolves, the clinician or dispatcher labels it true-positive, false-positive, true-negative, or false-negative. A nightly cron ingests those labels and nudges per-feature weights on top of the calibrated baseline — but only after 30+ labelled outcomes, with multipliers capped at [0.5×, 2.0×], and with the full audit chain still authoritative.
- Three personalisation layers stack on the population rule: CSV calibration → patient Welford baseline → cohort outcome loop
- Per-feature multipliers are versioned (`rule-v1+csv+learned-vN`) so production logs can attribute every score to a specific training run
- Gradient nudge is feature-share-weighted: a feature that didn’t contribute doesn’t move, so a single noisy outcome can’t poison the cohort model
- Below the 30-sample threshold the learned layer is a no-op — we keep the CSV-calibrated weights and never amplify on noise
- `/api/health/ai` exposes `learned.trained`, `learned.lastTrainedAt`, and the per-feature confusion matrix — operators see the loop status from a single unauthenticated probe
Patient-held consent via Verifiable Credentials
Patients hold their own consent and credential documents as W3C Verifiable Credentials. The hospital never stores a copy it doesn’t need — it verifies the VC issuer chain, runs the presentation, and discards. Withdrawal is a one-step revocation on the issuer’s status list.
- VCs follow the W3C VC Data Model 2.0 with HMAC-signed `credentialSubject` claims
- Issuer registry is whitelisted — only organisations whose DID is anchored on-chain (or whose public key is published in /api/ssid/issuers) can mint credentials VitalMesh accepts
- Revocation: 2021-status-list with cached bitmap, refreshed on every present / verify call, ≤60s freshness window
- Patients can revoke a credential from their own dashboard — the revoke propagates to all consumers within one cache cycle
Hosting, dependencies, and patching cadence
VitalMesh runs on Vercel for the app, Neon for Postgres, and Cloudflare in front of the edge. Dependencies are pinned to exact versions in `package.json` (`npm ci` is the only install path in CI) and patched within 48 hours of a CVE.
- Vercel build pipeline: every commit runs `prisma migrate deploy` + `vitest` + `next build` before promotion
- Neon branching: every PR gets a disposable DB branch, so tests never touch production data
- Cloudflare: DDoS mitigation, WAF, bot protection on the marketing + login paths. Rate-limited at the edge before traffic reaches the origin
- Secrets: API keys live in Vercel env vars, never in repo, rotated quarterly. Stripe keys live behind a publishable/secret split
- Backup: Neon’s point-in-time recovery with 7-day retention. Patient-data deletion on request removes all rows + dependent audit rows within 30 days
Found a vulnerability?
Email security@vitalmesh.example with a reproduction. We acknowledge within 24 hours, triage within 3 business days, and credit reporters in the changelog unless you ask to stay anonymous.
Report a vulnerabilityQuestions CISOs always ask.
Need something specific not covered here? Talk to our security team or read the privacy policy.