Encrypted secrets, no infrastructure, no plaintext on disk.
notenv replaces .env files: secrets are encrypted on your machine with
age, kept as ciphertext in a local vault or on storage
you already own (Backblaze B2, S3, Google Drive, SFTP, WebDAV, anything
rclone speaks), and decrypted only into the environment of the command you
run. Plaintext never touches disk.
A .env file is plaintext: everything on your machine can read it, and sharing it means pasting it
somewhere it will outlive. notenv removes the file instead of guarding it.
- Nothing on disk to leak. A test runner, a package's postinstall script, or a coding agent in your checkout cannot read a secret that exists only inside the process you ran, only while it runs.
- You hold the key, not a provider. Secrets are age-encrypted locally; storage only ever sees ciphertext, so it can live anywhere: a local vault, the NAS under your desk, B2, S3, Drive, dozens more.
- Nothing to operate. No server, no SaaS, no cloud account to stand up and babysit.
notenv setupis one passphrase and zero accounts. - Joining and leaving are one command. Onboard a teammate with a string over chat; their first run swaps it for a credential only they know. Offboarding re-encrypts everything, so leaving actually revokes.
- Built for agents and CI.
notenv runlets a process use a secret without seeing it, and your CI secret store holds one credential instead of thirty.
Not this if you want a platform: there is no web console or SSO, and access is scoped per vault, not per secret (everyone in a vault can read that vault). If a platform team already runs Vault, keep Vault.
uv tool install notenv # also: pipx install notenv, or pip
go install github.com/DvGils/notenv/cmd/notenv@latest # with GoOr download a prebuilt binary for Linux, macOS, or Windows (amd64 / arm64) from
Releases and put notenv on your PATH. Releases are
reproducible, signed with cosign (keyless), and carry SLSA
build provenance; the
installation guide shows how to
verify a download.
notenv setup # 1. set up this machine once (local vault by default)
cd my-project && notenv init # 2. declare the project (writes notenv.toml, which you commit)
notenv import .env && rm .env # 3. import existing secrets (or `notenv set KEY` one at a time)
notenv run -- npm run dev # 4. run anything with the secrets injectedThat is the whole loop. notenv is a process wrapper, so it works with any language that reads
environment variables. On a new machine: git clone, then notenv setup with your escrowed
passphrase, and you are ready. When syncing across machines starts to matter, notenv vault copy
moves the same vault to a cloud remote in one command.
Full docs live at dvgils.github.io/notenv:
- Quick start and installation
- Guides: teams and keys, cloud remotes, CI, AI agents
- Reference: commands, configuration
- Concepts: how it works, keys and slots
- Security: threat model
notenv run -- cmd
|
|-- fetch ciphertext <- rclone <- your B2 / S3 / Drive / ...
|-- unlock the master key (from your passphrase; cached after first use)
|-- decrypt secrets in memory
|-- build the child environment from notenv.toml
|-- exec cmd, stream its I/O, exit with its code
nothing written to disk
A random master key encrypts every secret and never exists in plaintext at rest: a small header holds it wrapped under one or more key slots (a person's passphrase or a machine's age public key), the way LUKS and restic do it. The header is authenticated and version-pinned, so a party who can write your storage but holds no key cannot tamper with it or roll it back undetected. Full detail in Concepts.
A .env on disk eventually lands in a coding agent's context. notenv gives the agent a verb that
separates using a credential from knowing it:
notenv run -- cmdinjects secrets into the child only; the value never appears in what the model reads, and captured output is masked.notenv listshows which secrets exist and what they are for, never their values.
There is also an MCP server (notenv mcp) and an installable agent skill (skills/notenv/). See the
AI agents guide.
SOPS + age nail client-side encryption and process injection but leave storage and onboarding to you; Teller brokers cloud secret managers, where the provider holds your secrets. notenv is client-side encryption with the storage and the onboarding built in, and no provider in the loop.
| notenv | teller | SOPS + age (DIY) | |
|---|---|---|---|
| Plaintext on disk | never | never | never |
| You hold the key | yes | no (provider does) | yes |
| Storage backends | local vault or any rclone remote | per-provider code | you wire it up |
| Infrastructure to run | none | none (uses your cloud) | none |
| One-command onboarding | yes | partial | no |
At rest, anywhere, only age ciphertext exists; it is useless without your key, which lives in your password manager and never on the storage backend. The threat model covers what notenv defends, against whom, and the explicit non-goals. To report a vulnerability, see SECURITY.md.
git clone https://github.com/DvGils/notenv
cd notenv
make build # compile ./notenv
make test # run the test suite
make install # install into $(go env GOPATH)/binReleases are produced with GoReleaser; make snapshot builds the full set
of release artifacts locally without publishing.
Actively developed and being tested. See the roadmap for what works today, what is planned, and the non-goals.