A live punchlist from running this in production. Listed not because they're hidden, but because pretending they're not there would be worse than admitting them.
-
Privilege leak risk in magic-link mode. Magic-link auth uses a shared service account to mint Medplum sessions on the user's behalf. If the service account ever gets a wider AccessPolicy than the recipient should have, the issued session inherits it. Today both the service account and the AccessPolicy are scoped to a single project, so the failure mode is contained — but a multi-project deployment must rotate the service account per project. See
apps/portal/src/lib/auth/medplum-login.tsfor the implementation; the threat model is inapps/portal/src/lib/auth/magic-link.ts. -
Allergies extension trust-on-read. The portal renders allergies from a
ServiceRequest.extensionbecause Medplum'sAllergyIntoleranceresource isn't yet in the AccessPolicy. The extension is unsigned — a user with write access to the SR can lie about allergies. Acceptable for v1.0 (only ClinicStaff and the originating Referrer can write); needs a realAllergyIntoleranceAccessPolicy entry before multi-tenant. -
Walk-in detection is heuristic. The wizard branches into "walk-in" vs "scheduled" based on whether the user is ClinicStaff and the form has no slot picked. There's no resource-level marker —
ServiceRequest.intentisordereither way. Replace with aServiceRequest.tagthe day before this matters for analytics. -
Inbox sort-stability under concurrent writes. When two ClinicStaff accept different referrals within the same Postgres LSN tick, the inbox's optimistic resort flicks them in unpredictable order until refresh. Cosmetic; visible only to the small front-desk team.
- TOTP MFA (mandatory for ClinicStaff + Admin).
- Self-service forgot-password (currently staff-mediated only).
- ClamAV upload scanning (single-tenant trusted-uploader is OK without).
- Error aggregation (Sentry / Honeybadger) + log shipping (
journalctlon the VPS for v1.0). - Multi-region failover, hot-standby DB.
- Public status page.
- Patient-facing accounts (everything is referrer + clinic-staff today).
A few things we hit while building this would be cleaner if Medplum itself
fixed them. Each is described as a PR-ready proposal in
docs/upstream/:
- Better error when
auth/loginsucceeds but noProjectMembershipexists — PR open: medplum#9137. - First-class magic-link / passwordless flow — Medplum is already developing this as an OAuth pre-authorized-code grant (see upstream discussion #9109); our RFC #9140 closed with three downstream-use notes added to inform the integration.
(A third draft, "admin-mediated auth/setpassword", was dropped after we
discovered Medplum already ships that endpoint at
POST /admin/projects/setpassword.)
apps/portal/src/components/clinic/InboxTable.tsxis doing too much — filtering, sorting, optimistic state, derive-stage rendering. Split into hooks before the next feature.- The seed package mixes "set up an empty project" with "fill in demo data"
in the same scripts. Separate
bootstrap-*fromrealistic-seedso they can run independently in CI. apps/portal/src/app/(auth)/layout.tsxandapps/portal/src/components/shell/Sidebar.tsxduplicate the brand-block markup. After three more places copy it, fold into a<BrandLockup />.
- Whether to expose the
BRANDconfig via runtime headers (e.g. for white-label SaaS) instead of build-time env vars. Build-time is simpler; runtime supports per-request branding without rebuild. - Whether to keep the Medplum admin app on a public hostname (it lives at
medplum.example.comtoday) or hide it behind a private IP / VPN.