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
A fresh key per secret
A random 256-bit key is made for each KEY=value line.
- 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
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
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 happens | What it means |
|---|---|
| Our database is breached | The attacker gets ciphertext, wrapped keys, public keys and metadata. Reading a value still needs a member's private key. |
| Our servers are compromised | A 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 stolen | The thief can call the API as you and receives ciphertext. Tokens are short-lived, and you can remove devices and sessions. |
| A laptop is stolen | The private key is encrypted at rest behind the OS keychain. Remove the device, then re-key. |
| A removed teammate kept values | Re-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 tricks | CLI 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.