From cc3dd3f70839a5bd88fe4b8278d124d223792fe5 Mon Sep 17 00:00:00 2001 From: Hermes Bot Date: Sat, 25 Jul 2026 13:20:48 -0400 Subject: [PATCH] security: drop guessable LITELLM_MASTER_KEY fallback on gateway clients PR #111 hardened the model-gateway to refuse a guessable master key. Downstream clients still fell back to the literal `local` when calling the gateway, so an unset key now silently locks the client out (401) instead of failing loud. Remove every guessable `local` default on the client side; an unset key now yields an empty bearer the gateway rejects loudly. Each client is provided the real key: LITELLM_MASTER_KEY is a CORE_SECRET_KEY rendered into secrets.env, and the stack (and per-service recreate) is brought up with `--env-file .env --env-file secrets.env`, so `${LITELLM_MASTER_KEY}` interpolates the real value at compose-up (comfyui/open-webui/dashboard/n8n) and reaches the hermes/agent container in-process via its secrets.env env_file. Sites fixed: - services/comfyui/plugin.yaml LITELLM_MASTER_KEY ${..:-local} -> ${..} - services/open-webui/plugin.yaml (x2) OPENAI_API_KEY / RAG_OPENAI_API_KEY - services/v1-parity/dashboard.yaml MODEL_GATEWAY_API_KEY - hermes/entrypoint.sh model.api_key: add `:?` fail-loud guard, drop :-local - services/automation/plugin.yaml n8n OPENAI_API_KEY: hardcoded `local` -> ${LITELLM_MASTER_KEY} (bare-literal sibling of the same hole, found by grep beyond the reported set) Co-Authored-By: Claude Opus 4.8 --- hermes/entrypoint.sh | 7 ++++++- services/automation/plugin.yaml | 4 +++- services/comfyui/plugin.yaml | 2 +- services/open-webui/plugin.yaml | 4 ++-- services/v1-parity/dashboard.yaml | 2 +- 5 files changed, 13 insertions(+), 6 deletions(-) diff --git a/hermes/entrypoint.sh b/hermes/entrypoint.sh index df66a3e9..11250e45 100644 --- a/hermes/entrypoint.sh +++ b/hermes/entrypoint.sh @@ -56,11 +56,16 @@ fi HERMES_BIN=/opt/hermes-agent/.venv/bin/hermes +# Fail loud: LITELLM_MASTER_KEY is the gateway bearer this agent authenticates with. It arrives +# from secrets.env (SOPS) via the service's env_file. A guessable `local` default silently locks +# the agent out once the gateway rejects it — refuse to start with no key rather than seed a bad one. +: "${LITELLM_MASTER_KEY:?LITELLM_MASTER_KEY must be set (SOPS/secrets.env) — refusing to seed a guessable default}" + # Seed model + MCP endpoints to Docker-network DNS. hermes config set is idempotent # and overwrites stale values (e.g. localhost: from a prior host-mode install). gosu hermes "$HERMES_BIN" config set model.provider "custom" >/dev/null gosu hermes "$HERMES_BIN" config set model.base_url "http://model-gateway:11435/v1" >/dev/null -gosu hermes "$HERMES_BIN" config set model.api_key "${LITELLM_MASTER_KEY:-local}" >/dev/null +gosu hermes "$HERMES_BIN" config set model.api_key "${LITELLM_MASTER_KEY}" >/dev/null gosu hermes "$HERMES_BIN" config set model.default "local-chat" >/dev/null # Context window: single source of truth is LLAMACPP_CTX_SIZE in .env. The # compose file plumbs it into this container's env; the seed below overwrites diff --git a/services/automation/plugin.yaml b/services/automation/plugin.yaml index ced19c61..d84bbdc8 100644 --- a/services/automation/plugin.yaml +++ b/services/automation/plugin.yaml @@ -25,7 +25,9 @@ services: N8N_PROXY_HOPS: "1" N8N_USER_MANAGEMENT_DISABLED: "true" OPENAI_API_BASE_URL: ${OPENAI_API_BASE:-http://model-gateway:11435/v1} - OPENAI_API_KEY: local + # Interpolated from secrets.env at compose-up (both --env-file passed); no guessable + # default — an unset key fails loud (gateway rejects) rather than silently sending `local`. + OPENAI_API_KEY: ${LITELLM_MASTER_KEY} WEBHOOK_URL: ${N8N_WEBHOOK_URL:-} N8N_EDITOR_BASE_URL: ${N8N_WEBHOOK_URL:-} N8N_AUTH_EXCLUDE_ENDPOINTS: rest/oauth2-credential/callback,webhook diff --git a/services/comfyui/plugin.yaml b/services/comfyui/plugin.yaml index 0ec47792..3b4b2198 100644 --- a/services/comfyui/plugin.yaml +++ b/services/comfyui/plugin.yaml @@ -38,7 +38,7 @@ services: PYTORCH_CUDA_ALLOC_CONF: ${PYTORCH_CUDA_ALLOC_CONF:-expandable_segments:True,pinned_use_cuda_host_register:True} OPS_CONTROLLER_URL: http://ops-controller:9000 OPS_CONTROLLER_TOKEN: ${OPS_CONTROLLER_TOKEN:-} - LITELLM_MASTER_KEY: ${LITELLM_MASTER_KEY:-local} + LITELLM_MASTER_KEY: ${LITELLM_MASTER_KEY} HF_TOKEN: ${HF_TOKEN:-} GITHUB_TOKEN: ${GITHUB_PERSONAL_ACCESS_TOKEN:-} volumes: diff --git a/services/open-webui/plugin.yaml b/services/open-webui/plugin.yaml index 8f9df898..5ce93b2b 100644 --- a/services/open-webui/plugin.yaml +++ b/services/open-webui/plugin.yaml @@ -22,7 +22,7 @@ services: env: ENABLE_OLLAMA_API: "false" OPENAI_API_BASE_URL: ${OPENAI_API_BASE:-http://model-gateway:11435/v1} - OPENAI_API_KEY: ${LITELLM_MASTER_KEY:-local} + OPENAI_API_KEY: ${LITELLM_MASTER_KEY} WEBUI_AUTH: ${WEBUI_AUTH:-False} DEFAULT_MODELS: ${OPEN_WEBUI_DEFAULT_MODEL:-local-chat} VECTOR_DB: qdrant @@ -30,7 +30,7 @@ services: QDRANT_URL: http://qdrant:6333 RAG_EMBEDDING_ENGINE: openai RAG_OPENAI_API_BASE_URL: http://model-gateway:11435/v1 - RAG_OPENAI_API_KEY: ${LITELLM_MASTER_KEY:-local} + RAG_OPENAI_API_KEY: ${LITELLM_MASTER_KEY} RAG_EMBEDDING_MODEL: ${EMBED_MODEL:-${LLAMACPP_EMBED_MODEL:-nomic-embed-text-v1.5.Q4_K_M.gguf}} volumes: - ${DATA_PATH:-./data}/open-webui:/app/backend/data diff --git a/services/v1-parity/dashboard.yaml b/services/v1-parity/dashboard.yaml index 0ebfd635..0c6b67c5 100644 --- a/services/v1-parity/dashboard.yaml +++ b/services/v1-parity/dashboard.yaml @@ -41,7 +41,7 @@ environment: # Backend service URLs the dashboard reads at runtime (all V2 service names). LLAMACPP_URL: http://llamacpp:8080 MODEL_GATEWAY_URL: http://model-gateway:11435 - MODEL_GATEWAY_API_KEY: ${LITELLM_MASTER_KEY:-local} + MODEL_GATEWAY_API_KEY: ${LITELLM_MASTER_KEY} MCP_GATEWAY_URL: http://mcp-gateway:8811 # Single source of truth for the MCP server list: the SAME out/mcp/servers.txt the # gateway reads (mounted below at /mcp-config). Without this the dashboard falls back