secretshield
Version:
🛡️ SecretShield is a CLI tool that detects API keys and sensitive credentials before you commit code.
203 lines (174 loc) • 10.5 kB
JavaScript
const fs = require('fs');
const path = require('path');
const chalk = require('chalk');
const secretPatterns = [
// Generic API Keys and Tokens
/API[_-]?KEY\s*=\s*['"].+['"]/i,
/TOKEN\s*=\s*['"][A-Za-z0-9-_]{32,}['"]/i,
/SECRET_KEY\s*=\s*['"][A-Za-z0-9-_]{32,}['"]/i,
/PRIVATE_KEY\s*=\s*['"][A-Za-z0-9-_]{32,}['"]/i,
/ACCESS[_-]?TOKEN\s*=\s*['"][A-Za-z0-9-_]{32,}['"]/i,
/AUTH[_-]?TOKEN\s*=\s*['"][A-Za-z0-9-_]{32,}['"]/i,
/BEARER[_-]?TOKEN\s*=\s*['"][A-Za-z0-9-_]{32,}['"]/i,
/APP[_-]?SECRET\s*=\s*['"][A-Za-z0-9-_]{32,}['"]/i,
/CLIENT[_-]?SECRET\s*=\s*['"][A-Za-z0-9-_]{32,}['"]/i,
// Passwords and Credentials
/PASSWORD\s*=\s*['"][A-Za-z0-9-_!@#$%^&*()]{8,}['"]/i,
/CREDENTIALS?\s*=\s*['"][A-Za-z0-9-_!@#$%^&*()]{8,}['"]/i,
/AUTH[_-]?PASSWORD\s*=\s*['"][A-Za-z0-9-_!@#$%^&*()]{8,}['"]/i,
/ADMIN[_-]?PASSWORD\s*=\s*['"][A-Za-z0-9-_!@#$%^&*()]{8,}['"]/i,
/ROOT[_-]?PASSWORD\s*=\s*['"][A-Za-z0-9-_!@#$%^&*()]{8,}['"]/i,
// Database Credentials
/DB[_-]?(?:PASSWORD|PASS|SECRET)\s*=\s*['"][A-Za-z0-9-_!@#$%^&*()]{8,}['"]/i,
/DATABASE[_-]?(?:PASSWORD|PASS|SECRET)\s*=\s*['"][A-Za-z0-9-_!@#$%^&*()]{8,}['"]/i,
/MONGO[_-]?(?:URI|PASSWORD|PASS)\s*=\s*['"][^'"]+['"]/i,
/MYSQL[_-]?(?:PASSWORD|PASS)\s*=\s*['"][A-Za-z0-9-_!@#$%^&*()]{8,}['"]/i,
/POSTGRES[_-]?(?:PASSWORD|PASS)\s*=\s*['"][A-Za-z0-9-_!@#$%^&*()]{8,}['"]/i,
/SQLSERVER[_-]?(?:PASSWORD|PASS)\s*=\s*['"][A-Za-z0-9-_!@#$%^&*()]{8,}['"]/i,
/REDIS[_-]?(?:PASSWORD|PASS)\s*=\s*['"][A-Za-z0-9-_!@#$%^&*()]{8,}['"]/i,
/RABBITMQ[_-]?(?:PASSWORD|PASS)\s*=\s*['"][A-Za-z0-9-_!@#$%^&*()]{8,}['"]/i,
/CASSANDRA[_-]?(?:PASSWORD|PASS)\s*=\s*['"][A-Za-z0-9-_!@#$%^&*()]{8,}['"]/i,
/ELASTICSEARCH[_-]?(?:PASSWORD|PASS)\s*=\s*['"][A-Za-z0-9-_!@#$%^&*()]{8,}['"]/i,
/NEO4J[_-]?(?:PASSWORD|PASS)\s*=\s*['"][A-Za-z0-9-_!@#$%^&*()]{8,}['"]/i,
// Cloud Provider Secrets
/AWS[_-]?(?:ACCESS[_-]?KEY|SECRET[_-]?KEY|SESSION[_-]?TOKEN)\s*=\s*['"][A-Za-z0-9/+=]{20,}['"]/i,
/AZURE[_-]?(?:SECRET|KEY|TOKEN)\s*=\s*['"][A-Za-z0-9-_]{32,}['"]/i,
/GCP[_-]?(?:SECRET|KEY|TOKEN)\s*=\s*['"][A-Za-z0-9-_]{32,}['"]/i,
/GOOGLE[_-]?(?:API[_-]?KEY|SECRET|TOKEN)\s*=\s*['"][A-Za-z0-9-_]{32,}['"]/i,
/DIGITAL[_-]?OCEAN[_-]?(?:TOKEN|KEY)\s*=\s*['"][A-Za-z0-9-_]{32,}['"]/i,
/HEROKU[_-]?(?:API[_-]?KEY|TOKEN)\s*=\s*['"][A-Za-z0-9-_]{32,}['"]/i,
/ALIBABA[_-]?(?:SECRET|KEY)\s*=\s*['"][A-Za-z0-9-_]{32,}['"]/i,
/ORACLE[_-]?(?:SECRET|KEY)\s*=\s*['"][A-Za-z0-9-_]{32,}['"]/i,
/IBM[_-]?(?:SECRET|KEY)\s*=\s*['"][A-Za-z0-9-_]{32,}['"]/i,
/VULTR[_-]?(?:API[_-]?KEY|TOKEN)\s*=\s*['"][A-Za-z0-9-_]{32,}['"]/i,
/LINODE[_-]?(?:API[_-]?KEY|TOKEN)\s*=\s*['"][A-Za-z0-9-_]{32,}['"]/i,
// Payment Processing
/STRIPE[_-]?(?:SECRET[_-]?KEY|PUBLISHABLE[_-]?KEY)\s*=\s*['"](?:sk|pk)_(?:live|test)_[A-Za-z0-9]{24}['"]/i,
/PAYPAL[_-]?(?:CLIENT[_-]?(?:SECRET|ID)|SECRET)\s*=\s*['"][A-Za-z0-9-_]{32,}['"]/i,
/SQUARE[_-]?(?:SECRET|TOKEN)\s*=\s*['"][A-Za-z0-9-_]{32,}['"]/i,
/BRAINTREE[_-]?(?:SECRET|TOKEN)\s*=\s*['"][A-Za-z0-9-_]{32,}['"]/i,
/ADYEN[_-]?(?:SECRET|TOKEN)\s*=\s*['"][A-Za-z0-9-_]{32,}['"]/i,
/KLARNA[_-]?(?:SECRET|TOKEN)\s*=\s*['"][A-Za-z0-9-_]{32,}['"]/i,
/WISE[_-]?(?:API[_-]?KEY|TOKEN)\s*=\s*['"][A-Za-z0-9-_]{32,}['"]/i,
// Social Media and Communication
/TWITTER[_-]?(?:API[_-]?(?:KEY|SECRET)|TOKEN)\s*=\s*['"][A-Za-z0-9-_]{32,}['"]/i,
/FACEBOOK[_-]?(?:APP[_-]?SECRET|TOKEN)\s*=\s*['"][A-Za-z0-9-_]{32,}['"]/i,
/INSTAGRAM[_-]?(?:ACCESS[_-]?TOKEN|APP[_-]?SECRET)\s*=\s*['"][A-Za-z0-9-_]{32,}['"]/i,
/LINKEDIN[_-]?(?:CLIENT[_-]?SECRET|TOKEN)\s*=\s*['"][A-Za-z0-9-_]{32,}['"]/i,
/DISCORD[_-]?(?:TOKEN|SECRET)\s*=\s*['"][A-Za-z0-9-_]{24,}['"]/i,
/TELEGRAM[_-]?(?:BOT[_-]?TOKEN|SECRET)\s*=\s*['"][A-Za-z0-9-_]{35,}['"]/i,
/SLACK[_-]?(?:TOKEN|SECRET)\s*=\s*['"][A-Za-z0-9-]{24,}['"]/i,
/TIKTOK[_-]?(?:API[_-]?KEY|SECRET)\s*=\s*['"][A-Za-z0-9-_]{32,}['"]/i,
/PINTEREST[_-]?(?:API[_-]?KEY|SECRET)\s*=\s*['"][A-Za-z0-9-_]{32,}['"]/i,
/TWITCH[_-]?(?:API[_-]?KEY|SECRET)\s*=\s*['"][A-Za-z0-9-_]{32,}['"]/i,
/REDDIT[_-]?(?:API[_-]?KEY|SECRET)\s*=\s*['"][A-Za-z0-9-_]{32,}['"]/i,
/SNAPCHAT[_-]?(?:API[_-]?KEY|SECRET)\s*=\s*['"][A-Za-z0-9-_]{32,}['"]/i,
// Email Services
/SENDGRID[_-]?(?:API[_-]?KEY|TOKEN)\s*=\s*['"][A-Za-z0-9-_]{32,}['"]/i,
/MAILCHIMP[_-]?(?:API[_-]?KEY|TOKEN)\s*=\s*['"][A-Za-z0-9-_]{32,}['"]/i,
/SMTP[_-]?(?:PASSWORD|PASS)\s*=\s*['"][A-Za-z0-9-_!@#$%^&*()]{8,}['"]/i,
/MAILGUN[_-]?(?:API[_-]?KEY|SECRET)\s*=\s*['"][A-Za-z0-9-_]{32,}['"]/i,
/POSTMARK[_-]?(?:API[_-]?KEY|TOKEN)\s*=\s*['"][A-Za-z0-9-_]{32,}['"]/i,
/AMAZON[_-]?SES[_-]?(?:KEY|SECRET)\s*=\s*['"][A-Za-z0-9-_]{32,}['"]/i,
/SPARKPOST[_-]?(?:API[_-]?KEY|TOKEN)\s*=\s*['"][A-Za-z0-9-_]{32,}['"]/i,
// Infrastructure and DevOps
/DOCKER[_-]?(?:PASSWORD|TOKEN)\s*=\s*['"][A-Za-z0-9-_!@#$%^&*()]{8,}['"]/i,
/KUBERNETES[_-]?(?:TOKEN|SECRET)\s*=\s*['"][A-Za-z0-9-_]{32,}['"]/i,
/CLOUDFLARE[_-]?(?:API[_-]?TOKEN|KEY)\s*=\s*['"][A-Za-z0-9-_]{32,}['"]/i,
/GITHUB[_-]?(?:TOKEN|SECRET)\s*=\s*['"][A-Za-z0-9-_]{32,}['"]/i,
/GITLAB[_-]?(?:TOKEN|SECRET)\s*=\s*['"][A-Za-z0-9-_]{32,}['"]/i,
/BITBUCKET[_-]?(?:TOKEN|SECRET)\s*=\s*['"][A-Za-z0-9-_]{32,}['"]/i,
/JENKINS[_-]?(?:TOKEN|SECRET)\s*=\s*['"][A-Za-z0-9-_]{32,}['"]/i,
/TRAVIS[_-]?(?:TOKEN|SECRET)\s*=\s*['"][A-Za-z0-9-_]{32,}['"]/i,
/CIRCLECI[_-]?(?:TOKEN|SECRET)\s*=\s*['"][A-Za-z0-9-_]{32,}['"]/i,
/ANSIBLE[_-]?(?:VAULT[_-]?PASSWORD|SECRET)\s*=\s*['"][A-Za-z0-9-_]{32,}['"]/i,
/TERRAFORM[_-]?(?:TOKEN|SECRET)\s*=\s*['"][A-Za-z0-9-_]{32,}['"]/i,
// Security and Authentication
/JWT[_-]?(?:SECRET|KEY)\s*=\s*['"][A-Za-z0-9-_!@#$%^&*()]{32,}['"]/i,
/OAUTH[_-]?(?:TOKEN|SECRET|KEY)\s*=\s*['"][A-Za-z0-9-_]{32,}['"]/i,
/SSH[_-]?(?:KEY|PRIVATE[_-]?KEY)\s*=\s*['"][A-Za-z0-9-_]{32,}['"]/i,
/SSL[_-]?(?:KEY|CERT)\s*=\s*['"][A-Za-z0-9-_]{32,}['"]/i,
/PGP[_-]?(?:KEY|SECRET)\s*=\s*['"][A-Za-z0-9-_]{32,}['"]/i,
/GPG[_-]?(?:KEY|SECRET)\s*=\s*['"][A-Za-z0-9-_]{32,}['"]/i,
/2FA[_-]?(?:SECRET|KEY)\s*=\s*['"][A-Za-z0-9-_]{32,}['"]/i,
/MFA[_-]?(?:SECRET|KEY)\s*=\s*['"][A-Za-z0-9-_]{32,}['"]/i,
// Specific Service Patterns
/["'](?:AIza[0-9A-Za-z-_]{35})["']/g, // Google API key
/ghp_[A-Za-z0-9]{36}/, // GitHub Personal Token
/gho_[A-Za-z0-9]{36}/, // GitHub OAuth Token
/ghu_[A-Za-z0-9]{36}/, // GitHub User Token
/ghs_[A-Za-z0-9]{36}/, // GitHub Server Token
/ghr_[A-Za-z0-9]{36}/, // GitHub Refresh Token
/eyJ[A-Za-z0-9-_=]+\.eyJ[A-Za-z0-9-_=]+\.?[A-Za-z0-9-_.+/=]*/, // JWT tokens
/[A-Za-z0-9-_]{32,}\.[A-Za-z0-9-_]{32,}\.[A-Za-z0-9-_]{32,}/, // Generic JWT-like tokens
// Generic Secret Patterns
/SECRET\s*=\s*['"][A-Za-z0-9-_]{16,}['"]/i,
/KEY\s*=\s*['"][A-Za-z0-9-_]{16,}['"]/i,
/PASS(?:WORD)?\s*=\s*['"][A-Za-z0-9-_!@#$%^&*()]{8,}['"]/i,
/TOKEN\s*=\s*['"][A-Za-z0-9-_]{16,}['"]/i,
// Connection Strings
/(?:MONGODB|MYSQL|POSTGRES|SQLSERVER|REDIS|RABBITMQ)[_-]?URI\s*=\s*['"][^'"]+['"]/i,
/(?:DATABASE|DB)[_-]?URL\s*=\s*['"][^'"]+['"]/i,
// API Endpoints with Credentials
/https?:\/\/[^:]+:[^@]+@[^'"]+/i,
// Private Keys
/BEGIN[_-]?(?:RSA|EC|DSA|OPENSSH)[_-]?PRIVATE[_-]?KEY/i,
/END[_-]?(?:RSA|EC|DSA|OPENSSH)[_-]?PRIVATE[_-]?KEY/i,
// Additional Cloud Services
/FIREBASE[_-]?(?:API[_-]?KEY|SECRET)\s*=\s*['"][A-Za-z0-9-_]{32,}['"]/i,
/SUPABASE[_-]?(?:API[_-]?KEY|SECRET)\s*=\s*['"][A-Za-z0-9-_]{32,}['"]/i,
/VERCEL[_-]?(?:TOKEN|SECRET)\s*=\s*['"][A-Za-z0-9-_]{32,}['"]/i,
/NETLIFY[_-]?(?:TOKEN|SECRET)\s*=\s*['"][A-Za-z0-9-_]{32,}['"]/i,
// Analytics and Monitoring
/MIXPANEL[_-]?(?:TOKEN|SECRET)\s*=\s*['"][A-Za-z0-9-_]{32,}['"]/i,
/SEGMENT[_-]?(?:WRITE[_-]?KEY|SECRET)\s*=\s*['"][A-Za-z0-9-_]{32,}['"]/i,
/AMPLITUDE[_-]?(?:API[_-]?KEY|SECRET)\s*=\s*['"][A-Za-z0-9-_]{32,}['"]/i,
/DATADOG[_-]?(?:API[_-]?KEY|SECRET)\s*=\s*['"][A-Za-z0-9-_]{32,}['"]/i,
/NEW[_-]?RELIC[_-]?(?:LICENSE[_-]?KEY|SECRET)\s*=\s*['"][A-Za-z0-9-_]{32,}['"]/i,
// CDN and Storage
/CLOUDFRONT[_-]?(?:KEY|SECRET)\s*=\s*['"][A-Za-z0-9-_]{32,}['"]/i,
/S3[_-]?(?:KEY|SECRET)\s*=\s*['"][A-Za-z0-9-_]{32,}['"]/i,
/RACKSPACE[_-]?(?:KEY|SECRET)\s*=\s*['"][A-Za-z0-9-_]{32,}['"]/i,
/BACKBLAZE[_-]?(?:KEY|SECRET)\s*=\s*['"][A-Za-z0-9-_]{32,}['"]/i,
// Search and Cache
/ALGOLIA[_-]?(?:API[_-]?KEY|SECRET)\s*=\s*['"][A-Za-z0-9-_]{32,}['"]/i,
/ELASTIC[_-]?(?:API[_-]?KEY|SECRET)\s*=\s*['"][A-Za-z0-9-_]{32,}['"]/i,
/MEMCACHED[_-]?(?:PASSWORD|PASS)\s*=\s*['"][A-Za-z0-9-_!@#$%^&*()]{8,}['"]/i,
// Maps and Location
/MAPBOX[_-]?(?:TOKEN|SECRET)\s*=\s*['"][A-Za-z0-9-_]{32,}['"]/i,
/GOOGLE[_-]?MAPS[_-]?(?:API[_-]?KEY|SECRET)\s*=\s*['"][A-Za-z0-9-_]{32,}['"]/i,
/TOMTOM[_-]?(?:API[_-]?KEY|SECRET)\s*=\s*['"][A-Za-z0-9-_]{32,}['"]/i,
// SMS and Voice
/TWILIO[_-]?(?:AUTH[_-]?TOKEN|ACCOUNT[_-]?SID)\s*=\s*['"][A-Za-z0-9-_]{32,}['"]/i,
/NEXMO[_-]?(?:API[_-]?KEY|SECRET)\s*=\s*['"][A-Za-z0-9-_]{32,}['"]/i,
/PLIVO[_-]?(?:AUTH[_-]?TOKEN|SECRET)\s*=\s*['"][A-Za-z0-9-_]{32,}['"]/i,
/SINCH[_-]?(?:API[_-]?KEY|SECRET)\s*=\s*['"][A-Za-z0-9-_]{32,}['"]/i
];
function scanFilesForSecrets(files = []) {
let found = false;
for (const filePath of files) {
try {
const content = fs.readFileSync(filePath, 'utf-8');
const lines = content.split('\n');
let fileHasSecret = false;
console.log(chalk.green('🔍 SecretShield scanning staged file... ', filePath));
lines.forEach((line, idx) => {
// Skip lines that have "safe to push" comment
if (line.includes('// secretshield: safe to push') || line.includes('/* secretshield: safe to push */') || line.includes('/*secretshield: safe to push */') || line.includes('//secretshield: safe to push') ) {
return;
}
for (const pattern of secretPatterns) {
if (pattern.test(line)) {
console.log(chalk.red(`🚨 Possible secret found in ${filePath} at line ${idx + 1}`));
console.log(`🔎 Matched Line: ${line.trim()}`);
fileHasSecret = true;
}
}
});
} catch (err) {
console.warn(chalk.yellow(`⚠️ Skipped unreadable file: ${filePath}`));
}
}
return found;
}
module.exports = scanFilesForSecrets;