Skip to content
Back to projects
Systems & Architecture
Published
2026

EnvRune

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

Key metrics

Open source (Apache-2.0)
Windows, macOS & Linux
End-to-end encrypted
Sole engineer
EnvRune — Open-source CLI that gets secrets out of .env files

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

  1. Secret value

  2. Encrypted vault

    • XChaCha20-Poly1305
    • Argon2id
  3. Named reference

    • shop.stripe.test-key
  4. envrune.yml

    • Names only, versionable
  5. 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.

Full stack

Golang
Argon2id
XChaCha20-Poly1305
MCP
Next.js 16
Supabase
PostgreSQL
Row Level Security
age (X25519)
Ed25519
GoReleaser
GitHub Actions