no-more-leaked-keys 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,171 @@
1
+ # Workflow: Add MCP Server Securely
2
+
3
+ <purpose>
4
+ Securely add an MCP server with authentication to Claude Code or OpenCode WITHOUT exposing the API key. This replaces the unsafe `claude mcp add` command which echoes secrets to the terminal.
5
+
6
+ **NEVER use `claude mcp add` with `--header "Authorization: Bearer ..."` - it will expose the key!**
7
+ </purpose>
8
+
9
+ <required_reading>
10
+ **Read these reference files NOW:**
11
+ 1. references/keychain-commands.md
12
+ </required_reading>
13
+
14
+ <process>
15
+ ## Step 1: Gather MCP Information
16
+
17
+ Ask the user for:
18
+ 1. **MCP Name** - e.g., `tally`, `github`, `sentry`
19
+ 2. **MCP URL** - e.g., `https://api.tally.so/mcp`
20
+ 3. **Key Name in Keychain** - e.g., `TALLY_API_KEY` (must already be stored via add-key workflow)
21
+ 4. **Target** - `claude` (Claude Code) or `opencode` or `both`
22
+
23
+ ## Step 2: Verify Key Exists in Keychain
24
+
25
+ ```bash
26
+ security find-generic-password -a "$USER" -s "KEY_NAME" -w >/dev/null 2>&1 && \
27
+ echo "[OK] KEY_NAME found in Keychain" || \
28
+ echo "[MISSING] KEY_NAME not in Keychain - add it first with the add-key workflow"
29
+ ```
30
+
31
+ If missing, switch to `add-key.md` workflow first.
32
+
33
+ ## Step 3: Add to Claude Code (~/.claude.json)
34
+
35
+ **CRITICAL: We directly edit the JSON file. We NEVER echo or print the key.**
36
+
37
+ Use this Python script to safely add the MCP without exposing the key:
38
+
39
+ ```bash
40
+ python3 << 'PYTHON_SCRIPT'
41
+ import json
42
+ import subprocess
43
+ import os
44
+
45
+ # Configuration - REPLACE THESE
46
+ MCP_NAME = "MCP_NAME_HERE"
47
+ MCP_URL = "MCP_URL_HERE"
48
+ KEY_NAME = "KEY_NAME_HERE"
49
+
50
+ # Get key from Keychain (never printed)
51
+ result = subprocess.run(
52
+ ["security", "find-generic-password", "-a", os.environ["USER"], "-s", KEY_NAME, "-w"],
53
+ capture_output=True, text=True
54
+ )
55
+ if result.returncode != 0:
56
+ print(f"[ERROR] Could not retrieve {KEY_NAME} from Keychain")
57
+ exit(1)
58
+
59
+ api_key = result.stdout.strip()
60
+
61
+ # Read existing config
62
+ config_path = os.path.expanduser("~/.claude.json")
63
+ with open(config_path, "r") as f:
64
+ config = json.load(f)
65
+
66
+ # Add MCP server
67
+ if "mcpServers" not in config:
68
+ config["mcpServers"] = {}
69
+
70
+ config["mcpServers"][MCP_NAME] = {
71
+ "type": "http",
72
+ "url": MCP_URL,
73
+ "headers": {
74
+ "Authorization": f"Bearer {api_key}"
75
+ }
76
+ }
77
+
78
+ # Write back
79
+ with open(config_path, "w") as f:
80
+ json.dump(config, f, indent=2)
81
+
82
+ print(f"[SUCCESS] Added {MCP_NAME} MCP to Claude Code")
83
+ print(f"[SUCCESS] Key retrieved from Keychain (never displayed)")
84
+ PYTHON_SCRIPT
85
+ ```
86
+
87
+ ## Step 4: Add to OpenCode (~/.config/opencode/opencode.json)
88
+
89
+ For OpenCode, we can use environment variable syntax which is safer:
90
+
91
+ ```bash
92
+ python3 << 'PYTHON_SCRIPT'
93
+ import json
94
+ import os
95
+
96
+ # Configuration - REPLACE THESE
97
+ MCP_NAME = "MCP_NAME_HERE"
98
+ MCP_URL = "MCP_URL_HERE"
99
+ KEY_NAME = "KEY_NAME_HERE"
100
+
101
+ config_path = os.path.expanduser("~/.config/opencode/opencode.json")
102
+
103
+ # Read existing config
104
+ with open(config_path, "r") as f:
105
+ config = json.load(f)
106
+
107
+ # Add MCP server with env var reference (OpenCode expands this at runtime)
108
+ if "mcp" not in config:
109
+ config["mcp"] = {}
110
+
111
+ config["mcp"][MCP_NAME] = {
112
+ "type": "remote",
113
+ "url": MCP_URL,
114
+ "enabled": True,
115
+ "headers": {
116
+ "Authorization": "Bearer {env:" + KEY_NAME + "}"
117
+ }
118
+ }
119
+
120
+ # Write back
121
+ with open(config_path, "w") as f:
122
+ json.dump(config, f, indent=2)
123
+
124
+ print(f"[SUCCESS] Added {MCP_NAME} MCP to OpenCode")
125
+ print(f"[SUCCESS] Configured to use {{env:{KEY_NAME}}} (loaded from environment at runtime)")
126
+ PYTHON_SCRIPT
127
+ ```
128
+
129
+ **Note:** OpenCode requires the key to be exported in ~/.zshrc:
130
+ ```bash
131
+ grep -q "KEY_NAME" ~/.zshrc || echo 'export KEY_NAME=$(security find-generic-password -a "$USER" -s "KEY_NAME" -w 2>/dev/null)' >> ~/.zshrc
132
+ ```
133
+
134
+ ## Step 5: Verify Installation
135
+
136
+ ```bash
137
+ # For Claude Code
138
+ claude mcp list 2>/dev/null | grep -i "MCP_NAME" && echo "[OK] MCP_NAME visible in Claude Code" || echo "[CHECK] Restart Claude Code to see MCP_NAME"
139
+
140
+ # For OpenCode - just confirm the config exists
141
+ grep -q "MCP_NAME" ~/.config/opencode/opencode.json && echo "[OK] MCP_NAME configured in OpenCode"
142
+ ```
143
+
144
+ ## Step 6: Remind User to Restart
145
+
146
+ Tell the user:
147
+ - "Restart Claude Code / OpenCode for the MCP to be available"
148
+ - "Use `/mcp` to see connected servers"
149
+ - "The API key was pulled from Keychain and written directly to config - it was never displayed"
150
+ </process>
151
+
152
+ <warning>
153
+ ## NEVER DO THIS
154
+
155
+ ```bash
156
+ # UNSAFE - exposes key in terminal output!
157
+ claude mcp add --transport http tally https://api.tally.so/mcp \
158
+ --header "Authorization: Bearer $(security find-generic-password -a "$USER" -s "TALLY_API_KEY" -w)"
159
+ ```
160
+
161
+ The `claude mcp add` command echoes the full config including headers back to the terminal. Always use this workflow instead.
162
+ </warning>
163
+
164
+ <success_criteria>
165
+ This workflow is complete when:
166
+ - [ ] Key verified in Keychain
167
+ - [ ] MCP config added to target (Claude Code and/or OpenCode)
168
+ - [ ] Key was NEVER echoed to terminal
169
+ - [ ] User reminded to restart their tool
170
+ - [ ] User can verify MCP is connected
171
+ </success_criteria>
@@ -0,0 +1,83 @@
1
+ # Workflow: List Stored Keys
2
+
3
+ <required_reading>
4
+ **Read these reference files NOW:**
5
+ 1. references/keychain-commands.md
6
+ </required_reading>
7
+
8
+ <process>
9
+ ## Step 1: List All Generic Passwords
10
+
11
+ The `security` command can dump keychain info, but we need to filter for our API keys:
12
+
13
+ ```bash
14
+ # List all generic password entries (shows service names, not values)
15
+ security dump-keychain | grep -A 4 'class: "genp"' | grep '"svce"' | cut -d'"' -f4 | sort -u
16
+ ```
17
+
18
+ This shows the service names (which are our key names like `OPENAI_API_KEY`).
19
+
20
+ ## Step 2: Filter for Common API Keys
21
+
22
+ Check for commonly used API key names:
23
+
24
+ ```bash
25
+ COMMON_KEYS=(
26
+ "OPENAI_API_KEY"
27
+ "ANTHROPIC_API_KEY"
28
+ "STRIPE_SECRET_KEY"
29
+ "STRIPE_PUBLISHABLE_KEY"
30
+ "DATABASE_URL"
31
+ "SUPABASE_URL"
32
+ "SUPABASE_ANON_KEY"
33
+ "SUPABASE_SERVICE_ROLE_KEY"
34
+ "GITHUB_TOKEN"
35
+ "VERCEL_TOKEN"
36
+ "AWS_ACCESS_KEY_ID"
37
+ "AWS_SECRET_ACCESS_KEY"
38
+ "GOOGLE_API_KEY"
39
+ "SENDGRID_API_KEY"
40
+ "TWILIO_ACCOUNT_SID"
41
+ "TWILIO_AUTH_TOKEN"
42
+ )
43
+
44
+ echo "Checking for common API keys in Keychain..."
45
+ echo "==========================================="
46
+ for key in "${COMMON_KEYS[@]}"; do
47
+ if security find-generic-password -a "$USER" -s "$key" -w >/dev/null 2>&1; then
48
+ echo "[FOUND] $key"
49
+ fi
50
+ done
51
+ ```
52
+
53
+ ## Step 3: Present Results
54
+
55
+ Display found keys to the user in a clear format:
56
+
57
+ ```
58
+ Available API Keys in Keychain:
59
+ ================================
60
+ [FOUND] OPENAI_API_KEY
61
+ [FOUND] ANTHROPIC_API_KEY
62
+ [FOUND] DATABASE_URL
63
+
64
+ Not in Keychain (if you need them):
65
+ ====================================
66
+ [ ] STRIPE_SECRET_KEY
67
+ [ ] GITHUB_TOKEN
68
+ ```
69
+
70
+ ## Step 4: Offer Next Steps
71
+
72
+ Ask the user:
73
+ 1. **Add a missing key** - Switch to add-key.md workflow
74
+ 2. **Populate .env** - Switch to populate-env.md workflow
75
+ 3. **Done** - Exit skill
76
+ </process>
77
+
78
+ <success_criteria>
79
+ This workflow is complete when:
80
+ - [ ] Available keys listed
81
+ - [ ] User can see which keys are stored
82
+ - [ ] User offered next steps
83
+ </success_criteria>
@@ -0,0 +1,128 @@
1
+ # Workflow: Populate .env from Keychain
2
+
3
+ <required_reading>
4
+ **Read these reference files NOW:**
5
+ 1. references/keychain-commands.md
6
+ 2. references/env-file-patterns.md
7
+ </required_reading>
8
+
9
+ <process>
10
+ ## Step 1: Identify Required Keys
11
+
12
+ Ask the user or detect from the project:
13
+ - Check for `.env.example` or `.env.template` in the project
14
+ - Check for environment variable references in code (e.g., `process.env.X`, `os.environ["X"]`)
15
+ - Ask the user what keys the project needs
16
+
17
+ ```bash
18
+ # Check for .env.example
19
+ cat .env.example 2>/dev/null || echo "No .env.example found"
20
+
21
+ # Search for env var usage
22
+ grep -r "process\.env\." --include="*.js" --include="*.ts" . 2>/dev/null | head -20
23
+ grep -r "os\.environ" --include="*.py" . 2>/dev/null | head -20
24
+ ```
25
+
26
+ ## Step 2: Check Keychain for Available Keys
27
+
28
+ For each required key, check if it exists in Keychain:
29
+
30
+ ```bash
31
+ # Check if a key exists (returns 0 if found, non-zero if not)
32
+ security find-generic-password -a "$USER" -s "KEY_NAME" -w >/dev/null 2>&1 && echo "Found" || echo "Not found"
33
+ ```
34
+
35
+ Report which keys are available and which are missing.
36
+
37
+ ## Step 3: Handle Missing Keys
38
+
39
+ If keys are missing from Keychain:
40
+
41
+ 1. Ask the user if they want to add the missing keys now
42
+ 2. If yes, switch to the `add-key.md` workflow for each missing key
43
+ 3. If no, proceed with available keys only (warn about missing ones)
44
+
45
+ ## Step 4: Ensure .gitignore Protection
46
+
47
+ Before writing .env, verify it's protected:
48
+
49
+ ```bash
50
+ # Check if .gitignore exists and contains .env
51
+ if [ -f .gitignore ]; then
52
+ grep -q "^\.env$" .gitignore || echo ".env" >> .gitignore
53
+ else
54
+ echo ".env" > .gitignore
55
+ fi
56
+ ```
57
+
58
+ ## Step 5: Generate .env File
59
+
60
+ Write each key to .env by retrieving from Keychain, then secure the file:
61
+
62
+ ```bash
63
+ # Create or overwrite .env
64
+ > .env
65
+
66
+ # Add each key
67
+ echo "OPENAI_API_KEY=$(security find-generic-password -a "$USER" -s "OPENAI_API_KEY" -w 2>/dev/null)" >> .env
68
+ echo "ANTHROPIC_API_KEY=$(security find-generic-password -a "$USER" -s "ANTHROPIC_API_KEY" -w 2>/dev/null)" >> .env
69
+ # ... repeat for each key
70
+
71
+ # IMPORTANT: Secure file permissions (only owner can read/write)
72
+ chmod 600 .env
73
+ ```
74
+
75
+ **Alternative: Create a reusable script**
76
+
77
+ ```bash
78
+ cat > populate-env.sh << 'EOF'
79
+ #!/bin/bash
80
+ # Populate .env from macOS Keychain
81
+ # Add key names to the KEYS array
82
+
83
+ KEYS=(
84
+ "OPENAI_API_KEY"
85
+ "ANTHROPIC_API_KEY"
86
+ # Add more keys here
87
+ )
88
+
89
+ # Ensure .gitignore protection
90
+ [ -f .gitignore ] && grep -q "^\.env$" .gitignore || echo ".env" >> .gitignore
91
+
92
+ > .env
93
+ for key in "${KEYS[@]}"; do
94
+ value=$(security find-generic-password -a "$USER" -s "$key" -w 2>/dev/null)
95
+ if [ -n "$value" ]; then
96
+ echo "$key=$value" >> .env
97
+ echo "[OK] $key"
98
+ else
99
+ echo "[MISSING] $key"
100
+ fi
101
+ done
102
+
103
+ # Secure file permissions
104
+ chmod 600 .env
105
+ echo "Done. .env created with secure permissions (600)."
106
+ EOF
107
+ chmod +x populate-env.sh
108
+ ```
109
+
110
+ ## Step 6: Verify Success
111
+
112
+ ```bash
113
+ # Count lines in .env (should match expected key count)
114
+ wc -l .env
115
+
116
+ # Show keys present (values hidden)
117
+ cut -d= -f1 .env
118
+ ```
119
+ </process>
120
+
121
+ <success_criteria>
122
+ This workflow is complete when:
123
+ - [ ] All required keys identified
124
+ - [ ] Missing keys reported to user (and optionally added)
125
+ - [ ] .env is in .gitignore
126
+ - [ ] .env file created with keys from Keychain
127
+ - [ ] User can verify which keys were populated
128
+ </success_criteria>
@@ -0,0 +1,71 @@
1
+ # Workflow: Remove Key from Keychain
2
+
3
+ <required_reading>
4
+ **Read these reference files NOW:**
5
+ 1. references/keychain-commands.md
6
+ </required_reading>
7
+
8
+ <process>
9
+ ## Step 1: Confirm Key Name
10
+
11
+ Ask the user which key they want to remove. Verify it exists first:
12
+
13
+ ```bash
14
+ security find-generic-password -a "$USER" -s "KEY_NAME" -w >/dev/null 2>&1 && \
15
+ echo "Key found: KEY_NAME" || \
16
+ echo "Key not found: KEY_NAME"
17
+ ```
18
+
19
+ ## Step 2: Confirm Deletion
20
+
21
+ **IMPORTANT**: Deletion is permanent. Confirm with the user:
22
+
23
+ "Are you sure you want to delete KEY_NAME from Keychain? This cannot be undone. You will need to re-add the key if you need it again."
24
+
25
+ ## Step 3: Delete the Key
26
+
27
+ ```bash
28
+ security delete-generic-password -a "$USER" -s "KEY_NAME"
29
+ ```
30
+
31
+ Expected output on success:
32
+ ```
33
+ password has been deleted.
34
+ ```
35
+
36
+ ## Step 4: Verify Deletion
37
+
38
+ ```bash
39
+ security find-generic-password -a "$USER" -s "KEY_NAME" -w >/dev/null 2>&1 && \
40
+ echo "Error: Key still exists" || \
41
+ echo "Confirmed: KEY_NAME has been removed"
42
+ ```
43
+
44
+ ## Step 5: Update .env if Needed
45
+
46
+ If a .env file exists and contains this key, warn the user:
47
+
48
+ ```bash
49
+ if [ -f .env ] && grep -q "KEY_NAME=" .env; then
50
+ echo "Warning: .env still contains KEY_NAME"
51
+ echo "You may want to regenerate .env or remove this line"
52
+ fi
53
+ ```
54
+
55
+ ## Step 6: Offer Next Steps
56
+
57
+ Ask the user:
58
+ 1. **Remove another key** - Repeat this workflow
59
+ 2. **Re-add this key** - Switch to add-key.md workflow
60
+ 3. **Update .env** - Switch to populate-env.md workflow
61
+ 4. **Done** - Exit skill
62
+ </process>
63
+
64
+ <success_criteria>
65
+ This workflow is complete when:
66
+ - [ ] Key existence confirmed
67
+ - [ ] User confirmed deletion
68
+ - [ ] Key deleted from Keychain
69
+ - [ ] Deletion verified
70
+ - [ ] User warned about .env if applicable
71
+ </success_criteria>
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "no-more-leaked-keys",
3
+ "version": "1.0.0",
4
+ "description": "Stop accidentally exposing API keys. Secure key management for Claude Code using macOS Keychain.",
5
+ "bin": {
6
+ "no-more-leaked-keys": "./bin/install.js"
7
+ },
8
+ "scripts": {
9
+ "postinstall": "node bin/install.js"
10
+ },
11
+ "keywords": [
12
+ "api-keys",
13
+ "security",
14
+ "secrets",
15
+ "keychain",
16
+ "macos",
17
+ "claude",
18
+ "claude-code",
19
+ "opencode",
20
+ "dotenv",
21
+ "env",
22
+ "environment-variables",
23
+ "mcp",
24
+ "credentials",
25
+ "secret-management"
26
+ ],
27
+ "author": "Andrew Naegele",
28
+ "license": "SEE LICENSE IN LICENSE",
29
+ "repository": {
30
+ "type": "git",
31
+ "url": "https://github.com/Vibe-Marketer/no-more-leaked-keys.git"
32
+ },
33
+ "homepage": "https://github.com/Vibe-Marketer/no-more-leaked-keys#readme",
34
+ "bugs": {
35
+ "url": "https://github.com/Vibe-Marketer/no-more-leaked-keys/issues"
36
+ },
37
+ "os": ["darwin"],
38
+ "engines": {
39
+ "node": ">=14.0.0"
40
+ },
41
+ "files": [
42
+ "bin/",
43
+ "keychain-secrets/",
44
+ "commands/",
45
+ "hooks/",
46
+ "LICENSE",
47
+ "README.md"
48
+ ]
49
+ }