ctrlshiftleft
Version:
AI-powered toolkit for embedding QA and security testing into development workflows
280 lines (244 loc) • 10.6 kB
text/typescript
import { ChecklistItem, RiskScoreInfo, ImpactLevel, LikelihoodLevel } from '../types/checklistTypes';
import chalk from 'chalk';
/**
* Calculates a risk rating based on impact and likelihood
* @param impact Impact rating
* @param likelihood Likelihood rating
* @returns Risk rating (critical, high, medium, low, info)
*/
export function calculateRiskRating(
impact: ImpactLevel,
likelihood: LikelihoodLevel
): 'critical' | 'high' | 'medium' | 'low' | 'info' {
// Convert string ratings to numerical values for calculation
const impactValue = impactToValue(impact);
const likelihoodValue = likelihoodToValue(likelihood);
// Calculate risk score on a scale of 1-25
const score = impactValue * likelihoodValue;
// Map score to risk rating
if (score >= 20) return 'critical';
if (score >= 12) return 'high';
if (score >= 6) return 'medium';
if (score >= 3) return 'low';
return 'info';
}
/**
* Maps impact level to a numerical value (1-5)
*/
function impactToValue(impact: ImpactLevel): number {
switch (impact) {
case 'critical': return 5;
case 'high': return 4;
case 'medium': return 3;
case 'low': return 2;
case 'info': return 1;
default: return 1;
}
}
/**
* Maps likelihood level to a numerical value (1-5)
*/
function likelihoodToValue(likelihood: LikelihoodLevel): number {
switch (likelihood) {
case 'very-high': return 5;
case 'high': return 4;
case 'medium': return 3;
case 'low': return 2;
case 'very-low': return 1;
default: return 1;
}
}
/**
* Formats a CVSS score with color based on severity
* @param score CVSS score (0.0-10.0)
* @returns Colored string representation
*/
export function formatCVSSScore(score: number): string {
if (score >= 9.0) return chalk.bgRed.white(` ${score.toFixed(1)} Critical `);
if (score >= 7.0) return chalk.bgRed(` ${score.toFixed(1)} High `);
if (score >= 4.0) return chalk.bgYellow.black(` ${score.toFixed(1)} Medium `);
if (score >= 0.1) return chalk.bgGreen.black(` ${score.toFixed(1)} Low `);
return chalk.bgBlue.white(` ${score.toFixed(1)} Info `);
}
/**
* Generates a colored security risk badge based on severity
* @param severity Risk severity
* @returns Colored badge string
*/
export function formatSeverityBadge(severity: string): string {
switch (severity.toLowerCase()) {
case 'critical': return chalk.bgRed.white(' CRITICAL ');
case 'high': return chalk.bgRed(' HIGH ');
case 'medium': return chalk.bgYellow.black(' MEDIUM ');
case 'low': return chalk.bgGreen.black(' LOW ');
case 'info': return chalk.bgBlue.white(' INFO ');
default: return chalk.gray(` ${severity.toUpperCase()} `);
}
}
/**
* Creates a visual risk matrix representation
* @returns ASCII risk matrix as a string
*/
export function generateRiskMatrix(): string {
const matrix = [
'╔════════════════╤══════════════════════════════════════════╗',
'║ │ LIKELIHOOD ║',
'║ ├──────┬──────┬────────┬─────────┬────────╢',
'║ IMPACT │ V.LOW│ LOW │ MEDIUM │ HIGH │ V.HIGH ║',
'╟────────────────┼──────┼──────┼────────┼─────────┼────────╢',
`║ CRITICAL │${chalk.bgGreen.black(' MED ')}│${chalk.bgYellow.black(' HIGH ')}│${chalk.bgRed(' HIGH ')}│${chalk.bgRed.white(' CRIT ')}│${chalk.bgRed.white(' CRIT ')}║`,
`║ HIGH │${chalk.bgGreen.black(' LOW ')}│${chalk.bgYellow.black(' MED ')}│${chalk.bgYellow.black(' HIGH ')}│${chalk.bgRed(' HIGH ')}│${chalk.bgRed.white(' CRIT ')}║`,
`║ MEDIUM │${chalk.bgGreen.black(' LOW ')}│${chalk.bgGreen.black(' LOW ')}│${chalk.bgYellow.black(' MED ')}│${chalk.bgYellow.black(' HIGH ')}│${chalk.bgRed(' HIGH ')}║`,
`║ LOW │${chalk.bgBlue.white(' INFO ')}│${chalk.bgGreen.black(' LOW ')}│${chalk.bgGreen.black(' LOW ')}│${chalk.bgYellow.black(' MED ')}│${chalk.bgYellow.black(' MED ')}║`,
`║ INFO │${chalk.bgBlue.white(' INFO ')}│${chalk.bgBlue.white(' INFO ')}│${chalk.bgGreen.black(' LOW ')}│${chalk.bgGreen.black(' LOW ')}│${chalk.bgGreen.black(' LOW ')}║`,
'╚════════════════╧══════╧══════╧════════╧═════════╧════════╝',
].join('\n');
return matrix;
}
/**
* Generates a detailed risk report for a list of security checklist items
* @param items Array of security checklist items
* @returns Formatted risk report as a string
*/
export function generateSecurityRiskReport(items: ChecklistItem[]): string {
// Only include security items with risk scores
const securityItems = items.filter(
item => item.category === 'Security' && item.riskScore
);
if (securityItems.length === 0) {
return chalk.green('No security issues found with risk scores.');
}
// Sort by risk score (highest first)
securityItems.sort((a, b) => {
const scoreA = a.riskScore?.score || 0;
const scoreB = b.riskScore?.score || 0;
return scoreB - scoreA;
});
let report = chalk.bold('\n🔒 SECURITY RISK REPORT\n\n');
// Add risk matrix
report += generateRiskMatrix() + '\n\n';
// Summary section
const criticalCount = securityItems.filter(item => item.severity === 'critical').length;
const highCount = securityItems.filter(item => item.severity === 'high').length;
const mediumCount = securityItems.filter(item => item.severity === 'medium').length;
const lowCount = securityItems.filter(item => item.severity === 'low').length;
const infoCount = securityItems.filter(item => item.severity === 'info').length;
report += chalk.bold('SUMMARY:\n');
report += `${formatSeverityBadge('critical')} ${criticalCount} issues\n`;
report += `${formatSeverityBadge('high')} ${highCount} issues\n`;
report += `${formatSeverityBadge('medium')} ${mediumCount} issues\n`;
report += `${formatSeverityBadge('low')} ${lowCount} issues\n`;
report += `${formatSeverityBadge('info')} ${infoCount} issues\n\n`;
// Detailed findings
report += chalk.bold('TOP RISKS:\n\n');
securityItems.slice(0, 5).forEach((item, index) => {
const score = item.riskScore?.score || 0;
const references = item.references || [];
const remediation = item.remediation;
report += `${index + 1}. ${chalk.bold(item.title)} ${formatCVSSScore(score)}\n`;
report += ` ${chalk.gray('ID:')} ${item.id}\n`;
report += ` ${chalk.gray('File:')} ${item.file || 'Unknown'}\n`;
report += ` ${chalk.gray('Description:')} ${item.description}\n`;
if (references.length > 0) {
const ref = references[0];
report += ` ${chalk.gray('Reference:')} ${ref.cwe || ''} ${ref.owasp || ''}\n`;
}
if (remediation) {
report += ` ${chalk.gray('Remediation:')} ${remediation.description}\n`;
if (remediation.codeExample) {
report += ` ${chalk.gray('Example:')} ${chalk.green(remediation.codeExample)}\n`;
}
report += ` ${chalk.gray('Priority:')} ${remediation.priority || 'medium'}\n`;
}
report += '\n';
});
return report;
}
/**
* Converts a CVSS vector string to a human-readable explanation
* @param vector CVSS vector string (e.g., "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N")
* @returns Human-readable explanation
*/
export function explainCVSSVector(vector: string): string {
if (!vector.includes('/')) return 'Invalid CVSS vector format';
const parts = vector.split('/');
let explanation = '';
for (const part of parts) {
if (!part.includes(':')) continue;
const [key, value] = part.split(':');
switch (key) {
case 'AV':
explanation += `Attack Vector: ${explainAttackVector(value)}\n`;
break;
case 'AC':
explanation += `Attack Complexity: ${explainAttackComplexity(value)}\n`;
break;
case 'PR':
explanation += `Privileges Required: ${explainPrivilegesRequired(value)}\n`;
break;
case 'UI':
explanation += `User Interaction: ${explainUserInteraction(value)}\n`;
break;
case 'S':
explanation += `Scope: ${explainScope(value)}\n`;
break;
case 'C':
explanation += `Confidentiality Impact: ${explainImpact(value)}\n`;
break;
case 'I':
explanation += `Integrity Impact: ${explainImpact(value)}\n`;
break;
case 'A':
explanation += `Availability Impact: ${explainImpact(value)}\n`;
break;
}
}
return explanation;
}
// Helper functions for CVSS vector explanation
function explainAttackVector(value: string): string {
switch (value) {
case 'N': return 'Network (Remotely exploitable)';
case 'A': return 'Adjacent Network (Adjacent access required)';
case 'L': return 'Local (Local access required)';
case 'P': return 'Physical (Physical access required)';
default: return value;
}
}
function explainAttackComplexity(value: string): string {
switch (value) {
case 'L': return 'Low (No specialized conditions needed)';
case 'H': return 'High (Specialized conditions required)';
default: return value;
}
}
function explainPrivilegesRequired(value: string): string {
switch (value) {
case 'N': return 'None (No privileges required)';
case 'L': return 'Low (Low level privileges required)';
case 'H': return 'High (High level privileges required)';
default: return value;
}
}
function explainUserInteraction(value: string): string {
switch (value) {
case 'N': return 'None (No user interaction required)';
case 'R': return 'Required (User interaction required)';
default: return value;
}
}
function explainScope(value: string): string {
switch (value) {
case 'U': return 'Unchanged (Vulnerability impacts only the vulnerable component)';
case 'C': return 'Changed (Vulnerability can affect resources beyond the vulnerable component)';
default: return value;
}
}
function explainImpact(value: string): string {
switch (value) {
case 'N': return 'None';
case 'L': return 'Low';
case 'H': return 'High';
default: return value;
}
}