feat: add configuration management and MCP secrets workflows (closes #16204)
Major additions to address critical gaps in Claude Code configuration: ## New Documentation Sections 1. Section 3.2.1 "Version Control & Backup" (guide/ultimate-guide.md:4085) - Configuration hierarchy: global → project → local - Git strategy for ~/.claude (symlinks approach) - Backup strategies: Git remote, cloud sync, cron - Multi-machine sync workflows - Disaster recovery procedures - Documented .claude/settings.local.json (previously undocumented) 2. Section 8.3.1 "MCP Secrets Management" (guide/ultimate-guide.md:8113) - Three practical approaches: OS Keychain, .env, Secret Vaults - Secrets rotation workflow - Pre-commit secret detection - Verification checklist - Best practices summary ## New Templates 1. sync-claude-config.sh (examples/scripts/) - Commands: setup, sync, backup, restore, validate - .env parsing + envsubst for variable substitution - Git repo creation with symlinks - Validation checks (secrets not in Git) 2. pre-commit-secrets.sh (examples/hooks/bash/) - Detects 10+ secret patterns (OpenAI, GitHub, AWS, etc.) - Whitelist system for false positives - Clear error messages with remediation steps 3. settings.local.json.example (examples/config/) - Machine-specific overrides template - Example use cases and patterns ## Resource Evaluation - Added docs/resource-evaluations/ratinaud-config-management-evaluation.md - Score: 5/5 (CRITICAL) - Validated via 3 Perplexity searches + technical-writer agent challenge - Community demand: GitHub #16204 + brianlovin/claude-config ## Updated References - machine-readable/reference.yaml: 22 new entries - Configuration management sections - MCP secrets workflows - Community resources (Ratinaud, brianlovin, GitHub issue) ## Impact - Security: Pre-commit hook prevents secret leaks - Productivity: Multi-machine sync reduces manual reconfig - Team coordination: Onboarding workflow for ~/.claude setup - Disaster recovery: Backup/restore strategies documented Credits: - Martin Ratinaud (504 sessions, LinkedIn post) - brianlovin/claude-config (community example) - GitHub Issue #16204 (community request) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
5b69db64a9
commit
0630fcd883
6 changed files with 1591 additions and 0 deletions
|
|
@ -4083,6 +4083,239 @@ The `.claude/` folder is your project's Claude Code directory for memory, settin
|
|||
| Personal preferences | `CLAUDE.md` | ❌ Gitignore |
|
||||
| Personal permissions | `settings.local.json` | ❌ Gitignore |
|
||||
|
||||
### 3.2.1 Version Control & Backup
|
||||
|
||||
**Problem**: Without version control, losing your Claude Code configuration means hours of manual reconfiguration across agents, skills, hooks, and MCP servers.
|
||||
|
||||
**Solution**: Version control your configuration with Git + strategic `.gitignore` patterns for secrets.
|
||||
|
||||
#### Configuration Hierarchy
|
||||
|
||||
Claude Code uses a three-tier configuration system with clear precedence:
|
||||
|
||||
```
|
||||
~/.claude/settings.json (global user defaults)
|
||||
↓ overridden by
|
||||
.claude/settings.json (project settings, team shared)
|
||||
↓ overridden by
|
||||
.claude/settings.local.json (machine-specific, personal)
|
||||
```
|
||||
|
||||
**Precedence rules**:
|
||||
- **Global** (`~/.claude/settings.json`): Applied to all projects unless overridden
|
||||
- **Project** (`.claude/settings.json`): Shared team configuration, committed to Git
|
||||
- **Local** (`.claude/settings.local.json`): Machine-specific overrides, gitignored
|
||||
|
||||
This hierarchy enables:
|
||||
- **Team coordination**: Share hooks/rules in `.claude/settings.json`
|
||||
- **Personal flexibility**: Override settings in `.local.json` without Git conflicts
|
||||
- **Multi-machine consistency**: Global defaults in `~/.claude/` synced separately
|
||||
|
||||
#### Git Strategy for Project Configuration
|
||||
|
||||
**What to commit** (`.claude/` in project):
|
||||
|
||||
```gitignore
|
||||
# .gitignore for project root
|
||||
.claude/CLAUDE.md # Personal instructions
|
||||
.claude/settings.local.json # Machine-specific overrides
|
||||
.claude/plans/ # Saved plan files (optional)
|
||||
```
|
||||
|
||||
**What to share**:
|
||||
```bash
|
||||
git add .claude/settings.json # Team hooks/permissions
|
||||
git add .claude/agents/ # Custom agents
|
||||
git add .claude/commands/ # Slash commands
|
||||
git add .claude/hooks/ # Automation scripts
|
||||
git add .claude/rules/ # Team conventions
|
||||
git add .claude/skills/ # Knowledge modules
|
||||
```
|
||||
|
||||
#### Version Control for Global Config (~/.claude/)
|
||||
|
||||
Your `~/.claude/` directory contains **global configuration** (settings, MCP servers, session history) that should be backed up but contains secrets.
|
||||
|
||||
**Recommended approach** (inspired by [Martin Ratinaud](https://www.linkedin.com/posts/martinratinaud_claudecode-devtools-buildinpublic-activity-7424055660247629824-hBsL), 504 sessions):
|
||||
|
||||
```bash
|
||||
# 1. Create Git repo for global config
|
||||
mkdir ~/claude-config-backup
|
||||
cd ~/claude-config-backup
|
||||
git init
|
||||
|
||||
# 2. Symlink directories (not files with secrets)
|
||||
ln -s ~/.claude/agents ./agents
|
||||
ln -s ~/.claude/commands ./commands
|
||||
ln -s ~/.claude/hooks ./hooks
|
||||
ln -s ~/.claude/skills ./skills
|
||||
|
||||
# 3. Copy settings template (without secrets)
|
||||
cp ~/.claude/settings.json ./settings.template.json
|
||||
# Manually replace secrets with ${env:VAR_NAME} placeholders
|
||||
|
||||
# 4. .gitignore for secrets
|
||||
cat > .gitignore << EOF
|
||||
# Never commit these
|
||||
.env
|
||||
settings.json # Contains resolved secrets
|
||||
mcp.json # Contains API keys
|
||||
*.local.json
|
||||
|
||||
# Session history (large, personal)
|
||||
projects/
|
||||
EOF
|
||||
|
||||
# 5. Commit and push to private repo
|
||||
git add .
|
||||
git commit -m "Initial Claude Code global config backup"
|
||||
git remote add origin git@github.com:yourusername/claude-config-private.git
|
||||
git push -u origin main
|
||||
```
|
||||
|
||||
**Why symlinks?**
|
||||
- Changes in `~/.claude/agents/` immediately reflected in Git repo
|
||||
- No manual sync needed
|
||||
- Works across macOS/Linux (Windows: use junction points)
|
||||
|
||||
#### Backup Strategies
|
||||
|
||||
| Strategy | Pros | Cons | Use Case |
|
||||
|----------|------|------|----------|
|
||||
| **Git remote (private)** | Full version history, branching | Requires Git knowledge | Developers, power users |
|
||||
| **Cloud sync (Dropbox/iCloud)** | Automatic, cross-device | No version history, sync conflicts | Solo users, simple setup |
|
||||
| **Cron backup script** | Automated, timestamped | No cross-machine sync | Disaster recovery only |
|
||||
| **Third-party tools** | `claudebot backup --config` | Dependency on external tool | Quick setup |
|
||||
|
||||
**Example: Automated backup with cron**:
|
||||
|
||||
```bash
|
||||
# ~/claude-config-backup/backup.sh
|
||||
#!/bin/bash
|
||||
BACKUP_DIR=~/claude-backups
|
||||
DATE=$(date +%Y-%m-%d_%H-%M-%S)
|
||||
|
||||
# Create timestamped backup
|
||||
mkdir -p "$BACKUP_DIR"
|
||||
tar -czf "$BACKUP_DIR/claude-config-$DATE.tar.gz" \
|
||||
~/.claude/agents \
|
||||
~/.claude/commands \
|
||||
~/.claude/hooks \
|
||||
~/.claude/skills \
|
||||
~/.claude/settings.json
|
||||
|
||||
# Keep only last 30 days
|
||||
find "$BACKUP_DIR" -name "claude-config-*.tar.gz" -mtime +30 -delete
|
||||
|
||||
echo "Backup created: $BACKUP_DIR/claude-config-$DATE.tar.gz"
|
||||
```
|
||||
|
||||
Schedule with cron:
|
||||
```bash
|
||||
# Backup daily at 2 AM
|
||||
crontab -e
|
||||
0 2 * * * ~/claude-config-backup/backup.sh >> ~/claude-backups/backup.log 2>&1
|
||||
```
|
||||
|
||||
#### Multi-Machine Sync
|
||||
|
||||
**Scenario**: Laptop + desktop, need consistent Claude Code experience.
|
||||
|
||||
**Option 1: Git + symlinks**
|
||||
|
||||
```bash
|
||||
# Machine 1 (setup)
|
||||
cd ~/claude-config-backup
|
||||
git add agents/ commands/ hooks/ skills/
|
||||
git commit -m "Add latest configs"
|
||||
git push
|
||||
|
||||
# Machine 2 (sync)
|
||||
cd ~/claude-config-backup
|
||||
git pull
|
||||
# Symlinks automatically sync ~/.claude/ directories
|
||||
```
|
||||
|
||||
**Option 2: Cloud storage symlinks**
|
||||
|
||||
```bash
|
||||
# Both machines
|
||||
# 1. Move ~/.claude/ to Dropbox
|
||||
mv ~/.claude ~/Dropbox/claude-config
|
||||
|
||||
# 2. Symlink back
|
||||
ln -s ~/Dropbox/claude-config ~/.claude
|
||||
|
||||
# Changes sync automatically via Dropbox
|
||||
```
|
||||
|
||||
**Option 3: Hybrid (Git for agents/hooks, cloud for MCP configs)**
|
||||
|
||||
```bash
|
||||
# Git for code (agents, hooks, skills)
|
||||
~/claude-config-backup/ → Git repo
|
||||
|
||||
# Cloud for data (settings, MCP, sessions)
|
||||
~/Dropbox/claude-mcp/ → settings.json, mcp.json (encrypted secrets)
|
||||
ln -s ~/Dropbox/claude-mcp/settings.json ~/.claude/settings.json
|
||||
```
|
||||
|
||||
#### Security Considerations
|
||||
|
||||
**Never commit these to Git**:
|
||||
- API keys, tokens, passwords
|
||||
- `.env` files with secrets
|
||||
- `mcp.json` with resolved credentials
|
||||
- Session history (may contain sensitive code)
|
||||
|
||||
**Always commit these**:
|
||||
- Template files with `${env:VAR_NAME}` placeholders
|
||||
- `.gitignore` to prevent secret leaks
|
||||
- Public agents/hooks/skills (if safe to share)
|
||||
|
||||
**Best practices**:
|
||||
1. Use `settings.template.json` with placeholders → Generate `settings.json` via script
|
||||
2. Run [pre-commit hook](../../examples/hooks/bash/pre-commit-secrets.sh) to detect secrets
|
||||
3. For MCP secrets, see [Section 8.3.1 MCP Secrets Management](#831-mcp-secrets-management)
|
||||
|
||||
#### Disaster Recovery
|
||||
|
||||
**Restore from backup**:
|
||||
|
||||
```bash
|
||||
# From Git backup
|
||||
cd ~/claude-config-backup
|
||||
git clone git@github.com:yourusername/claude-config-private.git
|
||||
cd claude-config-private
|
||||
|
||||
# Recreate symlinks
|
||||
ln -sf ~/.claude/agents ./agents
|
||||
ln -sf ~/.claude/commands ./commands
|
||||
# ... etc
|
||||
|
||||
# Restore settings (fill in secrets manually or via .env)
|
||||
cp settings.template.json ~/.claude/settings.json
|
||||
# Edit and replace ${env:VAR_NAME} with actual values
|
||||
```
|
||||
|
||||
**From tarball backup**:
|
||||
```bash
|
||||
cd ~/claude-backups
|
||||
# Find latest backup
|
||||
ls -lt claude-config-*.tar.gz | head -1
|
||||
|
||||
# Extract
|
||||
tar -xzf claude-config-YYYY-MM-DD_HH-MM-SS.tar.gz -C ~/
|
||||
```
|
||||
|
||||
#### Community Solutions
|
||||
|
||||
- **[brianlovin/claude-config](https://github.com/brianlovin/claude-config)**: Public repo with `sync.sh` script for backups and restore
|
||||
- **Martin Ratinaud approach**: Git repo + symlinks + `sync-mcp.sh` for secrets (504 sessions tested)
|
||||
- **Script template**: See [sync-claude-config.sh](../../examples/scripts/sync-claude-config.sh) for full automation
|
||||
|
||||
**GitHub Issue**: [#16204 - Proactive migration guidance for backup/restore workflows](https://github.com/anthropics/claude-code/issues/16204)
|
||||
|
||||
## 3.3 Settings & Permissions
|
||||
|
||||
### settings.json (Team Configuration)
|
||||
|
|
@ -8110,6 +8343,384 @@ claude mcp add --help
|
|||
|
||||
> **Source**: CLI syntax adapted from [Shipyard Claude Code Cheat Sheet](https://shipyard.build/blog/claude-code-cheat-sheet/)
|
||||
|
||||
### 8.3.1 MCP Secrets Management
|
||||
|
||||
**Problem**: MCP servers require API keys and credentials. Storing them in plaintext `mcp.json` creates security risks (accidental Git commits, exposure in logs, lateral movement after breach).
|
||||
|
||||
**Solution**: Separate secrets from configuration using environment variables, OS keychains, or secret vaults.
|
||||
|
||||
#### Security Principles
|
||||
|
||||
Before implementing secrets management, understand the baseline requirements from [Security Hardening Guide](./security-hardening.md):
|
||||
|
||||
- **Encryption at rest**: Secrets must be encrypted on disk (OS keychain > plaintext .env)
|
||||
- **Least privilege**: Use read-only credentials when possible
|
||||
- **Token rotation**: Short-lived tokens with automated refresh
|
||||
- **Audit logging**: Track secret access without logging the secrets themselves
|
||||
- **Never in Git**: Secrets must never be committed to version control
|
||||
|
||||
For full threat model and CVE details, see [Section 8.6 MCP Security](#86-mcp-security).
|
||||
|
||||
#### Three Practical Approaches
|
||||
|
||||
| Approach | Security | Complexity | Use Case |
|
||||
|----------|----------|------------|----------|
|
||||
| **OS Keychain** | High (encrypted at rest) | Medium | Solo developers, macOS/Linux |
|
||||
| **.env + .gitignore** | Medium (file permissions) | Low | Small teams, rapid prototyping |
|
||||
| **Secret Vaults** | Very High (centralized, audited) | High | Enterprise, compliance requirements |
|
||||
|
||||
---
|
||||
|
||||
#### Approach 1: OS Keychain (Recommended)
|
||||
|
||||
**Best for**: Solo developers on macOS/Linux with high security needs.
|
||||
|
||||
**Pros**: Encrypted at rest, OS-level access control, no plaintext files
|
||||
**Cons**: Platform-specific, requires scripting for automation
|
||||
|
||||
**macOS Keychain Setup**:
|
||||
|
||||
```bash
|
||||
# Store secret in Keychain
|
||||
security add-generic-password \
|
||||
-a "claude-mcp" \
|
||||
-s "github-token" \
|
||||
-w "ghp_your_token_here"
|
||||
|
||||
# Verify storage
|
||||
security find-generic-password -s "github-token" -w
|
||||
```
|
||||
|
||||
**MCP configuration with keychain retrieval**:
|
||||
|
||||
```json
|
||||
{
|
||||
"servers": {
|
||||
"github": {
|
||||
"command": "bash",
|
||||
"args": ["-c", "GITHUB_TOKEN=$(security find-generic-password -s 'github-token' -w) npx @github/mcp-server"],
|
||||
"env": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Linux Secret Service** (GNOME Keyring, KWallet):
|
||||
|
||||
```bash
|
||||
# Install secret-tool (part of libsecret)
|
||||
sudo apt install libsecret-tools # Ubuntu/Debian
|
||||
|
||||
# Store secret
|
||||
secret-tool store --label="GitHub Token" service claude key github-token
|
||||
# Prompt will ask for the secret value
|
||||
|
||||
# Retrieve in MCP config (bash wrapper)
|
||||
# ~/.claude/scripts/mcp-github.sh
|
||||
#!/bin/bash
|
||||
export GITHUB_TOKEN=$(secret-tool lookup service claude key github-token)
|
||||
npx @github/mcp-server
|
||||
|
||||
# mcp.json
|
||||
{
|
||||
"servers": {
|
||||
"github": {
|
||||
"command": "~/.claude/scripts/mcp-github.sh",
|
||||
"args": []
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Windows Credential Manager**:
|
||||
|
||||
```powershell
|
||||
# Store secret
|
||||
cmdkey /generic:"claude-mcp-github" /user:"token" /pass:"ghp_your_token_here"
|
||||
|
||||
# Retrieve in PowerShell wrapper
|
||||
$password = cmdkey /list:"claude-mcp-github" | Select-String -Pattern "Password" | ForEach-Object { $_.ToString().Split(":")[1].Trim() }
|
||||
$env:GITHUB_TOKEN = $password
|
||||
npx @github/mcp-server
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### Approach 2: .env + .gitignore (Simple)
|
||||
|
||||
**Best for**: Small teams, rapid prototyping, adequate security with proper `.gitignore`.
|
||||
|
||||
**Pros**: Simple, cross-platform, easy onboarding
|
||||
**Cons**: Plaintext on disk (file permissions only), requires discipline
|
||||
|
||||
**Setup**:
|
||||
|
||||
```bash
|
||||
# 1. Create .env file (project root or ~/.claude/)
|
||||
cat > ~/.claude/.env << EOF
|
||||
GITHUB_TOKEN=ghp_your_token_here
|
||||
OPENAI_API_KEY=sk-your-key-here
|
||||
DATABASE_URL=postgresql://user:pass@localhost/db
|
||||
EOF
|
||||
|
||||
# 2. Secure permissions (Unix only)
|
||||
chmod 600 ~/.claude/.env
|
||||
|
||||
# 3. Add to .gitignore
|
||||
echo ".env" >> ~/.claude/.gitignore
|
||||
```
|
||||
|
||||
**MCP configuration with .env variables**:
|
||||
|
||||
```json
|
||||
{
|
||||
"servers": {
|
||||
"github": {
|
||||
"command": "npx",
|
||||
"args": ["@github/mcp-server"],
|
||||
"env": {
|
||||
"GITHUB_TOKEN": "${env:GITHUB_TOKEN}"
|
||||
}
|
||||
},
|
||||
"postgres": {
|
||||
"command": "npx",
|
||||
"args": ["@modelcontextprotocol/server-postgres"],
|
||||
"env": {
|
||||
"DATABASE_URL": "${env:DATABASE_URL}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Load .env before Claude Code**:
|
||||
|
||||
```bash
|
||||
# Option 1: Shell wrapper
|
||||
# ~/bin/claude-with-env
|
||||
#!/bin/bash
|
||||
export $(cat ~/.claude/.env | xargs)
|
||||
claude "$@"
|
||||
|
||||
# Option 2: direnv (automatic per-directory)
|
||||
# Install: https://direnv.net/
|
||||
echo 'dotenv ~/.claude/.env' > ~/.config/direnv/direnvrc
|
||||
direnv allow ~/.claude
|
||||
```
|
||||
|
||||
**Template approach for teams**:
|
||||
|
||||
```bash
|
||||
# Commit template (no secrets)
|
||||
cat > ~/.claude/mcp.json.template << EOF
|
||||
{
|
||||
"servers": {
|
||||
"github": {
|
||||
"command": "npx",
|
||||
"args": ["@github/mcp-server"],
|
||||
"env": {
|
||||
"GITHUB_TOKEN": "\${env:GITHUB_TOKEN}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
# Generate actual config from template + .env
|
||||
envsubst < ~/.claude/mcp.json.template > ~/.claude/mcp.json
|
||||
|
||||
# .gitignore
|
||||
mcp.json # Generated, contains resolved secrets
|
||||
.env # Never commit
|
||||
```
|
||||
|
||||
**See also**: [sync-claude-config.sh](../../examples/scripts/sync-claude-config.sh) for automated template substitution.
|
||||
|
||||
---
|
||||
|
||||
#### Approach 3: Secret Vaults (Enterprise)
|
||||
|
||||
**Best for**: Enterprise, compliance (SOC 2, HIPAA), centralized secret management.
|
||||
|
||||
**Pros**: Centralized, audited, automated rotation, fine-grained access control
|
||||
**Cons**: Complex setup, requires infrastructure, vendor lock-in
|
||||
|
||||
**HashiCorp Vault**:
|
||||
|
||||
```bash
|
||||
# Store secret in Vault
|
||||
vault kv put secret/claude/github token=ghp_your_token_here
|
||||
|
||||
# Retrieve in wrapper script
|
||||
# ~/.claude/scripts/mcp-github-vault.sh
|
||||
#!/bin/bash
|
||||
export GITHUB_TOKEN=$(vault kv get -field=token secret/claude/github)
|
||||
npx @github/mcp-server
|
||||
|
||||
# mcp.json
|
||||
{
|
||||
"servers": {
|
||||
"github": {
|
||||
"command": "~/.claude/scripts/mcp-github-vault.sh",
|
||||
"args": []
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**AWS Secrets Manager**:
|
||||
|
||||
```bash
|
||||
# Store secret
|
||||
aws secretsmanager create-secret \
|
||||
--name claude/github-token \
|
||||
--secret-string "ghp_your_token_here"
|
||||
|
||||
# Retrieve in wrapper
|
||||
export GITHUB_TOKEN=$(aws secretsmanager get-secret-value \
|
||||
--secret-id claude/github-token \
|
||||
--query SecretString \
|
||||
--output text)
|
||||
npx @github/mcp-server
|
||||
```
|
||||
|
||||
**1Password CLI** (team-friendly):
|
||||
|
||||
```bash
|
||||
# Store in 1Password (via GUI or CLI)
|
||||
op item create --category=password \
|
||||
--title="Claude MCP GitHub Token" \
|
||||
token=ghp_your_token_here
|
||||
|
||||
# Retrieve in wrapper
|
||||
export GITHUB_TOKEN=$(op read "op://Private/Claude MCP GitHub Token/token")
|
||||
npx @github/mcp-server
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### Secrets Rotation Workflow
|
||||
|
||||
**Problem**: API keys expire or are compromised. Rotating secrets across multiple MCP servers is manual and error-prone.
|
||||
|
||||
**Solution**: Centralized `.env` file with rotation script.
|
||||
|
||||
```bash
|
||||
# ~/.claude/rotate-secret.sh
|
||||
#!/bin/bash
|
||||
SECRET_NAME=$1
|
||||
NEW_VALUE=$2
|
||||
|
||||
# 1. Update .env file
|
||||
sed -i.bak "s|^${SECRET_NAME}=.*|${SECRET_NAME}=${NEW_VALUE}|" ~/.claude/.env
|
||||
|
||||
# 2. Regenerate mcp.json from template
|
||||
envsubst < ~/.claude/mcp.json.template > ~/.claude/mcp.json
|
||||
|
||||
# 3. Restart MCP servers (if running)
|
||||
pkill -f "mcp-server" || true
|
||||
|
||||
echo "✅ Rotated $SECRET_NAME"
|
||||
echo "⚠️ Restart Claude Code to apply changes"
|
||||
```
|
||||
|
||||
**Usage**:
|
||||
```bash
|
||||
# Rotate GitHub token
|
||||
./rotate-secret.sh GITHUB_TOKEN ghp_new_token_here
|
||||
|
||||
# Rotate database password
|
||||
./rotate-secret.sh DATABASE_URL postgresql://user:new_pass@localhost/db
|
||||
```
|
||||
|
||||
**Automated rotation with Vault** (advanced):
|
||||
|
||||
```bash
|
||||
# vault-rotate.sh
|
||||
#!/bin/bash
|
||||
# Fetch latest secrets from Vault, update .env, restart Claude
|
||||
|
||||
vault kv get -format=json secret/claude | jq -r '.data.data | to_entries[] | "\(.key)=\(.value)"' > ~/.claude/.env
|
||||
envsubst < ~/.claude/mcp.json.template > ~/.claude/mcp.json
|
||||
|
||||
echo "✅ Secrets rotated from Vault"
|
||||
```
|
||||
|
||||
Schedule with cron:
|
||||
```bash
|
||||
# Rotate daily at 3 AM
|
||||
0 3 * * * ~/claude-rotate.sh >> ~/claude-rotate.log 2>&1
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### Pre-Commit Secret Detection
|
||||
|
||||
**Problem**: Developers accidentally commit secrets to Git despite `.gitignore` (e.g., adding `.env` with `git add -f`).
|
||||
|
||||
**Solution**: [Pre-commit hook](../../examples/hooks/bash/pre-commit-secrets.sh) to block commits containing secrets.
|
||||
|
||||
```bash
|
||||
# Install hook
|
||||
cp examples/hooks/bash/pre-commit-secrets.sh .git/hooks/pre-commit
|
||||
chmod +x .git/hooks/pre-commit
|
||||
|
||||
# Test (should fail)
|
||||
echo "GITHUB_TOKEN=ghp_test" > test.txt
|
||||
git add test.txt
|
||||
git commit -m "Test"
|
||||
# ❌ Blocked: Secret detected in test.txt
|
||||
```
|
||||
|
||||
**Detection patterns** (see hook for full list):
|
||||
- OpenAI keys: `sk-[A-Za-z0-9]{48}`
|
||||
- GitHub tokens: `ghp_[A-Za-z0-9]{36}`
|
||||
- AWS keys: `AKIA[A-Z0-9]{16}`
|
||||
- Generic API keys: `api[_-]?key[\"']?\s*[:=]\s*[\"']?[A-Za-z0-9]{20,}`
|
||||
|
||||
---
|
||||
|
||||
#### Verification Checklist
|
||||
|
||||
Before deploying MCP servers with secrets:
|
||||
|
||||
| Check | Command | Pass Criteria |
|
||||
|-------|---------|---------------|
|
||||
| **.env not in Git** | `git ls-files | grep .env` | No output |
|
||||
| **File permissions** | `ls -l ~/.claude/.env` | `-rw-------` (600) |
|
||||
| **Template committed** | `git ls-files | grep template` | `mcp.json.template` present |
|
||||
| **Pre-commit hook** | `cat .git/hooks/pre-commit` | Secret detection script present |
|
||||
| **Secrets resolved** | `claude mcp list` | All servers start without errors |
|
||||
|
||||
**Test secret isolation**:
|
||||
```bash
|
||||
# Should work (secret from .env)
|
||||
export $(cat ~/.claude/.env | xargs)
|
||||
claude
|
||||
|
||||
# Should fail (no secrets in environment)
|
||||
unset GITHUB_TOKEN DATABASE_URL
|
||||
claude
|
||||
# ❌ MCP servers fail to start (expected)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### Best Practices Summary
|
||||
|
||||
| Practice | Rationale |
|
||||
|----------|-----------|
|
||||
| **Use OS keychain when possible** | Encrypted at rest, OS-level security |
|
||||
| **Never commit .env to Git** | One leak = full compromise |
|
||||
| **Commit .env.example template** | Team onboarding without secrets |
|
||||
| **Use ${env:VAR} in mcp.json** | Separation of config and secrets |
|
||||
| **Rotate secrets quarterly** | Limit blast radius of old leaks |
|
||||
| **Audit .gitignore before push** | Prevent accidental exposure |
|
||||
| **Least privilege credentials** | Read-only DB users, scoped API tokens |
|
||||
| **Monitor for leaked secrets** | GitHub secret scanning, GitGuardian |
|
||||
|
||||
For production deployments, consider [zero standing privilege](https://www.rkon.com/articles/mcp-server-security-navigating-the-new-ai-attack-surface/) where MCP servers start with no secrets and request just-in-time credentials on tool invocation.
|
||||
|
||||
## 8.4 Server Selection Guide
|
||||
|
||||
### Decision Tree
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue