claude-secrets
Encrypted token store for Claude Code sessions, providing MCP tools for secure secret management with macOS Keychain integration, per-project allowlists, and native dialog input.
README
<div align="center">

@vaultry/claude-secrets
Encrypted token store for Claude Code sessions and your shell/apps.
macOS Keychain backed · MCP server · CLI · .env placeholder expansion.
</div>
Stop pasting tokens into every new Claude session. Store them once, reference them everywhere — including commit-safe
.envfiles.
Quick install (one line)
curl -fsSL https://raw.githubusercontent.com/vaultry/claude-secrets/main/install.sh | bash
Or via npm:
npm install -g @vaultry/claude-secrets
claude-secrets-setup
claude mcp add claude-secrets --scope user -- claude-secrets-mcp
Three interfaces
| Interface | Who uses it | Policy-gated |
|---|---|---|
| MCP server | Claude Code sessions | Yes (per-project allowlist) |
CLI claude-secrets |
Your shell, apps, scripts | No (user has Keychain access) |
.env placeholders |
Any tool that reads .env | Via CLI |
Store secrets

Pipe from stdin (keeps the value out of shell history):
echo "ghp_xxxxx" | claude-secrets set GITHUB_TOKEN
op read "op://Private/Gitea/token" | claude-secrets set GITEA_TOKEN
claude-secrets set DB_PASSWORD # interactive — type, Ctrl-D
Encrypted with AES-256-GCM, master key stored in macOS Keychain (syncs between Macs via iCloud Keychain).
Commit-safe .env files

Replace real values with secret://NAME references. The .env file can now be committed to git safely — it contains only names, no credentials.
# .env
API_KEY=secret://GITHUB_TOKEN
DB_URL=postgres://app:secret://DB_PASSWORD@db.local/app
PORT=3000
Placeholder syntax: secret://NAME — URI-style, avoids bash parameter-expansion collisions. Names can contain A-Z, 0-9, _, ., -.
Expand placeholders

