import type { CodeFile, SecurityIssue } from '../types/index.js';
import { FileReader } from '../utils/file-reader.js';
export class SecurityAnalyzer {
/**
* Scan code for security issues
*/
async scanSecurityIssues(files: CodeFile[] | string[]): Promise<SecurityIssue[]> {
const codeFiles = await this.getCodeFiles(files);
const issues: SecurityIssue[] = [];
for (const file of codeFiles) {
// Detect hardcoded secrets
issues.push(...this.detectSecrets(file));
// Detect weak authentication
issues.push(...this.detectWeakAuth(file));
// Detect insecure dependencies (would check package.json in real implementation)
// Detect permission issues
issues.push(...this.detectPermissionIssues(file));
}
return issues;
}
/**
* Detect hardcoded secrets in code
*/
private detectSecrets(file: CodeFile): SecurityIssue[] {
const issues: SecurityIssue[] = [];
const lines = file.content.split('\n');
// Common secret patterns
const secretPatterns = [
{
pattern: /(?:password|passwd|pwd)\s*[=:]\s*["']([^"']+)["']/gi,
type: 'password' as const,
severity: 'critical' as const,
},
{
pattern: /(?:api[_-]?key|apikey)\s*[=:]\s*["']([^"']+)["']/gi,
type: 'api_key' as const,
severity: 'critical' as const,
},
{
pattern: /(?:secret|token)\s*[=:]\s*["']([^"']+)["']/gi,
type: 'secret' as const,
severity: 'high' as const,
},
{
pattern: /(?:aws[_-]?access[_-]?key|aws[_-]?secret)\s*[=:]\s*["']([^"']+)["']/gi,
type: 'aws_credentials' as const,
severity: 'critical' as const,
},
{
pattern: /(?:private[_-]?key|ssh[_-]?key)\s*[=:]\s*["']([^"']+)["']/gi,
type: 'private_key' as const,
severity: 'critical' as const,
},
];
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
for (const { pattern, type, severity } of secretPatterns) {
if (pattern.test(line)) {
issues.push({
type: 'secret',
severity,
location: `${file.path}:${i + 1}`,
description: `Potential hardcoded ${type} detected`,
recommendation: 'Move secrets to environment variables or secure configuration',
detectedAt: new Date(),
});
}
}
}
return issues;
}
/**
* Detect weak authentication patterns
*/
private detectWeakAuth(file: CodeFile): SecurityIssue[] {
const issues: SecurityIssue[] = [];
const content = file.content.toLowerCase();
// Weak password validation
if (content.includes('password.length') && !content.includes('password.length >= 8')) {
issues.push({
type: 'weak_auth',
severity: 'medium',
location: file.path,
description: 'Weak password validation detected',
recommendation: 'Enforce minimum password length and complexity requirements',
detectedAt: new Date(),
});
}
// SQL injection vulnerabilities
if (content.includes('query') && content.includes('+') && !content.includes('parameterized')) {
const sqlKeywords = ['select', 'insert', 'update', 'delete'];
if (sqlKeywords.some((keyword) => content.includes(keyword))) {
issues.push({
type: 'other',
severity: 'high',
location: file.path,
description: 'Potential SQL injection vulnerability',
recommendation: 'Use parameterized queries or prepared statements',
detectedAt: new Date(),
});
}
}
// XSS vulnerabilities
if (content.includes('innerhtml') || content.includes('document.write')) {
issues.push({
type: 'other',
severity: 'high',
location: file.path,
description: 'Potential XSS vulnerability',
recommendation: 'Use textContent or sanitize HTML input',
detectedAt: new Date(),
});
}
return issues;
}
/**
* Detect permission issues
*/
private detectPermissionIssues(file: CodeFile): SecurityIssue[] {
const issues: SecurityIssue[] = [];
const content = file.content.toLowerCase();
// File system operations without permission checks
if (content.includes('fs.writefile') || content.includes('fs.unlink')) {
if (!content.includes('permission') && !content.includes('access')) {
issues.push({
type: 'permission',
severity: 'medium',
location: file.path,
description: 'File system operations without permission checks',
recommendation: 'Add permission checks before file operations',
detectedAt: new Date(),
});
}
}
return issues;
}
/**
* Get code files from paths or CodeFile objects
*/
private async getCodeFiles(files: CodeFile[] | string[]): Promise<CodeFile[]> {
if (files.length === 0) return [];
if (typeof files[0] === 'string') {
return await FileReader.readFiles((files as string[]).join(','), {
ignore: ['node_modules/**', 'dist/**', 'build/**'],
});
}
return files as CodeFile[];
}
}