[CRITICAL] Secrets stored as plaintext in world-readable Nix store #10

Open
opened 2026-05-05 15:42:59 -05:00 by pjennings · 0 comments
Owner

Labels: area:security, priority:critical, type:bug

Description

The custom secrets management system (modules/profiles/secrets.nix) stores all secret values as Nix string literals. These values end up in /nix/store which is world-readable by default.

Examples:

  • WireGuard private keys passed via builtins.toFile
  • Database passwords as direct environment variable strings
  • API tokens as Nix string literals in secret definition files
  • Authentik secret key, Grafana admin password, etc.

Impact

CRITICAL — Any user or process on ANY host can read ALL secrets from the Nix store:

# On any host, secrets are readable:
grep -r 'SECRET_KEY_BASE' /nix/store/

Compromising a single host exposes credentials for every service across the entire fleet. This is a systemic architectural flaw.

Migrate to one of:

  1. sops-nix (recommended) — Encrypts secrets with age/GPG keys, decrypts only at activation time into /run/secrets with proper permissions
  2. agenix — Similar to sops-nix but uses age encryption
  3. NixOS vault-integration — For HashiCorp Vault users

Migration steps:

  1. Install sops-nix as a flake input
  2. Create .sops.yaml with age keys for each host
  3. Encrypt existing secrets from modules/secrets/ with sops
  4. Replace builtins.toFile and direct string assignments with sops.secrets references
  5. Update all host configs to use config.sops.secrets.<name>.path instead of config.secrets.<name>.keys
  6. Test each host individually before fleet-wide deployment

References

**Labels:** `area:security`, `priority:critical`, `type:bug` ## Description The custom secrets management system (`modules/profiles/secrets.nix`) stores all secret values as Nix string literals. These values end up in `/nix/store` which is world-readable by default. **Examples:** - WireGuard private keys passed via `builtins.toFile` - Database passwords as direct environment variable strings - API tokens as Nix string literals in secret definition files - Authentik secret key, Grafana admin password, etc. ## Impact **CRITICAL** — Any user or process on ANY host can read ALL secrets from the Nix store: ```bash # On any host, secrets are readable: grep -r 'SECRET_KEY_BASE' /nix/store/ ``` Compromising a single host exposes credentials for every service across the entire fleet. This is a systemic architectural flaw. ## Recommended Fix Migrate to one of: 1. **sops-nix** (recommended) — Encrypts secrets with age/GPG keys, decrypts only at activation time into `/run/secrets` with proper permissions 2. **agenix** — Similar to sops-nix but uses age encryption 3. **NixOS vault-integration** — For HashiCorp Vault users **Migration steps:** 1. Install sops-nix as a flake input 2. Create `.sops.yaml` with age keys for each host 3. Encrypt existing secrets from `modules/secrets/` with sops 4. Replace `builtins.toFile` and direct string assignments with `sops.secrets` references 5. Update all host configs to use `config.sops.secrets.<name>.path` instead of `config.secrets.<name>.keys` 6. Test each host individually before fleet-wide deployment ## References - https://github.com/Mic92/sops-nix - https://github.com/ryantm/agenix - https://nixos.wiki/wiki/Comparison_of_secret_managing_schemes
Sign in to join this conversation.
No description provided.