Sitemap

Rethinking .env Files: A Zero-Trust Storage Model

--

Why I Built EnvCrypt

Secret management always feels simple… right up until you actually have to manage secrets across machines, environments, and CI pipelines.

I’ve built ed-tech platforms, creator tools, backend systems and all of them needed environment variables. In the beginning, it’s easy. Just a .env file. Throw it in .gitignore. Done.

Then it isn’t.

One laptop becomes two. Local becomes staging. Staging becomes production. CI enters the picture. A teammate joins.

If you’ve built anything beyond a weekend project, you’ve probably watched this happen:

“Bro, send me the latest `.env`.”
Rotating an API key locally… and forgetting GitHub Secrets exists.
Commenting out half the `.env` file just to test something.
CI failing because staging and production were technically “the same”… but not really.

Nothing catastrophic happened though. It just felt fragile. Like every deployment depended on remembering one more manual step.

That feeling led to EnvCrypt.

What I Wanted

I didn’t want to build the next enterprise vault. I didn’t want compliance dashboards, policy engines, or a tool that only makes sense once you have 300 engineers and a security team.

I wanted something that:

  • Encrypts secrets before they leave my machine and not after they hit a server.
  • Works when I switch laptops without me scratching my head.
  • Doesn’t require a “did you update GitHub Secrets?” checklist.
  • Is self-hostable because I like owning my infrastructure. :-)

Most importantly, I wanted to stop pretending that plaintext on a server was “good enough.” If secrets have to exist somewhere, they should exist encrypted by default. That’s the idea behind EnvCrypt.

The Core Idea

The idea wasn’t to build a better secret dashboard. It was to remove plaintext from the server entirely.

If a database leaks, if an admin misconfigures something, if infrastructure gets compromised, it shouldn’t matter. The server shouldn’t have anything readable in the first place.

So I made one rule:

Secrets are encrypted on your machine. The server only stores ciphertext. That’s it.

But once you think about teammates, CI, versioning, and revocation.. that simple rule needs structure. That’s where the layered model comes in.

The Layered Key Model

At first, I thought: “Why not just encrypt the .env file for every user?”

That sounds simple. Until I wanted to add or remove someone. Or rotate a key. Or support CI. Re-encrypting everything for every user quickly becomes messy. So instead, EnvCrypt uses layered encryption.

There are three pieces:

  • Data Encryption Key (DEK): Generated fresh for every version. This encrypts the actual .env file.
  • Project Root Key (PRK): A long-lived project key that wraps those DEKs.
  • User Keypairs: These wrap the PRK.

In simple terms: Data is encrypted once and the Access is controlled by wrapping keys.

Creating a Project

When you create a project, a Project Root Key (PRK) is generated locally. This key becomes the root of trust for that project. It doesn’t encrypt your .env directly, it encrypts the per-version keys that do.

But the PRK is never sent to the server in plaintext. Instead, it’s wrapped using ECDHE.

Each user has an X25519 keypair. Using ECDHE, a shared secret is derived between your private key and the other service’s public key. That shared secret is then used to encrypt (wrap) the PRK. Only the wrapped version is stored on the server. To unwrap it, you must derive the same shared secret again using your private key.

Press enter or click to view image in full size
Project Creation Architecture
Project creation logic

Fun note:

ECDHE is the same mechanism used in modern TLS, the protocol securing HTTPS. When your browser connects to a website, they use ECDHE to derive a shared secret for encryption. We use the same idea just for protecting your project keys instead of web.

What Actually Happens on push or pull

When you run ‘envcrypt push’, everything happens locally first. A fresh DEK is generated. Every version gets its own key.

Your .env file is encrypted using AES-256-GCM and that DEK. Your PRK is then unwrapped locally using your private key, and it wraps the DEK.

Get Vijayvenkatj’s stories in your inbox

Join Medium for free to get updates from this writer.

What the server receives is simple:

  • Encrypted `.env`
  • Wrapped DEK
  • Metadata

The server stores an immutable version. If the database leaks, all anyone sees are encrypted blobs.

Press enter or click to view image in full size
Pushing of ENV

Delegation & What It Protects Against

One of the things I cared about was delegation. I didn’t want to re-encrypt everything every time I added someone. And I didn’t want access control to be purely “database policy.” With the layered model, delegation becomes key wrapping.

When you add a teammate:

  • You unwrap the PRK locally.
  • You derive a shared secret using ECDHE with their public key.
  • You wrap the PRK for them.
  • The server stores the wrapped copy.

The data itself doesn’t change. Access is controlled by who can unwrap the PRK.

Press enter or click to view image in full size
Delegation of access

And Yes — It’s Actually Usable

All of this cryptography would be pointless if using it felt like working on a puzzle. So the CLI stays simple.

You can:

  • Register and generate your cryptographic identity.
  • Create projects (which generate root keys locally).
  • push and pull encrypted environments.
  • Diff versions.
  • Roll back to immutable states.
  • Grant and revoke teammates.
  • Audit project events.

And for CI? You can create Service Roles tied to a repository and branch, grant them scoped access, and let your pipeline authenticate using OIDC, no long-lived passwords, no manual secret syncing every time something changes.

It’s still just:

envcrypt push
envcrypt pull

The goal wasn’t to make secret management “more secure but painful.” It was to make it feel normal just without all the pain-points.

Conclusion

I didn’t build EnvCrypt because secret managers are terrible.

I built it because I got tired of treating `.env` files like shared infrastructure without proper guarantees. Encrypting locally just felt like the more honest default.

The server stores ciphertext. Keys stay with clients. CI doesn’t rely on long-lived credentials. That’s it.

If you’ve felt the same quiet friction managing secrets across laptops, environments, and pipelines then give it a try. And if it helps your workflow, star the repo and share it with your team.

GitHub: https://github.com/envcrypts
Website: https://envcrypt.vijayvenkatj.in

Connect With Me

If you enjoyed this write-up or want to follow my work, feel free to reach out or connect with me here:

Always happy to connect, chat about backend systems, Go, or anything tech!

--

--