bugnitor-security-scanner
Version:
AI-Era Security Scanner: Intelligent automated security review agent specializing in AI-generated vulnerability patterns
388 lines ⢠18.6 kB
JavaScript
;
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