Skip to content
xecret
Sign in

Features

Encrypted environment variables, injected by a CLI, logged on every read

xecret stores your environment variables once, encrypted per environment, and hands them to whatever you run — locally, in CI, in production. This is the whole feature set, including the limits.

Feature set

What xecret does with your environment variables

Nine of them, each documented in full. If a claim on this page is not in the documentation, treat it as a bug and tell us.

A key for every environment

Each environment is created with its own data key, written in the same database transaction as the environment itself — there is no moment where one exists without the other. That key is wrapped under an organisation key, which is wrapped under a root key. Ciphertext copied out of staging cannot be read with production's key.

Key hierarchy

root → organisation → environment → version

xecret run, instead of a file on disk

xecret run -- npm run dev fetches the environment, injects it into the child process, forwards signals, and exits with the child's exit code. Secrets never touch disk, argv or stdout. Argv is the one people forget: anything passed there is readable by every other process on the machine.

Command

$ xecret run -- npm run dev

Import that parses in your browser

.env, JSON, YAML and export KEY=value scripts are parsed client-side, and only the resulting plan is sent — so a pasted blob of production credentials never becomes a request body. The dry run is produced by the same planning code the real import runs, which is why the preview cannot disagree with the outcome.

Command

$ xecret import .env --dry-run

An append-only audit log

Every mutation, every decryption and every denial is recorded; a log of what succeeded cannot show an attack in progress. Append-only is enforced by database grants rather than by convention, so the application's own credentials cannot alter it. No record can hold a secret value — the metadata type is a fixed allowlist of field names with no catch-all.

Audit events

secret.read · secret.revealed · access.denied

Service tokens CI can hold

A service token is scoped to one organisation, one project and one environment, and it acts as itself rather than as the person who minted it. Two separate columns hold the two kinds of actor, with a database constraint requiring exactly one to be set. Putting somebody's name on a write they did not make is worse than no attribution at all.

Environment

XECRET_TOKEN=xst_live_…

Roles, and production denied by default

Four roles — viewer, developer, admin, owner — decide what class of action exists for you, and per-project or per-environment grants decide where. Developers and viewers get no production access until somebody who manages members grants it explicitly, and that grant lands in the audit log with a name attached. The safe state is the one that costs nothing to maintain.

Access rule

developer → production = none

An offline cache with one rule

The CLI keeps an AES-256-GCM copy of the last environment it fetched, with the key in your OS keychain and the exact scope bound into the ciphertext, so a cache file cannot be renamed from staging to production and decrypted. It answers when the API cannot — DNS failure, timeout, 5xx — and never when the API will not. A 401 is a decision, usually a revocation, and a local file is not allowed to soften it.

Command

$ xecret run --offline -- npm run dev

Production marked three ways

A production environment carries a reserved colour, an uppercase label and diagonal hazard hatching. Three signals rather than one, because the person about to paste a value into the wrong environment may be colour blind, on a monitor with a failing backlight, or reading a greyscale print-out. Nothing else in the interface may use that colour.

Command

$ xecret run --environment production -- ./verify.sh

Self-hosting, with the friction stated

The server is AGPL-3.0 and the CLI is MIT, so the binary that ships inside your Docker images carries a licence your legal team has already approved. The self-hosting guide lists the real dependencies, including the WorkOS account needed for identity, which is genuine friction and is described that way. No feature is held back from the self-hosted build.

Command

$ npx wrangler deploy

Up close

Three parts worth seeing before you migrate

The command that replaces the file, the log that records every read, and the reason production never looks like its neighbours.

Delete the file. Keep the values.

A .env file is a copy, and copies multiply. Every laptop that has run the application has one, every CI job has one, and none of them agree about which line is current.

xecret run resolves the environment at the moment the process starts and injects it into the child. Everything after -- is your command, unchanged — there is no SDK to install and no import to write, because the application still reads process.env exactly as it did before.

Set it up in a minuteEvery CLI command

