You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
🐳 Docker Packaging: First-class Docker support so you can run FCM without installing Node.js. The official image is published to ghcr.io/vava-nessa/free-coding-models on every release and tag push.
Multi-arch friendly Dockerfile based on node:20-alpine, running as a non-root fcm user.
docker-entrypoint.sh auto-generates ~/.free-coding-models.json from any *_API_KEY / *_API_TOKEN env vars you pass to the container — no manual config step.
docker-compose.yml template wired up for every supported provider.
GitHub Actions workflow (.github/workflows/docker.yml) handles build + publish to GHCR on release: published, push: v*.*.* tags, and workflow_dispatch (with a test_mode dry-run input).
Trivy vulnerability scan blocks releases that introduce any CRITICAL/HIGH CVEs.
Quick start: docker run -p 19280:19280 -e OPENROUTER_API_KEY=... ghcr.io/vava-nessa/free-coding-models:latest.
🌐 Combined Daemon + Web Dashboard: The router daemon now serves the web dashboard from the same port — no more juggling two processes. New REST surface area baked into the daemon:
GET /api/models — full model catalog with latency stats, status, p95, jitter, stability, verdict, uptime, and inRouterSet flag.
GET /api/config — provider catalog with masked API keys (••••••••XXXX) and enabled state.
GET /api/events — SSE stream the dashboard subscribes to for live updates.
GET /api/key/<provider> — reveal the raw API key for a configured provider (same-origin only).
POST /api/settings — save API keys and per-provider enabled flags from the dashboard, then trigger a probe burst.
When you add a new provider's API key from the dashboard, FCM now mirrors --sync-set behavior and automatically adds that provider's best-tier model to your active router set.
Changed
--web flag removed: replaced by --daemon, which now serves both the OpenAI-compatible router API and the dashboard on the same port. Existing tooling using --web should switch to --daemon.
Preserve user-created router sets on daemon start: previously the daemon rebuilt the active set from favorites or defaults on every restart, silently overwriting sets created with --sync-set. Named sets are now preserved.
Faster config reload: CONFIG_RELOAD_INTERVAL_MS shortened from 60s → 10s so dashboard-driven changes (toggling providers, adding keys) propagate quickly.
Contributors: welcome @stgreenb 🎉 — author of the daemon-web merge and Docker packaging work.
Fixed
🔒 Path traversal in dashboard static file serving (security): requests like GET /../../etc/passwd could escape web/dist/ and read arbitrary files reachable by the daemon user. The mitigation (127.0.0.1 bind) was bypassed inside Docker where the daemon binds 0.0.0.0. All static paths are now resolved against WEB_DIST_DIR and rejected with 403 when they escape.
🔒 Cross-site write / key exfiltration on dashboard endpoints (security): POST /api/settings (writes API keys) and GET /api/key/<provider> (reveals raw keys) previously accepted any request, including those triggered by malicious tabs visiting a page that fetched http://localhost:19280/.... Both now enforce a same-origin / loopback Origin header check. Header-less CLI callers (curl, native apps) keep working.
🔒 Config file permissions tightened in Docker: the entrypoint previously created ~/.free-coding-models.json with mode 0666. Tightened to 0600 since it stores plaintext API keys.
🐛 /api/key/:provider route never matched: the handler compared url.pathname === '/api/key/:provider' literally, so the endpoint was unreachable. Now correctly matches via startsWith and 404s unknown providers.
🐛 "Excellent" verdict for fully-down models: when a model had probe history but every ping failed, avg collapsed to 0 and the dashboard showed "Excellent". Verdict now correctly returns — whenever no usable latency sample exists.
🐛 --daemon-bg mode signalled "down" for everything in dashboard: the type-mismatch comparison (=== between string code and number) made every model render as down. Codes are now compared as strings.
⚙️ Docker GitHub Actions workflow YAML repaired: the top-level env: block and the GHCR login step were incorrectly indented and would have prevented GitHub from parsing the workflow at all.
⚙️ Docker container lifecycle: the container no longer outlives a crashed daemon. The entrypoint now runs the daemon in the foreground (--daemon instead of --daemon-bg) so Docker's restart policy can recover from crashes instead of waiting for the healthcheck to time out.
Internal
Removed a dead createReadStream import in router-daemon.js.
Hoisted routerConfig() and getSet() lookups out of the per-model loop in getWebModelsPayload() — saves ~200 redundant runtime calls per dashboard refresh — and uses a Set index for in-set membership checks.
Renamed a local window variable to probeWindow to avoid shadowing globals.
Removed a nested router shadow in /api/settings.
Added X-Content-Type-Options: nosniff header on all static dashboard responses.
Added 6 new tests covering path-traversal blocking, cross-origin write rejection, same-origin write success, CLI key fetch, and unknown-provider 404. Total: 371 tests, all green.