Encrypted before it leaves your machine

StoreEnv's servers store ciphertext, wrapped keys and public keys. Secret values, encryption keys and your recovery phrase never reach us.

Visible to StoreEnv

  • · GitHub username, email and avatar
  • · Project, service and environment names
  • · Key names (needed for templates, diffs and search)
  • · Value sizes, padded to 32-byte buckets
  • · Version messages and who changed what
  • · Who has access to what
  • · IP and device details for security

Never visible to StoreEnv

  • · Secret values
  • · The key of any secret
  • · Private keys
  • · Your recovery phrase

Template descriptions and examples are plain text that a project owner chooses to publish.

How a secret is encrypted

  1. 1

    A fresh key per secret

    A random 256-bit key is made for each KEY=value line.

  2. 2

    AES-256-GCM

    The value is sealed with a random 96-bit nonce. The auth tag detects any tampering, and decryption fails closed.

  3. 3

    Bound to its place

    Project, environment, service, key name and revision are authenticated with the ciphertext, so it can't be moved to another key.

  4. 4

    Wrapped per reader

    The secret's key is wrapped to the public key of every member allowed to read it: one-time X25519, HKDF-SHA-256, then AES-256-GCM.

All of this happens in the CLI or in your browser. The stored record carries an algorithm label, so stronger schemes can be added later without migrating data.

Your identity

Signing in with GitHub proves who you are to the API. Decrypting needs something else: a key pair derived from a 24-word recovery phrase that is generated on your device. The same phrase gives the same key pair everywhere. A new device gets the key from the phrase, or from a device you already use, sealed to a one-time key and confirmed with a short code.

We can't recover your data without your recovery phrase. If you lose the phrase and every device, teammates can share project secrets with a new identity, but secrets only you could read are gone.

Sharing and revoking

Access has two layers. A grant is the policy, enforced by the API. A key share is the capability: the secret's key wrapped for one person. Both must exist to decrypt. Because the server never sees a secret's key, some unlocked client has to do the wrapping, which is why a new member can show as "pending key share" for a moment.

Removing someone deletes their grants and key shares at once. Every secret they could read then gets a new key and is marked "rotation due".

Threat model

If this happensWhat it means
Our database is breachedThe attacker gets ciphertext, wrapped keys, public keys and metadata. Reading a value still needs a member's private key.
Our servers are compromisedA server could hand out a wrong public key to capture new shares. Fingerprints are shown in the CLI and the app so teammates can verify each other.
A session token is stolenThe thief can call the API as you and receives ciphertext. Tokens are short-lived, and you can remove devices and sessions.
A laptop is stolenThe private key is encrypted at rest behind the OS keychain. Remove the device, then re-key.
A removed teammate kept valuesRe-keying stops future access through StoreEnv. Anything they copied is only safe again after you rotate it at the provider. We say so in the product.
Login CSRF or redirect tricksCLI logins use a single-use state, localhost-only callbacks and a one-time exchange code. Tokens never travel in a URL.

The design has not had an independent cryptographic review yet. That review is planned before general availability.

Found a vulnerability? Write to security@storeenv.app. We reply to every report.

Stop pasting secrets into chat.

$ npm i -g storeenv Get started