A public URL that is still a secret
Serve encrypted JSON at a public URL. Anything that speaks HTTP can read it, and values stay ciphertext.
How a public URL stays safe
One environment, served as encrypted JSON at a public URL with a plain GET. The consumer brings only the workspace passphrase or the raw key.
Full definition
Some consumers cannot install a CLI or an SDK: a shell script, a language with no client, an appliance, a build step in a toolchain you do not control. A virtual environment serves one environment as a plain GET at a public URL, returning a JSON bundle that carries the encrypted values plus everything needed to decrypt them apart from the secret itself. The consumer brings only the workspace passphrase or the raw key. It is opt-in twice over, it refuses to touch a private product, and it never carries a plaintext value.
Virtual environments
The URL shape, the response schema, worked decrypt examples, and the security model in full.
Choosing a passphrase
The strength floor, and why it matters more here than anywhere else in the product.
Publishing a version
Only published versions are served, so this is what decides what a consumer sees.
Generate a passphrase worth exposing behind
A free client-side generator, with the entropy maths shown so you can judge it yourself.
Before anything is served
2 opt-insThe workspace flag and the per-environment flag must both be on. Turning the workspace flag back off unexposes every exposed environment in one write and tells you how many. An environment inside a private product can never be exposed at all.
Every value in the bundle
ciphertextValues are the same AES-256-GCM ciphertext the server stores, and the feed never carries the workspace key or the passphrase. Everything around them is readable: variable names, the slugs in the URL, and the version label and note. Treat those as public and keep secrets out of a version note.
For every failure mode
one 404A wrong slug, an unexposed environment, a disabled workspace flag and a private product all answer with the identical message, so probing the URL tells an attacker nothing. The single exception is an exposed environment that has never been published, which says so plainly.
Versions served
published onlyThe feed serves published versions, so what a consumer reads is a snapshot you deliberately cut, not whatever someone last typed into the dashboard. Pin a version id for reproducible builds or use latest to always track the newest.
The bundle explains itself
self-describingTagged envless.encrypted-export/v1 and carrying the KDF parameters, the derived salt, the cipher spec and the ciphertext format, plus each variable's type, visibility and required flag. A consumer needs no lookup and no documentation to decrypt.
Owners and admins, on exposure
notifiedFlipping the toggle emails every owner and admin in the workspace, minus whoever did it, and emits an environment.exposed event to your webhooks with environment.unexposed on the way back. Nobody can quietly publish a feed.
Virtual environments FAQ
It would be, if the URL served secrets. It serves ciphertext, the same bytes the server itself stores, and the server cannot read those either. What changes compared to the rest of the product is the perimeter: everywhere else an attacker needs a credential before they can even fetch the ciphertext, whereas here they can fetch it and are left facing the encryption alone. That is a real trade and the reason the feature is off by default and needs two separate switches.
The workspace passphrase, and only that. Anyone can download the bundle, and the bundle deliberately includes the KDF parameters and salt so that legitimate consumers need no lookup. The direct consequence is that the response is enough to start an offline guessing attack. A generated passphrase of real length is fine against that; a human-chosen one is not. Rotate before you expose.
Because a salt is not secret, and withholding it would only break honest consumers. A salt exists to stop one precomputed table attacking many workspaces at once, and ours is derived per workspace, which achieves that whether or not it is published. Serving it is what lets a script decrypt with nothing but the passphrase and the response body.
No, and the refusal is deliberate rather than incidental. Privacy on the product wins over the virtual-env feature, so the write is rejected with a clear message. If you want a feed for work that lives in a private product, move that environment into a product that is not private and re-check who can reach it.
Yes, if they are an owner or an admin. Exposure emails every owner and admin in the workspace apart from the person who did it, and it emits environment.exposed to your webhook endpoints, with environment.unexposed when it is turned back off. Ordinary members are not emailed, so if you want everyone to see it, the webhook is the surface to wire up.
A JSON object tagged envless.encrypted-export/v1 containing the workspace, product, project and environment slugs, the version id, label, note and variable count, a decryption block with the KDF parameters, salt, cipher and ciphertext format, and the variables themselves with name, encrypted value, type, visibility and required flag. Everything needed to decrypt except the secret. Note what that list implies: only the values are encrypted, so variable names and your version note are readable by anyone who fetches the URL.
Yes, and prefer the version uuid over the label if you do. Labels are derived from a per-environment count at publish time, so v13 describes a position rather than a fixed snapshot. Use latest when you always want current values, and the uuid when you want the same bytes every time.
Yes. Claim the hostname, add the DNS records Envless prints for it, and verify it. Envless provisions the certificate, and once the domain is active the feed is served on your own hostname. The workspace is read from the host, so its slug drops out of the path and the URL you hand out gets shorter: https://vars.acme.com/exposed/watch/website/production/versions/latest instead of the Envless one, which keeps answering alongside it. A custom domain needs a paid plan: Dev, Team and Enterprise each hold one per workspace.
