0.25.0 — Security hardening Tier-2
🔐 Time-limited resolve URLs — every per-play /resolve URL now carries an HMAC signature and expires (default 4h, RESOLVE_URL_TTL_MINUTES). Leaked URLs go dead instead of working forever. Install URL stays stable. One-time impact: any Stremio/Nuvio client with cached pre-0.25.0 stream rows will get a 403 the first time it tries to click one — close and re-open the event to fetch fresh signed URLs.
🔒 Debrid keys encrypted at rest — RD/TB/PM API keys are now AES-256-GCM encrypted inside users.json (key derived from SESSION_SECRET via scrypt). Pre-0.25.0 plaintext keys still load fine and auto-encrypt on the next save. Stolen volume backups no longer leak debrid keys.
⚙️ CI workflow — GitHub Actions on every push runs node --check over every committed .js, requires every lib/ module, validates the promotion-registry shape, and builds the Docker image. Catches local-dev gaps the sandbox-mount-staleness has been hiding.
🥊 Boxing tile fix — brandedPoster() now verifies the local asset file exists before serving it; missing files cleanly fall back. Boxing's fallback swapped from the wide TSDB banner (crops badly) to the square league badge (letterboxes cleanly). Drop public/boxing-upcoming.jpg later for a custom branded card and it'll take over automatically.
Upgrading: standard deploy. Reminder: changing SESSION_SECRET ever invalidates all encrypted debrid keys + all sessions — users would need to re-paste their debrid keys. Don't rotate SESSION_SECRET unless you have to.