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
163
examples/hooks/bash/pre-commit-secrets.sh
Normal file
163
examples/hooks/bash/pre-commit-secrets.sh
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
#!/bin/bash
|
||||
# pre-commit-secrets.sh - Pre-commit hook to detect secrets in staged files
|
||||
# Version: 1.0.0
|
||||
# Purpose: Prevent accidental commits of API keys, tokens, and credentials
|
||||
#
|
||||
# Installation:
|
||||
# cp examples/hooks/bash/pre-commit-secrets.sh .git/hooks/pre-commit
|
||||
# chmod +x .git/hooks/pre-commit
|
||||
#
|
||||
# Test:
|
||||
# echo "GITHUB_TOKEN=ghp_test" > test.txt
|
||||
# git add test.txt
|
||||
# git commit -m "Test"
|
||||
# # Should fail with secret detection error
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Secret patterns (extended regex)
|
||||
declare -A PATTERNS=(
|
||||
["OpenAI API Key"]="sk-[A-Za-z0-9]{48}"
|
||||
["GitHub Token (ghp)"]="ghp_[A-Za-z0-9]{36}"
|
||||
["GitHub Token (gho)"]="gho_[A-Za-z0-9]{36}"
|
||||
["GitHub Token (ghu)"]="ghu_[A-Za-z0-9]{36}"
|
||||
["GitHub Token (ghs)"]="ghs_[A-Za-z0-9]{36}"
|
||||
["GitHub Token (ghr)"]="ghr_[A-Za-z0-9]{36}"
|
||||
["AWS Access Key"]="AKIA[A-Z0-9]{16}"
|
||||
["AWS Secret Key"]="[A-Za-z0-9/+=]{40}"
|
||||
["Anthropic API Key"]="sk-ant-[A-Za-z0-9-]{95,}"
|
||||
["Generic API Key"]="api[_-]?key[\"']?\s*[:=]\s*[\"']?[A-Za-z0-9]{20,}"
|
||||
["Generic Secret"]="secret[\"']?\s*[:=]\s*[\"']?[A-Za-z0-9]{20,}"
|
||||
["Generic Token"]="token[\"']?\s*[:=]\s*[\"']?[A-Za-z0-9]{20,}"
|
||||
["Database URL with Password"]="(postgres|mysql|mongodb)://[^:]+:[^@]+@"
|
||||
["Private Key"]="-----BEGIN (RSA |EC |OPENSSH )?PRIVATE KEY-----"
|
||||
["JWT Token"]="eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}"
|
||||
)
|
||||
|
||||
# Whitelisted patterns (safe to ignore)
|
||||
WHITELIST=(
|
||||
"your_token_here"
|
||||
"your_key_here"
|
||||
"example.com"
|
||||
"localhost"
|
||||
"placeholder"
|
||||
"XXXXXX"
|
||||
"\${env:" # Template variable syntax
|
||||
"sk-ant-example" # Example in documentation
|
||||
)
|
||||
|
||||
# Files to always skip (even if staged)
|
||||
SKIP_FILES=(
|
||||
"*.md" # Documentation often contains example secrets
|
||||
"*.txt" # Same for text files
|
||||
"*example*"
|
||||
"*template*"
|
||||
"*.sample"
|
||||
)
|
||||
|
||||
# Check if a file should be skipped
|
||||
should_skip_file() {
|
||||
local file=$1
|
||||
for pattern in "${SKIP_FILES[@]}"; do
|
||||
# Convert glob to regex
|
||||
local regex="${pattern//\*/.*}"
|
||||
if [[ $file =~ $regex ]]; then
|
||||
return 0 # Skip this file
|
||||
fi
|
||||
done
|
||||
return 1 # Don't skip
|
||||
}
|
||||
|
||||
# Check if a match is whitelisted
|
||||
is_whitelisted() {
|
||||
local match=$1
|
||||
for whitelist in "${WHITELIST[@]}"; do
|
||||
if [[ $match == *"$whitelist"* ]]; then
|
||||
return 0 # Whitelisted
|
||||
fi
|
||||
done
|
||||
return 1 # Not whitelisted
|
||||
}
|
||||
|
||||
# Main secret detection logic
|
||||
detect_secrets() {
|
||||
local files
|
||||
files=$(git diff --cached --name-only --diff-filter=ACM)
|
||||
|
||||
if [ -z "$files" ]; then
|
||||
exit 0 # No staged files
|
||||
fi
|
||||
|
||||
local found_secrets=0
|
||||
local secrets_report=""
|
||||
|
||||
# Iterate through staged files
|
||||
while IFS= read -r file; do
|
||||
# Skip if file should be ignored
|
||||
if should_skip_file "$file"; then
|
||||
continue
|
||||
fi
|
||||
|
||||
# Skip if file doesn't exist (deleted)
|
||||
if [ ! -f "$file" ]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
# Get staged content
|
||||
local content
|
||||
content=$(git show ":$file" 2>/dev/null || continue)
|
||||
|
||||
# Check each pattern
|
||||
for pattern_name in "${!PATTERNS[@]}"; do
|
||||
local pattern="${PATTERNS[$pattern_name]}"
|
||||
local matches
|
||||
matches=$(echo "$content" | grep -noE "$pattern" || true)
|
||||
|
||||
if [ -n "$matches" ]; then
|
||||
# Check each match against whitelist
|
||||
while IFS= read -r match; do
|
||||
local line_num="${match%%:*}"
|
||||
local matched_text="${match#*:}"
|
||||
|
||||
if ! is_whitelisted "$matched_text"; then
|
||||
found_secrets=1
|
||||
secrets_report+=" ${file}:${line_num} - ${pattern_name}\n"
|
||||
secrets_report+=" Content: ${matched_text:0:50}...\n"
|
||||
fi
|
||||
done <<< "$matches"
|
||||
fi
|
||||
done
|
||||
done <<< "$files"
|
||||
|
||||
# Report findings
|
||||
if [ $found_secrets -eq 1 ]; then
|
||||
echo -e "${RED}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||||
echo -e "${RED}✗ COMMIT BLOCKED: Secrets detected in staged files${NC}"
|
||||
echo -e "${RED}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||||
echo ""
|
||||
echo -e "${YELLOW}Found potential secrets:${NC}"
|
||||
echo -e "$secrets_report"
|
||||
echo ""
|
||||
echo -e "${YELLOW}Remediation steps:${NC}"
|
||||
echo " 1. Remove secrets from files"
|
||||
echo " 2. Use environment variables: \${env:VAR_NAME}"
|
||||
echo " 3. Store secrets in ~/.claude/.env (gitignored)"
|
||||
echo " 4. See: guide/ultimate-guide.md Section 8.3.1"
|
||||
echo ""
|
||||
echo -e "${YELLOW}If this is a false positive:${NC}"
|
||||
echo " - Edit .git/hooks/pre-commit and add to WHITELIST array"
|
||||
echo " - Or skip hook: git commit --no-verify (USE WITH CAUTION)"
|
||||
echo ""
|
||||
exit 1
|
||||
fi
|
||||
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Run detection
|
||||
detect_secrets
|
||||
Loading…
Add table
Add a link
Reference in a new issue