# @dasl/tile-server The minimal server that makes browser tile loading safe. Isolating untrusted [DASL](https://dasl.ing/) tiles in the browser needs two things a normal static host doesn't give you: a **fresh origin per tile**, and a service worker allowed to control that origin. This package is a tiny Express router (and a CLI) that does exactly that — it serves the loader runtime (the shuttle and worker from [`@dasl/tile-loader`](../tile-loader)) under a hardened set of headers, and redirects each load onto its own random subdomain. It never sees or stores tile content; it only serves the fixed runtime assets. ## Install ```sh npm install @dasl/tile-server ``` ## As a library `createTileLoadingRouter(baseHost)` returns an Express router. Give it your base host; it listens on `load.` and redirects each request to a random `.`: ```js import express from 'express'; import { createTileLoadingRouter } from '@dasl/tile-server'; const app = express(); app.set('trust proxy', 'loopback'); // needed so req.hostname is right behind a proxy app.use(createTileLoadingRouter('example.site')); app.listen(1503); ``` You are responsible for pointing `load.example.site` and `*.example.site` at this app (wildcard DNS + TLS), and for setting `loadDomain: 'load.example.site'` on the [mothership](../tile-loader). ## As a CLI ```sh # installed globally, or via npx tiles-loading-server example.site 8080 ``` Starts an Express app on the given port (default `1503`) serving the router for the given base host. ## What it enforces The policy itself lives in [`@dasl/tile-lexicon`](../tile-lexicon) (`runtimeHeaders`, `deadHeaders`, `tileCSP`), so that this server, the loader and every other deployment read the same one and it is edited in one place. - **Per-tile origin isolation**: a request to `load.` gets a `303` redirect to a fresh 20-character random subdomain (keeping the port, for a desktop app on `*.localhost:`), so every tile loads on its own origin, cross-origin from your site and from every other tile. - **The runtime, and nothing else, on tile origins.** Under `/.well-known/web-tiles/` the shuttle and worker are served with the runtime CSP (`default-src 'self'`, `connect-src 'none'`, `form-action 'none'`, `webrtc 'block'`, a `sandbox` that grants scripts, forms, modals and popups but not top navigation) and `Service-Worker-Allowed: /`. **Every other path on a tile origin is a dead end**: a 404 with `default-src 'none'; sandbox; frame-ancestors 'none'`, so a document from it has an opaque origin the tile cannot script. That is what a tile gets if it unregisters its worker and navigates a frame to the network; it must never get a page of yours. - **Never on your own host.** The base host (and anything `isTileOrigin` does not claim) passes through untouched, so the runtime, and the worker it registers at scope `/`, cannot end up on a site origin. Mount the router before your own routes; it only answers for tile origins. - **Hardening headers** on everything it serves: `Cross-Origin-Opener-Policy: same-origin`, `Cross-Origin-Resource-Policy: cross-origin`, `Origin-Agent-Cluster: ?1`, `X-Content-Type-Options: nosniff`, `Referrer-Policy: no-referrer`, `X-DNS-Prefetch-Control: off`, interest-cohort/browsing-topics disabled. The documents a tile is made of are served by its worker, not by this server: the [mothership](../tile-loader) stamps the tile CSP on each of them. `createTileLoadingRouter(baseHost, { isTileOrigin })` lets a deployment that mints its own hostnames say which ones are tile origins. ## Part of DASL tiles - [`@dasl/tiles`](../tiles) — meta-package re-exporting the whole toolkit - [`@dasl/tile-loader`](../tile-loader) — load tiles in the browser - [`@dasl/tile-writer`](../tile-writer) — write tiles into `.tile` files - [`@dasl/tile-car-reader`](../tile-car-reader) — read tiles from a CAR file - [`@dasl/tile-lexicon`](../tile-lexicon) — MASL schema and shared types - [`@dasl/atile`](../atile) — the tile-publishing CLI ## License Apache-2.0