api-key-guard 1.0.3 → 1.1.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/README.md +446 -44
- package/bin/cli.js +19 -0
- package/package.json +1 -1
- package/src/index.js +3 -1
- package/src/keyFixer.js +406 -0
- package/src/readmeGenerator.js +1 -1
package/README.md
CHANGED
|
@@ -1,84 +1,486 @@
|
|
|
1
|
-
# 🔐
|
|
1
|
+
# 🔐 API Key Guard
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/api-key-guard)
|
|
4
4
|
[](https://www.npmjs.com/package/api-key-guard)
|
|
5
5
|
[](https://opensource.org/licenses/MIT)
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
A comprehensive CLI tool for detecting, preventing, and managing API key leaks in your codebase with AI-powered documentation generation.
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
## ⚡ Quick Start
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
```bash
|
|
12
|
+
# Install globally
|
|
13
|
+
npm install -g api-key-guard
|
|
12
14
|
|
|
13
|
-
|
|
15
|
+
# Scan for API key leaks
|
|
16
|
+
api-key-guard scan
|
|
14
17
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
* **Reputational Damage:** Erosion of trust from customers and partners.
|
|
18
|
-
* **Service Interruptions:** Malicious actors disabling or disrupting your services.
|
|
18
|
+
# Setup git hooks for automatic scanning
|
|
19
|
+
api-key-guard setup-hooks
|
|
19
20
|
|
|
20
|
-
|
|
21
|
+
# Generate AI-powered README (requires GEMINI_API_KEY)
|
|
22
|
+
export GEMINI_API_KEY=your_api_key_here
|
|
23
|
+
api-key-guard readme
|
|
24
|
+
```
|
|
21
25
|
|
|
22
|
-
##
|
|
26
|
+
## 🚨 The Problem
|
|
23
27
|
|
|
24
|
-
|
|
28
|
+
API key leaks in code repositories are a critical security vulnerability that can lead to:
|
|
25
29
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
* Set up pre-commit hooks to automatically scan staged files before every commit, preventing secrets from ever reaching your repository.
|
|
31
|
-
* Provides clear feedback and blocks commits if a leak is detected.
|
|
32
|
-
* 🚀 **Powerful CLI Commands:**
|
|
33
|
-
* `scan`: On-demand scanning of entire directories or specific files.
|
|
34
|
-
* `setup-hooks`: Automates the installation of Git pre-commit hooks.
|
|
35
|
-
* `readme`: Leverages AI to generate comprehensive `README.md` files based on your project's structure and existing documentation.
|
|
36
|
-
* 🤖 **AI-Powered README Generation:**
|
|
37
|
-
* A unique feature that helps you quickly generate professional and informative `README.md` files, improving project documentation and onboarding.
|
|
38
|
-
* 📁 **Multiple File Format Support:**
|
|
39
|
-
* Intelligently parses and scans a broad spectrum of text-based file formats, ensuring no stone is left unturned.
|
|
40
|
-
* ⚙️ **Configurable Ignore Patterns:**
|
|
41
|
-
* Define custom ignore rules in configuration files or via CLI flags to exclude specific files, directories (e.g., `node_modules`, `dist`), or even patterns of "false positive" keys, reducing noise.
|
|
42
|
-
* 🌈 **Colorful and Clear Output:**
|
|
43
|
-
* Leverages `chalk` to provide easy-to-read, color-coded output in the terminal, highlighting detected keys and scan results.
|
|
30
|
+
- **Data breaches** and unauthorized access
|
|
31
|
+
- **Financial losses** from misused cloud resources
|
|
32
|
+
- **Service disruptions** and security incidents
|
|
33
|
+
- **Reputational damage** from exposed credentials
|
|
44
34
|
|
|
45
|
-
##
|
|
35
|
+
## ✨ Features
|
|
36
|
+
|
|
37
|
+
- 🔍 **Smart Detection**: Advanced regex patterns detect AWS keys, GitHub tokens, Google API keys, and more
|
|
38
|
+
- � **Auto-Fix Keys**: Automatically replace hardcoded keys with environment variables
|
|
39
|
+
- �🔒 **Git Hooks Integration**: Automatic pre-commit scanning to prevent leaks
|
|
40
|
+
- 🤖 **AI-Powered README**: Generate professional documentation using Google's Gemini API
|
|
41
|
+
- ⚡ **Fast Scanning**: Efficient file parsing with configurable ignore patterns
|
|
42
|
+
- 🌈 **Clear Output**: Color-coded results with detailed reporting
|
|
43
|
+
- 📋 **Multiple Formats**: Support for JS, TS, Python, JSON, YAML, ENV files, and more
|
|
46
44
|
|
|
47
|
-
|
|
45
|
+
## 📋 CLI Commands
|
|
48
46
|
|
|
49
|
-
|
|
50
|
-
|
|
47
|
+
### Scan for API Keys
|
|
48
|
+
```bash
|
|
49
|
+
# Scan current directory
|
|
50
|
+
api-key-guard scan
|
|
51
|
+
|
|
52
|
+
# Scan specific path
|
|
53
|
+
api-key-guard scan --path ./src
|
|
51
54
|
|
|
52
|
-
|
|
55
|
+
# Verbose output with pattern details
|
|
56
|
+
api-key-guard scan --verbose
|
|
57
|
+
```
|
|
53
58
|
|
|
59
|
+
### Fix Hardcoded Keys
|
|
54
60
|
```bash
|
|
55
|
-
|
|
61
|
+
# Automatically fix hardcoded API keys
|
|
62
|
+
api-key-guard fix
|
|
63
|
+
|
|
64
|
+
# Preview fixes without applying
|
|
65
|
+
api-key-guard fix --dry-run
|
|
66
|
+
|
|
67
|
+
# Fix specific file only
|
|
68
|
+
api-key-guard fix --file src/config.js
|
|
69
|
+
|
|
70
|
+
# Fix without creating backups
|
|
71
|
+
api-key-guard fix --no-backup
|
|
56
72
|
```
|
|
57
73
|
|
|
58
|
-
|
|
74
|
+
### Git Hooks Setup
|
|
75
|
+
```bash
|
|
76
|
+
# Install pre-commit hook
|
|
77
|
+
api-key-guard setup-hooks
|
|
78
|
+
```
|
|
59
79
|
|
|
80
|
+
### AI README Generation
|
|
81
|
+
```bash
|
|
82
|
+
# Generate README.md
|
|
83
|
+
api-key-guard readme
|
|
84
|
+
|
|
85
|
+
# Force overwrite existing file
|
|
86
|
+
api-key-guard readme --force
|
|
87
|
+
|
|
88
|
+
# Custom output file
|
|
89
|
+
api-key-guard readme --output DOCUMENTATION.md
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## 🛠️ Installation
|
|
93
|
+
|
|
94
|
+
**Prerequisites:** Node.js 14+ and npm
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
# Global installation (recommended)
|
|
98
|
+
npm install -g api-key-guard
|
|
99
|
+
|
|
100
|
+
# Local project installation
|
|
101
|
+
npm install --save-dev api-key-guard
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
**Verify installation:**
|
|
60
105
|
```bash
|
|
61
106
|
api-key-guard --version
|
|
62
107
|
```
|
|
63
108
|
|
|
109
|
+
## 🔑 API Key Types Detected
|
|
110
|
+
|
|
111
|
+
- **AWS Access Keys**: `AKIA...`
|
|
112
|
+
- **GitHub Tokens**: `ghp_...`, `github_pat_...`
|
|
113
|
+
- **Google API Keys**: `AIza...`
|
|
114
|
+
- **Generic Patterns**: `api_key`, `secret_key`, `access_token`
|
|
115
|
+
- **Bearer Tokens**: `Bearer ...`
|
|
116
|
+
- **Custom Patterns**: High-entropy strings
|
|
117
|
+
|
|
118
|
+
## 🤖 AI README Generation
|
|
119
|
+
|
|
120
|
+
The `readme` command uses Google's Gemini API to generate comprehensive documentation:
|
|
121
|
+
|
|
122
|
+
### Setup
|
|
123
|
+
1. Get API key from [Google AI Studio](https://makersuite.google.com/app/apikey)
|
|
124
|
+
2. Set environment variable:
|
|
125
|
+
```bash
|
|
126
|
+
export GEMINI_API_KEY=your_api_key_here
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Generated Content
|
|
130
|
+
- Project overview and description
|
|
131
|
+
- Installation instructions
|
|
132
|
+
- Usage examples and CLI documentation
|
|
133
|
+
- Security best practices
|
|
134
|
+
- Contributing guidelines
|
|
135
|
+
|
|
136
|
+
## ⚙️ Configuration
|
|
137
|
+
|
|
138
|
+
Create `.api-key-guard.json` in your project root:
|
|
139
|
+
|
|
140
|
+
```json
|
|
141
|
+
{
|
|
142
|
+
"ignorePatterns": [
|
|
143
|
+
"node_modules/**",
|
|
144
|
+
"dist/**",
|
|
145
|
+
"*.min.js",
|
|
146
|
+
"test/**/*.fixture.js"
|
|
147
|
+
],
|
|
148
|
+
"customPatterns": [
|
|
149
|
+
{
|
|
150
|
+
"name": "Custom API Key",
|
|
151
|
+
"pattern": "custom_key_[0-9a-f]{32}"
|
|
152
|
+
}
|
|
153
|
+
]
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## 🔐 Git Hooks
|
|
158
|
+
|
|
159
|
+
The `setup-hooks` command creates a pre-commit hook that:
|
|
160
|
+
|
|
161
|
+
1. Scans staged files for API keys
|
|
162
|
+
2. Blocks commits if leaks are detected
|
|
163
|
+
3. Provides clear feedback on detected patterns
|
|
164
|
+
4. Allows bypass with `--no-verify` if needed
|
|
165
|
+
|
|
166
|
+
## 🛡️ Security Features
|
|
167
|
+
|
|
168
|
+
- **Zero Storage**: API keys are never stored or logged
|
|
169
|
+
- **Environment Variables**: Secure handling of authentication tokens
|
|
170
|
+
- **Pattern Matching**: Regular expressions detect common key formats
|
|
171
|
+
- **Entropy Analysis**: Identifies high-entropy strings that may be secrets
|
|
172
|
+
- **Configurable Scanning**: Customize patterns and ignore rules
|
|
173
|
+
|
|
174
|
+
## 📊 Usage Examples
|
|
175
|
+
|
|
176
|
+
**Basic Scanning:**
|
|
177
|
+
```bash
|
|
178
|
+
api-key-guard scan
|
|
179
|
+
# ✅ No potential API key leaks detected!
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
**With API Key Detection:**
|
|
183
|
+
```bash
|
|
184
|
+
api-key-guard scan --verbose
|
|
185
|
+
# 🚨 Found 2 potential API key leak(s):
|
|
186
|
+
# 📄 src/config.js:15
|
|
187
|
+
# Pattern: AKIA1234567890ABCDEF
|
|
188
|
+
# 📄 .env.example:3
|
|
189
|
+
# Pattern: sk-1234567890abcdef...
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
**Automatic Key Fixing:**
|
|
193
|
+
```bash
|
|
194
|
+
api-key-guard fix --dry-run
|
|
195
|
+
# 📋 Found 3 fixable API key(s):
|
|
196
|
+
# 📄 src/config.js:15
|
|
197
|
+
# const apiKey = "sk-1234567890abcdef"
|
|
198
|
+
# → const apiKey = process.env.API_KEY
|
|
199
|
+
#
|
|
200
|
+
# 📄 src/auth.js:8
|
|
201
|
+
# const token = "ghp_abcdefghijklmnop"
|
|
202
|
+
# → const token = process.env.GITHUB_TOKEN
|
|
203
|
+
|
|
204
|
+
# Apply fixes
|
|
205
|
+
api-key-guard fix
|
|
206
|
+
# 🔧 Applying fixes...
|
|
207
|
+
# ✅ Successfully fixed 3 API keys!
|
|
208
|
+
# 📝 Updated 2 file(s)
|
|
209
|
+
# 🔐 Added 3 environment variable(s)
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
## 🤝 Contributing
|
|
213
|
+
|
|
214
|
+
1. Fork the repository
|
|
215
|
+
2. Create a feature branch: `git checkout -b feature/amazing-feature`
|
|
216
|
+
3. Commit changes: `git commit -m 'Add amazing feature'`
|
|
217
|
+
4. Push to branch: `git push origin feature/amazing-feature`
|
|
218
|
+
5. Open a Pull Request
|
|
219
|
+
|
|
220
|
+
## 📄 License
|
|
221
|
+
|
|
222
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
223
|
+
|
|
224
|
+
## 🙋♂️ Support
|
|
225
|
+
|
|
226
|
+
- 📖 [Documentation](https://www.npmjs.com/package/api-key-guard)
|
|
227
|
+
- 🐛 [Report Issues](https://github.com/yourusername/api-key-guard/issues)
|
|
228
|
+
- 💬 [Discussions](https://github.com/yourusername/api-key-guard/discussions)
|
|
229
|
+
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
**Made with ❤️ for developer security**
|
|
233
|
+
npm install --save-dev api-key-guard
|
|
234
|
+
```
|
|
235
|
+
|
|
64
236
|
## 🚀 CLI Usage Examples
|
|
65
237
|
|
|
66
|
-
`api-key-guard` provides
|
|
238
|
+
`api-key-guard` provides several commands for different use cases.
|
|
67
239
|
|
|
68
|
-
###
|
|
240
|
+
### 🔍 `api-key-guard scan` - Detect API Keys
|
|
69
241
|
|
|
70
|
-
|
|
242
|
+
Scans your project for potential API key leaks.
|
|
71
243
|
|
|
72
|
-
|
|
244
|
+
**Basic Scan (current directory):**
|
|
73
245
|
|
|
74
246
|
```bash
|
|
75
247
|
api-key-guard scan .
|
|
76
248
|
```
|
|
77
249
|
|
|
78
|
-
|
|
250
|
+
**Scan a specific directory:**
|
|
251
|
+
|
|
252
|
+
```bash
|
|
253
|
+
api-key-guard scan src/
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
**Scan a specific file:**
|
|
257
|
+
|
|
258
|
+
```bash
|
|
259
|
+
api-key-guard scan config/secrets.js
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
**Verbose output (shows more details about detection):**
|
|
263
|
+
|
|
264
|
+
```bash
|
|
265
|
+
api-key-guard scan . --verbose
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
**Fail on leak (exit with a non-zero code if leaks are found, useful for CI/CD):**
|
|
269
|
+
|
|
270
|
+
```bash
|
|
271
|
+
api-key-guard scan . --fail-on-leak
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
**Include specific file types (comma-separated):**
|
|
275
|
+
|
|
276
|
+
```bash
|
|
277
|
+
api-key-guard scan . --include "*.js,*.ts,*.env"
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
**Exclude specific file types (comma-separated):**
|
|
281
|
+
|
|
282
|
+
```bash
|
|
283
|
+
api-key-guard scan . --exclude "*.min.js,*.lock"
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
**Ignore files or directories using patterns (shell-like glob patterns):**
|
|
287
|
+
|
|
288
|
+
```bash
|
|
289
|
+
api-key-guard scan . --ignore-pattern "node_modules/**" --ignore-pattern "dist/*"
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
**Use a `.gitignore` file for ignore patterns:**
|
|
293
|
+
|
|
294
|
+
```bash
|
|
295
|
+
api-key-guard scan . --ignore-file .gitignore
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
**Combine options:**
|
|
299
|
+
|
|
300
|
+
```bash
|
|
301
|
+
api-key-guard scan src/ --fail-on-leak --verbose --ignore-file .gitignore --include "*.js,*.ts"
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
### 🎣 `api-key-guard setup-hooks` - Integrate Git Hooks
|
|
305
|
+
|
|
306
|
+
Sets up Git pre-commit and/or pre-push hooks to automatically scan for keys before commits or pushes.
|
|
307
|
+
|
|
308
|
+
**Set up pre-commit hook (default):**
|
|
309
|
+
|
|
310
|
+
```bash
|
|
311
|
+
api-key-guard setup-hooks
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
This will configure your local `.git/hooks/pre-commit` script to run `api-key-guard scan --fail-on-leak` on staged files.
|
|
315
|
+
|
|
316
|
+
**Set up pre-push hook:**
|
|
317
|
+
|
|
318
|
+
```bash
|
|
319
|
+
api-key-guard setup-hooks --hook pre-push
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
This will configure your local `.git/hooks/pre-push` script to run `api-key-guard scan --fail-on-leak` on all changes being pushed.
|
|
323
|
+
|
|
324
|
+
**Remove existing hooks (if you need to clean up):**
|
|
79
325
|
|
|
80
326
|
```bash
|
|
81
|
-
api-key-guard
|
|
327
|
+
api-key-guard setup-hooks --remove
|
|
82
328
|
```
|
|
83
329
|
|
|
84
|
-
|
|
330
|
+
### 📝 `api-key-guard readme` - Generate READMEs with AI
|
|
331
|
+
|
|
332
|
+
Leverages AI to help you generate a professional README.md for your project. (Requires an active internet connection and potentially an API key for the AI service, configured via environment variables).
|
|
333
|
+
|
|
334
|
+
**Generate a README with a prompt:**
|
|
335
|
+
|
|
336
|
+
```bash
|
|
337
|
+
api-key-guard readme "Generate a README for a Node.js CLI tool that detects API keys, focusing on its security benefits and ease of use."
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
**Generate and save to a specific file:**
|
|
341
|
+
|
|
342
|
+
```bash
|
|
343
|
+
api-key-guard readme --output docs/PROJECT_README.md "Generate a simple README for a web application using React and Node.js."
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
## 🎣 Git Hooks Usage
|
|
347
|
+
|
|
348
|
+
Once you've set up Git hooks using `api-key-guard setup-hooks`, the tool will automatically run during your `git commit` or `git push` operations.
|
|
349
|
+
|
|
350
|
+
**Example of a pre-commit hook in action:**
|
|
351
|
+
|
|
352
|
+
1. You have `api-key-guard` pre-commit hook enabled.
|
|
353
|
+
2. You accidentally add a file containing a hardcoded API key:
|
|
354
|
+
```javascript
|
|
355
|
+
// src/config.js
|
|
356
|
+
const API_KEY = "sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; // BAD PRACTICE!
|
|
357
|
+
```
|
|
358
|
+
3. You stage the file:
|
|
359
|
+
```bash
|
|
360
|
+
git add src/config.js
|
|
361
|
+
```
|
|
362
|
+
4. You try to commit:
|
|
363
|
+
```bash
|
|
364
|
+
git commit -m "Add new config"
|
|
365
|
+
```
|
|
366
|
+
5. `api-key-guard` will detect the leak, prevent the commit, and display a warning:
|
|
367
|
+
```
|
|
368
|
+
🚨 api-key-guard: Potential API key leak detected in staged files! 🚨
|
|
369
|
+
Path: src/config.js
|
|
370
|
+
Line 2: const API_KEY = "sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
|
|
371
|
+
|
|
372
|
+
Commit aborted. Please remove or secure the sensitive information.
|
|
373
|
+
If you need to bypass, use 'git commit --no-verify'.
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
To bypass the hook for a single commit (use with extreme caution!):
|
|
377
|
+
|
|
378
|
+
```bash
|
|
379
|
+
git commit -m "Temporary commit, will fix secrets later" --no-verify
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
## ⚙️ Configuration
|
|
383
|
+
|
|
384
|
+
`api-key-guard` can be configured using a `api-key-guard.config.json` file at the root of your project. This allows you to define custom rules, ignore patterns, and scanner settings.
|
|
385
|
+
|
|
386
|
+
**Example `api-key-guard.config.json`:**
|
|
387
|
+
|
|
388
|
+
```json
|
|
389
|
+
{
|
|
390
|
+
"scanPaths": [
|
|
391
|
+
"src/",
|
|
392
|
+
"config/",
|
|
393
|
+
"server/"
|
|
394
|
+
],
|
|
395
|
+
"ignorePatterns": [
|
|
396
|
+
"node_modules/**",
|
|
397
|
+
"dist/**",
|
|
398
|
+
"*.min.js",
|
|
399
|
+
"*.log",
|
|
400
|
+
"testdata/**"
|
|
401
|
+
],
|
|
402
|
+
"ignoreFiles": [
|
|
403
|
+
".gitignore",
|
|
404
|
+
".dockerignore"
|
|
405
|
+
],
|
|
406
|
+
"includeFileTypes": [
|
|
407
|
+
"*.js",
|
|
408
|
+
"*.ts",
|
|
409
|
+
"*.env",
|
|
410
|
+
"*.json",
|
|
411
|
+
"*.yaml",
|
|
412
|
+
"*.yml"
|
|
413
|
+
],
|
|
414
|
+
"excludeFileTypes": [
|
|
415
|
+
"*.lock",
|
|
416
|
+
"package-lock.json"
|
|
417
|
+
],
|
|
418
|
+
"customRules": [
|
|
419
|
+
{
|
|
420
|
+
"name": "Custom-API-Key",
|
|
421
|
+
"regex": "MY_CUSTOM_API_KEY_[a-zA-Z0-9]{32,64}",
|
|
422
|
+
"description": "Detects specific internal API keys."
|
|
423
|
+
}
|
|
424
|
+
],
|
|
425
|
+
"failOnLeak": true,
|
|
426
|
+
"verbose": false
|
|
427
|
+
}
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
* **`scanPaths`**: An array of glob patterns for directories or files to explicitly scan. If empty, the current directory (`.`) is scanned.
|
|
431
|
+
* **`ignorePatterns`**: An array of glob patterns for files or directories to ignore during scanning.
|
|
432
|
+
* **`ignoreFiles`**: An array of filenames (e.g., `.gitignore`) whose contents will be used as additional ignore patterns.
|
|
433
|
+
* **`includeFileTypes`**: An array of glob patterns for file types to explicitly include. If specified, only these types will be scanned.
|
|
434
|
+
* **`excludeFileTypes`**: An array of glob patterns for file types to explicitly exclude.
|
|
435
|
+
* **`customRules`**: An array of custom regex rules for detecting specific patterns. Each rule should have a `name`, `regex`, and `description`.
|
|
436
|
+
* **`failOnLeak`**: Boolean. If `true`, the CLI will exit with a non-zero code if any leaks are found.
|
|
437
|
+
* **`verbose`**: Boolean. If `true`, more detailed output will be provided.
|
|
438
|
+
|
|
439
|
+
## 🔒 Security Best Practices
|
|
440
|
+
|
|
441
|
+
While `api-key-guard` is a powerful tool, it's part of a broader security strategy. Always adhere to these best practices:
|
|
442
|
+
|
|
443
|
+
* **🚫 Never Hardcode Secrets:** The golden rule. Avoid placing API keys, passwords, or sensitive tokens directly in your code.
|
|
444
|
+
* **🌳 Use Environment Variables:** For development and deployment, load secrets from environment variables (e.g., using `.env` files locally and proper environment variable injection in production).
|
|
445
|
+
* **🔐 Employ Secret Management Services:** For production environments, utilize dedicated secret management services like AWS Secrets Manager, Azure Key Vault, Google Secret Manager, HashiCorp Vault, or similar.
|
|
446
|
+
* **🔑 Implement Least Privilege:** Grant API keys only the minimum necessary permissions required for their function.
|
|
447
|
+
* **🔄 Rotate Keys Regularly:** Periodically change your API keys, especially if they are long-lived.
|
|
448
|
+
* **📚 Educate Your Team:** Ensure all developers understand the risks of secret exposure and the proper procedures for handling sensitive information.
|
|
449
|
+
* **⚙️ Integrate into CI/CD:** Incorporate `api-key-guard` scans into your Continuous Integration/Continuous Deployment pipelines to catch leaks before deployment.
|
|
450
|
+
|
|
451
|
+
`api-key-guard` helps you catch mistakes, but proactive secure coding practices are paramount.
|
|
452
|
+
|
|
453
|
+
## 🛣️ Future Roadmap
|
|
454
|
+
|
|
455
|
+
We are continuously working to enhance `api-key-guard`. Here are some planned features:
|
|
456
|
+
|
|
457
|
+
* **Advanced Entropy Analysis:** Improve detection of generic high-entropy strings that might indicate secrets without specific patterns.
|
|
458
|
+
* **Machine Learning-Based Detection:** Explore ML models for more intelligent and adaptive secret detection.
|
|
459
|
+
* **Cloud Provider Integrations:** Direct integrations with AWS, Azure, GCP for scanning cloud-specific credential formats.
|
|
460
|
+
* **Reporting & Alerting:** Generate detailed reports and integrate with alerting systems (e.g., Slack, email) when leaks are detected in CI/CD.
|
|
461
|
+
* **Support for More Secret Types:** Expand detection to include private keys, database connection strings, access tokens, etc.
|
|
462
|
+
* **IDE Extensions:** Develop extensions for popular IDEs (VS Code, IntelliJ) for real-time feedback.
|
|
463
|
+
* **Web UI for Centralized Management:** A future goal for larger teams to manage configurations and view scan results centrally.
|
|
464
|
+
|
|
465
|
+
## 🤝 Contributing
|
|
466
|
+
|
|
467
|
+
We welcome contributions to `api-key-guard`! Whether it's bug reports, feature requests, or code contributions, your help is valuable.
|
|
468
|
+
|
|
469
|
+
1. **Report Bugs:** If you find a bug, please open an issue on GitHub, providing detailed steps to reproduce, expected behavior, and actual behavior.
|
|
470
|
+
2. **Suggest Features:** Have an idea for a new feature or improvement? Open an issue to discuss it.
|
|
471
|
+
3. **Code Contributions:**
|
|
472
|
+
* Fork the repository.
|
|
473
|
+
* Create a new branch (`git checkout -b feature/your-feature-name` or `fix/bug-fix-description`).
|
|
474
|
+
* Make your changes.
|
|
475
|
+
* Write tests for your changes.
|
|
476
|
+
* Ensure your code adheres to the project's coding style.
|
|
477
|
+
* Commit your changes (`git commit -m "feat: Add new feature"`) using conventional commits.
|
|
478
|
+
* Push to your fork (`git push origin feature/your-feature-name`).
|
|
479
|
+
* Open a Pull Request to the `main` branch of the original repository.
|
|
480
|
+
|
|
481
|
+
Please adhere to our Code of Conduct.
|
|
482
|
+
|
|
483
|
+
## 📄 License
|
|
484
|
+
|
|
485
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
486
|
+
```
|
package/bin/cli.js
CHANGED
|
@@ -5,6 +5,7 @@ const chalk = require('chalk');
|
|
|
5
5
|
const { generateReadme } = require('../src/readmeGenerator');
|
|
6
6
|
const { scanForApiKeys } = require('../src/scanner');
|
|
7
7
|
const { setupGitHooks } = require('../src/gitHooks');
|
|
8
|
+
const { fixApiKeys } = require('../src/keyFixer');
|
|
8
9
|
|
|
9
10
|
const program = new Command();
|
|
10
11
|
|
|
@@ -59,4 +60,22 @@ program
|
|
|
59
60
|
}
|
|
60
61
|
});
|
|
61
62
|
|
|
63
|
+
// Fix API keys command
|
|
64
|
+
program
|
|
65
|
+
.command('fix')
|
|
66
|
+
.description('Automatically replace hardcoded API keys with environment variables')
|
|
67
|
+
.option('-p, --path <path>', 'Path to scan and fix', '.')
|
|
68
|
+
.option('-d, --dry-run', 'Preview changes without applying them')
|
|
69
|
+
.option('-f, --file <file>', 'Fix specific file only')
|
|
70
|
+
.option('--backup', 'Create backup files before fixing', true)
|
|
71
|
+
.action(async (options) => {
|
|
72
|
+
try {
|
|
73
|
+
console.log(chalk.blue('🔧 Fixing hardcoded API keys...'));
|
|
74
|
+
await fixApiKeys(options);
|
|
75
|
+
} catch (error) {
|
|
76
|
+
console.error(chalk.red('Error fixing keys:', error.message));
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
|
|
62
81
|
program.parse();
|
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
const { scanForApiKeys } = require('./scanner');
|
|
2
2
|
const { setupGitHooks } = require('./gitHooks');
|
|
3
3
|
const { generateReadme } = require('./readmeGenerator');
|
|
4
|
+
const { fixApiKeys } = require('./keyFixer');
|
|
4
5
|
|
|
5
6
|
module.exports = {
|
|
6
7
|
scanForApiKeys,
|
|
7
8
|
setupGitHooks,
|
|
8
|
-
generateReadme
|
|
9
|
+
generateReadme,
|
|
10
|
+
fixApiKeys
|
|
9
11
|
};
|
package/src/keyFixer.js
ADDED
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
const fs = require('fs').promises;
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const chalk = require('chalk');
|
|
4
|
+
const readline = require('readline');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Fix hardcoded API keys by replacing them with environment variables
|
|
8
|
+
*/
|
|
9
|
+
async function fixApiKeys(options = {}) {
|
|
10
|
+
const scanPath = options.path || '.';
|
|
11
|
+
const dryRun = options.dryRun || false;
|
|
12
|
+
const specificFile = options.file;
|
|
13
|
+
const createBackup = options.backup !== false;
|
|
14
|
+
|
|
15
|
+
console.log(chalk.blue(`🔍 Scanning for fixable API keys in ${scanPath}...`));
|
|
16
|
+
|
|
17
|
+
const findings = await scanForFixableKeys(scanPath, specificFile);
|
|
18
|
+
|
|
19
|
+
if (findings.length === 0) {
|
|
20
|
+
console.log(chalk.green('✅ No fixable API keys found!'));
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
console.log(chalk.yellow(`📋 Found ${findings.length} fixable API key(s):`));
|
|
25
|
+
findings.forEach(finding => {
|
|
26
|
+
console.log(chalk.cyan(` 📄 ${finding.file}:${finding.line}`));
|
|
27
|
+
console.log(chalk.gray(` ${finding.originalLine.trim()}`));
|
|
28
|
+
console.log(chalk.green(` → ${finding.fixedLine.trim()}`));
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
if (dryRun) {
|
|
32
|
+
console.log(chalk.blue('\n🔍 Dry run complete. No changes were made.'));
|
|
33
|
+
console.log(chalk.gray('Run without --dry-run to apply fixes.'));
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const shouldProceed = await promptConfirmation(
|
|
38
|
+
`\n❓ Apply these ${findings.length} fixes? (y/N): `
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
if (!shouldProceed) {
|
|
42
|
+
console.log(chalk.yellow('Operation cancelled.'));
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
console.log(chalk.blue('🔧 Applying fixes...'));
|
|
47
|
+
|
|
48
|
+
const results = await applyFixes(findings, createBackup);
|
|
49
|
+
await updateEnvironmentFiles(results.envVars);
|
|
50
|
+
await updateGitignore();
|
|
51
|
+
|
|
52
|
+
console.log(chalk.green(`✅ Successfully fixed ${results.fixedCount} API keys!`));
|
|
53
|
+
console.log(chalk.blue(`📝 Updated ${results.filesModified} file(s)`));
|
|
54
|
+
console.log(chalk.blue(`🔐 Added ${results.envVars.length} environment variable(s)`));
|
|
55
|
+
|
|
56
|
+
if (createBackup) {
|
|
57
|
+
console.log(chalk.gray(`💾 Backup files created with .backup extension`));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
console.log(chalk.yellow('\n⚠️ Next steps:'));
|
|
61
|
+
console.log(chalk.yellow(' 1. Review the generated .env file'));
|
|
62
|
+
console.log(chalk.yellow(' 2. Add .env to your .gitignore (done automatically)'));
|
|
63
|
+
console.log(chalk.yellow(' 3. Update your deployment with the new environment variables'));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Scan for API keys that can be automatically fixed
|
|
68
|
+
*/
|
|
69
|
+
async function scanForFixableKeys(scanPath, specificFile) {
|
|
70
|
+
const findings = [];
|
|
71
|
+
|
|
72
|
+
// Key patterns that can be fixed automatically
|
|
73
|
+
const fixablePatterns = [
|
|
74
|
+
{
|
|
75
|
+
name: 'JavaScript/TypeScript API Key Assignment',
|
|
76
|
+
pattern: /^(\s*)(const|let|var)\s+(\w+)\s*=\s*['"`]([A-Za-z0-9_\-]{20,})['"`]/gm,
|
|
77
|
+
language: 'javascript',
|
|
78
|
+
envVarNamer: (varName) => varName.replace(/([a-z])([A-Z])/g, '$1_$2').toUpperCase()
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
name: 'Python API Key Assignment',
|
|
82
|
+
pattern: /^(\s*)(\w+)\s*=\s*['"`]([A-Za-z0-9_\-]{20,})['"`]/gm,
|
|
83
|
+
language: 'python',
|
|
84
|
+
envVarNamer: (varName) => varName.replace(/([a-z])([A-Z])/g, '$1_$2').upper()
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
name: 'JSON Configuration',
|
|
88
|
+
pattern: /"(\w*[Kk]ey\w*|token|secret)"\s*:\s*"([A-Za-z0-9_\-]{20,})"/gm,
|
|
89
|
+
language: 'json',
|
|
90
|
+
envVarNamer: (keyName) => keyName.replace(/([a-z])([A-Z])/g, '$1_$2').toUpperCase()
|
|
91
|
+
}
|
|
92
|
+
];
|
|
93
|
+
|
|
94
|
+
const files = specificFile ? [specificFile] : await getFilesRecursively(scanPath);
|
|
95
|
+
|
|
96
|
+
for (const file of files) {
|
|
97
|
+
try {
|
|
98
|
+
const content = await fs.readFile(file, 'utf8');
|
|
99
|
+
const lines = content.split('\n');
|
|
100
|
+
|
|
101
|
+
for (const patternConfig of fixablePatterns) {
|
|
102
|
+
let match;
|
|
103
|
+
patternConfig.pattern.lastIndex = 0; // Reset regex
|
|
104
|
+
|
|
105
|
+
while ((match = patternConfig.pattern.exec(content)) !== null) {
|
|
106
|
+
const lineNumber = content.substring(0, match.index).split('\n').length;
|
|
107
|
+
const originalLine = lines[lineNumber - 1];
|
|
108
|
+
|
|
109
|
+
let envVarName, fixedLine;
|
|
110
|
+
|
|
111
|
+
if (patternConfig.language === 'javascript') {
|
|
112
|
+
const [, indent, declaration, varName, keyValue] = match;
|
|
113
|
+
envVarName = patternConfig.envVarNamer(varName);
|
|
114
|
+
fixedLine = `${indent}${declaration} ${varName} = process.env.${envVarName}`;
|
|
115
|
+
} else if (patternConfig.language === 'python') {
|
|
116
|
+
const [, indent, varName, keyValue] = match;
|
|
117
|
+
envVarName = patternConfig.envVarNamer(varName);
|
|
118
|
+
fixedLine = `${indent}${varName} = os.getenv('${envVarName}')`;
|
|
119
|
+
} else if (patternConfig.language === 'json') {
|
|
120
|
+
const [fullMatch, keyName, keyValue] = match;
|
|
121
|
+
envVarName = patternConfig.envVarNamer(keyName);
|
|
122
|
+
// For JSON, we might suggest moving to a config loader
|
|
123
|
+
fixedLine = `"${keyName}": "${process.env.${envVarName} || 'YOUR_${envVarName}_HERE'}"`;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Only include keys that look like real API keys
|
|
127
|
+
const keyValue = patternConfig.language === 'json' ? match[2] :
|
|
128
|
+
patternConfig.language === 'python' ? match[3] : match[4];
|
|
129
|
+
|
|
130
|
+
if (looksLikeApiKey(keyValue)) {
|
|
131
|
+
findings.push({
|
|
132
|
+
file: path.relative(process.cwd(), file),
|
|
133
|
+
line: lineNumber,
|
|
134
|
+
originalLine,
|
|
135
|
+
fixedLine,
|
|
136
|
+
envVarName,
|
|
137
|
+
keyValue,
|
|
138
|
+
language: patternConfig.language,
|
|
139
|
+
pattern: patternConfig.name
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
} catch (error) {
|
|
145
|
+
// Skip files we can't read
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return findings;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Apply the fixes to files
|
|
155
|
+
*/
|
|
156
|
+
async function applyFixes(findings, createBackup) {
|
|
157
|
+
const results = {
|
|
158
|
+
fixedCount: 0,
|
|
159
|
+
filesModified: 0,
|
|
160
|
+
envVars: []
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const fileGroups = groupFindingsByFile(findings);
|
|
164
|
+
|
|
165
|
+
for (const [filePath, fileFindings] of Object.entries(fileGroups)) {
|
|
166
|
+
try {
|
|
167
|
+
const fullPath = path.resolve(filePath);
|
|
168
|
+
const content = await fs.readFile(fullPath, 'utf8');
|
|
169
|
+
const lines = content.split('\n');
|
|
170
|
+
|
|
171
|
+
// Create backup if requested
|
|
172
|
+
if (createBackup) {
|
|
173
|
+
await fs.writeFile(`${fullPath}.backup`, content, 'utf8');
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Apply fixes (in reverse order to maintain line numbers)
|
|
177
|
+
const sortedFindings = fileFindings.sort((a, b) => b.line - a.line);
|
|
178
|
+
|
|
179
|
+
for (const finding of sortedFindings) {
|
|
180
|
+
lines[finding.line - 1] = finding.fixedLine;
|
|
181
|
+
results.fixedCount++;
|
|
182
|
+
|
|
183
|
+
// Collect environment variables
|
|
184
|
+
results.envVars.push({
|
|
185
|
+
name: finding.envVarName,
|
|
186
|
+
value: finding.keyValue,
|
|
187
|
+
comment: `# ${finding.pattern} from ${finding.file}:${finding.line}`
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Write the fixed content
|
|
192
|
+
await fs.writeFile(fullPath, lines.join('\n'), 'utf8');
|
|
193
|
+
results.filesModified++;
|
|
194
|
+
|
|
195
|
+
} catch (error) {
|
|
196
|
+
console.error(chalk.red(`Error fixing ${filePath}: ${error.message}`));
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return results;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Update or create environment files
|
|
205
|
+
*/
|
|
206
|
+
async function updateEnvironmentFiles(envVars) {
|
|
207
|
+
const envPath = path.join(process.cwd(), '.env');
|
|
208
|
+
const envExamplePath = path.join(process.cwd(), '.env.example');
|
|
209
|
+
|
|
210
|
+
// Read existing .env file if it exists
|
|
211
|
+
let existingEnvContent = '';
|
|
212
|
+
try {
|
|
213
|
+
existingEnvContent = await fs.readFile(envPath, 'utf8');
|
|
214
|
+
} catch (error) {
|
|
215
|
+
// File doesn't exist, will create new
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Prepare new environment variables
|
|
219
|
+
const newEnvLines = [];
|
|
220
|
+
const exampleLines = [];
|
|
221
|
+
|
|
222
|
+
for (const envVar of envVars) {
|
|
223
|
+
// Only add if not already in .env file
|
|
224
|
+
if (!existingEnvContent.includes(`${envVar.name}=`)) {
|
|
225
|
+
newEnvLines.push('');
|
|
226
|
+
newEnvLines.push(envVar.comment);
|
|
227
|
+
newEnvLines.push(`${envVar.name}=${envVar.value}`);
|
|
228
|
+
|
|
229
|
+
// Add to example file (without real values)
|
|
230
|
+
exampleLines.push('');
|
|
231
|
+
exampleLines.push(envVar.comment);
|
|
232
|
+
exampleLines.push(`${envVar.name}=your_${envVar.name.toLowerCase()}_here`);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (newEnvLines.length > 0) {
|
|
237
|
+
// Append to .env file
|
|
238
|
+
const updatedContent = existingEnvContent + '\n# Added by api-key-guard fix command' + newEnvLines.join('\n') + '\n';
|
|
239
|
+
await fs.writeFile(envPath, updatedContent, 'utf8');
|
|
240
|
+
|
|
241
|
+
// Create/update .env.example
|
|
242
|
+
let exampleContent = '';
|
|
243
|
+
try {
|
|
244
|
+
exampleContent = await fs.readFile(envExamplePath, 'utf8');
|
|
245
|
+
} catch (error) {
|
|
246
|
+
exampleContent = '# Environment variables template\n# Copy to .env and fill with real values\n';
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const updatedExampleContent = exampleContent + '\n# Added by api-key-guard fix command' + exampleLines.join('\n') + '\n';
|
|
250
|
+
await fs.writeFile(envExamplePath, updatedExampleContent, 'utf8');
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Update .gitignore to include .env
|
|
256
|
+
*/
|
|
257
|
+
async function updateGitignore() {
|
|
258
|
+
const gitignorePath = path.join(process.cwd(), '.gitignore');
|
|
259
|
+
|
|
260
|
+
try {
|
|
261
|
+
let gitignoreContent = '';
|
|
262
|
+
try {
|
|
263
|
+
gitignoreContent = await fs.readFile(gitignorePath, 'utf8');
|
|
264
|
+
} catch (error) {
|
|
265
|
+
// File doesn't exist, create new
|
|
266
|
+
gitignoreContent = '';
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Check if .env is already ignored
|
|
270
|
+
if (!gitignoreContent.includes('.env') || !gitignoreContent.includes('*.env')) {
|
|
271
|
+
const envIgnoreRules = [
|
|
272
|
+
'\n# Environment variables (added by api-key-guard)',
|
|
273
|
+
'.env',
|
|
274
|
+
'.env.local',
|
|
275
|
+
'.env.*.local',
|
|
276
|
+
'*.env\n'
|
|
277
|
+
];
|
|
278
|
+
|
|
279
|
+
const updatedContent = gitignoreContent + envIgnoreRules.join('\n');
|
|
280
|
+
await fs.writeFile(gitignorePath, updatedContent, 'utf8');
|
|
281
|
+
}
|
|
282
|
+
} catch (error) {
|
|
283
|
+
console.warn(chalk.yellow('Warning: Could not update .gitignore'));
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Check if a string looks like a real API key
|
|
289
|
+
*/
|
|
290
|
+
function looksLikeApiKey(str) {
|
|
291
|
+
// Basic heuristics for API keys
|
|
292
|
+
const minLength = 20;
|
|
293
|
+
const hasLettersAndNumbers = /[a-zA-Z]/.test(str) && /[0-9]/.test(str);
|
|
294
|
+
const notCommonWords = !['example', 'test', 'demo', 'sample', 'placeholder', 'your'].some(word =>
|
|
295
|
+
str.toLowerCase().includes(word));
|
|
296
|
+
|
|
297
|
+
return str.length >= minLength && hasLettersAndNumbers && notCommonWords;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Group findings by file path
|
|
302
|
+
*/
|
|
303
|
+
function groupFindingsByFile(findings) {
|
|
304
|
+
const groups = {};
|
|
305
|
+
for (const finding of findings) {
|
|
306
|
+
if (!groups[finding.file]) {
|
|
307
|
+
groups[finding.file] = [];
|
|
308
|
+
}
|
|
309
|
+
groups[finding.file].push(finding);
|
|
310
|
+
}
|
|
311
|
+
return groups;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Get all files recursively, respecting ignore patterns
|
|
316
|
+
*/
|
|
317
|
+
async function getFilesRecursively(dir, ignorePatterns = []) {
|
|
318
|
+
let files = [];
|
|
319
|
+
|
|
320
|
+
const defaultIgnores = [
|
|
321
|
+
'node_modules', '.git', 'dist', 'build', '.next', 'coverage',
|
|
322
|
+
'*.min.js', '*.bundle.js', 'package-lock.json', 'yarn.lock'
|
|
323
|
+
];
|
|
324
|
+
|
|
325
|
+
const allIgnores = [...defaultIgnores, ...ignorePatterns];
|
|
326
|
+
|
|
327
|
+
try {
|
|
328
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
329
|
+
|
|
330
|
+
for (const entry of entries) {
|
|
331
|
+
const fullPath = path.join(dir, entry.name);
|
|
332
|
+
|
|
333
|
+
// Check if should be ignored
|
|
334
|
+
if (shouldIgnore(entry.name, fullPath, allIgnores)) {
|
|
335
|
+
continue;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
if (entry.isDirectory()) {
|
|
339
|
+
files = files.concat(await getFilesRecursively(fullPath, ignorePatterns));
|
|
340
|
+
} else {
|
|
341
|
+
// Only scan text files that might contain code
|
|
342
|
+
if (isCodeFile(entry.name)) {
|
|
343
|
+
files.push(fullPath);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
} catch (error) {
|
|
348
|
+
// Skip directories we can't read
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
return files;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Check if file should be ignored
|
|
356
|
+
*/
|
|
357
|
+
function shouldIgnore(name, fullPath, ignorePatterns) {
|
|
358
|
+
for (const pattern of ignorePatterns) {
|
|
359
|
+
if (pattern.includes('*')) {
|
|
360
|
+
const regex = new RegExp(pattern.replace(/\*/g, '.*'));
|
|
361
|
+
if (regex.test(name)) {
|
|
362
|
+
return true;
|
|
363
|
+
}
|
|
364
|
+
} else {
|
|
365
|
+
if (name === pattern || fullPath.includes(pattern)) {
|
|
366
|
+
return true;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
return false;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Check if file is a code file that might contain API keys
|
|
375
|
+
*/
|
|
376
|
+
function isCodeFile(filename) {
|
|
377
|
+
const codeExtensions = [
|
|
378
|
+
'.js', '.ts', '.jsx', '.tsx', '.py', '.java', '.go', '.php', '.rb',
|
|
379
|
+
'.cs', '.cpp', '.c', '.h', '.rs', '.swift', '.kt', '.scala',
|
|
380
|
+
'.json', '.yml', '.yaml', '.xml', '.env', '.config', '.conf'
|
|
381
|
+
];
|
|
382
|
+
|
|
383
|
+
const ext = path.extname(filename).toLowerCase();
|
|
384
|
+
return codeExtensions.includes(ext) || !ext; // Include files without extensions
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Prompt user for confirmation
|
|
389
|
+
*/
|
|
390
|
+
function promptConfirmation(question) {
|
|
391
|
+
return new Promise((resolve) => {
|
|
392
|
+
const rl = readline.createInterface({
|
|
393
|
+
input: process.stdin,
|
|
394
|
+
output: process.stdout
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
rl.question(question, (answer) => {
|
|
398
|
+
rl.close();
|
|
399
|
+
resolve(answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes');
|
|
400
|
+
});
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
module.exports = {
|
|
405
|
+
fixApiKeys
|
|
406
|
+
};
|