The server cannot read your secrets
Values are encrypted on your own machine before they are sent, and the API refuses to store anything that is not already ciphertext. Not a policy, a validation rule.
Envless encrypts every variable value in your browser or on your machine with AES-256-GCM, under a 256-bit key that PBKDF2-SHA256 stretches from your workspace passphrase at 200,000 iterations with a workspace-specific salt. The passphrase is never transmitted, no table in the database holds a key or a key hash, and the API rejects any value that does not arrive with a v1: or v2: envelope prefix. What the server can still see is the metadata around each secret: the variable name, its type, its visibility flag and who changed it. What it cannot see, and has no mechanism to recover, is the value.
The numbers, from the source
Every figure below is pinned in code and identical across the dashboard, the CLI, the SDK and the runtime package, so all four derive byte-identical keys.
- AES-256-GCM
- Each value gets a fresh 12-byte random IV from crypto.getRandomValues. The IV is prepended to the ciphertext, base64-encoded and prefixed v1:. A wrong passphrase fails the authentication tag and throws, rather than handing back corrupted plaintext.
- 200,000
- Hard-coded independently in the dashboard, the CLI, the SDK, the runtime and the API key-derivation descriptor. The v2: envelope can pin a different cost inline, bounded to between 100,000 and 1,000,000, and anything outside that range is refused rather than silently retried.
- 0 columns
- No table has a passphrase, key or key-hash column. The variables table stores value as text the client already encrypted; the workspaces table holds a name, slug, icon, plan and feature flags. There is no escrow and no recovery record.
- 400
- A value that does not begin v1: or v2: is refused with variable.ciphertext_invalid before the row is written. The check runs on single create, bulk create and the transactional apply endpoint, and one bad value fails the whole request.
- 16 chars
- Minimum 16 characters with at least three of four character classes, dropping to two from 24 characters up where length carries the weight. No character four times in a row, no ascending or descending run past three, and 14 known-weak fragments are blocklisted.
- non-extractable
- The dashboard derives the key with extractable set to false and keeps it in IndexedDB, so page JavaScript can use it but cannot read the raw bytes out. It is handed back only for the workspace currently being viewed.
Authenticated encryption
PBKDF2-SHA256 iterations
Key material stored server-side
Plaintext writes rejected
Passphrase strength floor
Browser key handling
Where encryption actually happens
Four points in the lifecycle, all of them on hardware you control. The network only ever carries ciphertext.
- 1
Derive the key on your machine
The salt is deterministic and workspace-bound: SHA-256 of "envless:workspace:" plus the workspace id, truncated to 16 bytes. The same passphrase in a different workspace produces a different key and cannot decrypt across the boundary. The CLI verifies the derived key against a real sample before caching it.
envless link envless passphrase set - 2
Encrypt before the request leaves
envless var set and envless push encrypt locally and send only ciphertext, which is why both fail fast on a locked workspace instead of transmitting a plaintext value. The same functions are exported from the SDK if you write through the API yourself.
import { encryptValue, decryptValue, isCiphertext } from '@goenvless/sdk' const ciphertext = await encryptValue(secret, passphrase, workspaceUniqueId) // 'v1:<base64 of the 12-byte IV followed by AES-256-GCM ciphertext>' isCiphertext(ciphertext) await decryptValue(ciphertext, passphrase, workspaceUniqueId) - 3
Watch the API refuse plaintext
You do not have to trust the client to do its job. Send a raw value over the public API and the request is rejected before anything is stored, with the offending field named in the error.
curl -sS -X POST \ "https://api.envless.cloud/projects/$PROJECT/environments/$ENV/variables" \ -H "Authorization: Bearer $ENVLESS_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"name":"API_KEY","value":"sk_live_plaintext"}' # 400 variable.ciphertext_invalid (error.field: "value") - 4
Rotate without the server ever seeing a key
Rotation derives both the old and the new key in the client, verifies the old one against a real sample, then decrypts and re-encrypts every live variable and every version-history entry before storing anything. The server only ever receives new ciphertext. Rotate the version entries in the same run, or later rollbacks restore values nothing can decrypt.
import { createClient, rotateWorkspacePassphrase } from '@goenvless/sdk' await rotateWorkspacePassphrase({ client: createClient({ token: process.env.ENVLESS_TOKEN }), workspaceUniqueId, from: currentPassphrase, to: nextPassphrase, project: 'billing', environment: 'production' })
Read the details
The reference pages behind every claim on this page, including the exact test vectors that pin the key bytes.
- Choosing a workspace passphrase The enforced strength floor, why a lost passphrase is unrecoverable, and how rotation re-encrypts a workspace.
- Encryption reference The envelope format, the KDF parameters, and reference implementations for encrypting outside the SDK.
- Core concepts How the key, the cache and the three delivery modes fit together, and which one holds what.
- Machine access and key export Exporting the raw workspace key for CI so an unattended process never handles a human-chosen passphrase.
- Generate a passphrase that clears the floor A free client-side generator built for exactly this credential, with the entropy maths shown.
End-to-end encryption FAQ
Straight answers about how this works in practice.
No, and there is no mechanism by which we could. Values arrive already encrypted, the key is derived from a passphrase we never receive, and no table in the database holds a key or key hash. What we can see is the metadata around each secret: variable names, types, visibility flags, timestamps, and which user made a change. Say the server cannot read your values, not that it knows nothing about them.
The ciphertext becomes permanently unreadable. There is no key escrow, no recovery record and no server-side key material of any kind, so there is no reset link and no support ticket that can bring it back. This is the direct cost of the guarantee: an Envless that could recover your passphrase would be an Envless that could read your secrets. Store it in a password manager and treat it as the one credential with no recovery path.
Because it is what WebCrypto implements natively in every browser and runtime Envless targets, which lets the dashboard, the CLI, the SDK and the runtime derive byte-identical keys with no native module and no WASM payload. Memory-hard KDFs resist GPU attacks better, and that is a genuine trade-off we accept in exchange for one implementation everywhere. The 200,000-iteration cost and the enforced passphrase floor are what carry the weight instead.
A v1: value implies the default 200,000 iterations. A v2: value pins its own iteration count inline as v2:<iterations>:<base64>, so a value sealed at a different cost can still be decrypted years later without guessing. The read path supports both and derives a second key at the recorded cost when one appears; every client currently writes v1:.
Not by itself. There is one symmetric key per workspace, derived from one shared passphrase, so anyone who ever held that passphrase or an exported raw key can still decrypt any ciphertext they kept a copy of. Removing the member stops them fetching anything new. To actually retire their access to old values you rotate the workspace passphrase, which is exactly why rotation exists.
Only partially, and it is worth being precise. The dashboard derives the key as non-extractable, so a script running in the page cannot exfiltrate the raw key bytes. It can still ask the browser to decrypt with that key while the page is unlocked. Client-side encryption removes the server from your trust boundary; it does not remove the browser.
Yes, and that is the point of publishing the parameters. The repository carries cross-implementation test vectors that pin exact bytes: a given workspace id and passphrase must produce a specific salt and a specific key at 200,000 iterations, and ciphertext produced by the SDK must decrypt in the CLI. Reproduce the derivation with any PBKDF2 implementation and compare.
No. The floor is enforced when a workspace first sets a passphrase and on rotation. A passphrase created before the floor existed still works, because locking people out of their own ciphertext would be worse, but the CLI warns that it is below strength. Treat that warning as a rotation task rather than a cosmetic notice.
Explore the rest of Envless
Every capability, broken down the same way.
AI agents
Give a coding agent working secrets with no plaintext .env in the repository for it to read or commit.
TerminalThe CLI
24 commands, browser device login, local encryption, and JSON output on every list.
TypeScriptThe SDK
Zero-dependency typed client, full API coverage enforced by CI, with encryption and webhook helpers.
HistoryVersioning and history
Numbered snapshots in one locked transaction, attributed per-variable history, and signed events for every change.
TeamsTeam access control
44 permissions, custom roles, private products isolated to named members, and keys clamped to their creator.
EnvironmentsEnvironment variables
Typed, per-environment variables with server/client splitting, .env import and export, and a real diff.
Public feedsVirtual environments
Serve an environment as an encrypted JSON feed at a public URL that needs no SDK, CLI or API key.
SetupGetting set up
Install, sign in, bind the directory, unlock, run. What each step writes, and what it does not.
CloningOne-click duplicate
Clone an environment, project or whole product with every variable, in one transaction, without decrypting anything.
Ship secrets, not chaos.
Start free today and discover why developers trust Envless for end-to-end encrypted, versioned secrets across every environment.