// Planu — Security Analyzer (SPEC-483)
// Rule-based analyzer: hardcoded secrets, missing Zod validation, dangerous APIs, OWASP markers.
import { readFile, readdir } from 'node:fs/promises';
import { join, relative, extname, basename } from 'node:path';
// ---------------------------------------------------------------------------
// Patterns
// ---------------------------------------------------------------------------
/** Detects hardcoded high-entropy strings that look like secrets. */
const SECRET_PATTERNS = [
    {
        pattern: /(?:password|passwd|pwd)\s*[:=]\s*['"][^'"]{6,}['"]/i,
        ruleId: 'security/hardcoded-password',
        label: 'Hardcoded password',
    },
    {
        pattern: /(?:api_?key|apikey|secret_?key|access_?key|auth_?token)\s*[:=]\s*['"][^'"]{8,}['"]/i,
        ruleId: 'security/hardcoded-api-key',
        label: 'Hardcoded API key or secret',
    },
    {
        pattern: /(?:private_?key|privateKey)\s*[:=]\s*['"][^'"]{10,}['"]/i,
        ruleId: 'security/hardcoded-private-key',
        label: 'Hardcoded private key',
    },
    {
        pattern: /-----BEGIN (?:RSA |EC )?PRIVATE KEY-----/,
        ruleId: 'security/embedded-pem-key',
        label: 'Embedded PEM private key',
    },
    {
        pattern: /(?:connection_?string|connectionString)\s*[:=]\s*['"][^'"]{10,}['"]/i,
        ruleId: 'security/hardcoded-connection-string',
        label: 'Hardcoded database connection string',
    },
];
/** Dangerous APIs that should raise a finding. */
const DANGEROUS_API_PATTERNS = [
    {
        pattern: /\beval\s*\(/,
        ruleId: 'security/eval-usage',
        label: 'Direct eval() usage',
        fixHint: 'Replace eval() with a safe alternative such as JSON.parse or a parser library.',
    },
    {
        pattern: /\bexec\s*\(/,
        ruleId: 'security/exec-usage',
        label: 'exec() call detected (possible command injection)',
        fixHint: 'Prefer execFile() with explicit argument arrays, or use a safer library.',
    },
    {
        pattern: /child_process\.exec\s*\(/,
        ruleId: 'security/child-process-exec',
        label: 'child_process.exec() with shell interpolation risk',
        fixHint: 'Use child_process.execFile() or spawn() with a fixed argument array.',
    },
    {
        pattern: /shell\s*:\s*true/,
        ruleId: 'security/shell-true',
        label: 'spawn/exec with shell:true enables command injection',
        fixHint: 'Remove shell:true and pass arguments as an array.',
    },
    {
        pattern: /new\s+Function\s*\(/,
        ruleId: 'security/dynamic-function',
        label: 'new Function() is equivalent to eval()',
        fixHint: 'Avoid dynamic code construction; use static function definitions.',
    },
];
/** OWASP injection markers (SQL, XSS, path traversal). */
const OWASP_PATTERNS = [
    {
        pattern: /`\s*SELECT\s+.*\$\{/i,
        ruleId: 'security/sql-injection',
        label: 'Potential SQL injection via template literal',
        fixHint: 'Use parameterized queries or a query builder — never interpolate user input.',
    },
    {
        pattern: /innerHTML\s*=\s*[^'"]/,
        ruleId: 'security/xss-innerhtml',
        label: 'innerHTML assignment may introduce XSS',
        fixHint: 'Use textContent or a sanitizer (DOMPurify) before setting innerHTML.',
    },
    {
        pattern: /\.\.\//,
        ruleId: 'security/path-traversal',
        label: 'Possible path traversal with "../" in dynamic path',
        fixHint: 'Resolve and validate paths with path.resolve() and confirm they stay within the intended root.',
    },
];
// ---------------------------------------------------------------------------
// File discovery (same pattern as design-analyzer)
// ---------------------------------------------------------------------------
async function walkDir(dir, root) {
    const results = [];
    let entries;
    try {
        entries = await readdir(dir, { withFileTypes: true, encoding: 'utf-8' });
    }
    catch {
        return results;
    }
    for (const entry of entries) {
        if (entry.name.startsWith('.') || entry.name === 'node_modules' || entry.name === 'dist') {
            continue;
        }
        const full = join(dir, entry.name);
        if (entry.isDirectory()) {
            const sub = await walkDir(full, root);
            results.push(...sub);
        }
        else if (isSourceFile(entry.name)) {
            results.push(full);
        }
    }
    return results;
}
function isSourceFile(name) {
    const ext = extname(name);
    return ['.ts', '.js', '.tsx', '.jsx', '.env'].includes(ext) && !name.endsWith('.d.ts');
}
async function collectFiles(root, filterFiles) {
    if (filterFiles !== undefined && filterFiles.length > 0) {
        return filterFiles.map((f) => join(root, f)).filter((f) => isSourceFile(basename(f)));
    }
    return walkDir(root, root);
}
// ---------------------------------------------------------------------------
// Per-file analysis
// ---------------------------------------------------------------------------
async function analyzeFile(filePath, root) {
    let content;
    try {
        content = await readFile(filePath, 'utf-8');
    }
    catch {
        return [];
    }
    const rel = relative(root, filePath);
    const lines = content.split('\n');
    const findings = [];
    lines.forEach((line, idx) => {
        const lineNum = idx + 1;
        // Skip comment-only lines
        const trimmed = line.trim();
        if (trimmed.startsWith('//') || trimmed.startsWith('*') || trimmed.startsWith('#')) {
            return;
        }
        // Secret detection
        for (const { pattern, ruleId, label } of SECRET_PATTERNS) {
            if (pattern.test(line)) {
                findings.push({
                    severity: 'critical',
                    file: rel,
                    line: lineNum,
                    ruleId,
                    message: `${label} detected. Never commit secrets to source control.`,
                    fixHint: 'Move secrets to environment variables and load via process.env.',
                });
            }
        }
        // Dangerous APIs
        for (const { pattern, ruleId, label, fixHint } of DANGEROUS_API_PATTERNS) {
            if (pattern.test(line)) {
                findings.push({
                    severity: 'high',
                    file: rel,
                    line: lineNum,
                    ruleId,
                    message: label,
                    fixHint,
                });
            }
        }
        // OWASP markers
        for (const { pattern, ruleId, label, fixHint } of OWASP_PATTERNS) {
            if (pattern.test(line)) {
                findings.push({
                    severity: 'high',
                    file: rel,
                    line: lineNum,
                    ruleId,
                    message: label,
                    fixHint,
                });
            }
        }
    });
    // Input validation: tool files that do not import zod
    if ((rel.startsWith('src/tools/') || rel.includes('/tools/')) && !content.includes('zod')) {
        findings.push({
            severity: 'medium',
            file: rel,
            line: 0,
            ruleId: 'security/missing-zod-validation',
            message: 'Tool file does not import zod. All tool inputs must be validated with Zod.',
            fixHint: 'Add a Zod input schema and validate args before processing.',
        });
    }
    return findings;
}
// ---------------------------------------------------------------------------
// Public API
// ---------------------------------------------------------------------------
export async function runSecurityAnalyzer(projectPath, files) {
    const filePaths = await collectFiles(projectPath, files);
    const allFindings = [];
    for (const fp of filePaths) {
        const fileFindings = await analyzeFile(fp, projectPath);
        allFindings.push(...fileFindings);
    }
    return {
        domain: 'security',
        findings: allFindings,
        analyzedAt: new Date().toISOString(),
    };
}
//# sourceMappingURL=security-analyzer.js.map