| v3.0.0
Core Features

Encryption

How Shelve protects secrets at rest and in transit.

Shelve treats every secret value as encrypted data until the very moment you need it. This page describes the encryption scheme, the key hierarchy, and what actually hits the database.

Two-tier envelope encryption

Secret values are never encrypted directly with the global server key. Instead Shelve uses an envelope scheme:

value ──seal──▶ ciphertext     (key = DEK, per-project)
DEK   ──seal──▶ encryptedDek   (key = KEK, platform-wide)
  • KEKKey Encryption Key. A single secret sourced from NUXT_PRIVATE_ENCRYPTION_KEY at boot. It never touches stored data directly; it is only used to seal and unseal DEKs.
  • DEKData Encryption Key. Generated server-side on a project's first write (32 random bytes, base64-encoded), sealed with the KEK, and persisted in projects.encryptedDek. From then on every variable on that project is encrypted with that DEK.

The sealing primitive underneath is iron-webcrypto, configured with authenticated encryption (aes-256-gcm). A tampered ciphertext fails to decrypt — there is no silent downgrade.

Why envelope encryption?

Fast key rotation

Scoped blast radius

BYOK-ready

Backward compatibility

Projects created before the envelope upgrade have no encryptedDek column value. Their variables keep decrypting directly with the KEK and the first new write provisions a DEK automatically. Reads tolerate mixed-state data: the service tries the project DEK first, then falls back to the KEK so no variable is left unreadable during the transition.

What is stored in the database

ColumnWhat it holds
variables.encryptedValueSealed ciphertext of the secret value. Never plaintext.
projects.encryptedDekProject DEK, sealed by the KEK. null for pre-envelope projects.
tokens.hashsha256(token) as hex. The plaintext token is never stored.
tokens.prefixNon-secret 12-char prefix used for display and audit logs.

API tokens

API tokens follow a different (but compatible) model: they are hashed, not encrypted. See API Tokens for the full story.

In transit

Traffic to app.shelve.cloud uses TLS 1.3 end-to-end. The CLI pins the same endpoint and refuses downgrade. For self-hosted instances, configure HTTPS at your reverse proxy or platform.

Self-host checklist

  1. Generate a strong KEK: openssl rand -base64 48.
  2. Set it as NUXT_PRIVATE_ENCRYPTION_KEY on your platform.
  3. Never rotate the KEK in-place without re-sealing existing DEKs — existing data becomes unreadable. A safe rotation procedure is on the roadmap.
  4. Back up projects.encryptedDek alongside variables.encryptedValue; losing either makes the corresponding data unrecoverable.