claude-secrets

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.

Category
访问服务器

README

<div align="center">

Claude Secrets

@vaultry/claude-secrets

Encrypted token store for Claude Code sessions and your shell/apps. macOS Keychain backed · MCP server · CLI · .env placeholder expansion.

npm version npm downloads license platform node

</div>

Stop pasting tokens into every new Claude session. Store them once, reference them everywhere — including commit-safe .env files.


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

Store demo

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

dotenv demo

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

export demo

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

exec demo

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
  • .env with refs — commit-safe by construction
  • exec -- injection — secrets stay out of shell history and ps output

Know the trade-offs

  • inject_values: true puts 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", node can 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, account master-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.encrypted via Dropbox/iCloud Drive/git-crypt if needed — it's useless without the key

Requirements

  • macOS (uses the security CLI 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>

推荐服务器

Baidu Map

Baidu Map

百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。

官方
精选
JavaScript
Playwright MCP Server

Playwright MCP Server

一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。

官方
精选
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。

官方
精选
本地
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。

官方
精选
本地
TypeScript
VeyraX

VeyraX

一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。

官方
精选
本地
graphlit-mcp-server

graphlit-mcp-server

模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。

官方
精选
TypeScript
Kagi MCP Server

Kagi MCP Server

一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。

官方
精选
Python
e2b-mcp-server

e2b-mcp-server

使用 MCP 通过 e2b 运行代码。

官方
精选
Neon MCP Server

Neon MCP Server

用于与 Neon 管理 API 和数据库交互的 MCP 服务器

官方
精选
Exa MCP Server

Exa MCP Server

模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。

官方
精选