Files
hub/docs/decisions/ADR-008-secrets-encrypted-at-rest-with-key-versioning.md
glm-5.1 2b63cda1c7 Setup repo: migrate architecture specs, code stubs, and tasks from alkhub_ts
Copy architecture docs, ADRs, storage domain specs, research, reviews,
and 56 storage architecture tasks from the alkhub_ts monorepo. Adapt for
standalone @alkdev/hub repo structure (src/ not packages/hub/).

Sanitize all sensitive information:
- Replace private IPs (10.0.0.1) with localhost defaults
- Remove internal server hostnames (dev1, ns528096)
- Replace /workspace/ private paths with npm package references
- Remove hardcoded credentials from examples
- Rewrite infrastructure.md without private network details

Add Deno project scaffolding: deno.json (pinned deps), .gitignore,
AGENTS.md, entry point. Migrate existing code stubs (crypto, config
types, logger) with updated import paths.
2026-05-25 10:56:32 +00:00

3.2 KiB

ADR-008: Secrets encrypted at rest with key versioning

  • Status: Accepted (revised 2026-04-23)
  • Date: 2026-04-19
  • Revised: 2026-04-23
  • Deciders: alkdev

Context

API keys, passwords, OAuth tokens, and SSH keys for external services must be stored securely. The crypto.ts utility from ade-v0 (AES-256-GCM + PBKDF2 with key version support) is battle-tested.

The original decision specified reading the encryption key from an environment variable (HUB_ENCRYPTION_KEY). This is a security concern: environment variables are readable via /proc/PID/environ by any process with the same UID on the host, and are visible in docker inspect. In a multi-container Docker environment, this is a real attack surface.

Decision

Copy crypto.ts to packages/core/utils/crypto.ts. Store encrypted secrets in client_secrets.value as EncryptedData { keyVersion, salt, iv, data }.

Two-layer key model (revised from original):

  1. Master key — Provisioned via Docker secret (/run/secrets/hub_master_key). tmpfs-backed, never on container filesystem, not visible in /proc/environ. Used only to decrypt the config file's encrypted fields. Rarely rotated (requires redeploying the Docker secret).

  2. Data encryption keys — Stored in the config file's encryptionKeys field (itself encrypted with the master key). Multi-key format: v1:base64,v2:base64 — the first key is "current" (used for new encryptions), all keys are available for decryption (enables rotation). Generated via crypto.generateEncryptionKey(). Rotated by updating the config file and re-encrypting client_secrets rows — no Docker secret change needed.

Key versioning supports rotation — bump keyVersion, re-encrypt on next access. The rotation protocol is defined in storage/services.md.

No environment variables for secrets or important configuration. This is a hard rule. Non-sensitive convenience vars (e.g., ALKHUB_CONFIG_PATH) are acceptable. Nothing that would be damaging if exposed via /proc may be in an env var.

Full config system specification: docs/architecture/hub-config.md. Startup sequence: docs/architecture/hub-startup.md.

Consequences

Encryption keys must be available at runtime. If lost, all secrets unrecoverable. Standard for symmetric encryption.

Positive: Key versioning enables rotation without downtime. Proven crypto implementation. Docker secrets eliminate the /proc/environ leak vector. Two-layer keys allow independent rotation schedules (master key rarely, data keys as needed). Config file with encrypted fields is safe to version-control (ciphertext only).

Negative: Encryption key loss means total data loss (same as before). Two keys to manage instead of one. Slightly more complex deployment (mount config file + secret, rather than just setting env vars). Config file must be prepared with the alkhub-config CLI tool before deployment.

Mitigated by: Storing master key in Docker secrets (not DB, not env), supporting key rotation so compromised keys can be cycled, alkhub-config tool automating config file preparation, infrastructure.md documenting the Docker deployment pattern.