api-key-guard 1.0.5 → 1.1.1
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 +37 -1
- package/bin/cli.js +19 -0
- package/package.json +1 -1
- package/src/index.js +3 -1
- package/src/keyFixer.js +406 -0
package/README.md
CHANGED
|
@@ -35,7 +35,8 @@ API key leaks in code repositories are a critical security vulnerability that ca
|
|
|
35
35
|
## ✨ Features
|
|
36
36
|
|
|
37
37
|
- 🔍 **Smart Detection**: Advanced regex patterns detect AWS keys, GitHub tokens, Google API keys, and more
|
|
38
|
-
-
|
|
38
|
+
- � **Auto-Fix Keys**: Automatically replace hardcoded keys with environment variables
|
|
39
|
+
- �🔒 **Git Hooks Integration**: Automatic pre-commit scanning to prevent leaks
|
|
39
40
|
- 🤖 **AI-Powered README**: Generate professional documentation using Google's Gemini API
|
|
40
41
|
- ⚡ **Fast Scanning**: Efficient file parsing with configurable ignore patterns
|
|
41
42
|
- 🌈 **Clear Output**: Color-coded results with detailed reporting
|
|
@@ -55,6 +56,21 @@ api-key-guard scan --path ./src
|
|
|
55
56
|
api-key-guard scan --verbose
|
|
56
57
|
```
|
|
57
58
|
|
|
59
|
+
### Fix Hardcoded Keys
|
|
60
|
+
```bash
|
|
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
|
|
72
|
+
```
|
|
73
|
+
|
|
58
74
|
### Git Hooks Setup
|
|
59
75
|
```bash
|
|
60
76
|
# Install pre-commit hook
|
|
@@ -173,6 +189,26 @@ api-key-guard scan --verbose
|
|
|
173
189
|
# Pattern: sk-1234567890abcdef...
|
|
174
190
|
```
|
|
175
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
|
+
|
|
176
212
|
## 🤝 Contributing
|
|
177
213
|
|
|
178
214
|
1. Fork the repository
|
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
|
+
};
|