# Security: secrets handling & rotation checklist ## How secrets are stored in this repo - **`secrets/`** — git-crypt encrypted (see `.gitattributes`: `secrets/** filter=git-crypt`). All real credentials live here. The working-tree copies are plaintext (git-crypt only encrypts the committed blobs), so Docker reads them normally. - **Root `.env`** — git-ignored (`/.env`) and **not** committed. It holds non-secret config (ports, paths, domains) plus a few `${VAR}` values interpolated across services. Use **`.env.example`** as the template; fill in the blanked secret values locally. - **Tracked compose / `*.env` files** — must contain **no secret values**. Pull secrets in via one of: - `env_file: - secrets/.env` (universal) - `FILE__VARNAME=/run/secrets/` (LinuxServer.io images, e.g. calibre-web-automated) - `VARNAME_FILE=/run/secrets/` (miniflux, gitea-runner, …) - `file:///run/secrets/` (authentik) ## Before you commit ```sh # git-crypt must be unlocked so secrets/ files encrypt on commit git-crypt status | grep -i 'not encrypted' || echo "all secrets/ files encrypted" ``` Quick scan for accidental plaintext secrets in tracked files: ```sh git ls-files | grep -vE '^secrets/|^\.env\.example$' | xargs grep -nIE \ '(PASSWORD|SECRET|TOKEN|API_?KEY|CLIENT_SECRET|MASTER_KEY)=[^ ]' 2>/dev/null \ | grep -vE '_FILE|/run/secrets/|file:///|\$\{|=\s*$' ``` ## Rotation checklist All values below were committed to git history in plaintext before the 2026-06-12 migration. Migrating them to `secrets/` only protects them **going forward** — each must be **rotated at its provider**, then history scrubbed (see bottom). Tick each once the credential has been regenerated and the new value written to the corresponding `secrets/` file. ### External / high priority (reachable beyond the LAN) - [ ] **Gmail app password** — `EMAIL_PASSWORD` in `.env` (reused by vaultwarden, mealie, baserow SMTP). Regenerate at Google Account → App passwords. - [ ] **ProtonMail SMTP token** — `secrets/authentik_email_password`. Regenerate in Proton → SMTP submission. - [ ] **mealie OIDC client secret** — `secrets/mealie.env`. Rotate the `mealie` provider in Authentik. - [ ] **Last.fm API key + secret** — `secrets/navidrome.env`. Reissue at last.fm/api/accounts. - [ ] **Meilisearch master key** — `secrets/meilisearch.env` (used by karakeep + meilisearch). Generate a new random key. - [ ] **karakeep `NEXTAUTH_SECRET`** — `secrets/karakeep.env`. `openssl rand -base64 36`. - [ ] **vaultwarden `ADMIN_TOKEN`** — `secrets/vaultwarden.env`. Regenerate with `vaultwarden hash` (Argon2). - [ ] **jelu `GOOGLE_API_KEY`** — `secrets/jelu.env`. Rotate in Google Cloud console. ### Internal (LAN-only, still rotate — `kralovna` is reused widely) - [ ] **Postgres password** — `POSTGRES_PASSWORD` in `.env` (`kralovna`). - [ ] **MySQL/MariaDB passwords** — `MYSQL_PASSWORD`, `MYSQL_ROOT_PASSWORD` in `.env` (`kralovna`). - [ ] **baserow DB password** — `secrets/baserow.env`. - [ ] **photoprism admin + DB passwords** — `secrets/photoprism.env`. - [ ] **jelu DB password** — `secrets/jelu.env`. - [ ] **komf komga password + Kavita API key** — `secrets/komf.env`. - [ ] **openldap admin + readonly passwords** — `secrets/openldap.env`. - [ ] **maloja force password** — `secrets/maloja.env`. - [ ] **valheim server password** — `secrets/valheim.env`. - [ ] **penpot postgres password** — `secrets/penpot.env`. ### Orphaned services (moved to `secrets/`; rotate if still running anywhere) - [ ] **mediawiki MySQL password** — `secrets/mediawiki.env`. - [ ] **rtorrent RPC2 password** — `secrets/rtorrent.env`. - [ ] **snibox `SECRET_KEY_BASE`** — `secrets/snibox.env`. ## Scrubbing git history After rotating, remove the old plaintext values from history so the leaked secrets become useless even to someone with an old clone: ```sh # Using git-filter-repo (recommended). Removes the old tracked paths entirely. git filter-repo --invert-paths \ --path .env \ --path mediawiki.env --path rtorrent.env --path snibox.env # Then force-push and have every clone re-clone (rewritten history diverges): git push --force --all git push --force --tags ``` For surgical edits to lines inside files that stay tracked (e.g. a secret that lived in `docker-compose.yml`), use `git filter-repo --replace-text ` with `old==>***REMOVED***` rules, or BFG's `--replace-text`. > Rotation is what actually neutralizes a leak. History scrubbing is best-effort — > assume anything ever pushed is already compromised and rotate regardless.