Secrets Management
Secrets Management
Section titled “Secrets Management”Golden rules
Section titled “Golden rules”- Never commit secrets — no keys, passwords, or tokens in source, config files, commit messages, logs, or docs.
- Never log secrets — not even partially.
- Local development uses
.envfiles (gitignored). - Production uses Docker secrets mounted at
/run/secrets/<name>.
How the app reads secrets
Section titled “How the app reads secrets”SecretService (common/services/secret.service.ts) is the single
access point:
// Docker secret file first, env var as fallback (dev mode)const key = this.secrets.get('backup_encryption_key', 'BACKUP_ENCRYPTION_KEY');// Or fail closed:const jwt = this.secrets.getOrThrow('jwt_access_secret');get(name, fallbackEnv?)→ cached value orundefinedgetOrThrow(name, fallbackEnv?)→ throws when missing- In containers,
entrypoint.shreads/run/secrets/*and composes derived values (e.g.DATABASE_URLfrom host/user/password parts).
Secret inventory (names only)
Section titled “Secret inventory (names only)”| Secret | Purpose |
|---|---|
db_password |
PostgreSQL authentication |
redis_password |
Redis authentication |
jwt_access_secret / jwt_refresh_secret |
JWT signing |
mistral_api_key |
Mistral Vision API |
smb_credentials_key |
AES-256-GCM for stored SMB credentials |
webhook_encryption_key |
AES-256-GCM for webhook secrets |
vapid_public_key / vapid_private_key |
Web Push |
tool_provisioning_secret |
M2M tenant provisioning |
backup_encryption_key |
AES-256-CBC backup archive encryption |
Key separation
Section titled “Key separation”Credentials have a single purpose each. Example: the database password
authenticates pg_dump; backup archives are encrypted with the separate
backup_encryption_key — so rotating or compromising one credential does
not cascade into the other.
Adding a new secret
Section titled “Adding a new secret”- Add a mapping row in
docker/secrets-mapping.env(file name → source key + generation flags). - Add the secret to the compose service(s) that need it.
- Read it in code via
SecretService— never viaprocess.envdirectly when a secret file may exist. - Document it in
.env.example(name + purpose, never a real value).