@ordojs/security
Version:
Security package for OrdoJS with XSS, CSRF, and injection protection
314 lines • 14.4 kB
JavaScript
import { execSync } from 'child_process';
import { existsSync, readFileSync } from 'fs';
import { join } from 'path';
export class SecurityAuditor {
vulnerabilities = [];
options;
constructor(options) {
this.options = {
includePatterns: ['**/*.ts', '**/*.js', '**/*.ordo'],
excludePatterns: ['**/node_modules/**', '**/dist/**', '**/*.test.*'],
enableDependencyCheck: true,
enableCodeAnalysis: true,
enableConfigurationCheck: true,
owaspLevel: 'standard',
...options,
};
}
async audit() {
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();
}
async auditDependencies() {
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]) => {
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);
}
}
checkKnownVulnerablePackages(dependencies) {
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'
});
}
});
}
async auditSourceCode() {
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));
}
}
async auditFile(filePath) {
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);
}
}
checkForXSSVulnerabilities(line, file, lineNumber) {
// 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'
});
}
}
checkForSQLInjection(line, file, lineNumber) {
// 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'
});
}
}
checkForCSRFVulnerabilities(line, file, lineNumber) {
// 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'
});
}
}
checkForInsecureCrypto(line, file, lineNumber) {
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'
});
}
}
checkForHardcodedSecrets(line, file, lineNumber) {
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'
});
}
});
}
async auditConfiguration() {
// 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) => 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'
});
}
}
}
generateReport() {
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[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(),
};
}
mapNpmSeverity(severity) {
switch (severity?.toLowerCase()) {
case 'critical': return 'critical';
case 'high': return 'high';
case 'moderate': return 'medium';
case 'low': return 'low';
default: return 'medium';
}
}
}
//# sourceMappingURL=security-auditor.js.map