@ordojs/security
Version:
Security package for OrdoJS with XSS, CSRF, and injection protection
485 lines (418 loc) ⢠16.1 kB
text/typescript
import { writeFileSync } from 'fs';
import { join } from 'path';
import { SecurityAuditor, type SecurityAuditOptions } from './security-auditor';
import { VulnerabilityScanner } from './vulnerability-scanner';
interface CLIOptions {
projectPath: string;
output?: string;
format?: 'json' | 'html' | 'markdown' | 'csv';
severity?: 'low' | 'medium' | 'high' | 'critical';
includePatterns?: string[];
excludePatterns?: string[];
enableDependencyCheck?: boolean;
enableCodeAnalysis?: boolean;
enableConfigurationCheck?: boolean;
generateReport?: boolean;
generateFixScript?: boolean;
verbose?: boolean;
}
class SecurityAuditCLI {
private options: CLIOptions;
constructor(options: CLIOptions) {
this.options = {
format: 'json',
severity: 'low',
enableDependencyCheck: true,
enableCodeAnalysis: true,
enableConfigurationCheck: true,
generateReport: true,
verbose: false,
...options,
};
}
async run(): Promise<void> {
try {
if (this.options.verbose) {
console.log('š Starting OrdoJS Security Audit...');
console.log(`š Project path: ${this.options.projectPath}`);
}
// Run security audit
const auditorOptions: SecurityAuditOptions = {
projectPath: this.options.projectPath,
};
if (this.options.includePatterns) {
auditorOptions.includePatterns = this.options.includePatterns;
}
if (this.options.excludePatterns) {
auditorOptions.excludePatterns = this.options.excludePatterns;
}
if (this.options.enableDependencyCheck !== undefined) {
auditorOptions.enableDependencyCheck = this.options.enableDependencyCheck;
}
if (this.options.enableCodeAnalysis !== undefined) {
auditorOptions.enableCodeAnalysis = this.options.enableCodeAnalysis;
}
if (this.options.enableConfigurationCheck !== undefined) {
auditorOptions.enableConfigurationCheck = this.options.enableConfigurationCheck;
}
const auditor = new SecurityAuditor(auditorOptions);
const auditResult = await auditor.audit();
// Filter by severity if specified
if (this.options.severity && this.options.severity !== 'low') {
const severityLevels = ['low', 'medium', 'high', 'critical'];
const minSeverityIndex = severityLevels.indexOf(this.options.severity);
auditResult.vulnerabilities = auditResult.vulnerabilities.filter(vuln => {
const vulnSeverityIndex = severityLevels.indexOf(vuln.severity);
return vulnSeverityIndex >= minSeverityIndex;
});
// Recalculate summary
auditResult.summary = auditResult.vulnerabilities.reduce(
(acc, vuln) => {
acc.total++;
acc[vuln.severity]++;
return acc;
},
{ total: 0, critical: 0, high: 0, medium: 0, low: 0 }
);
}
// Generate dependency vulnerability report
let dependencyReport;
if (this.options.enableDependencyCheck) {
const scanner = new VulnerabilityScanner({
projectPath: this.options.projectPath,
});
dependencyReport = await scanner.scanDependencies();
}
// Display results
this.displayResults(auditResult, dependencyReport);
// Generate output files
if (this.options.output || this.options.generateReport) {
await this.generateOutputFiles(auditResult, dependencyReport);
}
// Generate fix script
if (this.options.generateFixScript && dependencyReport) {
await this.generateFixScript(dependencyReport);
}
// Exit with appropriate code
const hasHighSeverityIssues = auditResult.summary.critical > 0 || auditResult.summary.high > 0;
process.exit(hasHighSeverityIssues ? 1 : 0);
} catch (error) {
console.error('ā Security audit failed:', error);
process.exit(1);
}
}
private displayResults(auditResult: any, dependencyReport?: any): void {
console.log('\nš Security Audit Results');
console.log('=' .repeat(50));
// Summary
console.log(`\nš Summary:`);
console.log(` Total vulnerabilities: ${auditResult.summary.total}`);
console.log(` š“ Critical: ${auditResult.summary.critical}`);
console.log(` š High: ${auditResult.summary.high}`);
console.log(` š” Medium: ${auditResult.summary.medium}`);
console.log(` š¢ Low: ${auditResult.summary.low}`);
// OWASP Compliance
console.log(`\nš”ļø OWASP Compliance Score: ${auditResult.owaspCompliance.score}%`);
// Dependency vulnerabilities
if (dependencyReport) {
console.log(`\nš¦ Dependency Vulnerabilities: ${dependencyReport.summary.total}`);
console.log(` š“ Critical: ${dependencyReport.summary.critical}`);
console.log(` š High: ${dependencyReport.summary.high}`);
console.log(` š” Medium: ${dependencyReport.summary.medium}`);
console.log(` š¢ Low: ${dependencyReport.summary.low}`);
}
// Top vulnerabilities
if (auditResult.vulnerabilities.length > 0) {
console.log('\nšØ Top Vulnerabilities:');
auditResult.vulnerabilities
.slice(0, 5)
.forEach((vuln: any, index: number) => {
const severityIcon = this.getSeverityIcon(vuln.severity);
console.log(` ${index + 1}. ${severityIcon} ${vuln.description}`);
if (vuln.file) {
console.log(` š ${vuln.file}:${vuln.line || '?'}`);
}
console.log(` š” ${vuln.recommendation}`);
console.log('');
});
}
// Recommendations
if (auditResult.summary.total > 0) {
console.log('\nš” Recommendations:');
console.log(' 1. Fix critical and high severity vulnerabilities immediately');
console.log(' 2. Review and update dependencies regularly');
console.log(' 3. Implement security headers and HTTPS');
console.log(' 4. Use input validation and output encoding');
console.log(' 5. Enable runtime security monitoring');
} else {
console.log('\nā
No security vulnerabilities found!');
}
}
private async generateOutputFiles(auditResult: any, dependencyReport?: any): Promise<void> {
const outputPath = this.options.output || join(this.options.projectPath, 'security-audit-report');
const reportData = {
audit: auditResult,
dependencies: dependencyReport,
generatedAt: new Date().toISOString(),
options: this.options,
};
switch (this.options.format) {
case 'json':
writeFileSync(`${outputPath}.json`, JSON.stringify(reportData, null, 2));
console.log(`\nš JSON report saved to: ${outputPath}.json`);
break;
case 'html':
const htmlReport = this.generateHTMLReport(reportData);
writeFileSync(`${outputPath}.html`, htmlReport);
console.log(`\nš HTML report saved to: ${outputPath}.html`);
break;
case 'markdown':
const markdownReport = this.generateMarkdownReport(reportData);
writeFileSync(`${outputPath}.md`, markdownReport);
console.log(`\nš Markdown report saved to: ${outputPath}.md`);
break;
case 'csv':
const csvReport = this.generateCSVReport(reportData);
writeFileSync(`${outputPath}.csv`, csvReport);
console.log(`\nš CSV report saved to: ${outputPath}.csv`);
break;
}
}
private async generateFixScript(dependencyReport: any): Promise<void> {
const scanner = new VulnerabilityScanner({
projectPath: this.options.projectPath,
});
const fixScript = await scanner.generateFixScript();
const scriptPath = join(this.options.projectPath, 'fix-vulnerabilities.sh');
writeFileSync(scriptPath, fixScript);
console.log(`\nš§ Fix script saved to: ${scriptPath}`);
console.log(' Run with: chmod +x fix-vulnerabilities.sh && ./fix-vulnerabilities.sh');
}
private generateHTMLReport(data: any): string {
return `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>OrdoJS Security Audit Report</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
.header { background: #f8f9fa; padding: 20px; border-radius: 8px; }
.summary { display: flex; gap: 20px; margin: 20px 0; }
.metric { background: #fff; border: 1px solid #ddd; padding: 15px; border-radius: 8px; }
.vulnerability { border-left: 4px solid #dc3545; padding: 10px; margin: 10px 0; background: #f8f9fa; }
.critical { border-left-color: #dc3545; }
.high { border-left-color: #fd7e14; }
.medium { border-left-color: #ffc107; }
.low { border-left-color: #28a745; }
.recommendation { background: #e7f3ff; padding: 10px; border-radius: 4px; margin: 5px 0; }
</style>
</head>
<body>
<div class="header">
<h1>š”ļø OrdoJS Security Audit Report</h1>
<p>Generated on: ${data.generatedAt}</p>
<p>Project: ${this.options.projectPath}</p>
</div>
<div class="summary">
<div class="metric">
<h3>Total Vulnerabilities</h3>
<h2>${data.audit.summary.total}</h2>
</div>
<div class="metric">
<h3>OWASP Compliance</h3>
<h2>${data.audit.owaspCompliance.score}%</h2>
</div>
${data.dependencies ? `
<div class="metric">
<h3>Dependency Issues</h3>
<h2>${data.dependencies.summary.total}</h2>
</div>
` : ''}
</div>
<h2>šØ Vulnerabilities</h2>
${data.audit.vulnerabilities.map((vuln: any) => `
<div class="vulnerability ${vuln.severity}">
<h4>${vuln.description}</h4>
${vuln.file ? `<p><strong>File:</strong> ${vuln.file}:${vuln.line || '?'}</p>` : ''}
<p><strong>Severity:</strong> ${vuln.severity.toUpperCase()}</p>
<p><strong>Type:</strong> ${vuln.type}</p>
${vuln.owaspCategory ? `<p><strong>OWASP:</strong> ${vuln.owaspCategory}</p>` : ''}
<div class="recommendation">
<strong>Recommendation:</strong> ${vuln.recommendation}
</div>
</div>
`).join('')}
${data.dependencies && data.dependencies.vulnerabilities.length > 0 ? `
<h2>š¦ Dependency Vulnerabilities</h2>
${data.dependencies.vulnerabilities.map((vuln: any) => `
<div class="vulnerability ${vuln.vulnerability.severity}">
<h4>${vuln.package}@${vuln.version}</h4>
<p><strong>Issue:</strong> ${vuln.vulnerability.title}</p>
<p><strong>Severity:</strong> ${vuln.vulnerability.severity.toUpperCase()}</p>
${vuln.vulnerability.cvss ? `<p><strong>CVSS Score:</strong> ${vuln.vulnerability.cvss.score}</p>` : ''}
<div class="recommendation">
<strong>Fix:</strong> ${vuln.fixAvailable.available ?
`Update to ${vuln.fixAvailable.version}` :
'No automatic fix available'}
</div>
</div>
`).join('')}
` : ''}
</body>
</html>
`.trim();
}
private generateMarkdownReport(data: any): string {
return `
# š”ļø OrdoJS Security Audit Report
**Generated:** ${data.generatedAt}
**Project:** ${this.options.projectPath}
## š Summary
- **Total Vulnerabilities:** ${data.audit.summary.total}
- **Critical:** ${data.audit.summary.critical}
- **High:** ${data.audit.summary.high}
- **Medium:** ${data.audit.summary.medium}
- **Low:** ${data.audit.summary.low}
- **OWASP Compliance Score:** ${data.audit.owaspCompliance.score}%
${data.dependencies ? `
## š¦ Dependency Vulnerabilities
- **Total:** ${data.dependencies.summary.total}
- **Critical:** ${data.dependencies.summary.critical}
- **High:** ${data.dependencies.summary.high}
- **Medium:** ${data.dependencies.summary.medium}
- **Low:** ${data.dependencies.summary.low}
` : ''}
## šØ Vulnerabilities
${data.audit.vulnerabilities.map((vuln: any) => `
### ${vuln.description}
- **Severity:** ${vuln.severity.toUpperCase()}
- **Type:** ${vuln.type}
${vuln.file ? `- **File:** ${vuln.file}:${vuln.line || '?'}` : ''}
${vuln.owaspCategory ? `- **OWASP Category:** ${vuln.owaspCategory}` : ''}
**Recommendation:** ${vuln.recommendation}
---
`).join('')}
${data.dependencies && data.dependencies.vulnerabilities.length > 0 ? `
## š¦ Dependency Issues
${data.dependencies.vulnerabilities.map((vuln: any) => `
### ${vuln.package}@${vuln.version}
- **Issue:** ${vuln.vulnerability.title}
- **Severity:** ${vuln.vulnerability.severity.toUpperCase()}
- **ID:** ${vuln.vulnerability.id}
${vuln.vulnerability.cvss ? `- **CVSS Score:** ${vuln.vulnerability.cvss.score}` : ''}
**Fix:** ${vuln.fixAvailable.available ?
`Update to ${vuln.fixAvailable.version}` :
'No automatic fix available'}
---
`).join('')}
` : ''}
`.trim();
}
private generateCSVReport(data: any): string {
const headers = ['Type', 'Severity', 'Description', 'File', 'Line', 'Recommendation', 'OWASP Category'];
const rows = data.audit.vulnerabilities.map((vuln: any) => [
vuln.type,
vuln.severity,
vuln.description,
vuln.file || '',
vuln.line || '',
vuln.recommendation,
vuln.owaspCategory || '',
]);
return [headers, ...rows]
.map(row => row.map((cell: any) => `"${cell}"`).join(','))
.join('\n');
}
private getSeverityIcon(severity: string): string {
switch (severity) {
case 'critical': return 'š“';
case 'high': return 'š ';
case 'medium': return 'š”';
case 'low': return 'š¢';
default: return 'āŖ';
}
}
}
// CLI argument parsing
function parseArgs(): CLIOptions {
const args = process.argv.slice(2);
const options: CLIOptions = {
projectPath: process.cwd(),
};
for (let i = 0; i < args.length; i++) {
const arg = args[i];
const nextArg = args[i + 1];
switch (arg) {
case '--path':
case '-p':
if (nextArg) options.projectPath = nextArg;
i++;
break;
case '--output':
case '-o':
if (nextArg) options.output = nextArg;
i++;
break;
case '--format':
case '-f':
options.format = nextArg as any;
i++;
break;
case '--severity':
case '-s':
options.severity = nextArg as any;
i++;
break;
case '--no-deps':
options.enableDependencyCheck = false;
break;
case '--no-code':
options.enableCodeAnalysis = false;
break;
case '--no-config':
options.enableConfigurationCheck = false;
break;
case '--fix':
options.generateFixScript = true;
break;
case '--verbose':
case '-v':
options.verbose = true;
break;
case '--help':
case '-h':
console.log(`
OrdoJS Security Audit CLI
Usage: ordojs-security-audit [options]
Options:
-p, --path <path> Project path (default: current directory)
-o, --output <file> Output file path
-f, --format <format> Output format: json, html, markdown, csv (default: json)
-s, --severity <level> Minimum severity: low, medium, high, critical (default: low)
--no-deps Skip dependency vulnerability check
--no-code Skip source code analysis
--no-config Skip configuration audit
--fix Generate vulnerability fix script
-v, --verbose Verbose output
-h, --help Show this help message
Examples:
ordojs-security-audit
ordojs-security-audit --path ./my-project --format html --output report
ordojs-security-audit --severity high --fix --verbose
`);
process.exit(0);
}
}
return options;
}
// Run CLI if this file is executed directly
if (require.main === module) {
const options = parseArgs();
const cli = new SecurityAuditCLI(options);
cli.run().catch(console.error);
}
export { SecurityAuditCLI };
export type { CLIOptions };