EnvRune
Open-source CLI that gets secrets out of .env files
Key metrics

Context
Every project has a .env: a plain-text file sitting next to the code, copied between machines, showing up in screenshots and logs, and the first file an AI coding agent reads. EnvRune keeps the same workflow — a name for each variable, a value per environment — and moves the values into an encrypted vault that only hands them to the processes you start.
The challenge
A security tool that gets in the way gets abandoned: if running the project becomes harder than it was with a .env, the team goes back to the .env. Each protection had to cost almost nothing day to day and still keep the value out of the repository, the terminal, the logs and an AI agent's context. The next step is harder still: sharing secrets with a team through a server without having to trust it.
The architecture
I built a Go CLI on my own, shipped as a single binary. Values live in a local vault encrypted with XChaCha20-Poly1305, its key protected by the master password through Argon2id; the project keeps only names, in a versionable envrune.yml; and envrune run injects the variables straight into the process and masks its output. For teams, EnvRune Cloud is optional and end-to-end encrypted: the server stores ciphertext and names and, by default, cannot read a single value.
How the code is organised
A Go module organised into 23 internal packages by responsibility — among them vault, crypto, runner, redact, agent, keychain, team, cloud, cloudcrypto and sealproxy — with about 290 tests. EnvRune Cloud is a Next.js app on Supabase: Row Level Security decides who may fetch ciphertext, and encryption decides who can read it. CI runs the unit suite and the integration suite, against the real binary, on Linux, macOS and Windows; releases ship through GoReleaser and GitHub Actions as installers for all three systems, plus Homebrew, Scoop and a signed apt repository.
Architecture flow
Secret value
Encrypted vault
- XChaCha20-Poly1305
- Argon2id
Named reference
- shop.stripe.test-key
envrune.yml
- Names only, versionable
Process
- Variables injected
- Masked output
Engineering decisions
Masking that survives streaming
Injected values the process prints come out as ****, including their URL-encoded and base64 forms. Because a value can arrive split across two chunks of output, the filter holds back only the tail that could still be the start of a secret — and in a terminal the process runs inside a pseudo-terminal, so colours, progress bars and prompts keep working.
AI agents can run, but not read
envrune mcp is an MCP server for Claude Code, Cursor and Copilot: the agent runs the tests or starts the dev server with the secrets injected and gets masked output back. No tool returns a value, and only the commands named in envrune.yml can be run — arbitrary commands are off by default.
A server that cannot read or grant itself access
In EnvRune Cloud, each environment's key is encrypted to every device with age (X25519), and every public key is reached through a chain of Ed25519 signatures ending at roots pinned by the team itself. A compromised server can refuse service or see who fetched what; it cannot read a value, or make a client encrypt to a key no admin signed.
Leaving the team means losing future access
Removing a member creates new keys for every environment they used and re-encrypts the current values; a guided rotation then lists each secret that person actually fetched, until someone replaces the value or decides to keep it. Fetches, writes and access changes land in an append-only audit log chained with SHA-256, which the CLI exports and verifies offline.
The declared exception: use without seeing
A secret marked sensitive never reaches the developer's machine: the program gets a placeholder, and a loopback proxy — with a certificate authority that exists only in memory, for that one command — detours its requests through the server, which puts the real value in only for the allowed hosts. It is the one case where the server can read a value: chosen per secret, after a warning, and documented as exactly that.
Limits written down, not hidden
Every protection ships with what it does not cover: masking prevents accidents, not a program that wants to leak; the consumer role stops carelessness, not extraction; and a compromised server still sees metadata. In a security tool, promising less and delivering it is part of the design.