The goal
Take this:
$ cat .env
STRIPE_KEY=sk_test_51Hxxx
OPENAI_API_KEY=sk-proj-xxx
DATABASE_URL=postgres://dev:pw@localhost/myappTurn it into an encrypted vault, and run the app with the same env vars, without editing a single line of application code. Budget: 60 seconds.
Step 1 — install
curl -sSfL https://tene.sh/install.sh | shThis downloads a single Go binary to /usr/local/bin/tene. No runtime
dependencies.
Step 2 — initialize
cd my-project
tene initYou will be prompted for a master password (twice). tene creates
.tene/vault.db and prints a 12-word BIP-39 recovery mnemonic. Write
the mnemonic down somewhere safe — if you ever lose the password, this
is the only way back.
Under the hood:
- Master password → Argon2id (64 MB memory, 3 iterations) → 256-bit master key.
- Master key → HKDF-SHA256 → encryption key.
- SQLite vault encrypted with XChaCha20-Poly1305 (192-bit nonces, secret name as AAD).
- Key cached in OS keychain (macOS Keychain, Linux libsecret, Windows Credential Vault).
tene init also generates AI-editor rule files (CLAUDE.md,
.cursor/rules/tene.mdc, .windsurfrules, GEMINI.md, AGENTS.md) so
every agent knows to use tene run -- instead of reading env files.
Step 3 — import the .env
tene import .envOutput:
3 secrets imported (encrypted, default)
• STRIPE_KEY
• OPENAI_API_KEY
• DATABASE_URLEach KEY=VALUE line becomes an encrypted record. The values are not written in plaintext anywhere during or after the import — the import parses the file, encrypts each value in memory, writes ciphertext to the vault, and discards the plaintext.
Step 4 — delete the plaintext
rm .envYes, really. This is the whole point. The plaintext file is gone.
If you commit your repo now, there is no env file to accidentally include.

Step 5 — run your app
tene run -- npm starttene run spawns a subshell, sets each vault secret as an environment
variable on that subshell, and executes npm start. Your app reads
process.env.STRIPE_KEY, process.env.OPENAI_API_KEY,
process.env.DATABASE_URL exactly as before.
Total elapsed time so far: about 45 seconds.
Verify it works
tene listOutput:
Active environment: default
3 secrets:
• STRIPE_KEY
• OPENAI_API_KEY
• DATABASE_URLValues are masked. You could run tene get STRIPE_KEY to see the actual
value, but inside an AI coding session prefer tene list — tene get
prints plaintext to stdout which enters the LLM context window.
Different languages, same pattern
tene is language-agnostic. It just sets env vars.
Node.js:
tene run -- npm start # or: tene run -- node app.jsGo:
tene run -- go run ./cmd/serverPython:
tene run -- python app.pyDocker:
tene run -- docker compose upYour app code keeps using process.env.KEY, os.Getenv("KEY"),
os.environ["KEY"]. Nothing changes inside the application.
Environments
The default vault environment is default. For dev / staging / prod
separation:
tene env create staging
tene env create prod
# Store a different DATABASE_URL per env
tene set DATABASE_URL postgres://stag/app --env staging
tene set DATABASE_URL postgres://prod/app --env prod
# Run in a specific env without switching
tene run --env prod -- node server.jsCI
Non-interactive environments (GitHub Actions, CircleCI, etc.):
env:
TENE_MASTER_PASSWORD: ${{ secrets.TENE_MASTER_PASSWORD }}
steps:
- run: tene run --no-keychain -- npm test--no-keychain tells tene to read the password from the environment
rather than the OS keychain. TENE_MASTER_PASSWORD needs to be the same
password you used during tene init on the machine where you generated
the vault.
Summary — the whole migration
# Total: ~60 seconds
curl -sSfL https://tene.sh/install.sh | sh # 15s
tene init # 15s (master password + mnemonic)
tene import .env # 5s
rm .env # 1s
tene run -- npm start # 5s startupYour code did not change. Your build did not change. The plaintext is gone from disk. AI coding agents cannot read what is no longer there.
Related: Your .env is not a secret — why this matters in the AI-agent era.