@ordojs/security
Version:
Security package for OrdoJS with XSS, CSRF, and injection protection
397 lines (354 loc) • 14 kB
text/typescript
import { execSync } from 'child_process';
import { existsSync, readFileSync } from 'fs';
import { join } from 'path';
export interface SecurityVulnerability {
id: string;
severity: 'low' | 'medium' | 'high' | 'critical';
type: 'xss' | 'injection' | 'csrf' | 'dependency' | 'configuration' | 'other';
description: string;
file?: string;
line?: number;
column?: number;
recommendation: string;
owaspCategory?: string;
}
export interface SecurityAuditResult {
vulnerabilities: SecurityVulnerability[];
summary: {
total: number;
critical: number;
high: number;
medium: number;
low: number;
};
owaspCompliance: {
score: number;
categories: Record<string, boolean>;
};
timestamp: Date;
}
export interface SecurityAuditOptions {
projectPath: string;
includePatterns?: string[];
excludePatterns?: string[];
enableDependencyCheck?: boolean;
enableCodeAnalysis?: boolean;
enableConfigurationCheck?: boolean;
owaspLevel?: 'basic' | 'standard' | 'advanced';
}
export class SecurityAuditor {
private vulnerabilities: SecurityVulnerability[] = [];
private options: SecurityAuditOptions;
constructor(options: SecurityAuditOptions) {
this.options = {
includePatterns: ['**/*.ts', '**/*.js', '**/*.ordo'],
excludePatterns: ['**/node_modules/**', '**/dist/**', '**/*.test.*'],
enableDependencyCheck: true,
enableCodeAnalysis: true,
enableConfigurationCheck: true,
owaspLevel: 'standard',
...options,
};
}
async audit(): Promise<SecurityAuditResult> {
this.vulnerabilities = [];
if (this.options.enableDependencyCheck) {
await this.auditDependencies();
}
if (this.options.enableCodeAnalysis) {
await this.auditSourceCode();
}
if (this.options.enableConfigurationCheck) {
await this.auditConfiguration();
}
return this.generateReport();
}
private async auditDependencies(): Promise<void> {
try {
const packageJsonPath = join(this.options.projectPath, 'package.json');
if (!existsSync(packageJsonPath)) {
return;
}
// Check for known vulnerable packages
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
const dependencies = { ...packageJson.dependencies, ...packageJson.devDependencies };
// Run npm audit if available
try {
const auditResult = execSync('npm audit --json', {
cwd: this.options.projectPath,
encoding: 'utf-8',
stdio: 'pipe'
});
const audit = JSON.parse(auditResult);
if (audit.vulnerabilities) {
Object.entries(audit.vulnerabilities).forEach(([pkg, vuln]: [string, any]) => {
this.vulnerabilities.push({
id: `dep-${pkg}-${vuln.via?.[0]?.source || 'unknown'}`,
severity: this.mapNpmSeverity(vuln.severity),
type: 'dependency',
description: `Vulnerable dependency: ${pkg} - ${vuln.via?.[0]?.title || 'Unknown vulnerability'}`,
recommendation: `Update ${pkg} to version ${vuln.fixAvailable?.version || 'latest'}`,
owaspCategory: 'A06:2021 – Vulnerable and Outdated Components'
});
});
}
} catch (error) {
// npm audit failed, check manually for known vulnerable packages
this.checkKnownVulnerablePackages(dependencies);
}
} catch (error) {
console.warn('Failed to audit dependencies:', error);
}
}
private checkKnownVulnerablePackages(dependencies: Record<string, string>): void {
const knownVulnerable = [
{ name: 'lodash', versions: ['<4.17.21'], issue: 'Prototype pollution vulnerability' },
{ name: 'axios', versions: ['<0.21.2'], issue: 'SSRF vulnerability' },
{ name: 'express', versions: ['<4.17.3'], issue: 'Open redirect vulnerability' },
{ name: 'jsonwebtoken', versions: ['<8.5.1'], issue: 'Algorithm confusion vulnerability' },
];
Object.entries(dependencies).forEach(([pkg, version]) => {
const vulnerable = knownVulnerable.find(v => v.name === pkg);
if (vulnerable) {
this.vulnerabilities.push({
id: `known-vuln-${pkg}`,
severity: 'high',
type: 'dependency',
description: `Known vulnerable package: ${pkg}@${version} - ${vulnerable.issue}`,
recommendation: `Update ${pkg} to a secure version`,
owaspCategory: 'A06:2021 – Vulnerable and Outdated Components'
});
}
});
}
private async auditSourceCode(): Promise<void> {
const glob = await import('glob');
const files = glob.globSync(this.options.includePatterns!, {
cwd: this.options.projectPath,
ignore: this.options.excludePatterns || [],
});
for (const file of files) {
await this.auditFile(join(this.options.projectPath, file));
}
}
private async auditFile(filePath: string): Promise<void> {
try {
const content = readFileSync(filePath, 'utf-8');
const lines = content.split('\n');
lines.forEach((line, index) => {
this.checkForXSSVulnerabilities(line, filePath, index + 1);
this.checkForSQLInjection(line, filePath, index + 1);
this.checkForCSRFVulnerabilities(line, filePath, index + 1);
this.checkForInsecureCrypto(line, filePath, index + 1);
this.checkForHardcodedSecrets(line, filePath, index + 1);
});
} catch (error) {
console.warn(`Failed to audit file ${filePath}:`, error);
}
}
private checkForXSSVulnerabilities(line: string, file: string, lineNumber: number): void {
// Check for dangerous innerHTML usage
if (line.includes('innerHTML') && !line.includes('sanitize') && !line.includes('DOMPurify')) {
this.vulnerabilities.push({
id: `xss-innerHTML-${file}-${lineNumber}`,
severity: 'high',
type: 'xss',
description: 'Potential XSS vulnerability: Direct innerHTML usage without sanitization',
file,
line: lineNumber,
recommendation: 'Use DOMPurify.sanitize() or textContent instead of innerHTML',
owaspCategory: 'A03:2021 – Injection'
});
}
// Check for dangerous eval usage
if (line.includes('eval(') || line.includes('Function(')) {
this.vulnerabilities.push({
id: `xss-eval-${file}-${lineNumber}`,
severity: 'critical',
type: 'xss',
description: 'Critical XSS vulnerability: Use of eval() or Function() constructor',
file,
line: lineNumber,
recommendation: 'Avoid using eval() or Function() constructor. Use safer alternatives',
owaspCategory: 'A03:2021 – Injection'
});
}
// Check for unescaped template literals in HTML context
if (line.includes('${') && (line.includes('<') || line.includes('>'))) {
this.vulnerabilities.push({
id: `xss-template-${file}-${lineNumber}`,
severity: 'medium',
type: 'xss',
description: 'Potential XSS vulnerability: Unescaped template literal in HTML context',
file,
line: lineNumber,
recommendation: 'Ensure template literals are properly escaped when used in HTML',
owaspCategory: 'A03:2021 – Injection'
});
}
}
private checkForSQLInjection(line: string, file: string, lineNumber: number): void {
// Check for string concatenation in SQL queries
const sqlKeywords = ['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'DROP', 'CREATE'];
const hasSQLKeyword = sqlKeywords.some(keyword =>
line.toUpperCase().includes(keyword) && line.includes('+')
);
if (hasSQLKeyword) {
this.vulnerabilities.push({
id: `sql-injection-${file}-${lineNumber}`,
severity: 'high',
type: 'injection',
description: 'Potential SQL injection vulnerability: String concatenation in SQL query',
file,
line: lineNumber,
recommendation: 'Use parameterized queries or prepared statements',
owaspCategory: 'A03:2021 – Injection'
});
}
}
private checkForCSRFVulnerabilities(line: string, file: string, lineNumber: number): void {
// Check for forms without CSRF protection
if (line.includes('<form') && !line.includes('csrf') && !line.includes('token')) {
this.vulnerabilities.push({
id: `csrf-form-${file}-${lineNumber}`,
severity: 'medium',
type: 'csrf',
description: 'Potential CSRF vulnerability: Form without CSRF protection',
file,
line: lineNumber,
recommendation: 'Add CSRF token to forms',
owaspCategory: 'A01:2021 – Broken Access Control'
});
}
}
private checkForInsecureCrypto(line: string, file: string, lineNumber: number): void {
const insecureAlgorithms = ['md5', 'sha1', 'des', 'rc4'];
const hasInsecureAlgo = insecureAlgorithms.some(algo =>
line.toLowerCase().includes(algo)
);
if (hasInsecureAlgo) {
this.vulnerabilities.push({
id: `crypto-weak-${file}-${lineNumber}`,
severity: 'medium',
type: 'other',
description: 'Weak cryptographic algorithm detected',
file,
line: lineNumber,
recommendation: 'Use strong cryptographic algorithms like SHA-256, AES, etc.',
owaspCategory: 'A02:2021 – Cryptographic Failures'
});
}
}
private checkForHardcodedSecrets(line: string, file: string, lineNumber: number): void {
const secretPatterns = [
/password\s*=\s*['"][^'"]+['"]/i,
/api[_-]?key\s*=\s*['"][^'"]+['"]/i,
/secret\s*=\s*['"][^'"]+['"]/i,
/token\s*=\s*['"][^'"]+['"]/i,
];
secretPatterns.forEach(pattern => {
if (pattern.test(line)) {
this.vulnerabilities.push({
id: `hardcoded-secret-${file}-${lineNumber}`,
severity: 'high',
type: 'other',
description: 'Hardcoded secret or credential detected',
file,
line: lineNumber,
recommendation: 'Use environment variables or secure configuration management',
owaspCategory: 'A07:2021 – Identification and Authentication Failures'
});
}
});
}
private async auditConfiguration(): Promise<void> {
// Check for security headers configuration
const configFiles = ['ordojs.config.ts', 'ordojs.config.js', 'next.config.js', 'vite.config.ts'];
for (const configFile of configFiles) {
const configPath = join(this.options.projectPath, configFile);
if (existsSync(configPath)) {
const content = readFileSync(configPath, 'utf-8');
if (!content.includes('helmet') && !content.includes('security')) {
this.vulnerabilities.push({
id: `config-no-security-headers-${configFile}`,
severity: 'medium',
type: 'configuration',
description: 'Missing security headers configuration',
file: configFile,
recommendation: 'Configure security headers using helmet or similar middleware',
owaspCategory: 'A05:2021 – Security Misconfiguration'
});
}
}
}
// Check for HTTPS configuration
const packageJsonPath = join(this.options.projectPath, 'package.json');
if (existsSync(packageJsonPath)) {
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
const scripts = packageJson.scripts || {};
const hasHttpsConfig = Object.values(scripts).some((script: any) =>
typeof script === 'string' && script.includes('https')
);
if (!hasHttpsConfig) {
this.vulnerabilities.push({
id: 'config-no-https',
severity: 'low',
type: 'configuration',
description: 'No HTTPS configuration found in development scripts',
recommendation: 'Configure HTTPS for development and production environments',
owaspCategory: 'A02:2021 – Cryptographic Failures'
});
}
}
}
private generateReport(): SecurityAuditResult {
const summary = this.vulnerabilities.reduce(
(acc, vuln) => {
acc.total++;
acc[vuln.severity]++;
return acc;
},
{ total: 0, critical: 0, high: 0, medium: 0, low: 0 }
);
const owaspCategories = {
'A01:2021 – Broken Access Control': false,
'A02:2021 – Cryptographic Failures': false,
'A03:2021 – Injection': false,
'A04:2021 – Insecure Design': false,
'A05:2021 – Security Misconfiguration': false,
'A06:2021 – Vulnerable and Outdated Components': false,
'A07:2021 – Identification and Authentication Failures': false,
'A08:2021 – Software and Data Integrity Failures': false,
'A09:2021 – Security Logging and Monitoring Failures': false,
'A10:2021 – Server-Side Request Forgery': false,
};
// Mark categories as compliant if no vulnerabilities found
this.vulnerabilities.forEach(vuln => {
if (vuln.owaspCategory && vuln.owaspCategory in owaspCategories) {
(owaspCategories as any)[vuln.owaspCategory] = false;
}
});
// Calculate compliance score
const compliantCategories = Object.values(owaspCategories).filter(Boolean).length;
const totalCategories = Object.keys(owaspCategories).length;
const complianceScore = Math.round((compliantCategories / totalCategories) * 100);
return {
vulnerabilities: this.vulnerabilities,
summary,
owaspCompliance: {
score: complianceScore,
categories: owaspCategories,
},
timestamp: new Date(),
};
}
private mapNpmSeverity(severity: string): 'low' | 'medium' | 'high' | 'critical' {
switch (severity?.toLowerCase()) {
case 'critical': return 'critical';
case 'high': return 'high';
case 'moderate': return 'medium';
case 'low': return 'low';
default: return 'medium';
}
}
}