Skip to content
EK

Search

Medical Avenue · Founding Engineer (Contract) · 2026

A healthcare-grade file platform with an audit log the database itself protects

  • Default-deny access registry
  • Append-only audit via PG triggers
  • Signed, time-limited URLs

Designed a secure file backend on S3-compatible storage — presigned upload → confirm → download → delete, a default-deny access registry, and append-only audit logging enforced by PostgreSQL triggers.

Node.js · PostgreSQL · Prisma · Cloudflare R2 · Redis

Context

I’m the sole engineer on a medical-travel platform — a React Native patient app, a coordinator dashboard, and a Node/Prisma/PostgreSQL backend I took over from a departed agency team and have extended since (roughly 70 new endpoints on top of the ~120 inherited). Patients upload documents that sit at the sensitive end of the spectrum: passports, medical records, clinical forms.

Problem

The inherited file handling used permanent public links — anyone with a URL had the document, forever. For a healthcare-grade product that’s not a bug, it’s a liability. And beyond access control, there was no answer to the question an audit would ask first: who touched this file, when, and was anything denied?

Constraints

  • Live clients on the old flow — the migration had to be incremental, not a flag-day cutover.
  • Sole engineer: every design had to be operable by one person, which rules out anything that needs babysitting.
  • Application-level guarantees weren’t enough for the audit trail. Application code has bugs; the log had to survive them.

My role

All of it — design, implementation, and the incremental migration off the old flow. This case study covers the backend; the same platform work included an auth overhaul (slim JWT payloads, a refresh flow that verifies signatures of expired tokens with machine-readable error codes, Redis-backed sliding-window sessions, Google/Apple OAuth, and rate limiting on sensitive routes).

Approach

The file service is built around one lifecycle: presigned upload → confirm → download → delete, on S3-compatible object storage (Cloudflare R2). The client never sends bytes through the API — it requests a presigned upload URL, uploads directly to storage, then calls confirm, where the backend verifies the object’s metadata before the file becomes real in the system. Downloads are signed, time-limited URLs; nothing is permanently public anymore.

Access control is a default-deny registry: every file belongs to a category (passport, medical trip document, and so on), and each category has an explicit guard that must positively authorize the requester against the linked entity. No registered guard, no access — the failure mode is denial, not exposure.

Every operation — including denied ones — writes to an audit table. The append-only property isn’t a code-review convention: PostgreSQL triggers reject UPDATE, DELETE, and TRUNCATE on the audit table outright, so even buggy or compromised application code can’t rewrite history. Denied events get their own index, because “show me every denial” is the first query an incident review runs.

Key decisions

  • Confirm as a separate step. A presigned URL that was never used, or an upload that didn’t match its declared size and type, never becomes a live file. The confirm step is where declared intent meets verified reality.
  • Enforce append-only in the database, not the application. Triggers cost nothing at this scale and turn “we promise we don’t edit the log” into “the database refuses.”
  • Default-deny per category, not per endpoint. Guards attach to file categories and their link tables, so a new endpoint can’t accidentally expose an old category — it gets denial for free until someone writes the guard.
  • Path-traversal-safe filename sanitization and MIME/size validation at the boundary, so storage keys are never attacker-influenced.

Trade-offs

The confirm step adds a round trip and a state machine to every upload — strictly more moving parts than “POST the bytes.” For public marketing images it would be over-engineering; for passports and medical records the verification step is the point. The two-phase design also left a class of orphaned pending uploads to clean up, which is a scheduled job — an acceptable cost for never trusting an unverified object.

Result

Permanent public links are gone, replaced by signed, time-limited URLs behind explicit per-category authorization. Every access and every denial is in an audit log the database itself makes immutable. The design has needed no operational babysitting since.

What I’d do differently

Introduce the access registry before building the first file endpoint, not while replacing the old ones. Retrofitting default-deny onto flows that assumed public access was the slowest part — the pattern is cheap when it’s there from the first migration.

Related case studies

Want to talk about a role or a system like these?