Skip to content

fix(docker): bind app/sidecar compose ports to loopback by default (#12568, #12578) - #13240

Merged
diegosouzapw merged 1 commit into
release/v3.8.51from
fix/12568-compose-bind
Sep 12, 2026
Merged

diegosouzapw merged 1 commit into
release/v3.8.51from
fix/12568-compose-bind

Conversation

@diegosouzapw

Copy link
Copy Markdown
Collaborator

Summary

docker-compose.yml and docker-compose.prod.yml defaulted API_HOST/LIVE_WS_HOST/HOSTNAME to 0.0.0.0 and published the dashboard/API/live-WS ports with bare, unscoped specs — Docker expands those to every interface. Combined with REQUIRE_API_KEY=false shipping as the .env.example default, this exposed the anonymous /v1 LLM proxy to the whole LAN/WAN (#12568). The optional cliproxyapi sidecar had the same unscoped publish spec and no forwarded auth env var, exposing a credential-bearing service (its data volume holds provider OAuth/API credentials) the same way (#12578); qdrant and bifrost had the identical gap, per the owner's own follow-up comment on #12578.

This applies the existing Redis loopback-bind precedent (tests/unit/compose-redis-loopback-bind.test.ts, from #9286) to the app's own ports and to the three sidecars.

Root cause

  • docker-compose.yml (x-common env block + the omniroute-web/omniroute-host service blocks that don't inherit it) and docker-compose.prod.yml both defaulted API_HOST/LIVE_WS_HOST to 0.0.0.0 and published DASHBOARD_PORT/API_PORT/LIVE_WS_PORT with bare port specs.
  • This overrides the already-safe 127.0.0.1 code-level defaults in src/server/ws/liveServer.ts:53 and src/lib/apiBridgeServer.ts:186.
  • cliproxyapi's environment: block carried HOST=0.0.0.0/PORT=..., which are dead no-ops for the pinned eceasy/cli-proxy-api:v6.9.7 image (verified against the upstream source at that tag — it has no env-var support for host/port/api-keys, only config.yaml); the one env var it does read for auth is MANAGEMENT_PASSWORD, which was never wired from our already-documented CLIPROXYAPI_MANAGEMENT_KEY.

Fix

  • New opt-in bind-host vars, all defaulting to 127.0.0.1, documented in .env.example and docs/reference/ENVIRONMENT.md: APP_BIND_HOST (dashboard/API/live-WS ports, both compose files), CLIPROXY_BIND_HOST, QDRANT_BIND_HOST, BIFROST_BIND_HOST.
  • API_HOST/LIVE_WS_HOST default to 127.0.0.1 in both compose files; docker-compose.prod.yml no longer hardcodes HOSTNAME=0.0.0.0.
  • cliproxyapi now forwards CLIPROXYAPI_MANAGEMENT_KEY as MANAGEMENT_PASSWORD (the env var the pinned binary actually reads).
  • New boot-time guard src/lib/startup/nonLoopbackApiKeyGuard.ts, wired into the API bridge server and the live-dashboard WebSocket server: logs a loud warning (never a hard failure, so a deployment intentionally fronted by a reverse-proxy auth layer keeps working) when the resolved bind host is non-loopback while REQUIRE_API_KEY is disabled.
  • docs/guides/DOCKER_GUIDE.md updated with the new vars.

Regression tests (TDD)

  • tests/unit/compose-app-ports-loopback-bind.test.ts (new) — asserts both compose files no longer publish the app ports unscoped and no longer default API_HOST/LIVE_WS_HOST/HOSTNAME to 0.0.0.0; asserts the boot-time guard exists; asserts .env.example documents APP_BIND_HOST.
  • tests/unit/compose-cliproxyapi-loopback-bind.test.ts (new) — asserts the cliproxyapi publish spec is loopback-scoped, forwards CLIPROXYAPI_MANAGEMENT_KEY, and that qdrant/bifrost are scoped too.
  • tests/unit/non-loopback-api-key-guard.test.ts (new) — unit-tests the guard's warn/silent matrix (loopback vs non-loopback host × REQUIRE_API_KEY true/false/unset).

RED confirmed against origin/release/v3.8.51 tip via git show <ref>:<path> (no worktree checkout, per the repo's cross-session-safety rule) — both bare publish specs ("${DASHBOARD_PORT:-20128}:${DASHBOARD_PORT:-20128}", "${CLIPROXYAPI_PORT:-8317}:${CLIPROXYAPI_PORT:-8317}") are present on the base tip. GREEN after the fix:

node --import tsx/esm --test tests/unit/compose-app-ports-loopback-bind.test.ts tests/unit/compose-cliproxyapi-loopback-bind.test.ts tests/unit/compose-redis-loopback-bind.test.ts tests/unit/non-loopback-api-key-guard.test.ts
...
ℹ tests 15
ℹ pass 15
ℹ fail 0

Existing compose-touching tests re-run and still pass unmodified: tests/unit/podman-machine-guidance-8497.test.ts, tests/unit/dockerfile-base-path-arg.test.ts, and the live-WS/API-bridge suites (instrumentation-live-ws, live-ws-cookie-parse, live-ws-standalone-wiring, live-ws-eaddrinuse-6324, runtime/live-ws-default, security/live-server-allowlist, httpClientAbortGuard*) — 40+13 tests, all green.

Gates run

  • node scripts/check/check-file-size.mjs — OK
  • node scripts/check/check-complexity.mjs — OK (2798 violations vs baseline 3218)
  • node scripts/check/check-cognitive-complexity.mjs — OK (1265 violations vs baseline 1437)
  • npm run typecheck:core — exit 0
  • npx eslint --suppressions-location config/quality/eslint-suppressions.json <changed files> — exit 0
  • node scripts/check/check-env-doc-sync.mjs — in sync
  • node scripts/check/check-changelog-integrity.mjs — OK
  • npm run check:docs-all — doc-links PASS, fabricated-docs PASS (only pre-existing stale-date/stale-version drift noise, unrelated to this diff)

Breaking change note

Existing LAN/cloud-VM deployments that relied on the previous 0.0.0.0 default with no .env override will need to set APP_BIND_HOST=0.0.0.0 (and CLIPROXY_BIND_HOST/QDRANT_BIND_HOST/BIFROST_BIND_HOST as needed) after upgrading — the same migration UX already accepted for REDIS_BIND_HOST. Both new .env.example blocks document this tradeoff and recommend pairing it with REQUIRE_API_KEY=true or a reverse-proxy auth layer.

⚠️ base-red inherited: #12732 — unit #12058, integration codex-cache, package-artifact, tarball-smoke, agent-skills-sync

Closes #12568
Closes #12578

…12568, #12578)

docker-compose.yml and docker-compose.prod.yml defaulted API_HOST/LIVE_WS_HOST/
HOSTNAME to 0.0.0.0 and published the dashboard/API/live-WS ports with bare,
unscoped specs, which Docker expands to every interface. Combined with
REQUIRE_API_KEY=false shipping as the .env.example default, this exposed the
anonymous /v1 LLM proxy to the whole LAN/WAN (#12568). The optional cliproxyapi
sidecar had the same unscoped publish spec plus no forwarded auth env var,
exposing a credential-bearing service the same way (#12578); qdrant and bifrost
had the identical gap.

Applies the existing Redis loopback-bind precedent (tests/unit/compose-redis-
loopback-bind.test.ts) to the app's own ports and to cliproxyapi/qdrant/bifrost:
- New APP_BIND_HOST / CLIPROXY_BIND_HOST / QDRANT_BIND_HOST / BIFROST_BIND_HOST
  opt-in vars, defaulting to 127.0.0.1, documented in .env.example and
  docs/reference/ENVIRONMENT.md.
- API_HOST/LIVE_WS_HOST default to 127.0.0.1 in both compose files; the prod
  file no longer hardcodes HOSTNAME=0.0.0.0.
- cliproxyapi now forwards CLIPROXYAPI_MANAGEMENT_KEY as MANAGEMENT_PASSWORD,
  the one env var the pinned image actually reads for its management API.
- A new boot-time guard (src/lib/startup/nonLoopbackApiKeyGuard.ts) logs a
  warning — never a hard failure — when the API bridge or live-WS server ends
  up bound to a non-loopback host while REQUIRE_API_KEY is disabled.

⚠️ base-red inherited: #12732 — unit #12058, integration codex-cache,
package-artifact, tarball-smoke, agent-skills-sync

Closes #12568
Closes #12578
@diegosouzapw
diegosouzapw merged commit 57a37ed into release/v3.8.51 Sep 12, 2026
15 of 21 checks passed
Githab-capibara added a commit to Githab-capibara/OmniRoute that referenced this pull request Sep 17, 2026
…heaper-inference#12568, cheaper-inference#12578) (cheaper-inference#13240)

Merged as part of the 39-PR owner batch of 2026-09-11, validated as a unit.

Boarded into one consolidated worktree cut from `release/v3.8.51` with the other 38 — zero conflicts between them.

- ESLint over every changed file: no errors (the only finding was one suppression entry the batch emptied, pruned on cheaper-inference#13243)
- `typecheck:core` clean; `check:dashboard-typecheck` OK (206 pre-existing, within baseline); `check:changelog-integrity` OK
- complexity 2821 / baseline 3218 and cognitive-complexity 1272 / baseline 1437 — both under baseline
- 256 assertions green: 246 under node:test and 10 under vitest, which is where `tests/unit/**/*.test.tsx` actually runs
- `check-file-size`: `chatCore.ts` rebaselined 6144 → 6146 for cheaper-inference#13278 and cheaper-inference#13276, annotated and landed on cheaper-inference#13243

⚠️ base-red inherited: cheaper-inference#12732 — the provider count (356 in the docs vs the 358 the modules define) and `open-sse/utils/stream.ts` at 3115 > frozen 3098 both reproduce on the pure tip with zero contribution from this batch.
muhamadgalihsaputra pushed a commit to niyatna/NiyatnaRoute that referenced this pull request Sep 27, 2026
…heaper-inference#12568, cheaper-inference#12578) (cheaper-inference#13240)

Merged as part of the 39-PR owner batch of 2026-09-11, validated as a unit.

Boarded into one consolidated worktree cut from `release/v3.8.51` with the other 38 — zero conflicts between them.

- ESLint over every changed file: no errors (the only finding was one suppression entry the batch emptied, pruned on cheaper-inference#13243)
- `typecheck:core` clean; `check:dashboard-typecheck` OK (206 pre-existing, within baseline); `check:changelog-integrity` OK
- complexity 2821 / baseline 3218 and cognitive-complexity 1272 / baseline 1437 — both under baseline
- 256 assertions green: 246 under node:test and 10 under vitest, which is where `tests/unit/**/*.test.tsx` actually runs
- `check-file-size`: `chatCore.ts` rebaselined 6144 → 6146 for cheaper-inference#13278 and cheaper-inference#13276, annotated and landed on cheaper-inference#13243

⚠️ base-red inherited: cheaper-inference#12732 — the provider count (356 in the docs vs the 358 the modules define) and `open-sse/utils/stream.ts` at 3115 > frozen 3098 both reproduce on the pure tip with zero contribution from this batch.
@diegosouzapw
diegosouzapw deleted the fix/12568-compose-bind branch October 18, 2026 23:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant