Keep core secrets (`JWT_SECRET`, `POSTGRES_PASSWORD`) out of every app's env scope (hardening)
Follow-up to #2617 — a separate, related hardening idea (different root cause, filing on its own).
What I noticed: each app's generated app.env is seeded from the global Runtipi .env, then app vars are merged on top — so every app's app.env carries the core infra secrets (JWT_SECRET, POSTGRES_PASSWORD, RABBITMQ_PASSWORD), not just its own.
packages/backend/src/modules/apps/app.helpers.ts—generateEnvFilereads the global env as its base, writes the merged result toapp.env.packages/backend/src/core/config/configuration.service.ts— those secrets live in that global env.app.envis written to the app-data root ({app-data}/{store}/{app}/app.env), a sibling of thedata/folder templates mount.
Two ways those core secrets can reach an app container:
docker compose --env-file app.envputs them all in the interpolation scope, so a template can reference them directly. Not theoretical —apps/calcom/docker-compose.ymlresolves${POSTGRES_PASSWORD}/${POSTGRES_USERNAME}(calcom has no such form fields, so these are the core creds), injects them into its containers, and sets its bundled DB to the core password.- A volume misconfig — mounting
${APP_DATA_DIR}instead of${APP_DATA_DIR}/datahands the wholeapp.envto the container.
Why it compounds with #2617: with POSTGRES_PASSWORD in hand, runtipi-db is reachable over the shared tipi_main_network → a compromised app can reach the core DB. And FWIW no official app actually reuses the core DB (no runtipi-db references anywhere; every DB app runs its own Postgres), so there's no efficiency reason for apps to hold the core creds — the calcom case looks like an accidental footgun, not intended reuse.
Suggestion — two small, independent changes:
- Scope
app.envto app-only vars (don't seed from the global.env). Core secrets stay in Runtipi's own env. Bonus: templates leaning on${POSTGRES_PASSWORD}would surface instead of silently getting the core secret. - Move
app.envout of the mountable data dir — it's Runtipi metadata, not app data, so I think it'd fit better next to the generated compose in the install dir rather than inapp-data/which users bind-mount. Fixes the wrong-mount exposure with no new var. (Backup-safe:backupAppalready archives both the data dir and the install dir —backup.manager.ts:70-77/ restore:169-170— so the env still rides along in restores.)
(calcom's template probably also wants its own random CALCOM_DB_PASSWORD regardless.)
Understood there may be history behind the current layout — just flagging in case it's worth tightening.
(Same heads-up: put together with AI assistance — Claude Opus 4.8 — so please sanity-check the code refs.)
Source: runtipi/runtipi