Files
ruvnet--RuView/vendor/midstream/npm/scripts/security-check.ts
T
rUv 407b46b206 feat: vendor midstream and sublinear-time-solver libraries (#109)
Add ruvnet/midstream (AIMDS real-time inference) and
ruvnet/sublinear-time-solver (sublinear optimization algorithms)
as vendored dependencies under vendor/.
2026-03-02 23:34:05 -05:00

601 lines
18 KiB
TypeScript

#!/usr/bin/env ts-node
/**
* MidStream Security Check Script
*
* Comprehensive security audit of MidStream components
* Created by rUv
*/
import * as fs from 'fs';
import * as path from 'path';
import chalk from 'chalk';
// ============================================================================
// Security Check Types
// ============================================================================
interface SecurityIssue {
severity: 'critical' | 'high' | 'medium' | 'low';
category: string;
file: string;
line?: number;
description: string;
recommendation: string;
}
interface SecurityReport {
timestamp: Date;
totalIssues: number;
critical: number;
high: number;
medium: number;
low: number;
issues: SecurityIssue[];
passed: string[];
}
// ============================================================================
// Security Checks
// ============================================================================
class SecurityChecker {
private issues: SecurityIssue[] = [];
private passed: string[] = [];
/**
* Run all security checks
*/
async runAllChecks(): Promise<SecurityReport> {
console.log(chalk.bold.cyan('\n🔐 MidStream Security Check'));
console.log(chalk.gray('═'.repeat(60)));
await this.checkEnvironmentVariables();
await this.checkAPIKeyExposure();
await this.checkDependencyVulnerabilities();
await this.checkInputValidation();
await this.checkAuthenticationMechanisms();
await this.checkDataEncryption();
await this.checkRateLimiting();
await this.checkErrorHandling();
await this.checkLogging();
await this.checkCORS();
return this.generateReport();
}
/**
* Check environment variables
*/
private async checkEnvironmentVariables(): Promise<void> {
console.log(chalk.yellow('\n📋 Checking environment variables...'));
const envExample = path.join(__dirname, '../../.env.example');
const env = path.join(__dirname, '../../.env');
// Check if .env.example exists
if (!fs.existsSync(envExample)) {
this.issues.push({
severity: 'medium',
category: 'Configuration',
file: '.env.example',
description: '.env.example file is missing',
recommendation: 'Create .env.example with all required environment variables',
});
} else {
this.passed.push('.env.example exists');
}
// Check if .env is in .gitignore
const gitignore = path.join(__dirname, '../../.gitignore');
if (fs.existsSync(gitignore)) {
const content = fs.readFileSync(gitignore, 'utf-8');
if (content.includes('.env')) {
this.passed.push('.env is in .gitignore');
} else {
this.issues.push({
severity: 'high',
category: 'Configuration',
file: '.gitignore',
description: '.env file not excluded from version control',
recommendation: 'Add .env to .gitignore to prevent credential leakage',
});
}
}
}
/**
* Check for API key exposure
*/
private async checkAPIKeyExposure(): Promise<void> {
console.log(chalk.yellow('\n🔑 Checking for API key exposure...'));
const srcDir = path.join(__dirname, '../src');
const files = this.getAllFiles(srcDir, '.ts');
const dangerousPatterns = [
/['"]sk-[a-zA-Z0-9]{32,}['"]/, // OpenAI keys
/['"][A-Z0-9]{32,}['"]/, // Generic API keys
/['"]api[_-]?key['"]:\s*['"][^'"]+['"]/i, // Hardcoded API keys
];
for (const file of files) {
const content = fs.readFileSync(file, 'utf-8');
const lines = content.split('\n');
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
for (const pattern of dangerousPatterns) {
if (pattern.test(line) && !line.includes('process.env')) {
this.issues.push({
severity: 'critical',
category: 'Credentials',
file: path.relative(srcDir, file),
line: i + 1,
description: 'Potential hardcoded API key detected',
recommendation: 'Use environment variables: process.env.API_KEY',
});
}
}
}
}
if (this.issues.filter((i) => i.category === 'Credentials').length === 0) {
this.passed.push('No hardcoded API keys found');
}
}
/**
* Check dependency vulnerabilities
*/
private async checkDependencyVulnerabilities(): Promise<void> {
console.log(chalk.yellow('\n📦 Checking dependencies...'));
const packageJson = path.join(__dirname, '../../package.json');
if (fs.existsSync(packageJson)) {
const pkg = JSON.parse(fs.readFileSync(packageJson, 'utf-8'));
// Check for known vulnerable packages (simplified check)
const knownVulnerable = ['event-stream@3.3.6', 'flatmap-stream'];
const allDeps = {
...pkg.dependencies,
...pkg.devDependencies,
};
for (const [name, version] of Object.entries(allDeps)) {
if (knownVulnerable.includes(name)) {
this.issues.push({
severity: 'high',
category: 'Dependencies',
file: 'package.json',
description: `Known vulnerable package: ${name}`,
recommendation: 'Update or remove the vulnerable package',
});
}
}
this.passed.push('Dependency check completed');
}
}
/**
* Check input validation
*/
private async checkInputValidation(): Promise<void> {
console.log(chalk.yellow('\n✅ Checking input validation...'));
const srcDir = path.join(__dirname, '../src');
const files = this.getAllFiles(srcDir, '.ts');
let validationFound = false;
for (const file of files) {
const content = fs.readFileSync(file, 'utf-8');
// Check for validation patterns
if (
content.includes('validate') ||
content.includes('sanitize') ||
content.includes('throw new Error')
) {
validationFound = true;
}
// Check for dangerous eval/exec usage
if (content.includes('eval(') && !content.includes('// safe')) {
this.issues.push({
severity: 'critical',
category: 'Input Validation',
file: path.relative(srcDir, file),
description: 'Potential unsafe eval() usage',
recommendation: 'Avoid eval(). Use safer alternatives like JSON.parse()',
});
}
}
if (validationFound) {
this.passed.push('Input validation mechanisms found');
}
}
/**
* Check authentication mechanisms
*/
private async checkAuthenticationMechanisms(): Promise<void> {
console.log(chalk.yellow('\n🔐 Checking authentication...'));
const srcDir = path.join(__dirname, '../src');
const files = this.getAllFiles(srcDir, '.ts');
let authFound = false;
for (const file of files) {
const content = fs.readFileSync(file, 'utf-8');
if (
content.includes('Authorization') ||
content.includes('apiKey') ||
content.includes('Bearer')
) {
authFound = true;
}
// Check for insecure auth
if (content.includes('Basic auth') && !content.includes('https')) {
this.issues.push({
severity: 'high',
category: 'Authentication',
file: path.relative(srcDir, file),
description: 'Basic auth without HTTPS',
recommendation: 'Always use HTTPS with Basic authentication',
});
}
}
if (authFound) {
this.passed.push('Authentication mechanisms present');
}
}
/**
* Check data encryption
*/
private async checkDataEncryption(): Promise<void> {
console.log(chalk.yellow('\n🔒 Checking data encryption...'));
const srcDir = path.join(__dirname, '../src');
const files = this.getAllFiles(srcDir, '.ts');
let httpsFound = false;
let wsssFound = false;
for (const file of files) {
const content = fs.readFileSync(file, 'utf-8');
// Check for HTTPS/WSS usage
if (content.includes('https://') || content.includes('wss://')) {
if (content.includes('https://')) httpsFound = true;
if (content.includes('wss://')) wsssFound = true;
}
// Check for insecure protocols
if (content.match(/['"]http:\/\/[^'"]+['"]/)) {
const match = content.match(/['"]http:\/\/[^'"]+['"]/);
if (match && !match[0].includes('localhost') && !match[0].includes('127.0.0.1')) {
this.issues.push({
severity: 'medium',
category: 'Encryption',
file: path.relative(srcDir, file),
description: 'Insecure HTTP protocol detected',
recommendation: 'Use HTTPS for all external connections',
});
}
}
}
if (httpsFound) this.passed.push('HTTPS usage detected');
if (wsssFound) this.passed.push('WSS (secure WebSocket) usage detected');
}
/**
* Check rate limiting
*/
private async checkRateLimiting(): Promise<void> {
console.log(chalk.yellow('\n⏱️ Checking rate limiting...'));
const srcDir = path.join(__dirname, '../src');
const files = this.getAllFiles(srcDir, '.ts');
let rateLimitingFound = false;
for (const file of files) {
const content = fs.readFileSync(file, 'utf-8');
if (
content.includes('rate') ||
content.includes('throttle') ||
content.includes('debounce') ||
content.includes('minInterval')
) {
rateLimitingFound = true;
}
}
if (rateLimitingFound) {
this.passed.push('Rate limiting mechanisms found');
} else {
this.issues.push({
severity: 'low',
category: 'Rate Limiting',
file: 'streaming.ts',
description: 'No rate limiting detected for API calls',
recommendation: 'Implement rate limiting to prevent abuse',
});
}
}
/**
* Check error handling
*/
private async checkErrorHandling(): Promise<void> {
console.log(chalk.yellow('\n⚠️ Checking error handling...'));
const srcDir = path.join(__dirname, '../src');
const files = this.getAllFiles(srcDir, '.ts');
let errorHandlingFound = 0;
for (const file of files) {
const content = fs.readFileSync(file, 'utf-8');
// Count try-catch blocks
const tryCount = (content.match(/try\s*\{/g) || []).length;
const catchCount = (content.match(/catch\s*\(/g) || []).length;
if (tryCount > 0 && catchCount > 0) {
errorHandlingFound++;
}
// Check for unhandled promises
if (content.match(/\.then\(/g) && !content.match(/\.catch\(/g)) {
this.issues.push({
severity: 'medium',
category: 'Error Handling',
file: path.relative(srcDir, file),
description: 'Promise without catch handler',
recommendation: 'Add .catch() to handle promise rejections',
});
}
}
if (errorHandlingFound > 0) {
this.passed.push(`Error handling found in ${errorHandlingFound} files`);
}
}
/**
* Check logging practices
*/
private async checkLogging(): Promise<void> {
console.log(chalk.yellow('\n📝 Checking logging practices...'));
const srcDir = path.join(__dirname, '../src');
const files = this.getAllFiles(srcDir, '.ts');
for (const file of files) {
const content = fs.readFileSync(file, 'utf-8');
const lines = content.split('\n');
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
// Check for sensitive data in logs
if (
line.includes('console.log') &&
(line.includes('password') ||
line.includes('apiKey') ||
line.includes('secret') ||
line.includes('token'))
) {
this.issues.push({
severity: 'high',
category: 'Logging',
file: path.relative(srcDir, file),
line: i + 1,
description: 'Potential sensitive data logging',
recommendation: 'Never log passwords, API keys, or secrets',
});
}
}
}
this.passed.push('Logging practices reviewed');
}
/**
* Check CORS configuration
*/
private async checkCORS(): Promise<void> {
console.log(chalk.yellow('\n🌐 Checking CORS configuration...'));
const srcDir = path.join(__dirname, '../src');
const files = this.getAllFiles(srcDir, '.ts');
let corsFound = false;
for (const file of files) {
const content = fs.readFileSync(file, 'utf-8');
if (content.includes('Access-Control-Allow-Origin')) {
corsFound = true;
// Check for unsafe CORS
if (content.includes('Access-Control-Allow-Origin: *')) {
this.issues.push({
severity: 'medium',
category: 'CORS',
file: path.relative(srcDir, file),
description: 'Wildcard CORS policy detected',
recommendation: 'Restrict CORS to specific origins in production',
});
}
}
}
if (corsFound) {
this.passed.push('CORS configuration present');
}
}
/**
* Get all files recursively
*/
private getAllFiles(dir: string, ext: string): string[] {
const files: string[] = [];
if (!fs.existsSync(dir)) {
return files;
}
const items = fs.readdirSync(dir);
for (const item of items) {
const fullPath = path.join(dir, item);
const stat = fs.statSync(fullPath);
if (stat.isDirectory()) {
files.push(...this.getAllFiles(fullPath, ext));
} else if (item.endsWith(ext)) {
files.push(fullPath);
}
}
return files;
}
/**
* Generate security report
*/
private generateReport(): SecurityReport {
const critical = this.issues.filter((i) => i.severity === 'critical').length;
const high = this.issues.filter((i) => i.severity === 'high').length;
const medium = this.issues.filter((i) => i.severity === 'medium').length;
const low = this.issues.filter((i) => i.severity === 'low').length;
return {
timestamp: new Date(),
totalIssues: this.issues.length,
critical,
high,
medium,
low,
issues: this.issues,
passed: this.passed,
};
}
}
// ============================================================================
// Report Generation
// ============================================================================
function printReport(report: SecurityReport): void {
console.log(chalk.bold.cyan('\n\n' + '═'.repeat(60)));
console.log(chalk.bold.cyan('Security Report'));
console.log(chalk.bold.cyan('═'.repeat(60)));
console.log(chalk.gray(`Generated: ${report.timestamp.toLocaleString()}\n`));
// Summary
console.log(chalk.bold('Summary:'));
console.log(` Total Issues: ${report.totalIssues}`);
console.log(` ${chalk.red('Critical:')} ${report.critical}`);
console.log(` ${chalk.yellow('High:')} ${report.high}`);
console.log(` ${chalk.blue('Medium:')} ${report.medium}`);
console.log(` ${chalk.gray('Low:')} ${report.low}`);
// Passed checks
console.log(chalk.bold.green('\n✓ Passed Checks:'));
report.passed.forEach((check) => {
console.log(chalk.green(`${check}`));
});
// Issues
if (report.issues.length > 0) {
console.log(chalk.bold.red('\n✗ Issues Found:'));
const sortedIssues = report.issues.sort((a, b) => {
const severityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
return severityOrder[a.severity] - severityOrder[b.severity];
});
sortedIssues.forEach((issue, index) => {
const severityColor =
issue.severity === 'critical'
? chalk.red
: issue.severity === 'high'
? chalk.yellow
: issue.severity === 'medium'
? chalk.blue
: chalk.gray;
console.log(`\n${index + 1}. ${severityColor(`[${issue.severity.toUpperCase()}]`)} ${issue.category}`);
console.log(` File: ${issue.file}${issue.line ? `:${issue.line}` : ''}`);
console.log(` ${chalk.gray(issue.description)}`);
console.log(` ${chalk.green('→')} ${issue.recommendation}`);
});
}
// Overall status
console.log(chalk.bold.cyan('\n' + '═'.repeat(60)));
if (report.critical > 0) {
console.log(chalk.red.bold('❌ SECURITY AUDIT FAILED'));
console.log(chalk.red(`Critical issues must be fixed before deployment`));
} else if (report.high > 0) {
console.log(chalk.yellow.bold('⚠️ SECURITY AUDIT WARNING'));
console.log(chalk.yellow(`High-priority issues should be addressed`));
} else if (report.issues.length > 0) {
console.log(chalk.blue.bold('✓ SECURITY AUDIT PASSED'));
console.log(chalk.blue(`Minor issues can be addressed incrementally`));
} else {
console.log(chalk.green.bold('✅ SECURITY AUDIT PASSED'));
console.log(chalk.green(`No security issues detected`));
}
console.log(chalk.bold.cyan('═'.repeat(60) + '\n'));
}
// ============================================================================
// Main
// ============================================================================
async function main() {
const checker = new SecurityChecker();
const report = await checker.runAllChecks();
printReport(report);
// Save report
const reportPath = path.join(__dirname, '../../security-report.json');
fs.writeFileSync(reportPath, JSON.stringify(report, null, 2));
console.log(chalk.gray(`Full report saved to: ${reportPath}\n`));
// Exit with appropriate code
if (report.critical > 0) {
process.exit(1);
} else if (report.high > 0) {
process.exit(1);
}
}
if (require.main === module) {
main().catch((error) => {
console.error(chalk.red('Security check failed:'), error);
process.exit(1);
});
}
export { SecurityChecker, SecurityReport, SecurityIssue };