Proposal: encrypt secrets at rest via SOPS (opt-in) #233
Labels
No labels
area:chat
area:core
area:llm
area:routes
area:tools
bug
documentation
duplicate
enhancement
good first issue
help wanted
invalid
question
refactor
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
sleepy/odysseus#233
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Background
Today secrets in Odysseus (OpenAI key, admin password seed, SearXNG signing key, MCP OAuth secrets, IMAP passwords, etc.) live as plaintext values in
.env. The file is gitignored, so they don't leak via git — but they sit unencrypted on disk and any process that can read the file (host backup, container exec, a misconfigured rsync, a leaked snapshot) reveals every secret simultaneously.PR #22 already moved the SearXNG signing key out of source into
${SEARXNG_SECRET}— the obvious next step is making the.envitself safe at rest for users who want it.Proposal
Add opt-in support for SOPS with age keys. Existing users see zero change — the feature is only active when a
secrets.env.encfile is present at boot.Scope: what gets encrypted vs stays plaintext
.env(plaintext, gitignored)secrets.env.enc(SOPS-encrypted, committable)APP_PORT,LLM_HOST,SEARXNG_INSTANCE,ALLOWED_ORIGINS,CHROMADB_BIND,LOCALHOST_BYPASSOPENAI_API_KEY,ODYSSEUS_ADMIN_PASSWORD,SEARXNG_SECRET,INTERNAL_TOOL_TOKEN, MCP OAuth client secrets, IMAP passwordsOnly true secrets move; non-secret config benefits from staying diff-able in PRs.
Why SOPS specifically
.env-compatible — encrypts values, keeps keys in cleartext, so structural diffs still work~/.config/sops/age/keys.txtsops exec-env— wraps process startup with decrypt-and-source; no plaintext on container disk, no application code changeNaming note
The codebase already has
routes/vault_routes.py(Bitwarden/Vaultwarden integration — a runtime feature). To avoid collision I'm using "encrypted secrets at rest" / "SOPS" /secrets.env.enc, not "vault" anywhere.Proposed implementation (minimal)
sopsbinary to the Dockerfile (~10MB Go binary).sops.yaml(creation rule) andsecrets.env.example(format reference)docker/entrypoint.sh: ifsecrets.env.encexists, wrapgosuwithsops exec-env— decrypted secrets become env vars JIT, plaintext never touches disk.gitignoreto mirror the.env/!.env.examplepattern forsecrets.envSECURITY.mdwith the age-keygen → sops -e → docker-compose mount workflowBackwards-compatible: a user without
secrets.env.encruns exactly as today.Open questions for the maintainer
sopsin the image — OK to add the binary unconditionally, or prefer it behind a build ARG / Compose profile so non-users don't pull it?PR with concrete code coming as a follow-up to this issue — easier to discuss against real diffs. Close this freely if the direction isn't wanted; the PR is self-contained and easy to drop.
There is a related PR in #236 for optional encrypted-at-rest secrets via SOPS. Leaving this open until that approach is reviewed, because secret storage touches setup, backups, Docker/native installs, and migration behavior.