Help us improve
Share bugs, ideas, or general feedback.
From toolbox
Extracts hardcoded secrets from CLAUDE.md, .mcp.json, and project config into gitignored .env file, wires SessionStart hook for auto-loading. Use for 'separate secrets' or 'extract API keys'.
npx claudepluginhub leejuoh/claude-code-zero --plugin toolboxHow this skill is triggered — by the user, by Claude, or both
Slash command
/toolbox:secret-setupThis skill is limited to the following tools:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
Extract hardcoded secrets from CLAUDE.md, `.mcp.json`, and project config into a gitignored env file, then wire up a SessionStart hook to load them automatically via `CLAUDE_ENV_FILE`.
Secures Claude Code sessions with nopeek CLI: loads .env secrets without exposing values, stores keys, redacts cloud CLI outputs to prevent API key leaks. Useful for secret and credential safety.
Initializes Claude Code security settings by detecting project tech stack via Glob (Node.js, Python, Go, Rust, Docker, etc.) and configuring file denial patterns in .claude/settings.json.
Provides secure environment variable management to prevent secrets exposure in Claude sessions, terminals, logs, or git commits.
Share bugs, ideas, or general feedback.
Extract hardcoded secrets from CLAUDE.md, .mcp.json, and project config into a gitignored env file, then wire up a SessionStart hook to load them automatically via CLAUDE_ENV_FILE.
Secrets in CLAUDE.md and .mcp.json get committed to git, shared with collaborators, and cached in Claude's context. This skill moves them to a gitignored file and loads them through a hook or shell profile.
If the user passes scan-only, stop after Phase 1 (report findings without modifying anything).
Read references/detection-patterns.md for the full list of regex patterns and scan targets.
Run Grep with each pattern against the scan targets. Present findings in a table:
| # | File | Line | Type | Value (masked) |
|---|------|------|------|----------------|
| 1 | CLAUDE.md:42 | API Key | sk-...abc1 |
| 2 | CLAUDE.md:55 | DB URL | postgres://...@host/db |
Ask the user to confirm which items are actual secrets to extract. Some may be intentional examples or documentation — do not force extraction.
For each confirmed secret, propose an environment variable name:
DATABASE_URL in code), reuse that name.Present the mapping and ask for confirmation. Mark the source so MCP secrets get handled differently in Phase 4:
| # | Source | Current value (masked) | Proposed env var |
|---|--------|----------------------|-----------------|
| 1 | CLAUDE.md | sk-...abc1 | OPENAI_API_KEY |
| 2 | CLAUDE.md | postgres://...@host/db | DATABASE_URL |
| 3 | .mcp.json | xoxb-...token | SLACK_TOKEN |
The user may rename variables or skip items. Wait for approval before proceeding.
Each step checks for existing infrastructure and merges rather than overwrites.
Check if the project already has a gitignored env file:
for f in .env.local .env.secret .env .env.development.local; do
git check-ignore "$f" 2>/dev/null && echo "FOUND: $f"
done
.env.local).git check-ignore -q "<chosen-file>" 2>/dev/null
echo $? # 0 = ignored, 1 = NOT ignored
If not ignored, propose adding the filename to .gitignore. Show the exact line and ask for confirmation.
Format:
# Claude Code secrets — loaded via SessionStart hook
# DO NOT commit this file to git
OPENAI_API_KEY=<paste-your-key-here>
DATABASE_URL=<paste-your-connection-string-here>
Tell the user to fill in actual values. Do NOT write real secret values — always use placeholders.
Read the template at assets/load-secrets.sh.template. Copy it to .claude/hooks/load-secrets.sh, replacing {{ENV_FILE_PATH}} with the actual relative path from Step 3.1.
Make it executable:
chmod +x .claude/hooks/load-secrets.sh
Read .claude/settings.local.json (create if missing). Merge the SessionStart hook entry:
{
"hooks": {
"SessionStart": [
{
"matcher": "startup",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/load-secrets.sh"
}
]
}
]
}
}
Merging rules:
hooks key → add it.SessionStart → add the array.SessionStart → append the new entry.Add deny rules to .claude/settings.local.json to prevent Claude from reading the env file directly. Adjust paths to match the chosen env file (example uses .env.local):
{
"permissions": {
"deny": [
"Read(.env.local)",
"Bash(cat .env.local*)",
"Bash(head .env.local*)",
"Bash(tail .env.local*)",
"Bash(less .env.local*)",
"Bash(more .env.local*)"
]
}
}
Merge with existing deny rules — do not remove existing entries.
Replace each hardcoded value with the $ENV_VAR_NAME reference.
Before:
API_KEY: sk-1234567890abcdef
After:
API_KEY: $OPENAI_API_KEY (loaded via SessionStart hook)
Add a brief note near the top of CLAUDE.md:
## Secrets
Environment variables are loaded automatically via SessionStart hook.
See `.claude/hooks/load-secrets.sh` for the loading mechanism.
Do not hardcode secrets in this file — use `$VAR_NAME` references.
MCP servers are spawned as separate processes when Claude Code starts — before SessionStart hooks run. This means MCP servers cannot receive env vars set via the SessionStart hook. They only inherit env vars already present in the parent shell environment.
Present the user with two options:
Option A (recommended for shared repos): Remove the hardcoded value from the env field. The user sets the env var in their shell profile (~/.zshrc or ~/.bashrc) instead. The MCP server inherits it from the parent process.
Before:
{
"env": {
"SLACK_TOKEN": "xoxb-actual-token"
}
}
After:
{
"env": {}
}
User adds to ~/.zshrc:
export SLACK_TOKEN="xoxb-actual-token"
Option B (simpler): Gitignore .mcp.json entirely. Secrets stay in the file but are not committed. Downside: MCP server configuration is no longer shared with collaborators.
Ask the user which option they prefer. If Option A, also add the env var to the .env.local file (as a reference, even though it won't be loaded via hook for MCP). Tell the user they must restart their shell and Claude Code for changes to take effect.
Test the hook script:
MOCK_ENV=$(mktemp)
CLAUDE_ENV_FILE="$MOCK_ENV" CLAUDE_PROJECT_DIR="$(pwd)" .claude/hooks/load-secrets.sh
echo "=== Loaded variables ==="
cat "$MOCK_ENV"
rm "$MOCK_ENV"
Expected output: one export VAR="value" line per secret (or placeholder).
After verification, inform the user:
/hooks to confirm the SessionStart hook appears..env.local with other variables.export lines to it, and Claude Code sources that file before each Bash command in the session.<paste-your-key-here> style placeholders so the user fills them in manually.command\r: not found errors.export KEY="value") to handle spaces and special characters. If a value itself contains double quotes, the user must escape them in the env file (KEY=value with \"quotes\").~/.zshrc) or the .mcp.json must be gitignored.env field in .mcp.json is empty or missing a key, the MCP server inherits that env var from the parent shell. This is standard Unix process behavior — removing a key from env does NOT block inheritance.