Threat model

Six kinds of attacker, from a stranger on the internet to a malicious file, and what stands between each of them and the household's media and accounts.

Who the attackers are

AttackerWhat they haveWhat they would want
A stranger on the internetThe install's public name, when remote access is onAn account; to keep the household out; to use the server's resources
Someone on the same Wi-FiPlain-http traffic on the LAN; the ability to answer router discoverySession cookies, photos in transit, to steer an app to their own server
A member of the householdA real account, upload rights to the shelves they can seeAnother person's photos or account, the owner's powers, shelves they were refused
A crafted fileWhatever a member uploads: EPUB, PDF, MP3, M4A, FLAC, image, zipTo run script as whoever opens it, or to exhaust memory and CPU
A malicious backend or serviceControl over answers SoundStorm reads: a backend, plex.tv, a router, an ACME serverTo redirect credentials elsewhere, crash the server, or reach internal services
Another installA trusted name under the same domain as every installCookie tossing, cross-site requests, the name service's shared budgets

Before the first account

Until an account exists, whoever reaches the port could claim the server. "Only from the home network" cannot be checked - under Docker Desktop every connection arrives from Docker's own address - so the first sign-up needs a setup code: eighty random bits the installer writes into .env, shows at the end of setup, and passes to the browser it opens. Signup closes for good the moment an account exists, and the role of every later account is decided by the server under a lock, so two first sign-ups racing cannot both become owner.

Signing in

What a member can reach

Two roles: the owner and members. A member's access is a list of media kinds; the middleware puts it in the request context, and Registry.All, Matching and ByID take a context, so no handler can reach a source without the restriction applied. Jellyfin serves films and TV from one account, so every Jellyfin target checks the item comes back from a query limited to its own types - an id alone cannot cross from TV to films.

Photos are private by design: each member has a folder of their own and an Immich account whose only library is that folder, so faces, places and search see their photos alone. The owner sees everybody's. Deleting, people, settings and remote access are the owner's routes, mounted behind an owner check.

Files from other people

The most-tested boundary. A book's chapters are rendered in same-origin frames, so a script inside a book would run with the reader's session. Defences, in layers:

Every parser is bounded: EPUB directories checked before archive/zip reads them, XMP packets and OPF documents capped, image decodes limited to 12 megapixels and two at a time, MP4 box nesting and tag walks capped, a zip that would unpack to far more than its size refused.

Backends and outside services

The network and the apps

Accepted and open

Recorded rather than forgotten: images are pulled by tag and the Windows installer is unsigned; the name service's daily budgets can be spent by many networks until the domain is on the Public Suffix List; backends' database passwords are fixed defaults reachable only on the compose network; the Android app is signed with a test key.