Contributor · Reference
Authentication and untrusted content#
The rules for passwords, sessions, admin elevation, and the files a tablet sends. Why they are this way: How sign-in and trust work.
Authentication#
- The tablet’s protocol needs
md5(password)to be recoverable, so there is no bcrypt or argon2.md5(password)is stored encrypted with XChaCha20-Poly1305, a nonce per row, undercred.key: a 32-byte file with mode 0600, kept outside the database. A stolen backup yields nothing; losing the key forces every password to be reset. Onlyservemay create the key. - The browser bundles its own MD5 and SHA-256, because
crypto.subtleexists only in secure contexts and a home deployment is plain HTTP. The password in plain text never leaves the browser. - Session tokens are stored as their SHA-256 only. Lifetimes: a tablet’s session about two years with no idle limit (it ends when the tablet is unlinked); the browser’s 14 days idle and 90 days absolute; a pending session (the forced change of password at a first sign-in) 15 minutes.
- The browser’s cookie is
HttpOnly; SameSite=Strict, andSecureunder TLS. That is the CSRF defence, and deliberately the only one. - Admin elevation re-runs the sign-in challenge, so nothing replayable crosses
plain HTTP. It lasts 15 minutes sliding, 60 minutes at most, stored on the
session’s row. A lapse answers 428
ELEVATION_REQUIRED, never 401 or 403, which the app would read as a dead session. Every refusal isINVALID_CREDENTIALS, so an attacker cannot learn who the admins are. - Rate limits on anything that checks a password are keyed by account, not by address: a household shares one address.
- Temporary passwords come from one generator (four diceware words), are chosen by the server so that no admin knows a housemate’s password, are shown once, and must always be changed at the next sign-in.
- Account routes name no target: the session is the subject. Admin routes
always name
:id. Both call the same service functions with anactorand atarget, so one ownership check covers both faces.
Untrusted content#
- No existence oracles. Blobs are shared across the household, so only a
filesrow says whose bytes are whose. An id or md5 that belongs to someone else answers exactly like one that does not exist: a 404 with a byte-identical body. /file/{id}serves inline only an allowlist of types (png, jpg, webp, gif, pdf), withnosniff; everything else is an attachment. An SVG or HTML file from a tablet, served inline, would be stored cross-site scripting.- Link schemes are allowlisted (http, https, mailto) in the server, in the PDF shim and in the browser, because the exporter reads the same endpoints.
Content-Dispositionnames are written by the tablet, so they are sent in the allowlisted quoted form plus RFC 5987’sfilename*.- A
.spdis a SQLite database an attacker could have written. It is copied and deserialised read-only into:memory:, never opened as a file. - The decoders bound every size with checked arithmetic: on wasm32
usizeis 32 bits, and aReleaseSmallbuild has no overflow traps.