Vibe Coding5 min readby agent-kay

Claude Code + API keys: the safe workflow

A practical pattern for using Claude Code with real API keys without leaking them into the context window. Covers CLAUDE.md auto-generation, 'tene run --' subshell, and concrete Stripe / OpenAI examples.

The pattern in one paragraph

Run tene init once per project. It makes a local encrypted vault and writes a CLAUDE.md file that teaches Claude Code how to handle secrets. Start Claude Code with tene run -- claude so the agent's subshell has the env vars but the plaintext never hits disk. Use tene set to add a new secret, tene list to see what is there, and never tene get KEY inside Claude Code's terminal — that prints the value.

Claude Code is launched through tene run. The user asks Claude to read the STRIPE_KEY value. Claude declines: the rule file tells it to use tene list and tene run, not to print plaintext.
Claude Code, launched through `tene run -- claude`, declines to print the STRIPE_KEY value. The CLAUDE.md rule makes the safe pattern the default.

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. The plaintext lands in the model's context window. Every chat with the agent — tool_result blocks, session summaries, auto-generated commit messages — may carry those values.

You can tell one session not to read .env. That instruction does not carry to the next session. Or to another machine. Or to CI. 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 init

tene init asks for a master password, derives a master key with Argon2id, and creates .tene/vault.db — an empty SQLite file encrypted with XChaCha20-Poly1305. It also adds .tene/ to your .gitignore and writes 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 — use tene run -- <command> to inject them.
  • Prefer tene list over tene get KEY in AI context.
  • Read secrets via process.env.KEY_NAME in your code.

2. Import existing secrets

If you already have a .env:

tene import .env
rm .env

tene import reads the file line by line and encrypts each value into the vault. The plaintext file is no longer needed.

3. Launch Claude Code through tene

tene run -- claude

This opens a subshell, decrypts the vault in memory, sets each secret as an env var on the subshell, and starts claude inside it. Code in the subshell reads process.env.STRIPE_KEY or os.getenv("OPENAI_API_KEY") exactly as before. The file on disk stays ciphertext.

4. Work normally

Inside Claude Code, ask the agent to debug, run tests, refactor — the usual. When the agent's code needs a secret, it will use process.env.* just like your handwritten 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, ask 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-xxxxx

You 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 context

With 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_51Hxxxxx

Example: 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 pollution

Common mistakes

Running tene get OPENAI_API_KEY inside Claude Code. That prints the plaintext to stdout, which Claude reads. The CLAUDE.md rule says don't. 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 belongs on your machine, not in the repo.

Writing values in code comments "for reference". Even redacted notes leak in cleartext. Keep the vault as the only source.

Reusing your account password as the master password. The master password is for the vault only. Treat it like a recovery phrase.

Summary

  • tene init plus the generated CLAUDE.md = a structural rule that survives across sessions.
  • tene run -- claude = the only launch command you need.
  • Claude Code works as usual; 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.

FAQ

Does Claude Code really read .env?

Yes, by default. Claude Code indexes project files to build context for reasoning. That includes .env unless you explicitly exclude it via the project's CLAUDE.md or similar mechanism.

What does 'tene init' change in my Claude Code project?

It creates a CLAUDE.md at the project root that tells the agent to use 'tene run --' and 'tene list' instead of reading env files, and to avoid 'tene get KEY' in AI context since that prints plaintext to stdout.

Can I share the vault with a teammate using Claude Code?

The free CLI is strictly local. For team sync there is an optional Pro plan that uses client-side encryption — the server only sees ciphertext. Each teammate gets the vault after 'tene pull' + master password verification.