Skip to main content
Glama
Derrbal
by Derrbal
security-scan.js5.54 kB
#!/usr/bin/env node const fs = require('fs'); const path = require('path'); // Patterns to detect secrets const SECRET_PATTERNS = [ // API Keys (20+ characters, alphanumeric with dots/dashes) { name: 'API Key', pattern: /(?:api[_-]?key|secret|token).*?=.*?['"`]?[a-zA-Z0-9._-]{20,}['"`]?/gi, severity: 'HIGH' }, // API Keys in object properties { name: 'API Key Property', pattern: /(?:api[_-]?key|secret|token).*?:.*?['"`]?[a-zA-Z0-9._-]{20,}['"`]?/gi, severity: 'HIGH' }, // Email addresses in config contexts { name: 'Email in Config', pattern: /(?:username|user|email).*?=.*?['"`]?[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}['"`]?/gi, severity: 'MEDIUM' }, // URLs with potential instance names { name: 'Instance URL', pattern: /(?:url|base_url).*?=.*?['"`]?https?:\/\/[a-zA-Z0-9.-]+\.testrail\.io['"`]?/gi, severity: 'LOW' }, // Generic password patterns { name: 'Password', pattern: /(?:password|passwd|pwd).*?=.*?['"`]?[^'"`\s]+['"`]?/gi, severity: 'HIGH' } ]; // Files to exclude const EXCLUDE_PATTERNS = [ /node_modules/, /\.git/, /\.env$/, /example\.env$/, /package-lock\.json$/, /\.gitleaks\.toml$/, /dist[\/\\]/, /\.js\.map$/, /scripts[\/\\]security-scan\.js$/ ]; // Allowlist patterns (safe to ignore) const ALLOWLIST_PATTERNS = [ /<your-testrail-username>/, /<your-testrail-api-key>/, /your-email@example\.com/, /your-api-key-here/, /https:\/\/your-instance\.testrail\.io/, /https:\/\/your-domain\.testrail\.io/, /\[REDACTED\]/, /placeholder/, /example\.com/, /config\.TESTRAIL_API_KEY/, /TESTRAIL_API_KEY:/, /password: config\./, /password\|passwd\|pwd/, /TESTRAIL_API_KEY.*?=.*?config\./, /password.*?=.*?config\./, /w\.TESTRAIL_API_KEY/, /P\.z\.string\(\)/, /\.min\(1\)/, /process\.env\.TESTRAIL_API_KEY/, /process\.env\.TESTRAIL_USERNAME/, /"<your-testrail-api-key>"/, /"<your-testrail-username>"/, /API_KEY.*?<your-testrail-api-key/, /API_KEY.*?your_testrail_api_key/ ]; function shouldExcludeFile(filePath) { return EXCLUDE_PATTERNS.some(pattern => pattern.test(filePath)); } function isAllowlisted(content) { return ALLOWLIST_PATTERNS.some(pattern => pattern.test(content)); } function scanFile(filePath) { try { const content = fs.readFileSync(filePath, 'utf8'); const issues = []; SECRET_PATTERNS.forEach(({ name, pattern, severity }) => { const matches = content.match(pattern); if (matches) { matches.forEach(match => { if (!isAllowlisted(match)) { issues.push({ file: filePath, pattern: name, match: match.substring(0, 50) + (match.length > 50 ? '...' : ''), severity, line: content.split('\n').findIndex(line => line.includes(match)) + 1 }); } }); } }); return issues; } catch (error) { console.warn(`Warning: Could not read ${filePath}: ${error.message}`); return []; } } function scanDirectory(dirPath) { const allIssues = []; function scanRecursive(currentPath) { try { const items = fs.readdirSync(currentPath); for (const item of items) { const fullPath = path.join(currentPath, item); const stat = fs.statSync(fullPath); if (stat.isDirectory()) { if (!shouldExcludeFile(fullPath)) { scanRecursive(fullPath); } } else if (stat.isFile()) { if (!shouldExcludeFile(fullPath)) { const issues = scanFile(fullPath); allIssues.push(...issues); } } } } catch (error) { console.warn(`Warning: Could not scan ${currentPath}: ${error.message}`); } } scanRecursive(dirPath); return allIssues; } function main() { console.log('🔍 Running security scan...\n'); const startTime = Date.now(); const issues = scanDirectory('.'); const scanTime = Date.now() - startTime; if (issues.length === 0) { console.log('✅ No security issues found!'); console.log(`⏱️ Scan completed in ${scanTime}ms`); process.exit(0); } console.log(`❌ Found ${issues.length} potential security issue(s):\n`); // Group by severity const bySeverity = { HIGH: issues.filter(i => i.severity === 'HIGH'), MEDIUM: issues.filter(i => i.severity === 'MEDIUM'), LOW: issues.filter(i => i.severity === 'LOW') }; ['HIGH', 'MEDIUM', 'LOW'].forEach(severity => { const severityIssues = bySeverity[severity]; if (severityIssues.length > 0) { console.log(`${severity === 'HIGH' ? '🔴' : severity === 'MEDIUM' ? '🟡' : '🟢'} ${severity} (${severityIssues.length}):`); severityIssues.forEach(issue => { console.log(` ${issue.file}:${issue.line} - ${issue.pattern}`); console.log(` ${issue.match}`); }); console.log(''); } }); console.log(`⏱️ Scan completed in ${scanTime}ms`); if (bySeverity.HIGH.length > 0) { console.log('\n🚨 CRITICAL: High severity issues found! Please fix immediately.'); process.exit(1); } else if (bySeverity.MEDIUM.length > 0) { console.log('\n⚠️ WARNING: Medium severity issues found. Review and fix as needed.'); process.exit(1); } else { console.log('\nℹ️ Only low severity issues found. Consider reviewing.'); process.exit(0); } } if (require.main === module) { main(); } module.exports = { scanDirectory, scanFile };

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Derrbal/testrail-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server