@dasl/tile-server #
The minimal server that makes browser tile loading safe.
Isolating untrusted DASL 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) 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 #
npm install @dasl/tile-server
As a library #
createTileLoadingRouter(baseHost) returns an Express router. Give it your base
host; it listens on load.<baseHost> and redirects each request to a random
<subdomain>.<baseHost>:
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.
As a CLI #
# 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
(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.<host>gets a303redirect to a fresh 20-character random subdomain (keeping the port, for a desktop app on*.localhost:<port>), 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', asandboxthat grants scripts, forms, modals and popups but not top navigation) andService-Worker-Allowed: /. Every other path on a tile origin is a dead end: a 404 withdefault-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
isTileOrigindoes 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 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— meta-package re-exporting the whole toolkit@dasl/tile-loader— load tiles in the browser@dasl/tile-writer— write tiles into.tilefiles@dasl/tile-car-reader— read tiles from a CAR file@dasl/tile-lexicon— MASL schema and shared types@dasl/atile— the tile-publishing CLI
License #
Apache-2.0