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.
- package/LICENSE +63 -0
- package/README.md +395 -0
- package/bin/install.js +216 -0
- package/commands/add-mcp.md +17 -0
- package/commands/secrets.md +12 -0
- package/hooks/block-unsafe-mcp-add.sh +17 -0
- package/keychain-secrets/SKILL.md +129 -0
- package/keychain-secrets/references/env-file-patterns.md +220 -0
- package/keychain-secrets/references/keychain-commands.md +159 -0
- package/keychain-secrets/workflows/add-key.md +99 -0
- package/keychain-secrets/workflows/add-mcp.md +171 -0
- package/keychain-secrets/workflows/list-keys.md +83 -0
- package/keychain-secrets/workflows/populate-env.md +128 -0
- package/keychain-secrets/workflows/remove-key.md +71 -0
- package/package.json +49 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
No More Leaked Keys - License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Andrew Naegele. All Rights Reserved.
|
|
4
|
+
|
|
5
|
+
================================================================================
|
|
6
|
+
GRANT OF LICENSE
|
|
7
|
+
================================================================================
|
|
8
|
+
|
|
9
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
10
|
+
of this software and associated documentation files (the "Software"), to use,
|
|
11
|
+
copy, modify, and distribute the Software for personal and commercial purposes,
|
|
12
|
+
subject to the following conditions:
|
|
13
|
+
|
|
14
|
+
================================================================================
|
|
15
|
+
CONDITIONS
|
|
16
|
+
================================================================================
|
|
17
|
+
|
|
18
|
+
1. ATTRIBUTION REQUIRED
|
|
19
|
+
You must give appropriate credit to Andrew Naegele, provide a link to the
|
|
20
|
+
original repository, and indicate if changes were made. You may do so in
|
|
21
|
+
any reasonable manner, but not in any way that suggests the author endorses
|
|
22
|
+
you or your use.
|
|
23
|
+
|
|
24
|
+
2. NOT FOR RESALE
|
|
25
|
+
You may NOT sell, sublicense, or otherwise commercialize the Software itself.
|
|
26
|
+
You MAY use the Software as part of a larger commercial project or service,
|
|
27
|
+
but the Software itself cannot be the product being sold.
|
|
28
|
+
|
|
29
|
+
3. NO WARRANTY
|
|
30
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
31
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
32
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
33
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
34
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
35
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
36
|
+
SOFTWARE.
|
|
37
|
+
|
|
38
|
+
4. SHARE IMPROVEMENTS
|
|
39
|
+
If you improve this Software, consider contributing back to the community
|
|
40
|
+
by submitting a pull request to the original repository.
|
|
41
|
+
|
|
42
|
+
================================================================================
|
|
43
|
+
SUPPORT THE CREATOR
|
|
44
|
+
================================================================================
|
|
45
|
+
|
|
46
|
+
If this Software helped you, consider:
|
|
47
|
+
|
|
48
|
+
- Starring the GitHub repository
|
|
49
|
+
- Following Andrew Naegele on social media
|
|
50
|
+
- Joining the Vibe Marketing community
|
|
51
|
+
|
|
52
|
+
CONNECT:
|
|
53
|
+
|
|
54
|
+
Community: https://skool.com/vibe-marketing
|
|
55
|
+
Website: https://callvaultai.com
|
|
56
|
+
X/Twitter: https://x.com/andrewnaegele
|
|
57
|
+
LinkedIn: https://linkedin.com/in/andrewnaegele
|
|
58
|
+
Instagram: https://instagram.com/andrew.naegele
|
|
59
|
+
Facebook: https://facebook.com/andrewnaegele
|
|
60
|
+
|
|
61
|
+
================================================================================
|
|
62
|
+
|
|
63
|
+
Copyright (c) 2026 Andrew Naegele. All Rights Reserved.
|
package/README.md
ADDED
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
# No More Leaked Keys
|
|
2
|
+
|
|
3
|
+
**Stop accidentally committing API keys to git.** Store them in macOS Keychain, populate `.env` files on demand.
|
|
4
|
+
|
|
5
|
+
[](#license)
|
|
6
|
+
[](#requirements)
|
|
7
|
+
[](#installation)
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## The Problem (You've Been Here)
|
|
12
|
+
|
|
13
|
+
You're building an app. You need API keys. So you do what everyone does:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
# .env file
|
|
17
|
+
OPENAI_API_KEY=sk-abc123...
|
|
18
|
+
STRIPE_SECRET_KEY=sk_live_...
|
|
19
|
+
DATABASE_URL=postgres://user:password@...
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Then one day:
|
|
23
|
+
|
|
24
|
+
- You forget `.env` isn't in `.gitignore`
|
|
25
|
+
- You `git add .` and push
|
|
26
|
+
- **Your keys are now public**
|
|
27
|
+
- Bots scrape GitHub constantly — they find your key in minutes
|
|
28
|
+
- You wake up to a $10,000 AWS bill
|
|
29
|
+
- Or your OpenAI account is drained
|
|
30
|
+
- Or your database is wiped
|
|
31
|
+
|
|
32
|
+
**This happens every single day to developers.**
|
|
33
|
+
|
|
34
|
+
Even if you're careful:
|
|
35
|
+
- Keys are visible when you paste them
|
|
36
|
+
- Keys sit in your clipboard (easy to accidentally paste in Slack)
|
|
37
|
+
- Keys are stored in plain text files
|
|
38
|
+
- Screen sharing? Recording? Your keys are exposed.
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## The Solution
|
|
43
|
+
|
|
44
|
+
**Store API keys in macOS Keychain. Never in files.**
|
|
45
|
+
|
|
46
|
+
- Keychain is **encrypted** (AES-256)
|
|
47
|
+
- Keychain is **local** (not synced to cloud)
|
|
48
|
+
- Keychain is **scriptable** (we can automate it)
|
|
49
|
+
- Generate `.env` files **on demand** from Keychain
|
|
50
|
+
- Keys are **invisible** when you paste them
|
|
51
|
+
- Clipboard is **auto-cleared** after storing
|
|
52
|
+
|
|
53
|
+
**One Keychain. Every project. Zero risk.**
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## How It Works
|
|
58
|
+
|
|
59
|
+
### Step 1: Store Your API Key (One Time)
|
|
60
|
+
|
|
61
|
+
A Terminal window opens with a secure prompt:
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
====================================
|
|
65
|
+
SECURE API KEY INPUT
|
|
66
|
+
====================================
|
|
67
|
+
|
|
68
|
+
Paste your key below (it will be invisible)
|
|
69
|
+
Then press ENTER
|
|
70
|
+
|
|
71
|
+
OPENAI_API_KEY: █
|
|
72
|
+
|
|
73
|
+
[SUCCESS] OPENAI_API_KEY stored in Keychain
|
|
74
|
+
[SUCCESS] Clipboard cleared
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
**What happens:**
|
|
78
|
+
1. You paste your key — **nothing appears on screen**
|
|
79
|
+
2. Key is encrypted and stored in macOS Keychain
|
|
80
|
+
3. Your clipboard is cleared (can't accidentally paste elsewhere)
|
|
81
|
+
4. The variable is cleared from memory
|
|
82
|
+
|
|
83
|
+
**You never see the key. It never touches a file. It's encrypted immediately.**
|
|
84
|
+
|
|
85
|
+
### Step 2: Populate .env (Per Project)
|
|
86
|
+
|
|
87
|
+
When you start a project that needs environment variables:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
# The skill reads .env.example and pulls from Keychain
|
|
91
|
+
[OK] OPENAI_API_KEY
|
|
92
|
+
[OK] ANTHROPIC_API_KEY
|
|
93
|
+
[OK] DATABASE_URL
|
|
94
|
+
[MISSING] STRIPE_KEY # Not in Keychain yet - prompts to add
|
|
95
|
+
|
|
96
|
+
Done. .env created with secure permissions (600).
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
The `.env` file is:
|
|
100
|
+
- Created with **owner-only permissions** (`chmod 600`)
|
|
101
|
+
- Automatically added to **`.gitignore`**
|
|
102
|
+
- **Never committed to git**
|
|
103
|
+
|
|
104
|
+
### Step 3: Use In Your Code (Normal .env Usage)
|
|
105
|
+
|
|
106
|
+
Your code works exactly the same:
|
|
107
|
+
|
|
108
|
+
**Python:**
|
|
109
|
+
```python
|
|
110
|
+
from dotenv import load_dotenv
|
|
111
|
+
import os
|
|
112
|
+
|
|
113
|
+
load_dotenv()
|
|
114
|
+
api_key = os.environ['OPENAI_API_KEY']
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
**Node.js:**
|
|
118
|
+
```javascript
|
|
119
|
+
require('dotenv').config()
|
|
120
|
+
const apiKey = process.env.OPENAI_API_KEY
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
**The difference:** Your keys came from Keychain, not a file you might accidentally commit.
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## Security Model
|
|
128
|
+
|
|
129
|
+
Every layer is protected:
|
|
130
|
+
|
|
131
|
+
| Attack Vector | Protection |
|
|
132
|
+
|---------------|-----------|
|
|
133
|
+
| **Visible on screen** | `read -s` — silent input, nothing displayed |
|
|
134
|
+
| **Stays in memory** | `unset` — variable cleared immediately |
|
|
135
|
+
| **Left in clipboard** | `pbcopy < /dev/null` — clipboard auto-cleared |
|
|
136
|
+
| **Stored in plain text** | macOS Keychain — AES-256 encryption |
|
|
137
|
+
| **Readable by others** | `chmod 600` — owner-only file permissions |
|
|
138
|
+
| **Committed to git** | Auto-adds `.env` to `.gitignore` |
|
|
139
|
+
| **In terminal history** | Key never part of a command string |
|
|
140
|
+
| **MCP server config** | Direct file edit — never echoed to terminal |
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## Adding MCP Servers Securely
|
|
145
|
+
|
|
146
|
+
**WARNING: Anthropic's `claude mcp add` command exposes your API key!**
|
|
147
|
+
|
|
148
|
+
The native `claude mcp add` command has a security flaw - it **echoes your API key** to the terminal:
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
# DANGEROUS - This prints your key to the terminal!
|
|
152
|
+
claude mcp add --transport http tally https://api.tally.so/mcp \
|
|
153
|
+
--header "Authorization: Bearer $TALLY_API_KEY"
|
|
154
|
+
# Output includes: "Authorization": "Bearer sk-actual-key-here" <-- EXPOSED!
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
**This skill fixes that.** After installation:
|
|
158
|
+
1. The unsafe command is **automatically blocked** by a security hook
|
|
159
|
+
2. You use `/secrets` or `/add-mcp` instead
|
|
160
|
+
3. Keys are pulled from Keychain silently and written directly to config
|
|
161
|
+
4. Keys are **never echoed anywhere**
|
|
162
|
+
|
|
163
|
+
```
|
|
164
|
+
> /secrets
|
|
165
|
+
> "Add MCP server securely"
|
|
166
|
+
> MCP name: tally
|
|
167
|
+
> MCP URL: https://api.tally.so/mcp
|
|
168
|
+
> Key name in Keychain: TALLY_API_KEY
|
|
169
|
+
> Target: claude
|
|
170
|
+
|
|
171
|
+
[SUCCESS] Added tally MCP to Claude Code
|
|
172
|
+
[SUCCESS] Key retrieved from Keychain (never displayed)
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## Installation
|
|
178
|
+
|
|
179
|
+
### NPM Install (Recommended)
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
npx no-more-leaked-keys
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Or install globally:
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
npm install -g no-more-leaked-keys
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### Alternative: Git Clone
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
git clone https://github.com/Vibe-Marketer/no-more-leaked-keys.git && cd no-more-leaked-keys && ./install.sh
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
This installs:
|
|
198
|
+
- The skill (`/secrets`)
|
|
199
|
+
- The commands (`/add-mcp`)
|
|
200
|
+
- **A security hook that blocks unsafe `claude mcp add` commands**
|
|
201
|
+
|
|
202
|
+
Then restart Claude Code.
|
|
203
|
+
|
|
204
|
+
### What the Security Hook Does
|
|
205
|
+
|
|
206
|
+
After installation, if anyone (you or Claude) tries to run `claude mcp add` with authentication headers, it gets **automatically blocked**:
|
|
207
|
+
|
|
208
|
+
```
|
|
209
|
+
BLOCKED: claude mcp add with auth headers exposes your API key in terminal output!
|
|
210
|
+
|
|
211
|
+
Use /secrets or /add-mcp instead - these pull keys from Keychain securely
|
|
212
|
+
without exposing them.
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
This protects you even if you forget and try to use the unsafe command.
|
|
216
|
+
|
|
217
|
+
### Manual Installation
|
|
218
|
+
|
|
219
|
+
If you prefer to install manually:
|
|
220
|
+
|
|
221
|
+
```bash
|
|
222
|
+
git clone https://github.com/Vibe-Marketer/no-more-leaked-keys.git
|
|
223
|
+
cd no-more-leaked-keys
|
|
224
|
+
|
|
225
|
+
# Install skill and commands
|
|
226
|
+
mkdir -p ~/.claude/skills ~/.claude/commands ~/.claude/hooks
|
|
227
|
+
cp -r keychain-secrets ~/.claude/skills/
|
|
228
|
+
cp commands/*.md ~/.claude/commands/
|
|
229
|
+
cp hooks/*.sh ~/.claude/hooks/
|
|
230
|
+
chmod +x ~/.claude/hooks/*.sh
|
|
231
|
+
|
|
232
|
+
# Then manually add the hook to ~/.claude/settings.json (see install.sh for details)
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
**Usage:**
|
|
236
|
+
- Type `/secrets` in Claude Code
|
|
237
|
+
- Or just say "add my API key" or "set up .env"
|
|
238
|
+
|
|
239
|
+
### For Everyone (Manual Terminal Usage)
|
|
240
|
+
|
|
241
|
+
You don't need Claude Code. Use these commands directly:
|
|
242
|
+
|
|
243
|
+
**Add a key to Keychain:**
|
|
244
|
+
```bash
|
|
245
|
+
echo -n "Paste API key: " && read -s K && \
|
|
246
|
+
security add-generic-password -a "$USER" -s "OPENAI_API_KEY" -w "$K" && \
|
|
247
|
+
unset K && pbcopy < /dev/null && echo -e "\nStored!"
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
**Retrieve a key:**
|
|
251
|
+
```bash
|
|
252
|
+
security find-generic-password -a "$USER" -s "OPENAI_API_KEY" -w
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
**Generate .env from Keychain:**
|
|
256
|
+
```bash
|
|
257
|
+
echo "OPENAI_API_KEY=$(security find-generic-password -a "$USER" -s "OPENAI_API_KEY" -w)" > .env
|
|
258
|
+
chmod 600 .env
|
|
259
|
+
echo ".env" >> .gitignore
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
---
|
|
263
|
+
|
|
264
|
+
## Why Keychain Over Other Methods?
|
|
265
|
+
|
|
266
|
+
| Method | Problem |
|
|
267
|
+
|--------|---------|
|
|
268
|
+
| **`.env` files** | Can be committed to git, stored in plain text |
|
|
269
|
+
| **Shell config (`~/.zshrc`)** | Visible in dotfiles, often synced to cloud |
|
|
270
|
+
| **Password managers (1Password, etc.)** | Requires manual copy/paste every time |
|
|
271
|
+
| **Environment variables in CI/CD** | Great for prod, but doesn't help local dev |
|
|
272
|
+
| **macOS Keychain** | Encrypted, local, scriptable, automatic |
|
|
273
|
+
|
|
274
|
+
---
|
|
275
|
+
|
|
276
|
+
## FAQ
|
|
277
|
+
|
|
278
|
+
### Is this actually secure?
|
|
279
|
+
|
|
280
|
+
Yes. macOS Keychain uses AES-256 encryption, the same standard used by banks and governments. Your keys are encrypted at rest and protected by your macOS login password.
|
|
281
|
+
|
|
282
|
+
### What if someone has access to my Mac?
|
|
283
|
+
|
|
284
|
+
If someone has your Mac AND your login password, they can access your Keychain. But at that point, they can also access everything else. Keychain is as secure as your Mac login.
|
|
285
|
+
|
|
286
|
+
### Does this work with Touch ID / Face ID?
|
|
287
|
+
|
|
288
|
+
Yes. Keychain integrates with biometric authentication. You may be prompted the first time an app accesses a key.
|
|
289
|
+
|
|
290
|
+
### Can I use this on Linux or Windows?
|
|
291
|
+
|
|
292
|
+
Not directly — this uses macOS Keychain. However:
|
|
293
|
+
- **Linux:** You could adapt this to use `gnome-keyring` or `pass`
|
|
294
|
+
- **Windows:** You could use Windows Credential Manager
|
|
295
|
+
|
|
296
|
+
PRs welcome for cross-platform support!
|
|
297
|
+
|
|
298
|
+
### What happens if I need to share keys with my team?
|
|
299
|
+
|
|
300
|
+
This is for **local development security**. For team/production secrets, use:
|
|
301
|
+
- 1Password / Bitwarden shared vaults
|
|
302
|
+
- AWS Secrets Manager
|
|
303
|
+
- HashiCorp Vault
|
|
304
|
+
- Doppler
|
|
305
|
+
|
|
306
|
+
### Will this slow down my workflow?
|
|
307
|
+
|
|
308
|
+
No. You add each key once. After that, populating `.env` for any project takes seconds.
|
|
309
|
+
|
|
310
|
+
### What if I delete a key by accident?
|
|
311
|
+
|
|
312
|
+
It's gone. Keychain doesn't have a recycle bin. You'll need to get the key again from wherever you originally got it (OpenAI dashboard, Stripe dashboard, etc.).
|
|
313
|
+
|
|
314
|
+
### Does the `.env` file contain my actual keys?
|
|
315
|
+
|
|
316
|
+
Yes, temporarily. The `.env` file is created with your keys so your code can read them. But:
|
|
317
|
+
- It has `600` permissions (only you can read it)
|
|
318
|
+
- It's in `.gitignore` (never committed)
|
|
319
|
+
- You can delete it and regenerate anytime
|
|
320
|
+
|
|
321
|
+
The difference is: you're not manually pasting keys where you might accidentally commit them.
|
|
322
|
+
|
|
323
|
+
---
|
|
324
|
+
|
|
325
|
+
## File Structure
|
|
326
|
+
|
|
327
|
+
```
|
|
328
|
+
no-more-leaked-keys/
|
|
329
|
+
├── README.md # You're reading this
|
|
330
|
+
├── LICENSE # Usage terms
|
|
331
|
+
├── install.sh # One-command installer
|
|
332
|
+
├── keychain-secrets/ # Claude Code skill
|
|
333
|
+
│ ├── SKILL.md
|
|
334
|
+
│ ├── workflows/
|
|
335
|
+
│ │ ├── add-key.md # Store keys in Keychain
|
|
336
|
+
│ │ ├── add-mcp.md # Securely add MCP servers
|
|
337
|
+
│ │ ├── list-keys.md
|
|
338
|
+
│ │ ├── populate-env.md
|
|
339
|
+
│ │ └── remove-key.md
|
|
340
|
+
│ └── references/
|
|
341
|
+
│ ├── keychain-commands.md
|
|
342
|
+
│ └── env-file-patterns.md
|
|
343
|
+
├── commands/
|
|
344
|
+
│ ├── secrets.md # /secrets slash command
|
|
345
|
+
│ └── add-mcp.md # /add-mcp slash command
|
|
346
|
+
├── hooks/
|
|
347
|
+
│ └── block-unsafe-mcp-add.sh # Security hook (blocks unsafe commands)
|
|
348
|
+
└── test-project/ # Example project for testing
|
|
349
|
+
├── .env.example
|
|
350
|
+
├── test-node.js
|
|
351
|
+
└── test-python.py
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
---
|
|
355
|
+
|
|
356
|
+
## Requirements
|
|
357
|
+
|
|
358
|
+
- **macOS** (uses Keychain and `security` CLI)
|
|
359
|
+
- **Claude Code** (optional, for the skill — or use manual commands)
|
|
360
|
+
|
|
361
|
+
---
|
|
362
|
+
|
|
363
|
+
## Contributing
|
|
364
|
+
|
|
365
|
+
PRs welcome! Please ensure no actual secrets are committed (obviously).
|
|
366
|
+
|
|
367
|
+
---
|
|
368
|
+
|
|
369
|
+
## Connect
|
|
370
|
+
|
|
371
|
+
Built by **Andrew Naegele**
|
|
372
|
+
|
|
373
|
+
- **Community:** [skool.com/vibe-marketing](https://skool.com/vibe-marketing)
|
|
374
|
+
- **Website:** [callvaultai.com](https://callvaultai.com)
|
|
375
|
+
- **X/Twitter:** [@andrewnaegele](https://x.com/andrewnaegele)
|
|
376
|
+
- **LinkedIn:** [linkedin.com/in/andrewnaegele](https://linkedin.com/in/andrewnaegele)
|
|
377
|
+
- **Instagram:** [@andrew.naegele](https://instagram.com/andrew.naegele)
|
|
378
|
+
- **Facebook:** [facebook.com/andrewnaegele](https://facebook.com/andrewnaegele)
|
|
379
|
+
|
|
380
|
+
---
|
|
381
|
+
|
|
382
|
+
## License
|
|
383
|
+
|
|
384
|
+
See [LICENSE](LICENSE) for full terms.
|
|
385
|
+
|
|
386
|
+
**TL;DR:** Free to use for personal and commercial projects. Not for resale. Give credit. Star the repo.
|
|
387
|
+
|
|
388
|
+
---
|
|
389
|
+
|
|
390
|
+
**Stop leaking keys. Start using Keychain.**
|
|
391
|
+
|
|
392
|
+
If this saved you from a $10,000 mistake, consider:
|
|
393
|
+
- Starring this repo
|
|
394
|
+
- Joining the community at [skool.com/vibe-marketing](https://skool.com/vibe-marketing)
|
|
395
|
+
- Sharing with a developer friend who needs this
|
package/bin/install.js
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const os = require('os');
|
|
6
|
+
const { execSync } = require('child_process');
|
|
7
|
+
|
|
8
|
+
// Colors for terminal output
|
|
9
|
+
const colors = {
|
|
10
|
+
reset: '\x1b[0m',
|
|
11
|
+
bright: '\x1b[1m',
|
|
12
|
+
green: '\x1b[32m',
|
|
13
|
+
yellow: '\x1b[33m',
|
|
14
|
+
red: '\x1b[31m',
|
|
15
|
+
cyan: '\x1b[36m'
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
function log(message, color = 'reset') {
|
|
19
|
+
console.log(`${colors[color]}${message}${colors.reset}`);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function logStep(step, total, message) {
|
|
23
|
+
log(`[${step}/${total}] ${message}`, 'cyan');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function logSuccess(message) {
|
|
27
|
+
log(` ✓ ${message}`, 'green');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function logWarning(message) {
|
|
31
|
+
log(` ⚠ ${message}`, 'yellow');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function logError(message) {
|
|
35
|
+
log(` ✗ ${message}`, 'red');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Check if running on macOS
|
|
39
|
+
if (os.platform() !== 'darwin') {
|
|
40
|
+
log('\n============================================', 'red');
|
|
41
|
+
log(' This tool only works on macOS', 'red');
|
|
42
|
+
log('============================================', 'red');
|
|
43
|
+
log('\nIt uses macOS Keychain for secure key storage.');
|
|
44
|
+
log('For other platforms, see the README for alternatives.\n');
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const HOME = os.homedir();
|
|
49
|
+
const PACKAGE_DIR = path.join(__dirname, '..');
|
|
50
|
+
|
|
51
|
+
// Directories
|
|
52
|
+
const CLAUDE_DIR = path.join(HOME, '.claude');
|
|
53
|
+
const SKILLS_DIR = path.join(CLAUDE_DIR, 'skills');
|
|
54
|
+
const COMMANDS_DIR = path.join(CLAUDE_DIR, 'commands');
|
|
55
|
+
const HOOKS_DIR = path.join(CLAUDE_DIR, 'hooks');
|
|
56
|
+
const SETTINGS_FILE = path.join(CLAUDE_DIR, 'settings.json');
|
|
57
|
+
|
|
58
|
+
log('\n======================================', 'bright');
|
|
59
|
+
log(' No More Leaked Keys - Installer', 'bright');
|
|
60
|
+
log('======================================\n', 'bright');
|
|
61
|
+
|
|
62
|
+
const TOTAL_STEPS = 6;
|
|
63
|
+
|
|
64
|
+
// Step 1: Create directories
|
|
65
|
+
logStep(1, TOTAL_STEPS, 'Creating directories...');
|
|
66
|
+
[SKILLS_DIR, COMMANDS_DIR, HOOKS_DIR].forEach(dir => {
|
|
67
|
+
if (!fs.existsSync(dir)) {
|
|
68
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
logSuccess('Directories ready');
|
|
72
|
+
|
|
73
|
+
// Step 2: Install skill
|
|
74
|
+
logStep(2, TOTAL_STEPS, 'Installing keychain-secrets skill...');
|
|
75
|
+
const skillSrc = path.join(PACKAGE_DIR, 'keychain-secrets');
|
|
76
|
+
const skillDest = path.join(SKILLS_DIR, 'keychain-secrets');
|
|
77
|
+
fs.cpSync(skillSrc, skillDest, { recursive: true });
|
|
78
|
+
logSuccess('Skill installed');
|
|
79
|
+
|
|
80
|
+
// Step 3: Install commands
|
|
81
|
+
logStep(3, TOTAL_STEPS, 'Installing slash commands...');
|
|
82
|
+
const commandsSrc = path.join(PACKAGE_DIR, 'commands');
|
|
83
|
+
fs.readdirSync(commandsSrc).forEach(file => {
|
|
84
|
+
if (file.endsWith('.md')) {
|
|
85
|
+
fs.copyFileSync(
|
|
86
|
+
path.join(commandsSrc, file),
|
|
87
|
+
path.join(COMMANDS_DIR, file)
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
logSuccess('Commands installed: /secrets, /add-mcp');
|
|
92
|
+
|
|
93
|
+
// Step 4: Install hook
|
|
94
|
+
logStep(4, TOTAL_STEPS, 'Installing security hook...');
|
|
95
|
+
const hookSrc = path.join(PACKAGE_DIR, 'hooks', 'block-unsafe-mcp-add.sh');
|
|
96
|
+
const hookDest = path.join(HOOKS_DIR, 'block-unsafe-mcp-add.sh');
|
|
97
|
+
fs.copyFileSync(hookSrc, hookDest);
|
|
98
|
+
fs.chmodSync(hookDest, '755');
|
|
99
|
+
logSuccess('Security hook installed');
|
|
100
|
+
|
|
101
|
+
// Step 5: Configure Claude Code settings
|
|
102
|
+
logStep(5, TOTAL_STEPS, 'Configuring Claude Code...');
|
|
103
|
+
|
|
104
|
+
let settings = {};
|
|
105
|
+
if (fs.existsSync(SETTINGS_FILE)) {
|
|
106
|
+
try {
|
|
107
|
+
settings = JSON.parse(fs.readFileSync(SETTINGS_FILE, 'utf8'));
|
|
108
|
+
} catch (e) {
|
|
109
|
+
logWarning('Could not parse existing settings.json, creating new one');
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (!settings.hooks) settings.hooks = {};
|
|
114
|
+
if (!settings.hooks.PreToolUse) settings.hooks.PreToolUse = [];
|
|
115
|
+
|
|
116
|
+
// Check if hook already exists
|
|
117
|
+
const hookExists = settings.hooks.PreToolUse.some(
|
|
118
|
+
hook => JSON.stringify(hook).includes('block-unsafe-mcp-add.sh')
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
if (!hookExists) {
|
|
122
|
+
settings.hooks.PreToolUse.push({
|
|
123
|
+
matcher: 'Bash',
|
|
124
|
+
hooks: [{
|
|
125
|
+
type: 'command',
|
|
126
|
+
command: 'bash ~/.claude/hooks/block-unsafe-mcp-add.sh'
|
|
127
|
+
}]
|
|
128
|
+
});
|
|
129
|
+
fs.writeFileSync(SETTINGS_FILE, JSON.stringify(settings, null, 2));
|
|
130
|
+
logSuccess('Claude Code hook configured');
|
|
131
|
+
} else {
|
|
132
|
+
logSuccess('Claude Code hook already configured');
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Step 6: Add shell protection
|
|
136
|
+
logStep(6, TOTAL_STEPS, 'Adding shell-level protection...');
|
|
137
|
+
|
|
138
|
+
const shellProtection = `
|
|
139
|
+
# ============================================
|
|
140
|
+
# No More Leaked Keys - Security Protection
|
|
141
|
+
# Blocks unsafe "claude mcp add" with auth headers
|
|
142
|
+
# ============================================
|
|
143
|
+
claude() {
|
|
144
|
+
if [[ "$1" == "mcp" && "$2" == "add" ]]; then
|
|
145
|
+
for arg in "$@"; do
|
|
146
|
+
if [[ "$arg" =~ [Aa]uthorization.*[Bb]earer || "$arg" =~ [Bb]earer ]]; then
|
|
147
|
+
echo ""
|
|
148
|
+
echo "============================================"
|
|
149
|
+
echo " BLOCKED: Unsafe MCP Command Detected"
|
|
150
|
+
echo "============================================"
|
|
151
|
+
echo ""
|
|
152
|
+
echo 'The "claude mcp add" command with auth headers'
|
|
153
|
+
echo "exposes your API key in terminal output!"
|
|
154
|
+
echo ""
|
|
155
|
+
echo "Use these safe alternatives instead:"
|
|
156
|
+
echo " - /secrets (in Claude Code)"
|
|
157
|
+
echo " - /add-mcp (in Claude Code)"
|
|
158
|
+
echo ""
|
|
159
|
+
echo "These pull keys from Keychain securely."
|
|
160
|
+
echo "See: https://github.com/Vibe-Marketer/no-more-leaked-keys"
|
|
161
|
+
echo ""
|
|
162
|
+
return 1
|
|
163
|
+
fi
|
|
164
|
+
done
|
|
165
|
+
fi
|
|
166
|
+
command claude "$@"
|
|
167
|
+
}
|
|
168
|
+
`;
|
|
169
|
+
|
|
170
|
+
// Detect shell config
|
|
171
|
+
let shellRc = path.join(HOME, '.zshrc');
|
|
172
|
+
if (!fs.existsSync(shellRc)) {
|
|
173
|
+
shellRc = path.join(HOME, '.bashrc');
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
let shellContent = '';
|
|
177
|
+
if (fs.existsSync(shellRc)) {
|
|
178
|
+
shellContent = fs.readFileSync(shellRc, 'utf8');
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (!shellContent.includes('No More Leaked Keys - Security Protection')) {
|
|
182
|
+
fs.appendFileSync(shellRc, shellProtection);
|
|
183
|
+
logSuccess(`Shell protection added to ${shellRc}`);
|
|
184
|
+
} else {
|
|
185
|
+
logSuccess('Shell protection already configured');
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Done!
|
|
189
|
+
log('\n======================================', 'green');
|
|
190
|
+
log(' Installation Complete!', 'green');
|
|
191
|
+
log('======================================\n', 'green');
|
|
192
|
+
|
|
193
|
+
log("What's installed:", 'bright');
|
|
194
|
+
log(' • Skill: ~/.claude/skills/keychain-secrets/');
|
|
195
|
+
log(' • Commands: /secrets, /add-mcp');
|
|
196
|
+
log(' • Claude Code hook: Blocks unsafe commands from Claude');
|
|
197
|
+
log(' • Shell protection: Blocks unsafe commands from terminal');
|
|
198
|
+
|
|
199
|
+
log('\nUsage:', 'bright');
|
|
200
|
+
log(' • Type /secrets to manage API keys');
|
|
201
|
+
log(' • Type /add-mcp to securely add MCP servers');
|
|
202
|
+
|
|
203
|
+
log('\nProtection is now active at TWO levels:', 'bright');
|
|
204
|
+
log(' 1. Claude Code hook - blocks Claude from running unsafe commands');
|
|
205
|
+
log(' 2. Shell function - blocks YOU from running unsafe commands');
|
|
206
|
+
|
|
207
|
+
log('\nTo activate shell protection now, run:', 'yellow');
|
|
208
|
+
log(` source ${shellRc}\n`);
|
|
209
|
+
|
|
210
|
+
log('Or restart your terminal.\n');
|
|
211
|
+
|
|
212
|
+
// Social links
|
|
213
|
+
log('─────────────────────────────────────', 'cyan');
|
|
214
|
+
log('Built by Andrew Naegele', 'cyan');
|
|
215
|
+
log('Community: https://skool.com/vibe-marketing', 'cyan');
|
|
216
|
+
log('─────────────────────────────────────\n', 'cyan');
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Securely add an MCP server without exposing API keys
|
|
3
|
+
allowed-tools: ["Skill", "Bash", "Read", "Write", "Edit"]
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Invoke the keychain-secrets skill to securely add an MCP server.
|
|
7
|
+
|
|
8
|
+
**NEVER use `claude mcp add` with authentication headers - it exposes keys in terminal output!**
|
|
9
|
+
|
|
10
|
+
This command will:
|
|
11
|
+
1. Verify the API key exists in Keychain
|
|
12
|
+
2. Add the MCP config by directly editing the config file
|
|
13
|
+
3. Never echo or display the key
|
|
14
|
+
|
|
15
|
+
Arguments: $ARGUMENTS
|
|
16
|
+
|
|
17
|
+
Use workflow: add-mcp.md
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Manage API keys securely with macOS Keychain
|
|
3
|
+
allowed-tools: ["Skill"]
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Invoke the keychain-secrets skill for: $ARGUMENTS
|
|
7
|
+
|
|
8
|
+
Use the keychain-secrets skill to help with API key management. This skill provides secure workflows for:
|
|
9
|
+
- Populating .env files from Keychain
|
|
10
|
+
- Adding new API keys securely (keys never visible on screen)
|
|
11
|
+
- Listing available keys
|
|
12
|
+
- Removing keys from Keychain
|