vibesec
Version:
Security scanner for AI-generated code - detects vulnerabilities in vibe-coded projects
284 lines • 14 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.PlainLanguageReporter = void 0;
const chalk_1 = __importDefault(require("chalk"));
const types_1 = require("../scanner/core/types");
const security_score_1 = require("../lib/utils/security-score");
class PlainLanguageReporter {
constructor() {
this.analogies = {
'secrets': 'password written on a sticky note on your monitor',
'injection': 'unlocked front door that anyone can walk through',
'sql-injection': 'unlocked front door that anyone can walk through',
'xss': 'poisoned water supply affecting everyone who drinks',
'command-injection': 'blank check handed to a stranger',
'path-traversal': 'skeleton key that opens any room',
'auth': 'broken lock on your front door',
'incomplete': 'half-built security fence with gaps',
'ai-specific': 'AI system that can be tricked into misbehaving',
};
this.severityMap = {
[types_1.Severity.CRITICAL]: {
emoji: '🚨',
label: 'Urgent - Fix Today',
timeframe: 'immediately',
businessImpact: 'High risk of data breach, legal liability, and financial loss',
},
[types_1.Severity.HIGH]: {
emoji: '⚠️',
label: 'Important - Fix This Week',
timeframe: 'within 7 days',
businessImpact: 'Moderate risk to data security and user trust',
},
[types_1.Severity.MEDIUM]: {
emoji: '📋',
label: 'Notable - Fix Soon',
timeframe: 'within 30 days',
businessImpact: 'Could lead to security problems if not addressed',
},
[types_1.Severity.LOW]: {
emoji: 'ℹ️',
label: 'Good to Know - Consider Fixing',
timeframe: 'when convenient',
businessImpact: 'Minimal risk, follows best practices',
},
};
this.fixTimeEstimates = {
'secrets': '10-15 minutes',
'injection': '15-30 minutes',
'sql-injection': '15-30 minutes',
'xss': '20-40 minutes',
'command-injection': '15-30 minutes',
'auth': '1-2 hours',
'incomplete': '30 minutes - 2 hours',
'ai-specific': '1-4 hours',
'default': '30 minutes - 1 hour',
};
this.whoCanFix = {
'secrets': 'Any developer',
'injection': 'Backend developer',
'sql-injection': 'Backend developer',
'xss': 'Frontend or full-stack developer',
'command-injection': 'Backend developer',
'auth': 'Senior developer or security engineer',
'incomplete': 'Original developer or tech lead',
'ai-specific': 'AI/ML engineer or senior developer',
'default': 'Any developer',
};
}
generate(result) {
const lines = [];
lines.push(chalk_1.default.bold('═'.repeat(70)));
lines.push(chalk_1.default.bold.cyan(' VibeSec Security Scan - Plain Language Report'));
lines.push(chalk_1.default.bold('═'.repeat(70)));
lines.push('');
lines.push(chalk_1.default.gray(`📁 Scanned: ${result.scan.path}`));
lines.push(chalk_1.default.gray(`📊 Files checked: ${result.scan.filesScanned}`));
lines.push(chalk_1.default.gray(`⏱️ Time taken: ${result.scan.duration.toFixed(2)} seconds`));
lines.push('');
const securityScore = (0, security_score_1.calculateSecurityScore)(result);
const scoreColor = this.getScoreColor(securityScore.score);
lines.push(chalk_1.default.bold('Security Score:'));
lines.push('');
lines.push(scoreColor(` ${securityScore.score}/100 (${securityScore.grade}) - ${securityScore.rating}`));
lines.push(chalk_1.default.gray(` ${(0, security_score_1.getBenchmarkComparison)(securityScore.score, result.scan.filesScanned)}`));
lines.push('');
lines.push(chalk_1.default.bold('Summary:'));
lines.push('');
const { bySeverity } = result.summary;
if (bySeverity.critical > 0) {
lines.push(chalk_1.default.red.bold(`🚨 ${bySeverity.critical} urgent issue${bySeverity.critical > 1 ? 's' : ''} need${bySeverity.critical === 1 ? 's' : ''} immediate attention`));
}
if (bySeverity.high > 0) {
lines.push(chalk_1.default.yellow.bold(`⚠️ ${bySeverity.high} important issue${bySeverity.high > 1 ? 's' : ''} should be fixed this week`));
}
if (bySeverity.medium > 0) {
lines.push(chalk_1.default.blue(`📋 ${bySeverity.medium} issue${bySeverity.medium > 1 ? 's' : ''} should be addressed soon`));
}
if (bySeverity.low > 0) {
lines.push(chalk_1.default.gray(`ℹ️ ${bySeverity.low} minor issue${bySeverity.low > 1 ? 's' : ''} to consider`));
}
lines.push('');
if (result.findings.length === 0) {
lines.push(chalk_1.default.green.bold('✅ Great news! No security issues found.'));
lines.push('');
lines.push('Your code looks secure. Keep up the good work!');
lines.push('');
}
else {
lines.push(chalk_1.default.bold('═'.repeat(70)));
lines.push(chalk_1.default.bold('Detailed Findings:'));
lines.push(chalk_1.default.bold('═'.repeat(70)));
lines.push('');
const grouped = this.groupBySeverity(result.findings);
const severityOrder = [
types_1.Severity.CRITICAL,
types_1.Severity.HIGH,
types_1.Severity.MEDIUM,
types_1.Severity.LOW,
];
let findingNumber = 1;
for (const severity of severityOrder) {
const findings = grouped[severity] || [];
if (findings.length === 0)
continue;
for (const finding of findings) {
lines.push(this.formatFinding(finding, findingNumber));
lines.push(chalk_1.default.bold('─'.repeat(70)));
lines.push('');
findingNumber++;
}
}
}
lines.push(chalk_1.default.bold('═'.repeat(70)));
lines.push('');
if (result.summary.total > 0) {
lines.push(chalk_1.default.bold('💡 What to do next:'));
lines.push('');
if (bySeverity.critical > 0) {
lines.push(chalk_1.default.red(` 1. Fix the ${bySeverity.critical} urgent issue${bySeverity.critical > 1 ? 's' : ''} TODAY`));
}
if (bySeverity.high > 0) {
lines.push(chalk_1.default.yellow(` ${bySeverity.critical > 0 ? '2' : '1'}. Address the ${bySeverity.high} important issue${bySeverity.high > 1 ? 's' : ''} this week`));
}
const nextStep = bySeverity.critical > 0 && bySeverity.high > 0 ? '3' : bySeverity.critical > 0 || bySeverity.high > 0 ? '2' : '1';
lines.push(chalk_1.default.blue(` ${nextStep}. Run 'vibesec scan --explain' again after making fixes`));
lines.push('');
lines.push(chalk_1.default.gray('💭 Need help understanding these issues?'));
lines.push(chalk_1.default.gray(' Ask your development team to review the findings with you.'));
lines.push('');
}
else {
lines.push(chalk_1.default.green('✨ Your code is secure!'));
lines.push('');
lines.push('Keep running scans regularly to catch issues early.');
lines.push('');
}
lines.push(chalk_1.default.bold('═'.repeat(70)));
return lines.join('\n');
}
formatFinding(finding, number) {
const lines = [];
const plainSeverity = this.severityMap[finding.severity];
const color = this.getSeverityColor(finding.severity);
lines.push(color.bold(`${plainSeverity.emoji} [${number}] ${plainSeverity.label}`));
lines.push('');
lines.push(chalk_1.default.bold('Found:'));
lines.push(`${finding.title} in ${chalk_1.default.cyan(finding.location.file + ':' + finding.location.line)}`);
lines.push('');
lines.push(chalk_1.default.bold('What this means:'));
lines.push(this.explainInPlainLanguage(finding));
lines.push('');
lines.push(chalk_1.default.bold('Why it matters:'));
lines.push(plainSeverity.businessImpact);
lines.push('');
lines.push(this.describeRealWorldImpact(finding));
lines.push('');
if (finding.snippet && finding.snippet.trim()) {
lines.push(chalk_1.default.bold('The code:'));
lines.push(chalk_1.default.gray(finding.snippet));
lines.push('');
}
lines.push(chalk_1.default.bold('How to fix:'));
lines.push(finding.fix.recommendation);
lines.push('');
if (finding.fix.before && finding.fix.after) {
lines.push(chalk_1.default.gray('Before:'));
lines.push(chalk_1.default.red(` ${finding.fix.before}`));
lines.push('');
lines.push(chalk_1.default.gray('After:'));
lines.push(chalk_1.default.green(` ${finding.fix.after}`));
lines.push('');
}
const fixTime = this.estimateFixTime(finding);
const whoFixes = this.suggestWhoCanFix(finding);
lines.push(chalk_1.default.bold('Practical details:'));
lines.push(`• Time needed: ${chalk_1.default.cyan(fixTime)}`);
lines.push(`• Who can fix: ${chalk_1.default.cyan(whoFixes)}`);
lines.push(`• Priority: ${chalk_1.default.cyan(plainSeverity.timeframe)}`);
lines.push('');
if (finding.fix.references && finding.fix.references.length > 0) {
lines.push(chalk_1.default.bold('Learn more:'));
finding.fix.references.forEach((ref) => {
lines.push(chalk_1.default.gray(` • ${ref}`));
});
lines.push('');
}
return lines.join('\n');
}
explainInPlainLanguage(finding) {
const analogy = this.getAnalogy(finding.category);
let explanation = finding.description;
if (analogy) {
explanation += `\n\nThink of this like having ${analogy}.`;
}
return explanation;
}
getAnalogy(category) {
const categoryKey = category.toLowerCase();
return this.analogies[categoryKey] || this.analogies['incomplete'];
}
describeRealWorldImpact(finding) {
const impacts = {
'secrets': 'If this code is shared (GitHub, email, etc.), anyone who sees it can:\n • Use your API keys and credentials\n • Rack up charges on your accounts\n • Access your private data',
'injection': 'An attacker could:\n • Steal all your data\n • Delete your database\n • Take over user accounts',
'sql-injection': 'An attacker could:\n • Read all data in your database\n • Modify or delete records\n • Bypass authentication and access any account',
'xss': 'Attackers could:\n • Steal user session cookies\n • Redirect users to malicious sites\n • Deface your website',
'command-injection': 'An attacker could:\n • Execute commands on your server\n • Read sensitive files\n • Take complete control of your system',
'auth': 'Users could:\n • Access features they haven\'t paid for\n • View other users\' private data\n • Bypass security restrictions',
'default': 'This could lead to:\n • Security vulnerabilities\n • Data exposure\n • System compromise',
};
const categoryKey = finding.category.toLowerCase();
const ruleKey = finding.rule.toLowerCase();
return impacts[ruleKey] || impacts[categoryKey] || impacts['default'];
}
estimateFixTime(finding) {
const categoryKey = finding.category.toLowerCase();
const ruleKey = finding.rule.toLowerCase();
return (this.fixTimeEstimates[ruleKey] ||
this.fixTimeEstimates[categoryKey] ||
this.fixTimeEstimates['default']);
}
suggestWhoCanFix(finding) {
const categoryKey = finding.category.toLowerCase();
const ruleKey = finding.rule.toLowerCase();
return (this.whoCanFix[ruleKey] ||
this.whoCanFix[categoryKey] ||
this.whoCanFix['default']);
}
getSeverityColor(severity) {
const colors = {
[types_1.Severity.CRITICAL]: chalk_1.default.red,
[types_1.Severity.HIGH]: chalk_1.default.yellow,
[types_1.Severity.MEDIUM]: chalk_1.default.blue,
[types_1.Severity.LOW]: chalk_1.default.gray,
};
return colors[severity] || chalk_1.default.white;
}
getScoreColor(score) {
if (score >= 90)
return chalk_1.default.green.bold;
if (score >= 80)
return chalk_1.default.blue.bold;
if (score >= 70)
return chalk_1.default.yellow.bold;
return chalk_1.default.red.bold;
}
groupBySeverity(findings) {
const grouped = {
[types_1.Severity.CRITICAL]: [],
[types_1.Severity.HIGH]: [],
[types_1.Severity.MEDIUM]: [],
[types_1.Severity.LOW]: [],
};
for (const finding of findings) {
grouped[finding.severity].push(finding);
}
return grouped;
}
}
exports.PlainLanguageReporter = PlainLanguageReporter;
//# sourceMappingURL=plain-language.js.map