Illustration: a terminal in which the .env file is printed, deleted, and the application is started again through xecret run.
one command, no file
$ cat .envDATABASE_URL=postgres://…STRIPE_SECRET_KEY=sk_live_…$ rm .env$ xecret run -- npm run dev> storefront@0.4.2 devready — http://localhost:3000

Every decryption leaves a row you can read

Mutations, decryptions and denials, all recorded. Denials are the half most tools leave out, and they are the half that shows an attack in progress rather than an attack that already worked.

secret.read and secret.revealed are the two rows that mean a plaintext left the server. A 200-secret pull writes one secret.read row with a count, not 200 rows — an audit table that grows with every value read becomes a denial-of-service surface aimed at itself.

Every event the audit log records

Illustration: four rows of an audit log, one of them a denied read of production.
Audit log · storefrontappend-only
  • secret.read

    deploy-bot · service token · 24 secrets

    success09:41
  • secret.revealed

    dev@acme.dev · session · STRIPE_SECRET_KEY

    success09:12
  • secret.read

    alex@acme.dev · CLI token · production

    denied08:57
  • environment.created

    sam@acme.dev · session · preview-412

    success08:30

Production does not look like anything else

One environment per project may be marked production, and marking it changes two things at once. Developers and viewers lose access to it until somebody who manages members grants that access by name. And it stops resembling its neighbours.

The marking is a reserved colour, an uppercase label and diagonal hazard hatching — three signals, so it survives colour blindness, a greyscale print-out and a failing external monitor. The single-signal version of this is how a value ends up in the wrong environment at two in the morning.

Roles and per-environment access

Illustration: three environments in one project, with the production row marked by an uppercase label and hazard hatching, and developer access set to none.
storefront · environments
  • development

    24 secrets · developer access: write

  • staging

    26 secrets · developer access: write

  • production

    31 secrets · developer access: none

    Production

The honest comparison

xecret against the .env file in your repository

Seven axes, including the last one, where the file you already have is the better answer and we are not going to pretend otherwise.

Where the value lives
A .env file in the repoOn every laptop that has ever run the application, in a chat thread, and in whichever CI provider was configured last. Usually all three, usually disagreeing.
xecretOnce, encrypted under a key belonging to that environment alone, fetched by the CLI at the moment a process starts.
Onboarding somebody
A .env file in the repoSomebody sends the file. Nobody is certain it is current, and there is now one more copy of production in a chat history that will outlive the job.
xecretxecret login, then xecret run. They get the environments their role and grants allow, and nothing else.
Rotating a leaked key
A .env file in the repoChange it, then find every laptop, CI job, Dockerfile and deploy script holding a copy. You will miss one.
xecretChange it once. The next run of every process picks up the new value, and the old one becomes a previous version in the history.
Knowing who read it
A .env file in the repoYou cannot. A file that has been copied leaves no record of the copy.
xecretOne row per read, with the actor, the outcome and a count — including the reads that were refused.
Somebody leaves
A .env file in the repoRotate everything, on the assumption they kept a copy. You are right to assume it.
xecretRemove them and every device they own stops working at once. Then rotate what actually matters, rather than all of it.
Protecting production
A .env file in the repoFour characters in a filename separate .env.production from .env.
xecretProduction is deny-by-default for developers and viewers, and marked three ways in the interface so the marking survives colour blindness.
What the provider can read
A .env file in the repoNothing. The file never leaves machines you control. This is the one row where the file you already have wins, and it wins outright.
xecretxecret can technically decrypt your secrets, and writes an audit row every time it does. That trade is what pays for the six rows above.

What xecret can and cannot see

xecret uses server-side envelope encryption. Every environment has its own data key, itself encrypted under a root key we hold; values are decrypted only inside a single request handler, and every decryption is written to an append-only audit log. That means we can technically decrypt your secrets — the same model Doppler uses, and the reason team sharing, CI tokens and browser-side import work without a key-exchange ceremony. If you need a provider that cannot read your secrets even in principle, you need a zero-knowledge product, and we would rather you learned that here than after migrating.

