Report vulnerabilities privately via GitHub Security Advisories on this repository. Do not file a public issue for an unpatched security problem.
Never commit:
.envor live tokens (HF_TOKEN, API keys)- Cluster inventory (
LAIL_CLUSTER_JSONvalues,data/cluster.json,data/multinode_serve.json) - Anything that exposes Docker on the host (
docker.sock, remote Docker URLs, or credentials that can reach them) - SQLite databases and run logs under
data/
Use .env.example as the template. Treat HF_TOKEN, LAIL_TOKEN, cluster JSON, and Docker access as operator secrets.
bun run dev and the controller default to LAIL_HOST=127.0.0.1. Anyone on the LAN must not be able to start/stop Docker or hit shell tools.
- Loopback bind: no token required.
- Off-loopback bind (
0.0.0.0,::, a LAN IP): setLAIL_TOKENor the process refuses to start. - Send the token as
Authorization: Bearer <token>orX-Lail-Token. EventSource (job logs, stream-run events) only:?token=. - Lab artifacts play from
/api/lab/play/<id>/<key>/…: the key is an HMAC of the run id underLAIL_TOKEN, so iframes need no token, a link opens only that run, and rotating the token revokes links. Artifacts are served with a strict CSP andsandbox allow-scripts(opaque origin), so a game can never read the dashboard's storage or token. Consequences: artifact responses carryAccess-Control-Allow-Origin: *(no credentials) so the game's own module scripts,fetch()ed data, WASM and fonts load; and HTML artifacts get a small in-memorylocalStorage/sessionStorageinjected ahead of their own scripts (real storage throws in an opaque origin), so a game that keeps a high score still runs; it is lost on reload and IndexedDB/cookies stay unavailable. Non-web files in a private run (e.g..py) download asapplication/octet-stream; public shares serve web assets only. - CORS trusts only the web origin (
:3000,LAIL_WEB_PORT,LAIL_CORS_ORIGINS). With or without a token, a cross-site non-GET is refused unless it is JSON (preflighted), by the controller and by the serve-engine alike. curl and Hermes send noOriginand are unaffected. - Without a token, the controller and the serve-engine also refuse a request whose
Host(orX-Forwarded-Host) is a dotted name other thanlocalhostor aLAIL_CORS_ORIGINShost (DNS rebinding). IP literals and single-label names (spark1, docker service names) pass. - The
/v1proxy never forwardsLAIL_TOKENor cookies to the model backend. - The web UI does not inject the operator secret. If a request returns 401, paste
LAIL_TOKENinto the banner; it is kept insessionStorageand sent asX-Lail-Token/?token=. - Docker Compose publishes
127.0.0.1:PORT:PORTand setsLAIL_INSECURE_BIND=1for the container-internal0.0.0.0listen. If you publish those ports on0.0.0.0, setLAIL_TOKENand enter it in the UI. Do not rely on the escape hatch.