← All posts

How to Store Secrets for Claude Code (Without Putting Them in .env)

The standard .env approach exposes your API keys to Claude's context window. Here's the correct way to store and use secrets with Claude Code.

If you’re using Claude Code and wondering where to store your API keys, database passwords, and other secrets — this is the page you need. The short answer: not in .env. Here’s the right approach and how to set it up.

Disclosure: We built OpaqueVault to solve this problem. This guide uses OpaqueVault as the specific solution.

The .env problem is covered in depth in Why your AI coding assistant is a secret leak waiting to happen. Short version: Claude Code is a filesystem agent running with your full user permissions. During debugging and development, it reads .env files — often without asking, as a natural part of understanding your project. When that happens, secrets enter its context window and can persist in conversation history. There is no documented per-message redaction of API keys or credentials before they reach the model.

The fix requires a different storage layer: one that Claude can use but cannot read. OpaqueVault is a local-first vault with optional cloud sync — secrets are encrypted on your machine before anything leaves it, and the remote server stores only ciphertext it cannot decrypt. The architecture and the local process (ov mcp serve) are what matter for the security guarantee; the cloud component is just durable encrypted storage.

The right approach: MCP vault injection

An MCP vault manager sits between Claude and your credentials. Claude asks the vault to run commands on its behalf. The vault decrypts secrets locally and injects them into the subprocess environment. Claude gets the command output. The secret value never enters Claude’s context.

OpaqueVault is built specifically for this pattern. Here’s how to set it up.

Step-by-step: storing secrets for Claude Code

1. Install OpaqueVault

Terminal window
brew install opaquevault-brew/tap/ov

On Linux or without Homebrew:

Terminal window
curl -fsSL https://install.opaquevault.com | sh

2. Initialize your vault

Terminal window
ov vault init

You’ll be prompted to set a master password. This is the only password you need to remember. It derives your Key Encryption Key (KEK) via Argon2id — the KEK never leaves your machine. Each secret gets its own randomly generated Data Encryption Key (DEK), encrypted under your KEK. The server stores only the encrypted DEK and encrypted secret value — it has no way to decrypt either. See the security model for full details including memory handling and session behavior.

3. Store your secrets

Use the app/environment/name format:

Terminal window
# Database
ov secret create my-app/development/DATABASE_URL
# Stripe (secret key only — publishable keys are safe to put in source code)
ov secret create my-app/development/STRIPE_SECRET_KEY
# OpenAI (if your app calls it directly)
ov secret create my-app/development/OPENAI_API_KEY
# Any other service
ov secret create my-app/development/SENDGRID_API_KEY

Each command prompts for the value. The value is encrypted client-side before anything is sent to the server.

4. Configure OpaqueVault as an MCP server

Open your Claude Code MCP config at ~/.claude/claude_desktop_config.json. On Claude Desktop for macOS the path is ~/Library/Application Support/Claude/claude_desktop_config.json — on Linux it’s ~/.config/claude/claude_desktop_config.json. For other MCP-compatible IDEs, check their documentation.

Add:

{
"mcpServers": {
"opaquevault": {
"command": "ov",
"args": ["mcp", "serve"]
}
}
}

Restart Claude Code after saving.

5. Set project context with .ov.yaml

In your project root, create .ov.yaml:

app: my-app
environment: development

This tells OpaqueVault which app/environment to use when Claude calls vault tools in this directory. You don’t have to specify it on every call.

6. Unlock your vault when you start working

Terminal window
ov vault unlock

This starts the local ov mcp serve process and holds your KEK in memory for the session. You’ll need to unlock once per session (or configure auto-unlock for convenience).

7. Add OpaqueVault context to your CLAUDE.md

The more reliable way to direct Claude’s behavior isn’t per-session instructions — it’s your project’s CLAUDE.md, which Claude reads at every session start. Add:

## Secret management
This project uses OpaqueVault for secrets.
- Use vault_run to run commands that need credentials
- Use vault_list_secrets to see what's available
- Never read .env files for credentials — use vault_run instead

With this in place, Claude applies the vault_run pattern automatically without you needing to repeat it each session. Per-session instructions (“use vault_run for this command”) work fine too, but CLAUDE.md is the durable version.

8. What happens if the vault isn’t unlocked

If ov mcp serve isn’t running or the session has expired, vault_run fails with a clear error:

Error: vault session not active — run 'ov vault unlock' to start a session

Claude will see this error and tell you to unlock. It won’t fall back to reading .env or prompting you for credentials directly. The failure mode is explicit, not silent.

Your .env file is now either empty or contains only non-sensitive values like:

Terminal window
# .env — safe to have Claude read this
NODE_ENV=development
LOG_LEVEL=debug
PORT=3000
# No secrets here

Organizing secrets across environments

One of OpaqueVault’s strengths is the app/environment/name hierarchy. It maps directly to how real projects are structured:

my-app/
├── development/
│ ├── DATABASE_URL → local postgres
│ ├── STRIPE_SECRET_KEY → Stripe test secret key
│ └── OPENAI_API_KEY → dev key with spending limits
├── staging/
│ ├── DATABASE_URL → staging RDS
│ ├── STRIPE_SECRET_KEY → Stripe test secret key
│ └── OPENAI_API_KEY → staging key
└── production/
├── DATABASE_URL → production RDS
├── STRIPE_SECRET_KEY → Stripe live secret key
└── OPENAI_API_KEY → production key

When you switch from development to staging work, update .ov.yaml:

app: my-app
environment: staging

vault_run now injects staging secrets automatically.

Listing and auditing what you’ve stored

Terminal window
# List all secrets (names only, no values)
ov secret list my-app/development
# Output:
# my-app/development/DATABASE_URL
# my-app/development/STRIPE_SECRET_KEY
# my-app/development/OPENAI_API_KEY
# my-app/development/SENDGRID_API_KEY

Claude can also call vault_list_secrets to see what’s available. This returns names only — never values.

What about sharing secrets with teammates?

OpaqueVault is currently designed for individual developer workflows. Each developer has their own vault, encrypted with their own master password. The namespace structure (app/environment/name) is defined in .ov.yaml which lives in version control — so your team shares the structure automatically, but each developer manages their own credential values.

For distributing the actual secret values to teammates, use a separate secure channel — 1Password shared vaults, an encrypted note, or your organization’s existing secrets distribution process. Team vaults with shared KEK management are on the roadmap.

Migrating from .env

If you have an existing .env file, use ov secret import — it handles quoted values, export prefixes, comments, and multi-line values correctly:

Terminal window
# Preview what will be imported (no writes)
ov secret import .env --dry-run
# Import with a namespace prefix
ov secret import .env --prefix my-app/development/
# Verify what landed
ov secret list my-app/development

After import, remove the secret values from .env and leave only non-sensitive configuration (ports, log levels, feature flags).

Summary

Don’t do thisDo this instead
Store API keys in .envStore in ov secret create app/env/NAME
Let Claude read your secretsConfigure OpaqueVault as MCP server
Run authenticated commands directlyUse vault_run for subprocess injection
One flat namespaceUse app/environment/name hierarchy

OpaqueVault is a zero-knowledge MCP secret manager for Claude Code and other AI coding agents. Get started free →

Related: How to keep API keys secure with Claude Code · MCP Secret Manager — How OpaqueVault Works

Zero-knowledge secrets for AI agents

Keep credentials out of Claude's context window.

OpaqueVault encrypts secrets client-side and injects them into subprocesses — your AI agent never sees the plaintext value.

Get started free → ← More posts