
How to Manage Secrets Safely with Claude Code
A practical guide to keeping your API keys, tokens, and passwords out of Claude Code's context window using the zero-knowledge inject approach.
GitGuardian's "State of Secrets Sprawl 2026" report, published March 17, 2026, found 28.65 million secrets exposed in public repositories, with AI-assisted commits showing a 3.2% leak rate. That is double the baseline for human-only commits. If you use Claude Code every day, your .env files are more exposed than you think.
A separate study found 24,008 secrets hardcoded in .mcp.json configuration files on public GitHub repos. Developers are leaking not just application secrets but also the keys that connect their AI tools to external services.
This guide shows you how to use secrets in Claude Code without the AI ever seeing them.
Why .env Files Are Dangerous with AI Assistants
When Claude Code runs in your project, it can read any file in the working directory. That includes .env, .env.local, and any other file where you keep API keys. The moment Claude reads your .env to "help you set up the database connection," your Stripe key, your database password, and your AWS credentials are sitting in the conversation context.
From there, secrets can end up in:
- Chat history stored on Anthropic's servers (retained 30 days by default)
- Commit messages or code suggestions that include the actual value
- Terminal output visible in the conversation
- Logs if Claude runs commands that print environment variables
For a deeper dive into this problem, read Why .env files are dangerous with AI agents.
What Zero-Knowledge Inject Means
The core idea: never let the AI see the secret value. Instead of returning sk_live_abc123 to the conversation, write it to a temporary file and tell Claude only the file path.
You ask Claude to deploy using STRIPE_KEY
1. MCP server fetches the encrypted value from the vault
2. Decrypts it server-side
3. Writes it to ~/.securecode/.session/a1b2c3.env
4. Returns to Claude: "STRIPE_KEY injected -> /home/you/.securecode/.session/a1b2c3.env"
5. Claude runs: source /home/you/.securecode/.session/a1b2c3.env && npm run deploy
The value never enters the context window. The chat history shows only the file path. Claude knows the secret exists but cannot read what it contains.
+------------------+ +------------------+ +------------------+
| Claude Code | | SecureCode MCP | | SecureCode |
| (your editor) | | (local server) | | Vault (cloud) |
+--------+---------+ +--------+---------+ +--------+---------+
| | |
| 1. get-secret | |
| name: STRIPE_KEY | |
|------------------------->| |
| | 2. Fetch + decrypt |
| |------------------------->|
| |<-------------------------|
| | |
| | 3. Write to session file |
| | (0600 permissions) |
| | |
| 4. "STRIPE_KEY injected | |
| -> /path/to/file" | |
|<-------------------------| |
| | |
| 5. source /path && deploy| |
| | |
Setting Up SecureCode MCP
SecureCode is a secrets vault built for this workflow. It connects to Claude Code via MCP (Model Context Protocol) and handles the inject flow automatically.
Step 1: Install the MCP server
Add SecureCode to your Claude Code MCP config. Create or edit .mcp.json in your project root:
{
"mcpServers": {
"securecode": {
"command": "npx",
"args": ["-y", "@securecode/mcp-server"],
"env": {
"SECURECODE_API_KEY": "${SECURECODE_API_KEY}"
}
}
}
}
The ${SECURECODE_API_KEY} syntax tells Claude Code to expand the variable from your environment. The actual key value never appears in the config file.
Step 2: Run the onboarding
Open Claude Code in your project and say:
"Set up SecureCode"
The MCP guides you through creating an account, importing your .env file via the web dashboard (so Claude never sees the values), and generating an API key. The key gets saved to .securecoderc, which is automatically added to .gitignore.
Step 3: Use secrets
Once configured, ask Claude to use any secret:
"Deploy this to production using the DATABASE_URL"
Claude calls get-secret through the MCP. The value gets injected to a local file. Claude runs your command with source /path && deploy-command. Done.
How Inject Mode Works Under the Hood
The MCP server creates a session directory at ~/.securecode/.session/ with permissions 0700 (owner only). Each session gets a unique file named with a hash of apiKey + PID, so parallel Claude Code sessions never collide.
The secret file itself is written with 0600 permissions (owner read-write only). Values with single quotes are escaped properly to prevent shell injection. Every new inject overwrites the previous file, keeping the exposure window minimal.
When you are done working, the byebye command locks the session and deletes all injected files from disk. No secret values remain on the filesystem.
Blocking Tips That Protect You
The first time you access a secret in a session, SecureCode returns a blocking tip instead of the value. Claude must acknowledge the tip and call get-secret again. This forces a pause where you see:
"Welcome to SecureCodeHQ! Remember: secret values returned here are injected directly into your workflow. Never paste them in chat messages, commit them to git, or include them in code files."
There is also a blocking warning when you access a production-tagged secret:
"Warning: You are accessing DATABASE_URL which is tagged as PRODUCTION. Make sure this is intentional."
These tips only fire once per session (max 3 tips total), so they do not get annoying.
Adding the SDK for Runtime Access
For your app to read secrets at runtime (not through Claude), install the SDK:
npm install @securecode/sdk
Add one line to your app's entry point:
import { loadEnv } from "@securecode/sdk";
await loadEnv({ override: true });
After this, process.env.DATABASE_URL, process.env.STRIPE_KEY, and every other secret works normally. No code changes needed beyond the setup.
The SDK reads the API key from .securecoderc (created during onboarding, added to .gitignore automatically). For production, set SECURECODE_API_KEY as an environment variable in your hosting platform.
Security Checklist
After setup, run security-check from the MCP to verify everything is locked down:
-
.securecodercexists and is in.gitignore -
.mcp.jsonis in.gitignore(if it contains API keys directly) -
.securecodercis listed inpermissions.denyin your Claude Code settings -
.envand.env.*are listed inpermissions.deny -
CLAUDE.mdinstructs Claude to use MCP for secrets
The permissions.deny setting in Claude Code's settings.json prevents Claude from reading specific files. This is the correct mechanism to block file access. Note: .claudeignore is not an official Claude Code feature. Use permissions.deny instead.
Example settings.json snippet:
{
"permissions": {
"deny": [
"Read(.securecoderc)",
"Read(.env)",
"Read(.env.*)"
]
}
}
The permission system uses three priority levels: deny (highest), ask, and allow (lowest). Anything in deny is blocked regardless of other settings.
The MCP runs this check automatically after SDK setup and fixes any issues it finds.
Claude Code's Built-in Security
Claude Code includes security features worth understanding:
Permission system. Every potentially dangerous action (file writes, bash commands, MCP tool calls) goes through the permission system. The three modes are deny, ask, and allow, evaluated in that priority order. You can lock down sensitive operations so Claude cannot perform them without your approval.
Sandbox. Claude Code runs bash commands inside a restricted sandbox. On Linux, it uses bubblewrap. On macOS, it uses seatbelt. This limits what processes can access, reducing the blast radius if a command tries to read files outside the project.
CVE-2025-55284. In 2025, researchers at Knostic reported a DNS exfiltration vulnerability in Claude Code's bash tool. This was patched, but it is a reminder that sandboxing and permission controls matter. Defense in depth is why you want secrets out of the context window entirely, not just behind file permissions.
What Happens to Your Data
Anthropic retains conversation data for 30 days by default. If you opt into training data sharing, retention extends to 5 years. You can opt out of training data sharing in your Anthropic account settings.
This is exactly why zero-knowledge inject matters: if the secret value never enters the conversation, it is not part of the data Anthropic retains. The chat history only shows "STRIPE_KEY injected" with a file path, not the actual key.
Reveal Mode for Debugging
Sometimes you actually need Claude to see the value, for example when debugging an authentication issue. You can use reveal: true when calling get-secret. This returns the raw value to the conversation, but it is logged as a conscious action in the audit trail. Your team can review who revealed what and when.
Use this sparingly. Every reveal puts the secret value into the conversation context and, by extension, into Anthropic's 30-day retention window.
Further Reading
- Why .env files are dangerous with AI agents covers the problem in depth
- AI agent security: the complete guide covers the broader security landscape
- LLM security: the complete guide explains prompt injection and data exfiltration risks
- How to prevent secrets in git commits covers pre-commit hooks and scanning
- Doppler vs Infisical vs SecureCode compares your vault options
- HashiCorp Vault alternatives for developers if you need something lighter than Vault
- Get started free with 50 secrets, no credit card required