2 March 2026
How to give Claude Code your secrets without exposing them
Claude Code is genuinely useful for real work. Real work involves real credentials. Pasting API keys into the chat context is the obvious answer — and a bad one. Here's the pattern we use.
The problem with secrets in agent context
Claude Code runs in your terminal and has access to your filesystem. When you paste an API key into the conversation, it enters the context window — and from there it can end up in:
- —Conversation history that syncs to Anthropic's infrastructure
- —Tool call logs and debug output
- —Files the agent writes, including accidentally committed code
- —Shell history via commands the agent runs
- —The
CLAUDE.mdfile, if you configure keys there for convenience
None of these are catastrophic on their own, but credentials that sit in long-lived context or config files have a way of accumulating and eventually leaking. The right model is: the agent gets the secret for the duration of the task, and nowhere else.
The pattern: a VoidNote link as a per-session credential bundle
Before starting a Claude Code session that needs credentials, you create a VoidNote containing exactly the environment variables you want to grant — and nothing else. You get a URL. You give Claude the URL. Claude reads it, the note burns. The credentials exist in Claude's memory for the session and nowhere else.
The note is encrypted with AES-256-GCM before it leaves your terminal. The decryption key lives only in the URL fragment — which is never sent to VoidNote's server. If someone intercepts the URL in transit, they still can't read the credentials without knowing the fragment. Add a password and even the URL alone is useless.
Step 1 — you, in your terminal, before opening Claude Code:
$ voidnote create <<EOF
STRIPE_SECRET_KEY=sk_live_abc123...
DATABASE_URL=postgresql://prod-host/myapp
GITHUB_TOKEN=ghp_xyz789...
EOF
https://voidnote.net/n/a1b2c3#secretkey
Step 2 — paste the URL into the Claude Code session:
Here are the credentials for this session:
https://voidnote.net/n/a1b2c3#secretkey
Read the note using the voidnote-sdk and use those env vars
Step 3 — Claude reads and consumes:
import { read } from 'voidnote-sdk';
const env = await read('https://voidnote.net/n/a1b2c3#secretkey');
// Note is now destroyed on the server. URL is dead.
Setting it up in CLAUDE.md
You can bake this pattern into your project's CLAUDE.md so Claude knows what to do without being told each time.
Crucially: you put the pattern in CLAUDE.md, not the keys themselves.
# CLAUDE.md
## Credentials
If you need API keys or environment variables for this project,
ask the user to create a VoidNote at https://voidnote.net and
share the link. Read it using:
import { read } from 'voidnote-sdk';
const env = await read(url);
// Parse as KEY=VALUE lines and inject into process.env
Do not ask for credentials any other way. Do not write them to files.
Do not log them. Use them in memory only for the duration of the task.
Per-project credential bundles
The pattern scales to a multi-project setup. For each project, you maintain a short script that bundles the relevant credentials into a fresh note at the start of each session:
#!/bin/bash
# scripts/claude-session.sh — run before opening Claude Code
voidnote create <<EOF
STRIPE_SECRET_KEY=${STRIPE_SECRET_KEY}
DATABASE_URL=${DATABASE_URL}
DEPLOY_KEY=${DEPLOY_KEY}
EOF
# Prints a fresh URL. Copy it, paste into Claude Code.
# URL is single-use and burns after Claude reads it.
Your real credentials stay in your local shell environment (a password manager, 1Password CLI, direnv, etc.). The VoidNote URL is what the agent sees. The URL is a dead link after one read — even if it ends up in logs, conversation history, or a committed file, it yields nothing.
Multi-view notes for shared sessions
If you're running multiple Claude Code sessions against the same set of credentials — say, a long-running agent loop that restarts — you can create a multi-view note that allows up to N reads before burning:
# Allow up to 5 reads before the note burns
$ voidnote create --views 5 <<EOF
API_KEY=sk_live_...
EOF
After the 5th read, the note is destroyed automatically. You set the limit based on how many sessions you expect to run. Fresh note per deployment cycle keeps the rotation implicit.
Adding a password for high-sensitivity credentials
For credentials where even the URL being leaked is unacceptable, add a password. The note requires both the URL fragment (cryptographic key) and the password before it decrypts:
$ voidnote create --password "morning-session-pw" <<EOF
PROD_DB_PASSWORD=hunter2
EOF
# Tell Claude the URL and the password separately
# URL from chat history + password from separate channel = useless without both
You share the password through a different channel than the URL — Bitwarden Send, SMS, a second message. Compromise of either channel alone yields nothing. This is the pattern for production credentials in high-trust Claude Code sessions.
Why this works cryptographically
The note URL looks like:
https://voidnote.net/n/abc123#def456...
Everything after the # is a URL fragment. Fragments are never transmitted
by HTTP — the browser, curl, fetch, and the voidnote-sdk all follow this rule.
VoidNote's server receives the tokenId (abc123) and returns the encrypted blob.
The SDK derives the AES-256-GCM key from the fragment locally, decrypts, and returns the plaintext.
The server stores: tokenId + encrypted blob + IV. There is no key on the server. Even a full database dump reveals nothing readable. The zero-knowledge guarantee is mathematical, not contractual.
Get set up in five minutes
Install the CLI, create a test note, and verify the pattern works in your Claude Code setup before you use it for real credentials.
# Install the CLI
brew install quantum-encoding/tap/voidnote
# Create a test note
echo "TEST_KEY=hello" | voidnote create
# Install the SDK for Claude to use
npm install voidnote-sdk
# or: pip install voidnote