Resolve placeholders to real values at runtime:
eval "$(claude-secrets export)" # into current shell
claude-secrets export --format json > resolved.json
claude-secrets export --format dotenv > .env.resolved
claude-secrets export --file .env.staging # different file
claude-secrets export --on-missing empty # empty for missing refs
--on-missing |
Behavior |
|---|---|
throw (default) |
Exit 1 with list of missing names |
empty |
Placeholder becomes empty string |
keep |
Placeholder left literal (secret://NAME) |
Run a command with secrets

claude-secrets exec -- pnpm dev # inject then run
claude-secrets exec -- node build.js
claude-secrets exec --file .env.prod -- npm run deploy
Secrets live only in the child process — not in the parent shell's environment, history, or ps output.
package.json scripts work seamlessly:
{
"scripts": {
"dev": "claude-secrets exec -- ts-node src/index.ts",
"test": "claude-secrets exec --file .env.test -- vitest",
"deploy": "claude-secrets exec --file .env.prod -- node deploy.js"
}
}
MCP server (for Claude Code)
After registering with claude mcp add, Claude Code gets 6 tools under mcp__claude-secrets__*:
| Tool | Policy check | Effect |
|---|---|---|
set_secret |
yes | Store/overwrite (requires allowlist) |
get_secret |
yes | Return value or Denied |
delete_secret |
yes | Remove or Denied |
list_secrets |
filter | {total, visible, names} |
search_secrets |
filter | Array of matches (regex, case-insensitive) |
input_secret |
yes | Native macOS dialog prompts user for value → stored directly. Value never passes through the model or chat. |
input_secret — secure user input
<div align="center">
<img src="./docs/dialog.png" alt="Native macOS dialog for secure secret input" width="420">
</div>
Use case: Claude needs a token the user hasn't stored yet. Instead of asking "please paste your token in chat" (which leaks the value into transcripts, API logs, and plan files), Claude calls input_secret — a native macOS dialog pops up, user types the value, value goes straight from dialog to encrypted store without Claude ever seeing it.
Claude: "I need a GITEA_TOKEN to push that branch. May I prompt you?"
User: "yes"
Claude: [calls input_secret with name=GITEA_TOKEN]
→ macOS dialog appears on your screen (hidden input)
→ you type the token, press OK
→ value stored in encrypted vault
→ Claude gets back "OK: 'GITEA_TOKEN' stored"
The value never appears in chat, transcripts, or API traffic.
list and search only return names that pass the allowlist. total shows the real count — Claude knows more secrets exist but can't see their names from a non-authorized project.
Per-project allowlist — .claude/secrets.yml
Without this file: all MCP reads and writes blocked — prevents Claude in project A from reading or overwriting secrets from project B.
allow:
- GITEA_TOKEN
- GITHUB_* # glob patterns supported
- OP_SERVICE_*
Special values:
allow: "*" # allow everything (not recommended)
allow:
- GITEA_TOKEN
inject_values: true # SessionStart hook injects values (opt-in)
secrets.yml is safe to commit — it contains names only.
SessionStart Hook (optional)
Inject available secret names into Claude's context at session start so Claude knows what's available without asking. Add to ~/.claude/settings.json:
{
"hooks": {
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "claude-secrets-session-hook"
}
]
}
]
}
}
With inject_values: true in secrets.yml, the hook also injects values — they appear in transcripts, history.jsonl, plan files, and API logs. Only use for short-lived tokens in trusted projects. The hook emits a visible warning when values are injected.
Slash commands for Claude Code
Two slash commands included in the package:
| Command | Effect |
|---|---|
/secret-set <NAME> |
Native macOS dialog (hidden input) → stored via CLI |
/secret-get <NAME> |
CLI fetch → copied to clipboard (value never in chat) |
Link them into Claude Code's commands directory:
ln -sf $(npm root -g)/@vaultry/claude-secrets/commands/secret-set.md ~/.claude/commands/secret-set.md
ln -sf $(npm root -g)/@vaultry/claude-secrets/commands/secret-get.md ~/.claude/commands/secret-get.md
(The one-line installer handles this automatically.)
Security model
Protects against
- Master key outside the encrypted file — held in Keychain, user-locked
- Per-project allowlist blocks cross-project leakage through Claude
- AES-256-GCM — authenticated encryption, tamper detection
- File mode
0600— owner-only - Atomic writes (write-to-temp + rename) — no partial-state corruption on crash
.envwith refs — commit-safe by constructionexec --injection — secrets stay out of shell history andpsoutput
Know the trade-offs
inject_values: trueputs values in Claude's system prompt → they appear in transcripts,history.jsonl, plan files, and API logs- CLI bypasses policy — anyone with your user ID and an unlocked Mac can read all secrets (correct: Keychain is what protects you as a user, policy protects Claude from itself)
- Keychain ACL — after the first "Always Allow",
nodecan read the key without a prompt - MCP writes are gated by allowlist since v0.1 — prevents cross-project overwrites
Not a defense against
- Malicious local processes running as your user
- Physical access to an unlocked Mac
- A compromised Keychain (root-level malware)
CLI reference
claude-secrets help
get <name> Print secret to stdout
set <name> [value] Store secret (value from stdin if omitted)
delete|rm <name> Delete a secret
list|ls List all secret names
search <pattern> Regex search (case-insensitive)
export [--file .env] Print 'export KEY=VAL' lines for shell eval
[--format shell|dotenv|json] Default: shell
[--on-missing throw|empty|keep] Default: throw
exec [--file .env] -- <cmd...> Run cmd with expanded env from .env
[--on-missing throw|empty|keep] Default: throw
Architecture
~/.claude/
└── secrets.encrypted # AES-256-GCM, mode 0600
@vaultry/claude-secrets (installed)
├── dist/
│ ├── index.js # MCP server (stdio)
│ ├── crypto.js # AES-256-GCM + Keychain I/O
│ ├── store.js # atomic read/write secrets.encrypted
│ ├── policy.js # isAllowed() via secrets.yml
│ ├── dotenv.js # .env parser + placeholder expansion
│ └── bin/
│ ├── setup.js # init CLI
│ ├── session-hook.js # SessionStart hook
│ └── cli.js # claude-secrets CLI
└── commands/ # Claude Code slash commands
Encryption details
- Algorithm: AES-256-GCM (authenticated encryption, tamper detection)
- IV: 12 bytes, random per write
- Key: 32 bytes, in macOS Keychain as service
claude-secrets-mcp, accountmaster-key - File format:
{iv_b64}:{authtag_b64}:{ciphertext_b64} - Decrypted payload: JSON
{name: value} - Writes: temp-file + rename, atomic on POSIX
Sync between Macs
- iCloud Keychain auto-syncs the master key
- Sync
secrets.encryptedvia Dropbox/iCloud Drive/git-crypt if needed — it's useless without the key
Requirements
- macOS (uses the
securityCLI for Keychain access) - Node.js ≥ 18
Cross-platform support (Linux/Windows via keytar) is planned for v0.2.
Workflow example
cd ~/projects/new-thing
git init
# Secrets already stored? Check:
claude-secrets search 'GITEA|DB'
# Add new ones:
op read "op://Private/Gitea/token" | claude-secrets set GITEA_TOKEN
claude-secrets set DB_PASSWORD # type, Ctrl-D
# Commit-safe .env:
cat > .env <<EOF
GITEA_TOKEN=secret://GITEA_TOKEN
DATABASE_URL=postgres://app:secret://DB_PASSWORD@localhost:5432/mydb
PORT=3000
EOF
# Let Claude read specific tokens (optional):
mkdir -p .claude
cat > .claude/secrets.yml <<EOF
allow:
- GITEA_TOKEN
- DB_PASSWORD
EOF
# Dev server with secrets injected:
claude-secrets exec -- pnpm dev
Troubleshooting
"Keychain entry not found"
Setup wasn't run. Run claude-secrets-setup.
"Invalid ciphertext format"
secrets.encrypted is corrupt or was encrypted with a different key. Restore from backup or delete and start over.
Keychain prompt on every read
Normal on the first session after login. Check "Always Allow". If it keeps prompting: Keychain Access app → find claude-secrets-mcp → right-click → Access Control → add the node binary.
claude-secrets: command not found
export PATH="$(npm bin -g):$PATH"
MCP not visible in Claude
claude mcp list | grep claude-secrets # should show "✓ Connected"
If not: restart the Claude session.
Secret in .env but expansion fails
Check: claude-secrets get NAME — exists? Name match is case-sensitive. Placeholder syntax must be exactly secret:// (not @secrets: or ${secrets:}).
Uninstall
claude mcp remove claude-secrets --scope user
security delete-generic-password -s claude-secrets-mcp -a master-key
rm ~/.claude/secrets.encrypted
npm uninstall -g @vaultry/claude-secrets
rm -f ~/.claude/commands/secret-set.md ~/.claude/commands/secret-get.md
# Remove the session-hook line from ~/.claude/settings.json manually
License
Source-available under the Vaultry Source-Available License v1.0. Free for personal, educational, and internal use. Commercial use (resale, SaaS, bundled products) requires a separate commercial license — contact mail@jorisslagter.nl.
See LICENSE.md for full terms.
<div align="center">
Made by Joris Slagter · Report an issue · npm
</div>
Recommended Servers
playwright-mcp
A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.
Magic Component Platform (MCP)
An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.
Audiense Insights MCP Server
Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.
VeyraX MCP
Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.
graphlit-mcp-server
The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.
Kagi MCP Server
An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.
E2B
Using MCP to run code via e2b.
Neon Database
MCP server for interacting with Neon Management API and databases
Qdrant Server
This repository is an example of how to create a MCP server for Qdrant, a vector search engine.
Exa Search
A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.