The key hierarchy: four layers, from the root key down to a single encrypted secret version, with where each one is stored.
Key hierarchyAES-256-GCM
  1. Root key

    Cloudflare Secrets Store, bound to the Worker. Never in the database.

  2. Organisation key

    In the database, encrypted under the root key.

  3. Environment key

    In the database, encrypted under its organisation's key.

  4. Secret version

    In the database, encrypted under its environment's key.

The threat model, the root-key ceremony and every architecture decision are documents in the repository rather than blog-post promises. We write the reasoning up as we go on the engineering blog, and who is building xecret is a short page rather than a stock photograph.

Questions

What developers ask before they migrate

The six that come up every time, answered where you can see them rather than in a document you have to request.

Is xecret zero-knowledge?

No, and we will not describe it that way. xecret uses server-side envelope encryption: every environment has its own data key, that key is encrypted under a root key we hold, and values are decrypted inside a single request handler with every decryption written to an append-only audit log. So xecret can technically decrypt your secrets. It is the same model Doppler uses, and it is what makes team sharing, CI tokens and browser-side .env import work without a key-exchange ceremony. If you need a provider that cannot read your secrets even in principle, you need a zero-knowledge product.

Do I still need a .env file?

No. xecret run -- npm run dev fetches the environment, injects it into the child process, forwards signals and exits with the child's exit code, so nothing is written to disk. Your application keeps reading process.env exactly as it does today; there is no SDK to add and no import to write. The one file that stays in the repository is .xecret.yaml, which holds project and environment slugs only, never values, and is meant to be committed.

What happens when the xecret API is unreachable?

The CLI falls back to an encrypted local cache of the last environment it fetched, and says so loudly on stderr rather than stdout, so it never contaminates a pipe. The rule is that the cache answers when the API cannot — a DNS failure, a timeout, a 5xx — and never when the API will not. A 401, 403 or 404 is a decision, revocation above all, and softening a revocation with a local file would make the revoke button a suggestion.

How does this work in CI?

Create a service token scoped to one organisation, one project and one environment, store it in your CI provider's secret store as XECRET_TOKEN, and wrap the build step in xecret run. The token acts as itself, so every action it takes is attributed to that token by name rather than to the person who created it, and revoking it stops that one pipeline without touching anybody's account. Service tokens never write an offline cache.

Can I self-host xecret?

Yes, and no feature is held back. The server is AGPL-3.0 and the CLI is MIT. It runs on Cloudflare Workers with PostgreSQL and Cloudflare Secrets Store for the root key, plus a WorkOS account for identity — that last dependency is real friction, and the self-hosting guide says so instead of burying it. One warning worth reading before you start: if you lose the root key, every secret is permanently unrecoverable, including by us.

What does xecret cost?

While xecret is pre-alpha no card is collected, and each plan’s limits apply now. The free tier is $0 forever for 1 organisation, 5 projects, 3 members, 3 environments per project and 7 days of audit history. Pro is $5 per member per month billed yearly — $8 month to month — for 3 organisations, 25 projects, 10 environments per project, unlimited members and 30 days of audit history. Team is $12 billed yearly, or $19 month to month, for 10 organisations, 100 projects, 25 environments per project, 180 days of history, roles and per-environment access. Enterprise is custom and drops the ceilings; SAML single sign-on is a $199 per connection per month add-on from Team, and is included with Enterprise; directory sync (SCIM) is included with Enterprise and not sold below it. Neither is built yet. Self-hosting is free, always.

The rest — SSO, data residency, what happens to your secrets if we shut down — are on the frequently asked questions page, and the numbers behind that last answer are on the pricing page.

Move one environment and see.

Create a project, drag in the .env file you already have, and run your app through the CLI. Nothing here needs a card, and the import parses in your browser before anything is sent.

No credit card · AGPL-3.0 server · MIT CLI · Self-host it for free