Under the Hood

The media pipeline: uploads you can trust, bytes you don't proxy

Receipts and photos are the one place a splitting app handles arbitrary user bytes, and that makes the media path both a bandwidth problem and a security problem. Fable's answer: the API never touches the bytes (clients upload and download directly to Cloudflare R2 via presigned URLs, served through a CDN), and it never trusts what the client says a file is (every upload is validated against its actual magic-byte signature, not its claimed MIME type). This chapter walks that pipeline, the access control that gates private media, and the honest list of what it deliberately doesn't do yet.

The media pipeline: uploads you can trust, bytes you don't proxy

Media is where Fable handles the one thing the rest of the app doesn't: arbitrary bytes from an untrusted client. A receipt photo, a profile picture, a PDF — large, binary, and potentially hostile. That combination makes the media path simultaneously a bandwidth problem (you don't want gigabytes flowing through your API) and a security problem (you can't trust a byte the client sends). Fable's pipeline is built around two refusals that answer exactly those two problems: the API never transports the bytes, and the API never trusts the client's claim about what a file is.

This chapter is the object-storage lesson made concrete, plus the validation layer that lesson didn't cover.

The upload path: validate, then hand off

When a client wants to attach a receipt, the flow is:

  1. The client asks the API to prepare an upload. The API validates the request and returns a presigned URL scoped to one object.
  2. The client PUTs the file directly to R2 using that URL — the bytes never touch the API.
  3. The API records the media metadata (owner, kind, MIME type, the R2 key) as a row, hung off a group like everything else.

The two refusals live in this path. Bytes bypass the API via presigned URLs — the object-storage lesson's central pattern — so a dozen concurrent 10 MB receipt uploads don't pin the API's memory or bandwidth; the API's job is authorization, not transport. And every upload is validated, which is the security half.

Trusting no byte: MIME whitelists and magic-byte checks

A client sends a Content-Type header saying "this is image/jpeg." That header is a claim, and claims from clients are not facts. A malicious client can declare image/jpeg and upload an executable, a script, or an HTML file crafted to be interpreted as something dangerous when served. So Fable validates in two layers:

  • A MIME whitelist per kind. Each media kind accepts only certain types — a receipt accepts common image formats (jpeg/png/webp/heic/heif), a general image adds gif, documents allow PDF and common office types. A type not on the kind's whitelist is rejected outright.
  • Magic-byte validation. This is the important one. Rather than believe the declared MIME type, the API inspects the file's actual leading bytes — its magic-byte signature — and confirms they match the claimed type. A real JPEG starts with specific bytes; a real WebP has RIFF…WEBP at a known offset. If the declared type and the actual signature disagree, the upload is rejected. This is what stops "an .exe renamed to .jpg" — the classic upload attack where a client lies about a file's type to smuggle something dangerous past a type check. The MIME string can lie; the bytes can't (as easily).

There's a pragmatic boundary: images are byte-validated (their signatures are simple and stable), but formats with many container variants — HEIC, office docs, audio/video — are only MIME-whitelisted, because robustly signature-checking every container variant is a losing battle. Validate what you can validate cheaply and reliably; whitelist the rest. It's a deliberate, honest trade, not an oversight.

The download path: access control on private bytes

Media isn't public by default — a receipt belongs to a group, and only that group's members should see it. So the read path enforces the same tenancy check as every query: the API's assertReadable confirms the viewer is a member of the group the media belongs to before handing back a way to fetch it. The client fetches the metadata by id first (which runs the access check), and then the bytes come either from a public CDN URL (for media that can be public, cached at the edge so R2 is hit once) or a presigned GET URL with a limited TTL (for private media, re-fetched when the presign expires). Either way the access decision is the API's and the transport is R2's — authorization here, bytes there, exactly as the upload path.

Planned (TDD)

A media pipeline with server-side processing: thumbnails, image variants, EXIF stripping, blurhash placeholders.

Shipped

Direct-to-R2 uploads via presigned URLs with MIME-whitelist + magic-byte validation and group-scoped access control; CDN/presigned delivery. Server-side processing (variants, EXIF strip, blurhash) deferred to a later phase.

Transport (don't proxy bytes) and security (don't trust the declared type) are non-negotiable and shipped inline. Image processing needs either inline CPU that competes with the money workload or a worker fleet that doesn't exist yet — so it's deferred as an optimization, with EXIF-stripping flagged as the item closest to privacy and first to promote.

Interview takeaway

For any "handle user uploads / media" prompt:

  • "Clients upload and download directly to object storage via presigned URLs; the API authorizes but never transports the bytes." The single highest-signal media statement — it shows you won't build the API into a bandwidth bottleneck.
  • "I validate the file's magic bytes, not just its declared MIME type, because the Content-Type is a client claim." Names the disguised-upload attack and its fix.
  • "Private media is access-controlled at the API, then served via a short-lived presigned URL or a CDN for public assets." Ties authorization and delivery together.
  • "Server-side processing (thumbnails, EXIF strip) I'd do async on a worker, and defer until there's a queue — except EXIF stripping, which is privacy, so it moves up." Shows the correctness-vs-optimization judgment.

The bytes live in R2 and reach users through the CDN; the metadata rows live in the data model; the direct-transport pattern is the object-storage lesson in production.

Go deeper

  • S3/R2 presigned uploads The mechanics of the direct-to-storage upload that keeps bytes off the API — the transport half of the pipeline.
  • OWASP — Unrestricted File Upload The catalog of upload attacks (disguised types, dangerous content) that magic-byte validation and per-kind whitelists defend against — the security half.
  • File signatures (magic bytes) reference The actual leading-byte signatures Fable checks (JPEG, PNG, WebP's RIFF/WEBP) — how "the bytes can't lie as easily as the header" works in practice.

Check yourself

Answer out loud, as if an interviewer asked. If you hand-wave, reread that section.

  1. Media handling is both a bandwidth problem and a security problem. State the two "refusals" Fable's pipeline is built on, and which problem each one solves.
  2. A client sends Content-Type: image/jpeg. Why is that not sufficient to trust the file, and what does magic-byte validation add? What specific attack does it stop?
  3. Fable byte-validates images but only MIME-whitelists HEIC/office/video. Explain the pragmatic reason for that boundary and why it is a deliberate trade rather than a gap.
  4. How is private media access-controlled, and how do the bytes actually reach the client (public CDN vs presigned GET)? Where is the authorization decision made vs the transport?
  5. Fable does no server-side image processing yet. Give the correctness-vs-optimization principle behind that choice, and explain why EXIF stripping is the item flagged to promote soonest.
  6. For a media-upload prompt, what is the highest-signal thing to say about where bytes flow, and what is the highest-signal thing to say about validation?