UNPKG

bugnitor-security-scanner

Version:

AI-Era Security Scanner: Intelligent automated security review agent specializing in AI-generated vulnerability patterns

388 lines • 18.6 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Reporter = void 0; const fs = __importStar(require("fs")); const path = __importStar(require("path")); const chalk_1 = __importDefault(require("chalk")); class Reporter { generateTextReport(result) { let report = ''; report += chalk_1.default.bold.blue('šŸ” Bugnitor Security Scan Report\n'); report += chalk_1.default.gray('━'.repeat(80) + '\n\n'); report += chalk_1.default.bold('šŸ“Š Project Analysis\n'); report += chalk_1.default.gray(`Project Path: ${result.projectPath}\n`); report += chalk_1.default.gray(`Scan Time: ${result.scanTime.toISOString()}\n`); report += chalk_1.default.gray(`Files Scanned: ${result.summary.filesScanned}\n`); report += chalk_1.default.gray(`Files with Issues: ${result.summary.filesWithIssues}\n`); report += chalk_1.default.gray(`Folders Analyzed: ${result.summary.foldersScanned}\n\n`); report += chalk_1.default.bold('šŸŽÆ Security Summary\n'); report += chalk_1.default.red(` šŸ”“ Critical: ${result.summary.critical}\n`); report += chalk_1.default.yellow(` 🟔 High: ${result.summary.high}\n`); report += chalk_1.default.blue(` šŸ”µ Medium: ${result.summary.medium}\n`); report += chalk_1.default.gray(` ⚪ Low: ${result.summary.low}\n`); report += chalk_1.default.bold(` šŸ“‹ Total Issues: ${result.summary.total}\n`); report += chalk_1.default.gray(` šŸŽÆ Average Confidence: ${Math.round(result.summary.averageConfidence * 100)}%\n\n`); // Security Grade report += chalk_1.default.bold('šŸ† Security Grade\n'); const gradeColor = this.getGradeColor(result.securityGrade.overall); report += gradeColor(` Overall Grade: ${result.securityGrade.overall} (${result.securityGrade.score}/100)\n`); report += chalk_1.default.gray(' Category Breakdown:\n'); report += chalk_1.default.gray(` • Injection Security: ${result.securityGrade.categories.injection}\n`); report += chalk_1.default.gray(` • Access Control: ${result.securityGrade.categories.access_control}\n`); report += chalk_1.default.gray(` • Sensitive Data: ${result.securityGrade.categories.sensitive_data}\n`); report += chalk_1.default.gray(` • Cryptography: ${result.securityGrade.categories.cryptography}\n`); report += chalk_1.default.gray(` • Dependencies: ${result.securityGrade.categories.dependencies}\n`); report += chalk_1.default.gray(` • Configuration: ${result.securityGrade.categories.configuration}\n\n`); if (result.findings.length === 0) { report += chalk_1.default.green('āœ… No security issues found! Your codebase looks secure.\n'); return report; } // Show folder structure with issues report += this.generateFolderStructureReport(result.folderStructure, 0); // Detailed file-by-file analysis report += chalk_1.default.bold('\nšŸ“‚ Detailed File Analysis\n'); report += chalk_1.default.gray('━'.repeat(80) + '\n'); const filesWithIssues = result.fileAnalyses.filter(fa => fa.findings.length > 0); for (const fileAnalysis of filesWithIssues) { report += this.generateFileReport(fileAnalysis); } // Security Recommendations if (result.securityGrade.recommendations.length > 0) { report += chalk_1.default.bold('\nšŸ’” Security Recommendations\n'); report += chalk_1.default.gray('━'.repeat(80) + '\n'); for (const recommendation of result.securityGrade.recommendations) { report += `${recommendation}\n`; } } // Next Steps if (result.nextSteps.length > 0) { report += chalk_1.default.bold('\nšŸ“‹ Next Steps\n'); report += chalk_1.default.gray('━'.repeat(80) + '\n'); for (let i = 0; i < result.nextSteps.length; i++) { report += `${i + 1}. ${result.nextSteps[i]}\n`; } } // Category Summary report += chalk_1.default.bold('\nšŸ“Š Issues by Category\n'); report += chalk_1.default.gray('━'.repeat(80) + '\n'); const sortedCategories = Object.entries(result.summary.byCategory) .sort(([, a], [, b]) => b - a) .slice(0, 10); for (const [category, count] of sortedCategories) { report += chalk_1.default.gray(` ${category}: ${count}\n`); } report += '\n' + chalk_1.default.gray('━'.repeat(80) + '\n'); report += chalk_1.default.yellow('⚔ Powered by Bugnitor - AI-Era Security Scanner\n'); report += chalk_1.default.gray('šŸ’” Secure your code, protect your future!\n'); return report; } generateFolderStructureReport(folder, depth) { let report = ''; const indent = ' '.repeat(depth); const folderName = depth === 0 ? 'Root' : path.basename(folder.folderPath); if (depth === 0) { report += chalk_1.default.bold('šŸ“ Folder Structure & Issue Distribution\n'); report += chalk_1.default.gray('━'.repeat(80) + '\n'); } if (folder.summary.total > 0) { const issueColor = folder.summary.critical > 0 ? chalk_1.default.red : folder.summary.high > 0 ? chalk_1.default.yellow : folder.summary.medium > 0 ? chalk_1.default.blue : chalk_1.default.gray; report += `${indent}${issueColor('šŸ“ ' + folderName)} `; report += chalk_1.default.gray(`(${folder.summary.filesScanned} files, ${folder.summary.total} issues)`); if (folder.summary.critical > 0) report += chalk_1.default.red(` šŸ”“${folder.summary.critical}`); if (folder.summary.high > 0) report += chalk_1.default.yellow(` 🟔${folder.summary.high}`); if (folder.summary.medium > 0) report += chalk_1.default.blue(` šŸ”µ${folder.summary.medium}`); if (folder.summary.low > 0) report += chalk_1.default.gray(` ⚪${folder.summary.low}`); report += '\n'; // Show files with issues in this folder const filesWithIssues = folder.files.filter(f => f.findings.length > 0); for (const file of filesWithIssues) { const fileIssueColor = file.summary.critical > 0 ? chalk_1.default.red : file.summary.high > 0 ? chalk_1.default.yellow : file.summary.medium > 0 ? chalk_1.default.blue : chalk_1.default.gray; report += `${indent} ${fileIssueColor('šŸ“„ ' + path.basename(file.filePath))} `; report += chalk_1.default.gray(`(${file.summary.total} issues)`); if (file.summary.critical > 0) report += chalk_1.default.red(` šŸ”“${file.summary.critical}`); if (file.summary.high > 0) report += chalk_1.default.yellow(` 🟔${file.summary.high}`); if (file.summary.medium > 0) report += chalk_1.default.blue(` šŸ”µ${file.summary.medium}`); if (file.summary.low > 0) report += chalk_1.default.gray(` ⚪${file.summary.low}`); report += '\n'; } } // Recursively show subfolders for (const subFolder of folder.subFolders) { if (subFolder.summary.total > 0) { report += this.generateFolderStructureReport(subFolder, depth + 1); } } return report; } generateFileReport(fileAnalysis) { let report = ''; report += chalk_1.default.bold.underline(`\nšŸ“„ ${fileAnalysis.relativePath}\n`); report += chalk_1.default.gray(`Path: ${fileAnalysis.absolutePath}\n`); report += chalk_1.default.gray(`Size: ${this.formatFileSize(fileAnalysis.size)} | `); report += chalk_1.default.gray(`Lines: ${fileAnalysis.linesOfCode} | `); report += chalk_1.default.gray(`Type: ${fileAnalysis.fileType}\n`); report += chalk_1.default.gray(`Issues: `); if (fileAnalysis.summary.critical > 0) report += chalk_1.default.red(`šŸ”“ ${fileAnalysis.summary.critical} Critical `); if (fileAnalysis.summary.high > 0) report += chalk_1.default.yellow(`🟔 ${fileAnalysis.summary.high} High `); if (fileAnalysis.summary.medium > 0) report += chalk_1.default.blue(`šŸ”µ ${fileAnalysis.summary.medium} Medium `); if (fileAnalysis.summary.low > 0) report += chalk_1.default.gray(`⚪ ${fileAnalysis.summary.low} Low `); report += '\n\n'; // Group findings by type const secrets = fileAnalysis.findings.filter(f => f.type === 'sensitive_data'); const vulnerabilities = fileAnalysis.findings.filter(f => f.type !== 'sensitive_data'); if (secrets.length > 0) { report += chalk_1.default.bold('šŸ” Exposed Secrets:\n'); for (const finding of secrets) { report += this.generateFindingReport(finding); } } if (vulnerabilities.length > 0) { report += chalk_1.default.bold('āš ļø Security Vulnerabilities:\n'); for (const finding of vulnerabilities) { report += this.generateFindingReport(finding); } } return report; } generateFindingReport(finding) { let report = ''; const severityColor = this.getSeverityColor(finding.severity); const typeIcon = this.getTypeIcon(finding.type); report += `\n ${typeIcon} ${severityColor(finding.title)} [${finding.severity.toUpperCase()}]\n`; report += ` ${chalk_1.default.gray('Category:')} ${finding.category}\n`; report += ` ${chalk_1.default.gray('Description:')} ${finding.description}\n`; report += ` ${chalk_1.default.gray('Location:')} Line ${finding.line}, Column ${finding.column}\n`; report += ` ${chalk_1.default.gray('Code:')} ${chalk_1.default.cyan(finding.code)}\n`; if (finding.codeContext) { if (finding.codeContext.before) { report += ` ${chalk_1.default.gray('Before:')} ${chalk_1.default.dim(finding.codeContext.before)}\n`; } if (finding.codeContext.after) { report += ` ${chalk_1.default.gray('After:')} ${chalk_1.default.dim(finding.codeContext.after)}\n`; } } report += ` ${chalk_1.default.gray('Fix:')} ${finding.recommendation}\n`; report += ` ${chalk_1.default.gray('Impact:')} ${finding.impact}\n`; report += ` ${chalk_1.default.gray('Effort:')} ${finding.effort}\n`; if (finding.confidence) { report += ` ${chalk_1.default.gray('Confidence:')} ${Math.round(finding.confidence * 100)}%\n`; } if (finding.cwe) { report += ` ${chalk_1.default.gray('CWE:')} ${finding.cwe}\n`; } if (finding.owasp) { report += ` ${chalk_1.default.gray('OWASP:')} ${finding.owasp}\n`; } return report; } getTypeIcon(type) { const icons = { injection: 'šŸ’‰', broken_access: 'šŸ”“', sensitive_data: 'šŸ”', deserialization: 'šŸ“¦', file_path: 'šŸ“', memory: '🧠', cryptography: 'šŸ”‘', dependency: 'šŸ“š', cicd: 'šŸ”„', config: 'āš™ļø' }; return icons[type] || 'āš ļø'; } getGradeColor(grade) { switch (grade) { case 'A': return chalk_1.default.green.bold; case 'B': return chalk_1.default.blue.bold; case 'C': return chalk_1.default.yellow.bold; case 'D': return chalk_1.default.red.bold; case 'F': return chalk_1.default.red.bold.underline; default: return chalk_1.default.gray; } } formatFileSize(bytes) { if (bytes < 1024) return bytes + ' B'; if (bytes < 1024 * 1024) return Math.round(bytes / 1024) + ' KB'; return Math.round(bytes / (1024 * 1024)) + ' MB'; } generateJsonReport(result) { return JSON.stringify(result, null, 2); } generateSarifReport(result) { const sarif = { version: '2.1.0', $schema: 'https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json', runs: [{ tool: { driver: { name: 'Bugnitor', version: '1.0.0', informationUri: 'https://github.com/bugnitor/bugnitor', rules: this.generateSarifRules(result.findings) } }, results: result.findings.map(finding => ({ ruleId: this.generateRuleId(finding), message: { text: finding.description }, level: this.mapSeverityToSarifLevel(finding.severity), locations: [{ physicalLocation: { artifactLocation: { uri: finding.file }, region: { startLine: finding.line || 1, startColumn: finding.column || 1 } } }] })) }] }; return JSON.stringify(sarif, null, 2); } async saveReport(result, format, outputPath) { let content; let extension; switch (format) { case 'json': content = this.generateJsonReport(result); extension = 'json'; break; case 'sarif': content = this.generateSarifReport(result); extension = 'sarif'; break; default: content = this.generateTextReport(result); extension = 'txt'; } if (outputPath) { fs.writeFileSync(outputPath, content); } else { const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); const filename = `bugnitor-report-${timestamp}.${extension}`; fs.writeFileSync(filename, content); console.log(chalk_1.default.green(`Report saved to: ${filename}`)); } } groupFindingsByFile(findings) { const grouped = {}; for (const finding of findings) { if (!grouped[finding.file]) { grouped[finding.file] = []; } grouped[finding.file].push(finding); } return grouped; } getSeverityColor(severity) { switch (severity) { case 'critical': return chalk_1.default.red.bold; case 'high': return chalk_1.default.red; case 'medium': return chalk_1.default.yellow; case 'low': return chalk_1.default.blue; default: return chalk_1.default.gray; } } generateRuleId(finding) { return finding.title.toLowerCase().replace(/\s+/g, '-').replace(/[^a-z0-9-]/g, ''); } generateSarifRules(findings) { const uniqueRules = new Map(); for (const finding of findings) { const ruleId = this.generateRuleId(finding); if (!uniqueRules.has(ruleId)) { uniqueRules.set(ruleId, { id: ruleId, name: finding.title, shortDescription: { text: finding.title }, fullDescription: { text: finding.description }, help: { text: finding.recommendation }, defaultConfiguration: { level: this.mapSeverityToSarifLevel(finding.severity) } }); } } return Array.from(uniqueRules.values()); } mapSeverityToSarifLevel(severity) { switch (severity) { case 'critical': case 'high': return 'error'; case 'medium': return 'warning'; case 'low': return 'note'; default: return 'info'; } } } exports.Reporter = Reporter; //# sourceMappingURL=reporter.js.map