Skip to content
Security · 00 / 03

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.

HSTS preload-readyArgon2id password hashingHash-chained audit ledgerDPDP Act §14BAA available
Transport · 01 / 06

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
Auth · 02 / 06

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
AuthZ · 03 / 06

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)
Ledger · 04 / 06

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
ML · 04.5 / 06

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
SSID · 05 / 06

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
Infra · 06 / 06

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
Disclosure · CTA

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 vulnerability
SLA on critical findings
24h
From triage to a fix merged, with a public changelog entry once shipped.
Disclosure window
90d
Standard coordinated-disclosure window. Shorter on request if the report carries a live exploit.
FAQ

Questions CISOs always ask.

Need something specific not covered here? Talk to our security team or read the privacy policy.