vibe-guard
Version:
██ Vibe-Guard Security Scanner - 28 essential security rules to catch vulnerabilities before they catch you! Zero dependencies, instant setup, works everywhere, optimized performance. Detects SQL injection, XSS, exposed secrets, CSRF, CORS issues, contain
500 lines (467 loc) • 20.1 kB
JavaScript
"use strict";
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 Table = require('cli-table3');
const chalk_1 = __importDefault(require("chalk"));
// Class for reporting the scan results
class Reporter {
// Formats the scan results as a table
formatTable(result) {
if (result.issues.length === 0) {
return this.formatSuccess(result);
}
const table = new Table({
head: [
chalk_1.default.bold('Rule'),
chalk_1.default.bold('Severity'),
chalk_1.default.bold('File'),
chalk_1.default.bold('Line'),
chalk_1.default.bold('Message')
],
colWidths: [25, 12, 40, 8, 60],
wordWrap: true
});
result.issues.forEach(issue => {
table.push([
issue.rule,
this.colorSeverity(issue.severity),
this.truncateFilePath(issue.file),
issue.line.toString(),
issue.message
]);
});
return this.formatHeader(result) + '\n\n' + table.toString() + '\n\n' + this.formatSummary(result);
}
// Formats the scan results as a JSON string
formatJson(result) {
return JSON.stringify(result, null, 2);
}
// Formats the scan results as a SARIF string
formatSarif(result) {
const sarif = {
$schema: "https://schemastore.azurewebsites.net/schemas/json/sarif-2.1.0-rtm.5.json",
version: "2.1.0",
runs: [
{
tool: {
driver: {
name: "Vibe-Guard",
version: "1.1.5",
informationUri: "https://github.com/Devjosef/vibe-guard",
rules: this.generateSarifRules(result)
}
},
results: this.generateSarifResults(result),
invocations: [
{
executionSuccessful: true,
toolExecutionNotifications: [
{
descriptor: {
id: "vibe-guard-scan"
},
message: {
text: `Scanned ${result.filesScanned} files and found ${result.issuesFound} security issues`
}
}
]
}
]
}
]
};
return JSON.stringify(sarif, null, 2);
}
// Formats the scan results as an HTML string
formatHtml(result) {
const html = `
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vibe-Guard Security Report</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
line-height: 1.6;
color: #333;
background: #f5f5f5;
padding: 2rem;
}
.container {
max-width: 1200px;
margin: 0 auto;
background: white;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
overflow: hidden;
}
.header {
background: linear-gradient(135deg, #ff4757, #ff3838);
color: white;
padding: 2rem;
text-align: center;
}
.header h1 {
font-size: 2.5rem;
margin-bottom: 0.5rem;
}
.header p {
font-size: 1.1rem;
opacity: 0.9;
}
.summary {
padding: 2rem;
background: #f8f9fa;
border-bottom: 1px solid #e9ecef;
}
.summary-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1rem;
margin-top: 1rem;
}
.summary-card {
background: white;
padding: 1.5rem;
border-radius: 8px;
text-align: center;
border-left: 4px solid;
}
.summary-card.critical { border-left-color: #dc3545; }
.summary-card.high { border-left-color: #fd7e14; }
.summary-card.medium { border-left-color: #ffc107; }
.summary-card.low { border-left-color: #17a2b8; }
.summary-card h3 {
font-size: 2rem;
margin-bottom: 0.5rem;
}
.summary-card.critical h3 { color: #dc3545; }
.summary-card.high h3 { color: #fd7e14; }
.summary-card.medium h3 { color: #ffc107; }
.summary-card.low h3 { color: #17a2b8; }
.issues {
padding: 2rem;
}
.issue {
background: white;
border: 1px solid #e9ecef;
border-radius: 8px;
margin-bottom: 1rem;
overflow: hidden;
}
.issue-header {
padding: 1rem;
border-bottom: 1px solid #e9ecef;
display: flex;
justify-content: space-between;
align-items: center;
}
.issue-title {
font-weight: 600;
font-size: 1.1rem;
}
.severity {
padding: 0.25rem 0.75rem;
border-radius: 20px;
font-size: 0.875rem;
font-weight: 600;
text-transform: uppercase;
}
.severity.critical { background: #dc3545; color: white; }
.severity.high { background: #fd7e14; color: white; }
.severity.medium { background: #ffc107; color: #212529; }
.severity.low { background: #17a2b8; color: white; }
.issue-content {
padding: 1rem;
}
.issue-meta {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1rem;
margin-bottom: 1rem;
}
.meta-item {
background: #f8f9fa;
padding: 0.75rem;
border-radius: 4px;
}
.meta-label {
font-size: 0.875rem;
color: #6c757d;
margin-bottom: 0.25rem;
}
.meta-value {
font-weight: 600;
font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace;
}
.code-snippet {
background: #f8f9fa;
border: 1px solid #e9ecef;
border-radius: 4px;
padding: 1rem;
font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace;
font-size: 0.875rem;
overflow-x: auto;
}
.suggestion {
background: #e7f3ff;
border: 1px solid #b3d9ff;
border-radius: 4px;
padding: 1rem;
margin-top: 1rem;
}
.suggestion h4 {
color: #0066cc;
margin-bottom: 0.5rem;
}
.footer {
background: #f8f9fa;
padding: 2rem;
text-align: center;
color: #6c757d;
border-top: 1px solid #e9ecef;
}
@media (max-width: 768px) {
body { padding: 1rem; }
.header h1 { font-size: 2rem; }
.issue-header { flex-direction: column; align-items: flex-start; gap: 0.5rem; }
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>██ Vibe-Guard Security Report</h1>
<p>Security scan completed on ${new Date().toLocaleDateString()}</p>
</div>
<div class="summary">
<h2>Summary</h2>
<div class="summary-grid">
<div class="summary-card critical">
<h3>${result.summary.critical}</h3>
<p>Critical Issues</p>
</div>
<div class="summary-card high">
<h3>${result.summary.high}</h3>
<p>High Issues</p>
</div>
<div class="summary-card medium">
<h3>${result.summary.medium}</h3>
<p>Medium Issues</p>
</div>
<div class="summary-card low">
<h3>${result.summary.low}</h3>
<p>Low Issues</p>
</div>
</div>
<p style="margin-top: 1rem; color: #6c757d;">
Scanned ${result.filesScanned} files and found ${result.issuesFound} security issues
</p>
</div>
<div class="issues">
<h2>Security Issues</h2>
${result.issues.length === 0 ?
'<div style="text-align: center; padding: 3rem; color: #28a745;"><h3>✅ No security issues found!</h3><p>Your code looks secure.</p></div>' :
result.issues.map(issue => this.formatHtmlIssue(issue)).join('')}
</div>
<div class="footer">
<p>Generated by Vibe-Guard v1.1.5 | <a href="https://github.com/Devjosef/vibe-guard">GitHub</a></p>
</div>
</div>
</body>
</html>`;
return html;
}
// Generates the SARIF rules
generateSarifRules(result) {
const ruleNames = [...new Set(result.issues.map(issue => issue.rule))];
return ruleNames.map(ruleName => ({
id: ruleName,
name: ruleName,
shortDescription: {
text: `Security rule: ${ruleName}`
}
}));
}
// Generates the SARIF results
generateSarifResults(result) {
return result.issues.map(issue => ({
ruleId: issue.rule,
level: this.mapSeverityToSarifLevel(issue.severity),
message: {
text: issue.message
},
locations: [
{
physicalLocation: {
artifactLocation: {
uri: issue.file
},
region: {
startLine: issue.line,
startColumn: issue.column
}
}
}
],
properties: {
suggestion: issue.suggestion
}
}));
}
// Maps the severity to the SARIF level
mapSeverityToSarifLevel(severity) {
switch (severity) {
case 'critical':
case 'high':
return 'error';
case 'medium':
return 'warning';
case 'low':
return 'note';
default:
return 'note';
}
}
// Formats the HTML issue
formatHtmlIssue(issue) {
return `
<div class="issue">
<div class="issue-header">
<div class="issue-title">${this.escapeHtml(issue.rule)}</div>
<div class="severity ${issue.severity}">${issue.severity.toUpperCase()}</div>
</div>
<div class="issue-content">
<p><strong>${this.escapeHtml(issue.message)}</strong></p>
<div class="issue-meta">
<div class="meta-item">
<div class="meta-label">File</div>
<div class="meta-value">${this.escapeHtml(issue.file)}</div>
</div>
<div class="meta-item">
<div class="meta-label">Line</div>
<div class="meta-value">${issue.line}:${issue.column}</div>
</div>
<div class="meta-item">
<div class="meta-label">Rule</div>
<div class="meta-value">${this.escapeHtml(issue.rule)}</div>
</div>
</div>
<div class="code-snippet">${this.escapeHtml(issue.code)}</div>
<div class="suggestion">
<h4>💡 Suggestion</h4>
<p>${this.escapeHtml(issue.suggestion)}</p>
</div>
</div>
</div>
`;
}
escapeHtml(text) {
return text
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
formatSuccess(result) {
const header = chalk_1.default.green.bold('╔══════════════════════════════════════════════════════════════════════════════╗');
const title = chalk_1.default.green.bold('║ VIBE-GUARD SECURITY SCAN COMPLETE ║');
const subtitle = chalk_1.default.green.bold('║ NO THREATS DETECTED ║');
const summary = chalk_1.default.green(`║ ✅ No security issues found in ${result.filesScanned} files`);
const footer = chalk_1.default.green.bold('╚══════════════════════════════════════════════════════════════════════════════╝');
return `${header}\n${title}\n${subtitle}\n${summary.padEnd(75)} ║\n${footer}\n`;
}
formatHeader(result) {
const header = chalk_1.default.red.bold('╔══════════════════════════════════════════════════════════════════════════════╗');
const title = chalk_1.default.red.bold('║ VIBE-GUARD SECURITY ISSUES DETECTED ║');
const subtitle = chalk_1.default.yellow.bold(`║ ${result.issuesFound} THREATS FOUND IN ${result.filesScanned} FILES ║`);
const footer = chalk_1.default.red.bold('╚══════════════════════════════════════════════════════════════════════════════╝');
return `${header}\n${title}\n${subtitle}\n${footer}\n`;
}
formatSummary(result) {
const { summary } = result;
const parts = [];
if (summary.critical > 0) {
parts.push(chalk_1.default.red.bold(`${summary.critical} CRITICAL`));
}
if (summary.high > 0) {
parts.push(chalk_1.default.red(`${summary.high} HIGH`));
}
if (summary.medium > 0) {
parts.push(chalk_1.default.yellow(`${summary.medium} MEDIUM`));
}
if (summary.low > 0) {
parts.push(chalk_1.default.blue(`${summary.low} LOW`));
}
const summaryText = parts.length > 0 ? parts.join(' | ') : 'No issues';
return chalk_1.default.bold('╔══════════════════════════════════════════════════════════════════════════════╗\n') +
chalk_1.default.bold('║ SUMMARY ║\n') +
chalk_1.default.bold('║ ') + summaryText.padEnd(73) + chalk_1.default.bold('║\n') +
chalk_1.default.bold('╚══════════════════════════════════════════════════════════════════════════════╝\n\n') +
this.formatRecommendations();
}
formatRecommendations() {
return chalk_1.default.cyan.bold('╔══════════════════════════════════════════════════════════════════════════════╗\n') +
chalk_1.default.cyan.bold('║ RECOMMENDATIONS ║\n') +
chalk_1.default.cyan.bold('║ • Review and fix critical and high severity issues immediately').padEnd(73) + chalk_1.default.cyan.bold('║\n') +
chalk_1.default.cyan.bold('║ • Consider implementing security linting in your CI/CD pipeline').padEnd(73) + chalk_1.default.cyan.bold('║\n') +
chalk_1.default.cyan.bold('║ • Run Vibe-Guard regularly during development').padEnd(73) + chalk_1.default.cyan.bold('║\n') +
chalk_1.default.cyan.bold('║ • Check our documentation for detailed fix suggestions').padEnd(73) + chalk_1.default.cyan.bold('║\n') +
chalk_1.default.cyan.bold('╚══════════════════════════════════════════════════════════════════════════════╝');
}
// Colors the severity
colorSeverity(severity) {
switch (severity) {
case 'critical':
return chalk_1.default.red.bold('CRITICAL');
case 'high':
return chalk_1.default.red('HIGH');
case 'medium':
return chalk_1.default.yellow('MEDIUM');
case 'low':
return chalk_1.default.blue('LOW');
default:
return String(severity).toUpperCase();
}
}
// Truncates the file path
truncateFilePath(filePath, maxLength = 35) {
let sanitizedPath = filePath.replace(/\\+/g, '/').replace(/\\/g, '/').replace(/\/+/g, '/');
sanitizedPath = sanitizedPath.replace(/\.\./g, '').replace(/\/+/g, '/');
sanitizedPath = sanitizedPath.replace(/^\/+/, '');
if (sanitizedPath.length <= maxLength) {
return sanitizedPath;
}
const parts = sanitizedPath.split('/');
if (parts.length <= 2) {
return sanitizedPath;
}
const first = parts[0];
const last = parts[parts.length - 1];
const truncated = `${first}/.../${last}`;
if (truncated.length <= maxLength) {
return truncated;
}
return '...' + sanitizedPath.slice(-(maxLength - 3));
}
formatIssueDetails(issue) {
const header = chalk_1.default.red.bold(`\n🚨 ${issue.rule} (${this.colorSeverity(issue.severity)})`);
const location = chalk_1.default.gray(`📍 ${issue.file}:${issue.line}:${issue.column}`);
const message = chalk_1.default.white(`💬 ${issue.message}`);
const code = chalk_1.default.gray(`📝 Code: ${issue.code}`);
const suggestion = chalk_1.default.green(`💡 Suggestion: ${issue.suggestion}`);
return `${header}\n${location}\n${message}\n${code}\n${suggestion}\n`;
}
}
exports.Reporter = Reporter;
//# sourceMappingURL=reporter.js.map