The pattern in one paragraph
Run tene init once per project. It creates a local encrypted vault and
generates a CLAUDE.md file that teaches Claude Code how to work with
secrets. Launch Claude Code through tene run -- claude so the agent's
subshell has the env vars but the plaintext never hits disk. Use
tene set when you need a new secret, tene list when you want to see
what is available, and never tene get KEY in Claude Code's terminal
because that prints the value.

Why the default Claude Code setup is unsafe
Out of the box, Claude Code reads project files as part of its context.
If your project has a .env at the root, the agent can read it, and the
plaintext ends up in the model's context window. Every conversation with
the agent — including tool_result blocks, session summaries, and
auto-generated commit messages — potentially contains those values.
You can tell a single session to avoid .env, but that instruction does
not carry across sessions or machines. The fix has to be structural: the
plaintext must not exist on disk.
Step-by-step safe setup
1. Install tene and initialize a vault
curl -sSfL https://tene.sh/install.sh | sh
cd my-project
tene inittene init prompts for a master password, derives a master key via
Argon2id, and creates .tene/vault.db — an empty SQLite file encrypted
with XChaCha20-Poly1305. It also appends .tene/ to your .gitignore
and creates a set of AI-editor rule files: CLAUDE.md, .cursor/rules/tene.mdc,
.windsurfrules, GEMINI.md, AGENTS.md.
Each rule file tells the local agent:
- Never hardcode secrets in source.
- Never create
.env— usetene run -- <command>to inject them. - Prefer
tene listovertene get KEYin AI context. - Access secrets via
process.env.KEY_NAMEin your code.
2. Import existing secrets
If you already have an .env:
tene import .env
rm .envtene import parses the file line by line and encrypts each value into
the vault. The original plaintext file is no longer needed.
3. Launch Claude Code through tene
tene run -- claudeThis spawns a subshell, decrypts the vault in memory, sets every secret
as an environment variable on the subshell, and then starts claude
inside it. Your code inside the subshell can read process.env.STRIPE_KEY
or os.getenv("OPENAI_API_KEY") exactly as before. The file on disk
remains ciphertext.
4. Work normally
Inside Claude Code, you can ask the agent to debug, run tests, refactor,
whatever. When the agent's generated code needs a secret, it will use
process.env.* just like your human-written code does. It will not see
the raw values — only the fact that certain env vars exist.
When you need to add a new secret, tell Claude Code something like: "We need a new ANTHROPIC_API_KEY — can you remind me to store it in tene?". The agent will produce a command like:
tene set ANTHROPIC_API_KEY sk-ant-api03-xxxxxYou run that yourself. The agent never types the raw value.
Example: adding Stripe
Before tene:
1. Open .env in editor
2. Paste STRIPE_KEY=sk_test_...
3. Ask Claude Code to wire up a webhook handler
4. Claude Code reads .env, plaintext goes into contextWith tene:
tene set STRIPE_KEY sk_test_51Hxxxxx
tene run -- claude
# Inside Claude: "wire up a /webhooks/stripe handler"
# Claude generates code using process.env.STRIPE_KEY
# The actual value is injected at runtime; Claude never sees sk_test_51HxxxxxExample: rotating OpenAI keys
# Old key is at OPENAI_API_KEY
tene set OPENAI_API_KEY sk-proj-newvalue-xxxxx --overwrite
# Restart whatever is running
# No file edit, no commit, no Claude context pollutionCommon mistakes
Running tene get OPENAI_API_KEY inside Claude Code. That prints the
plaintext value to stdout, which Claude reads. The CLAUDE.md rule says
don't do this — prefer tene list (names only) to check whether the key
exists.
Committing .tene/vault.db. Don't. tene init already adds it to
.gitignore. The vault is meant to live on your machine, not in the
repo.
Storing values in code comments "for reference". Even redacted references leak in cleartext. Keep the vault the only source.
Using the same master password as your account password. The master password is specifically for the vault. Treat it like a recovery phrase.
Summary
tene init+ generated CLAUDE.md = structural rule that survives across sessions.tene run -- claude= the only launch command you need.- Claude Code works normally; it just never sees plaintext values.
- Rotate by
tene set KEY new-value --overwrite; no code changes.
Deep dive on the threat model: Your .env is not a secret. Moving from dotenv-vault: dotenv-vault alternatives.