Encryption at rest uses a hardcoded, source-derivable key ("hero_db_default_key"); HERO_DB_ENCRYPTION_KEY is never read #46

Open
opened 2026-06-10 14:56:01 +00:00 by sameh-farouk · 0 comments
Member

hero_db's headline "ChaCha20-Poly1305 encryption at rest" runs with a single hardcoded key shared by every instance, and the documented override (HERO_DB_ENCRYPTION_KEY) is never read. The at-rest encryption therefore provides essentially no confidentiality against anyone with access to the (open) source.

Evidence (main @ aacaad1)

  • crates/hero_db_server/src/main.rs:129let encryption_key = None;
  • crates/hero_db_server/src/main.rs:223let encryption_key = encryption_key.unwrap_or_else(|| "hero_db_default_key".to_string());
  • crates/hero_db_server/src/lib.rs:133let encryption_key = "hero_db_default_key"; (same constant in the lib entry path)
  • HERO_DB_ENCRYPTION_KEY is advertised in the --help ENV VARS section (main.rs:118) but there is no env::var("HERO_DB_ENCRYPTION_KEY") anywhere, and — unlike the RESP port — no hero_proc-secret read for it either.
  • Key derivation: crates/hero_db/src/db/crypto.rs EncryptionKey::from_secret() = SHA-256(secret) → 32-byte ChaCha20-Poly1305 key. So every value is encrypted with ChaCha20Poly1305(SHA256("hero_db_default_key")).

The nonce is randomly generated per value (crypto.rs, 12-byte random prepended) — that part is correct — but the key is universal and recoverable from the public source.

Impact

  • The encryption key is a compile-time constant in a readable repo, identical across all deployments. Anyone who can read the source (or strings the binary) can decrypt any hero_db data file.
  • Effective protection reduces to "raw bytes on disk aren't plaintext" — obfuscation, not encryption. It does not protect against an attacker who reads the disk and knows the constant (trivially true here).
  • The README/PURPOSE present "ChaCha20-Poly1305 encryption" as a security feature (README.md:3, feature list), which overstates the guarantee given the fixed key.

Suggested fix

  1. Source the key from the hero_proc secret store (context core, key HERO_DB_ENCRYPTION_KEY) — mirror resolve_resp_port() which already does this for the port.
  2. Fail closed: if no key is configured, refuse to start (or run with encryption explicitly disabled + a loud warning) rather than silently using a shared default.
  3. Drop HERO_DB_ENCRYPTION_KEY from the OS-env help section once it's wired through the secret store (see the doc-accuracy issue #44).
  4. Until a per-deployment key is enforced, soften the "encryption at rest" claim in README/PURPOSE so it isn't read as a confidentiality guarantee.

Notes

  • Rotation/migration is out of scope for the first fix, but worth a follow-up: changing the key invalidates all existing ciphertext (key is not stored, it's re-derived each boot).

Found while verifying the doc-accuracy issue #44 — escalated to its own issue because it is a security/correctness bug, not just stale documentation.

hero_db's headline "ChaCha20-Poly1305 encryption at rest" runs with a **single hardcoded key shared by every instance**, and the documented override (`HERO_DB_ENCRYPTION_KEY`) is never read. The at-rest encryption therefore provides essentially no confidentiality against anyone with access to the (open) source. ## Evidence (`main` @ `aacaad1`) - `crates/hero_db_server/src/main.rs:129` — `let encryption_key = None;` - `crates/hero_db_server/src/main.rs:223` — `let encryption_key = encryption_key.unwrap_or_else(|| "hero_db_default_key".to_string());` - `crates/hero_db_server/src/lib.rs:133` — `let encryption_key = "hero_db_default_key";` (same constant in the lib entry path) - `HERO_DB_ENCRYPTION_KEY` is advertised in the `--help` `ENV VARS` section (`main.rs:118`) but there is **no** `env::var("HERO_DB_ENCRYPTION_KEY")` anywhere, and — unlike the RESP port — **no hero_proc-secret read for it** either. - Key derivation: `crates/hero_db/src/db/crypto.rs` `EncryptionKey::from_secret()` = `SHA-256(secret)` → 32-byte ChaCha20-Poly1305 key. So every value is encrypted with `ChaCha20Poly1305(SHA256("hero_db_default_key"))`. The nonce is randomly generated per value (`crypto.rs`, 12-byte random prepended) — that part is correct — but the **key is universal and recoverable from the public source**. ## Impact - The encryption key is a compile-time constant in a readable repo, identical across all deployments. Anyone who can read the source (or `strings` the binary) can decrypt any hero_db data file. - Effective protection reduces to "raw bytes on disk aren't plaintext" — obfuscation, not encryption. It does **not** protect against an attacker who reads the disk *and* knows the constant (trivially true here). - The README/PURPOSE present "ChaCha20-Poly1305 encryption" as a security feature (`README.md:3`, feature list), which overstates the guarantee given the fixed key. ## Suggested fix 1. Source the key from the hero_proc secret store (context `core`, key `HERO_DB_ENCRYPTION_KEY`) — mirror `resolve_resp_port()` which already does this for the port. 2. **Fail closed**: if no key is configured, refuse to start (or run with encryption explicitly disabled + a loud warning) rather than silently using a shared default. 3. Drop `HERO_DB_ENCRYPTION_KEY` from the OS-env help section once it's wired through the secret store (see the doc-accuracy issue #44). 4. Until a per-deployment key is enforced, soften the "encryption at rest" claim in README/PURPOSE so it isn't read as a confidentiality guarantee. ## Notes - Rotation/migration is out of scope for the first fix, but worth a follow-up: changing the key invalidates all existing ciphertext (key is not stored, it's re-derived each boot). --- *Found while verifying the doc-accuracy issue #44 — escalated to its own issue because it is a security/correctness bug, not just stale documentation.*
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
lhumina_code/hero_db#46
No description provided.