SECURITY.md
Rama main · réplica de lectura del repositorio original.
# Security Policy
## Reporting a Vulnerability
If you discover a security vulnerability in LobbyForge, **please do not
open a public GitHub issue**. Instead, report it privately:
1. **GitHub Security Advisories** (preferred): go to the
[Security tab](../../security/advisories/new) of this repo and create
a private security advisory. This is monitored and reaches the
maintainers directly.
2. **GitHub Confidential Vulnerability Reporting**: use the
"Report a vulnerability" button on the Security tab.
Please include:
- A description of the vulnerability and its impact.
- Steps to reproduce (proof of concept).
- The affected version/commit.
We acknowledge reports within **48 hours** and aim to publish a fix or
mitigation within **30 days** for critical issues.
## Supported Versions
There are no tagged releases yet (experimental alpha); report vulnerabilities against the current `main` branch. Self-hosted instances
should keep up to date via the built-in update system (Admin → Updates).
## Security Measures
LobbyForge implements defense-in-depth:
- **Session cookies**: HMAC-SHA256 signed, HttpOnly, SameSite=Lax, Secure in production.
web and the realtime gateway refuse to start with a missing, short or
published placeholder session secret (only the local dev stack, with
`LOBBYFORGE_DEV_STACK=true`, may use one); the dev stack binds `127.0.0.1` only.
- **Image uploads**: content-sniffed format and dimensions (every GIF frame
inside the image), a per-user stored quota, a per-address daily byte
budget, and no uploads for guest accounts.
- **Desktop app**: the window only shows the connected instance and its own
connect screen; other pages open in the system browser.
- **Guest IDs**: 128-bit cryptographic randomness (`crypto.randomBytes`).
- **Admin auth**: owner session or constant-time emergency token (≥32 chars).
- **CSRF protection**: Fetch Metadata headers + Origin validation on all mutations.
- **CSP**: strict `script-src 'self'`, `object-src 'none'`, `frame-ancestors 'none'`.
`frame-src` allows exactly one origin, `https://www.youtube-nocookie.com` —
the Watch Party player (framing only, sandboxed; see `docs/WATCH_PARTY.md`).
- **Request body limits**: per-route caps (default 1 MiB).
- **Rate limiting**: Redis-backed (fails closed), per-route limits.
- **Update signatures**: Ed25519-signed manifests required for self-host updates.
- **Input validation**: Zod schemas on every API route.
- **TURN relay credentials**: per-user, time-limited (coturn REST auth) —
no permanent shared TURN secret exists.
## Marketplace plugins — trust model (ADR-001)
**Current model: reviewed-only.** The marketplace accepts curated,
admin-reviewed plugins. The plugin-worker executes them in a hardened
container (child process, strict IPC, process-group kill, memory/pids
caps, read-only fs, internal network). This is adequate for
**reviewed code where the review process is the trust gate**.
**NOT a hostile-code sandbox.** Arbitrary untrusted JavaScript
requires per-plugin containers with separate UIDs, PID namespaces and
cgroups. Until that exists, the marketplace requires human review
before approval, and dynamic execution stays opt-in.
See docs/ARCHITECTURE_DECISIONS.md for the full ADR.
## Marketplace plugins — isolated worker runtime (LF-SEC-010)
Third-party plugin bundles execute in the **plugin-worker container**,
never in the web process:
- separate container, internal-only network (not behind nginx at all);
- NO host secrets in its environment — only the RPC token and the
storage-capability token;
- read-only root filesystem + read-only plugin bundle mount;
- `mem_limit 256m`, `pids_limit 64`, `cap_drop ALL`,
`no-new-privileges`, healthcheck-gated restarts;
- the web app calls it with hard timeouts — a hung plugin can at worst
restart the worker, never the web app;
- `ctx.storage.*` executes back on the host via the internal
`/api/internal/plugin-storage` endpoint (404 at the public edge),
hard-scoped to the plugin's own (serverId, pluginId) keyspace.
Dynamic execution STILL defaults to off. Enabling requires BOTH
`LOBBYFORGE_DYNAMIC_PLUGINS_ENABLED=true` AND
`LOBBYFORGE_PLUGIN_WORKER_URL` — the flag alone loads nothing (fail
closed), and the old in-process import path for third-party code has
been removed from the loader entirely. Workspace plugins (hushle,
quiz, …) remain trusted first-party code in the static registry.
## Marketplace plugins — dynamic execution is DISABLED (SEC-008)
Third-party plugin bundles (`plugins/installed/…`) are NOT executed by
default. Two independent gates must BOTH agree before any dynamic
plugin code runs, and both default to closed:
1. `POST /api/marketplace/install` refuses (503) unless
`LOBBYFORGE_DYNAMIC_PLUGINS_ENABLED=true`.
2. The boot-time loader (`lib/plugin-loader.ts`) skips the
`plugins/installed/` walk entirely without the same flag.
(Historical note — superseded by the isolated worker runtime above.)
The original in-process execution model is gone; the remaining gate
below is defense-in-depth while the feature stays opt-in:
## Self-Host Security Checklist
When deploying LobbyForge on your own server:
- [ ] Set `LOBBYFORGE_SESSION_SECRET` to ≥32 chars of random hex.
- [ ] Set `LOBBYFORGE_ADMIN_TOKEN` to ≥32 chars (different from session secret).
- [ ] Set `LOBBYFORGE_SETUP_TOKEN` and remove it after first-run setup.
- [ ] Use HTTPS (certbot/Let's Encrypt) — WebRTC requires it.
- [ ] Keep `registrationMode` on `invite_only` (the default).
- [ ] Run the Doctor health check after deployment.
- [ ] Configure backups: `node scripts/lfctl.mjs backup create --out backups --database-url ...` (see docs/BACKUP_DRILL.md — includes a destructive restore drill you should rehearse before trusting backups).