// engine/workers/handlers/security-audit.ts — Detects security vulnerabilities (SPEC-078 AC-06)
import { readFile, readdir, stat } from 'node:fs/promises';
import { join, extname, basename, relative } from 'node:path';
const PATTERNS = [
    // Hardcoded secrets
    {
        regex: /(?:AKIA[0-9A-Z]{16})/,
        severity: 'critical',
        message: 'Hardcoded AWS access key detected',
        suggestion: 'Use environment variables or a secrets manager',
        category: 'hardcoded-secret',
    },
    {
        regex: /(?:ghp_[A-Za-z0-9_]{36})/,
        severity: 'critical',
        message: 'Hardcoded GitHub personal access token detected',
        suggestion: 'Use environment variables or a secrets manager',
        category: 'hardcoded-secret',
    },
    {
        regex: /(?:sk_live_[A-Za-z0-9]{24,})/,
        severity: 'critical',
        message: 'Hardcoded Stripe secret key detected',
        suggestion: 'Use environment variables or a secrets manager',
        category: 'hardcoded-secret',
    },
    {
        regex: /(?:password|passwd|secret|api_key|apikey|token)\s*[:=]\s*["'][^"']{8,}["']/i,
        severity: 'high',
        message: 'Potential hardcoded credential detected',
        suggestion: 'Move secrets to environment variables or a vault',
        category: 'hardcoded-secret',
    },
    // Unsafe patterns
    {
        regex: /\beval\s*\(/,
        severity: 'high',
        message: 'Use of eval() detected',
        suggestion: 'Avoid eval(); use safer alternatives like JSON.parse()',
        category: 'unsafe-pattern',
        languages: ['.ts', '.js', '.py'],
    },
    {
        regex: /\bexec\s*\(/,
        severity: 'medium',
        message: 'Use of exec() detected — potential command injection',
        suggestion: 'Validate and sanitize inputs before executing commands',
        category: 'unsafe-pattern',
    },
    {
        regex: /dangerouslySetInnerHTML/,
        severity: 'high',
        message: 'dangerouslySetInnerHTML used — XSS risk',
        suggestion: 'Sanitize HTML content with DOMPurify or similar library',
        category: 'unsafe-pattern',
        languages: ['.ts', '.tsx', '.js', '.jsx'],
    },
    {
        regex: /\.innerHTML\s*=/,
        severity: 'high',
        message: 'Direct innerHTML assignment — XSS risk',
        suggestion: 'Use textContent or a sanitization library',
        category: 'unsafe-pattern',
    },
    {
        regex: /\bpickle\.load\b/,
        severity: 'high',
        message: 'pickle.load() is unsafe with untrusted data',
        suggestion: 'Use json or a safe deserialization format',
        category: 'unsafe-pattern',
        languages: ['.py'],
    },
    // SQL injection
    {
        regex: /["'`]\s*\+\s*\w+.*(?:SELECT|INSERT|UPDATE|DELETE|DROP|ALTER)\b/i,
        severity: 'high',
        message: 'Potential SQL injection via string concatenation',
        suggestion: 'Use parameterized queries or an ORM',
        category: 'sql-injection',
    },
    // Insecure defaults
    {
        regex: /http:\/\/(?!localhost|127\.0\.0\.1|0\.0\.0\.0)/,
        severity: 'medium',
        message: 'HTTP URL detected (not HTTPS)',
        suggestion: 'Use HTTPS for external URLs',
        category: 'insecure-default',
    },
    {
        regex: /cors.*:\s*['"][*]['"]/i,
        severity: 'medium',
        message: 'CORS wildcard (*) allows any origin',
        suggestion: 'Restrict CORS to specific allowed origins',
        category: 'insecure-default',
    },
];
// ---------------------------------------------------------------------------
// Ignore patterns
// ---------------------------------------------------------------------------
const IGNORE_DIRS = new Set([
    'node_modules',
    '.git',
    'dist',
    'build',
    '__pycache__',
    'target',
    '.venv',
    'vendor',
    'coverage',
]);
const SKIP_FILES = new Set(['.env.example', '.env.template', '.env.sample']);
const CODE_EXTS = new Set([
    '.ts',
    '.tsx',
    '.js',
    '.jsx',
    '.py',
    '.go',
    '.rs',
    '.java',
    '.kt',
    '.yml',
    '.yaml',
    '.env',
    '.toml',
    '.cfg',
]);
// ---------------------------------------------------------------------------
// File scanning
// ---------------------------------------------------------------------------
async function collectFiles(dir, files) {
    let entries;
    try {
        entries = await readdir(dir);
    }
    catch {
        return;
    }
    for (const entry of entries) {
        if (IGNORE_DIRS.has(entry)) {
            continue;
        }
        const fullPath = join(dir, entry);
        let st;
        try {
            st = await stat(fullPath);
        }
        catch {
            continue;
        }
        if (st.isDirectory()) {
            await collectFiles(fullPath, files);
        }
        else if (st.isFile() && CODE_EXTS.has(extname(fullPath))) {
            files.push(fullPath);
        }
    }
}
function isTestOrExample(filePath) {
    const base = basename(filePath);
    return (SKIP_FILES.has(base) ||
        base.includes('.test.') ||
        base.includes('.spec.') ||
        base.includes('_test.') ||
        base.startsWith('test_') ||
        filePath.includes('__tests__') ||
        filePath.includes('/tests/') ||
        filePath.includes('/test/'));
}
// ---------------------------------------------------------------------------
// Public handler
// ---------------------------------------------------------------------------
export async function runSecurityAudit(projectDir, _changedFiles) {
    const startedAt = new Date().toISOString();
    const start = Date.now();
    const findings = [];
    const allFiles = [];
    await collectFiles(projectDir, allFiles);
    const filesToScan = allFiles.filter((f) => !isTestOrExample(f));
    for (const file of filesToScan) {
        let content;
        try {
            content = await readFile(file, 'utf-8');
        }
        catch {
            continue;
        }
        const ext = extname(file);
        const lines = content.split('\n');
        for (let i = 0; i < lines.length; i++) {
            const line = lines[i];
            if (!line) {
                continue;
            }
            for (const pattern of PATTERNS) {
                if (pattern.languages && !pattern.languages.includes(ext)) {
                    continue;
                }
                if (pattern.regex.test(line)) {
                    findings.push({
                        severity: pattern.severity,
                        file: relative(projectDir, file),
                        line: i + 1,
                        message: pattern.message,
                        suggestion: pattern.suggestion,
                        category: pattern.category,
                    });
                }
            }
        }
    }
    return {
        workerName: 'security-audit',
        startedAt,
        completedAt: new Date().toISOString(),
        durationMs: Date.now() - start,
        status: 'success',
        findings,
        analyzedFiles: filesToScan.length,
    };
}
//# sourceMappingURL=security-audit.js.map