detect-secrets-js
Version:
A JavaScript implementation of Yelp's detect-secrets tool - no Python required
153 lines (124 loc) • 4.94 kB
JavaScript
const { program } = require('commander');
const chalk = require('chalk');
const ora = require('ora');
const path = require('path');
const fs = require('fs');
const detectSecrets = require('../dist/index');
// Set up the CLI
program
.name('detect-secrets-js')
.description('JavaScript implementation of Yelp\'s detect-secrets - no Python required')
.version(require('../package.json').version);
program
.option('-d, --directory <path>', 'Directory to scan (default: current directory)')
.option('-r, --root', 'Scan from project root')
.option('-e, --exclude-files <patterns>', 'File patterns to exclude (comma-separated)')
.option('-x, --exclude-dirs <patterns>', 'Directory patterns to exclude (comma-separated)')
.option('-m, --check-missed', 'Check for potentially missed secrets')
.option('-v, --verbose', 'Include additional information')
.option('-o, --output <file>', 'Output file path')
.option('-l, --limit-file-size', 'Enable file size limits to prevent memory issues')
.option('--max-file-size <size>', 'Maximum file size to scan in KB (default: no limit)', parseInt);
program.parse(process.argv);
const options = program.opts();
// Format results for display
function formatResults(results) {
const { secrets, missed_secrets, truncated } = results;
if (secrets.length === 0 && missed_secrets.length === 0) {
return chalk.green('No secrets found!');
}
let output = '';
// Note if any files were truncated
if (truncated) {
output += chalk.yellow('Note: Some files were truncated due to size limits.\n');
output += chalk.yellow('Use --max-file-size to increase the limit or remove --limit-file-size to scan without limits.\n\n');
}
// Group secrets by file
const fileGroups = {};
for (const secret of secrets) {
if (!fileGroups[secret.file]) {
fileGroups[secret.file] = [];
}
fileGroups[secret.file].push(secret);
}
// Format detected secrets
if (secrets.length > 0) {
output += chalk.bold.yellow(`\n${secrets.length} secret(s) detected:\n`);
for (const [file, fileSecrets] of Object.entries(fileGroups)) {
output += chalk.cyan(`\n${file}:\n`);
for (const secret of fileSecrets) {
const status = secret.is_false_positive
? chalk.gray('[Likely False Positive]')
: chalk.red('[Secret]');
output += ` ${status} Line ${secret.line}: ${secret.types.join(', ')}\n`;
}
}
}
// Format missed secrets
if (missed_secrets.length > 0) {
output += chalk.bold.yellow(`\n${missed_secrets.length} potentially missed secret(s):\n`);
const missedFileGroups = {};
for (const secret of missed_secrets) {
if (!missedFileGroups[secret.file]) {
missedFileGroups[secret.file] = [];
}
missedFileGroups[secret.file].push(secret);
}
for (const [file, fileSecrets] of Object.entries(missedFileGroups)) {
output += chalk.cyan(`\n${file}:\n`);
for (const secret of fileSecrets) {
output += ` ${chalk.yellow('[Potential]')} Line ${secret.line}: ${secret.type}\n`;
}
}
}
return output;
}
// Save results to file if requested
function saveResults(results, outputPath) {
try {
fs.writeFileSync(outputPath, JSON.stringify(results, null, 2));
console.log(chalk.green(`\nResults saved to ${outputPath}`));
} catch (error) {
console.error(chalk.red(`\nError saving results: ${error.message}`));
}
}
// Main function
async function main() {
const spinner = ora('Initializing WebAssembly module...').start();
try {
// Initialize the WebAssembly module
await detectSecrets.initialize();
// Prepare scan options
const scanOptions = {
directory: options.directory || process.cwd(),
root: options.root || false,
excludeFiles: options.excludeFiles ? options.excludeFiles.split(',') : [],
excludeDirs: options.excludeDirs ? options.excludeDirs.split(',') : [],
checkMissed: options.checkMissed || false,
verbose: options.verbose || false,
limitFileSize: options.limitFileSize || false,
maxFileSize: options.maxFileSize ? options.maxFileSize * 1024 : undefined
};
// Update spinner text
spinner.text = 'Scanning for secrets...';
// Scan the directory
const results = await detectSecrets.scanDirectory(scanOptions.directory, scanOptions);
// Stop spinner and display results
spinner.stop();
console.log(formatResults(results));
// Save results if output file is specified
if (options.output) {
saveResults(results, options.output);
}
// Exit with error code if secrets were found
if (results.secrets.length > 0) {
process.exit(1);
}
} catch (error) {
spinner.fail(chalk.red(`Error: ${error.message}`));
process.exit(1);
}
}
// Run the main function
main();