From 2ce2e0089a66ca508db041a71db9807f66d0bf24 Mon Sep 17 00:00:00 2001 From: mptyl Date: Tue, 4 Aug 2026 14:55:05 +0200 Subject: [PATCH] fix: harden compose Pi auth mounts --- .env.example | 1 + compose.yaml | 2 ++ deploy/compose.server.yaml | 1 + deploy/env/local.env.example | 2 ++ deploy/env/server.env.example | 2 ++ scripts/test-unified-compose.sh | 24 ++++++++++++++++++++++++ 6 files changed, 32 insertions(+) diff --git a/.env.example b/.env.example index 17e67a4a..123c5210 100644 --- a/.env.example +++ b/.env.example @@ -13,3 +13,4 @@ THT_DWH_REST_URL=https://dwh.example.invalid THT_VEC_REST_URL=https://vector.example.invalid THT_VEC_WRITE_REST_URL=https://vector-write.example.invalid THT_OLLAMA_URL=https://embeddings.example.invalid +THT_LLM_URL=https://llm.example.invalid diff --git a/compose.yaml b/compose.yaml index 36caa3d1..cf733104 100644 --- a/compose.yaml +++ b/compose.yaml @@ -25,10 +25,12 @@ services: THT_VEC_REST_URL: ${THT_VEC_REST_URL:-} THT_VEC_WRITE_REST_URL: ${THT_VEC_WRITE_REST_URL:-} THT_OLLAMA_URL: ${THT_OLLAMA_URL:-} + THT_LLM_URL: ${THT_LLM_URL:-} MAX_PI_PROCESSES: ${MAX_PI_PROCESSES:-4} volumes: - settings:/data/settings - pi-state:/home/thoth/.pi + - ${PI_AUTH_FILE:?set PI_AUTH_FILE}:/home/thoth/.pi/agent/auth.json:ro - workspace-registry:/data/workspace-registry - sessions:/data/sessions healthcheck: diff --git a/deploy/compose.server.yaml b/deploy/compose.server.yaml index 65c213c7..d684ae6b 100644 --- a/deploy/compose.server.yaml +++ b/deploy/compose.server.yaml @@ -8,6 +8,7 @@ services: volumes: !override - ${THT_DATA_ROOT:?set THT_DATA_ROOT}:/data - ${THT_PI_STATE_ROOT:?set THT_PI_STATE_ROOT}:/home/thoth/.pi + - ${PI_AUTH_FILE:?set PI_AUTH_FILE}:/home/thoth/.pi/agent/auth.json:ro - ${THT_WORKSPACE_REGISTRY_ROOT:?set THT_WORKSPACE_REGISTRY_ROOT}:/data/workspace-registry restart: unless-stopped diff --git a/deploy/env/local.env.example b/deploy/env/local.env.example index 040e7d36..40881f8f 100644 --- a/deploy/env/local.env.example +++ b/deploy/env/local.env.example @@ -3,6 +3,7 @@ THOTH_HTTP_PORT=8080 THOTH_CORE_HTTP_PORT=8787 MAX_PI_PROCESSES=4 +PI_AUTH_FILE=/absolute/path/to/pi-auth.json THT_WORKSPACE_GIT_REMOTE=https://git.example.invalid/platform/thoth-workspaces.git THT_WORKSPACE_GIT_BRANCH=main @@ -14,3 +15,4 @@ THT_DWH_REST_URL=https://dwh.example.invalid THT_VEC_REST_URL=https://vector.example.invalid THT_VEC_WRITE_REST_URL=https://vector-write.example.invalid THT_OLLAMA_URL=https://embeddings.example.invalid +THT_LLM_URL=https://llm.example.invalid diff --git a/deploy/env/server.env.example b/deploy/env/server.env.example index 9888b7f2..cc080031 100644 --- a/deploy/env/server.env.example +++ b/deploy/env/server.env.example @@ -3,6 +3,7 @@ THOTH_SERVER_BIND=127.0.0.1 THOTH_HTTP_PORT=8080 MAX_PI_PROCESSES=4 +PI_AUTH_FILE=/absolute/path/to/pi-auth.json THT_DATA_ROOT=/srv/thothii/data THT_PI_STATE_ROOT=/srv/thothii/pi-state @@ -17,3 +18,4 @@ THT_DWH_REST_URL=https://dwh.example.invalid THT_VEC_REST_URL=https://vector.example.invalid THT_VEC_WRITE_REST_URL=https://vector-write.example.invalid THT_OLLAMA_URL=https://embeddings.example.invalid +THT_LLM_URL=https://llm.example.invalid diff --git a/scripts/test-unified-compose.sh b/scripts/test-unified-compose.sh index 40e096a7..91203646 100755 --- a/scripts/test-unified-compose.sh +++ b/scripts/test-unified-compose.sh @@ -26,6 +26,18 @@ if (/omics_portal|chirone|localllm_default|\/home\/chirone/i.test(JSON.stringify throw new Error("forbidden application coupling"); } if (!config.networks || !config.networks.thothii) throw new Error("base stack must define the thothii network"); +if (!Object.hasOwn(config.services.core.environment || {}, "THT_LLM_URL")) { + throw new Error("core must expose a generic THT_LLM_URL endpoint contract"); +} +if (/docker\.sock|\/var\/run\/docker|docker[-_]?daemon/i.test(JSON.stringify(config.services))) { + throw new Error("Compose must not mount the Docker socket or daemon"); +} +const piAuthMounts = (config.services.core.volumes || []).filter( + (mount) => mount.target === "/home/thoth/.pi/agent/auth.json", +); +if (piAuthMounts.length !== 1 || piAuthMounts[0].type !== "bind" || !piAuthMounts[0].read_only) { + throw new Error("Pi auth must be one read-only file bind"); +} const ports = Object.fromEntries( Object.entries(config.services).map(([name, service]) => [name, service.ports || []]), @@ -74,6 +86,18 @@ if (!config.networks || !config.networks.thothii) throw new Error("base stack mu for (const volume of ["settings", "pi-state", "workspace-registry", "sessions"]) { if (!config.volumes || !config.volumes[volume]) throw new Error(`missing required volume: ${volume}`); } +if (!Object.hasOwn(config.services.core.environment || {}, "THT_LLM_URL")) { + throw new Error("core must expose a generic THT_LLM_URL endpoint contract"); +} +if (/docker\.sock|\/var\/run\/docker|docker[-_]?daemon/i.test(JSON.stringify(config.services))) { + throw new Error("Compose must not mount the Docker socket or daemon"); +} +const piAuthMounts = (config.services.core.volumes || []).filter( + (mount) => mount.target === "/home/thoth/.pi/agent/auth.json", +); +if (piAuthMounts.length !== 1 || piAuthMounts[0].type !== "bind" || !piAuthMounts[0].read_only) { + throw new Error("Pi auth must be one read-only file bind"); +} NODE render_profile local deploy/env/local.env.example deploy/compose.local.yaml