server
9 modules
server/app_backend.ts
App backend types and factory — database initialization + auth migrations + deps.
Provides AppBackend, CreateAppBackendOptions, and
create_app_backend().Vocabulary:
- AppDeps — stateless capabilities: injectable, swappable per environment
*Options— static values set at startup, per-factory configuration- Runtime state — mutable values (e.g.,
bootstrap_status) — NOT in deps or options
server/app_server_context.ts
The context threaded to route / RPC / WS endpoint factories.
Lives in its own module — separate from server/app_server.ts — so it can be consumed as a pure type without dragging in the server-assembly machinery. server/app_server.ts value-imports
hono(it builds theHonoapp), so importing anything from it forceshonoto be installed. Contract-only consumers — cross-process test surfaces, Rust-backed servers that reuse the route/RPC spec factories without running the TS server — need AppServerContext but nothono. Keeping the type here (onlyimport typedependencies, none of which value-importhono) lets them import it framework-free.server/app_server.ts
Server assembly factory.
Consumers provide a pre-initialized AppBackend and options (session, origins, routes);
create_app_server()handles middleware, bootstrap status, surface generation, and Hono app assembly.server/env.ts
Base server environment schema and validation.
Provides BaseServerEnv — a shared Zod schema for common server env vars that apps can use directly or extend with app-specific fields.
Generic env loading lives in env/load.ts.
server/serve_fact_route.ts
Content-addressed fact serving — cell-scoped, per-reference reads.
Two routes serve fact bytes from the PG-backed fact store:
GET /api/cells/:cell_id/facts/:hash— the per-reference read. The request names the referencing cell. Authz is scoped to that one reference:can_view_cell(caller, cell) AND cell.refs includes hash. This is the path non-admin callers use, and the only path for confidential content.GET /api/facts/:hash— the bare-hash read, restricted to admins.
Embedded facts stream from the
fact.bytesPG column; external facts (filesystem-backedfile:<shard>/<rest>URLs) either return anX-Accel-Redirectheader pointing into nginx's internal facts location (production) or stream from disk (dev / tests). The runtime mode is selected by the optionalx_accelfactory option — a validated XAccelConfig (built via create_x_accel_config in server/x_accel.ts) set in prod, unset in dev. The handle can only be obtained by proving the facts nginx location isinternal;, so X-Accel serving can't be enabled against a public location.REST, not RPC: binary responses don't fit the JSON-RPC envelope.
Authorization — authz lives on the cell→fact edge, not the hash
Facts are global, content-addressed, owner-less bytes: identical bytes from different owners dedup to one
factrow. Keying access control on the bare hash therefore unions visibility across every owner that references it — A's private bytes leak the instant B references identical bytes from a public cell. The fix is to scope authz to the(cell, hash)edge: a caller reads a fact *through a specific cell it can view that references the hash*. Dedup becomes a pure storage optimization with zero authz consequence — whether two owners' bytes share afactrow is invisible to the read check.The cell-scoped route resolves the named cell, requires
can_view_cell(caller, cell), and requirescell.refsto include the hash. B publishing identical bytes from B's public cell makes them readable *via B's cell* — it never touches A's private reference.The bare-hash route is admin-only: an admin's reach already spans every cell, so serving by bare hash grants no escalation. Non-admin callers are rejected at the auth phase and never reach the handler. (Explicitly-public facts — a producer opting bytes into world-readable status — are a future refinement; there is no such concept today, so the bare-hash route stays strictly admin-gated.)
Auth shape on the cell-scoped route is
{account: 'none', actor: 'none'}— the dispatcher's authorization phase is skipped for pure-public routes, so the handler builds the RequestContext itself fromc.var.account_id(populated by the/api/*session middleware) by resolving the caller's single actor and loading their role_grants. Unauthed callers pass through withreq_ctx: nulland are admitted only by a `cell.visibility === 'public'cell. Multi-actor accounts fall through withreq_ctx: null` — there's noacting?slot on a pure-public route, so multi-actor callers are treated as anonymous.404 is the universal "not viewable" response: missing fact, missing or unviewable cell, or the cell doesn't reference the hash. We deliberately don't distinguish 403 from 404 — neither the existence of a fact nor the existence of a cell→fact edge should leak through the public surface.
Content-addressed serving of inline
blake3:images (a markdown doc cell with embedded image refs) works through this model: the referencing cell is the doc cell, so serving goes view-doc-cell → doc-cell-refs-include-hash → serve.Untrusted-content hardening
Fact bytes are served with a producer-supplied
content_typeemitted verbatim, inline. To stop a fact stored as (or sniffable to)text/htmlfrom executing as stored XSS for any reader of a referencing cell, every served-fact response carriesX-Content-Type-Options: nosniff+Content-Security-Policy: default-src 'none'; sandbox(theFACT_SECURITY_HEADERSset, applied in all three serve branches). The same pair, byte-identical, is set by the Rust twin (fuz_fact_serving::serve).Content-Disposition: attachmentis deliberately not added — it would force-download and break legitimate inline image rendering.Defense-in-depth
external_urlis re-validated against thefile:<shard>/<rest>shape before it addresses the filesystem or anX-Accel-Redirect. PgFactStore writes only that shape, but the column is free text and query_put_fact inserts what it is given, so a row written past the store — or a row-injection bug upstream — would otherwise hand nginx an attacker-controlled path. Any other shape is a logged 404, the same unavailable thatPgFactStore.getreports for it asnull.server/startup.ts
Composable startup summary helpers.
Logs a human-readable summary from an AppSurface.
server/static.ts
Static file serving middleware for SvelteKit static builds.
Multi-step static serving:
- Step 1: Exact path match (handles /, assets, images)
- Step 2:
.htmlfallback for clean URLs (/about→/about.html) - Step 3 (optional): SPA fallback for client-side routes
server/surface_route.ts
The surface explorer route —
GET /api/surface, serving the generated AppSurface as JSON.Opt-in on create_app_server (
surface_route: true), and admin-only when mounted: the surface is a map of every route, its auth, and its schemas.server/x_accel.ts
Fail-loud validation of the X-Accel facts nginx location.
The
X-Accel-Redirectserving path's confidentiality depends on the facts nginx location beinginternal;— only the authz'd handler's redirect (an internal subrequest) may reach it. A *public* facts location would serve any fact's bytes to anyone who guesses the<shard>/<rest>path, bypassing every cell-visibility check. XAccelConfig makes that assertion structural: a consumer can only obtain the redirect prefix by building an XAccelConfig, which runs this check and throws loudly on a missing or non-internal;location — it cannot silently fall closed.This is a best-effort string check (brace-matched
locationblocks), the TS twin of the Rustfuz_fact_servingnginx.rs— not a real nginx parser, but it catches the security-critical omission. This is the only nginx property fuz_app validates; there is no general config validator.