/**
 * Brakeman Scanner Integration
 *
 * Ruby on Rails security scanner for common vulnerabilities.
 *
 * @module scanners/brakeman
 */
import { exec } from "child_process";
import { promisify } from "util";
import { access } from "fs/promises";
import { join } from "path";
const execAsync = promisify(exec);
/**
 * CWE mappings for common Brakeman warning types
 */
const WARNING_TO_CWE = {
    "SQL Injection": "CWE-89",
    "Cross-Site Scripting": "CWE-79",
    "Cross-Site Request Forgery": "CWE-352",
    "Remote Code Execution": "CWE-94",
    "Command Injection": "CWE-78",
    "Path Traversal": "CWE-22",
    "Redirect": "CWE-601",
    "Mass Assignment": "CWE-915",
    "Session Setting": "CWE-614",
    "Denial of Service": "CWE-400",
    "File Access": "CWE-22",
    "Dynamic Render Path": "CWE-79",
    "Dangerous Send": "CWE-94",
    "Dangerous Eval": "CWE-94",
    "SSL Verification Bypass": "CWE-295",
    "Information Disclosure": "CWE-200",
    "Authentication": "CWE-287",
    "Authorization": "CWE-862",
    "Weak Hash": "CWE-328",
};
/**
 * Check if Brakeman is available
 */
export async function checkBrakemanAvailable() {
    try {
        const { stdout } = await execAsync("brakeman --version", { timeout: 5000 });
        const version = stdout.trim();
        return {
            scanner: "brakeman",
            available: true,
            version,
        };
    }
    catch (error) {
        return {
            scanner: "brakeman",
            available: false,
            error: error instanceof Error ? error.message : "Brakeman not found",
        };
    }
}
/**
 * Convert Brakeman confidence to vaspera severity
 * High confidence + dangerous warning type = critical/high
 * Medium confidence = medium
 * Weak confidence = low
 */
function mapSeverity(confidence, warningType) {
    // Certain warning types are always critical regardless of confidence
    const criticalTypes = [
        "SQL Injection",
        "Remote Code Execution",
        "Command Injection",
        "Dangerous Eval",
    ];
    if (criticalTypes.includes(warningType) && confidence === "High") {
        return "critical";
    }
    if (confidence === "High") {
        return "high";
    }
    if (confidence === "Medium") {
        return "medium";
    }
    return "low";
}
/**
 * Run Brakeman on a Ruby on Rails project
 */
export async function runBrakeman(projectPath, options) {
    const startTime = Date.now();
    try {
        // Check if Brakeman is available
        const availability = await checkBrakemanAvailable();
        if (!availability.available) {
            return {
                scanner: "brakeman",
                findings: [],
                duration: Date.now() - startTime,
                success: false,
                error: "Brakeman is not installed. Install with: gem install brakeman",
            };
        }
        // Check if this is a Rails project
        const isRails = await detectRails(projectPath);
        if (!isRails) {
            return {
                scanner: "brakeman",
                findings: [],
                duration: Date.now() - startTime,
                success: false,
                error: "Not a Ruby on Rails project (no Gemfile or config/application.rb found)",
            };
        }
        // Build command
        let command = `brakeman "${projectPath}" -f json --no-pager --no-progress`;
        if (options?.skip && options.skip.length > 0) {
            command += ` --skip-checks ${options.skip.join(",")}`;
        }
        if (options?.only && options.only.length > 0) {
            command += ` --only-checks ${options.only.join(",")}`;
        }
        if (options?.configFile) {
            command += ` -c "${options.configFile}"`;
        }
        // Run Brakeman
        const { stdout, stderr } = await execAsync(command, {
            timeout: options?.timeout || 300000, // 5 minutes default
            maxBuffer: 10 * 1024 * 1024, // 10MB
        }).catch((error) => {
            // Brakeman exits with different codes for warnings
            // Exit code 0: No warnings
            // Exit code 3: Warnings found
            // Exit code 4: Errors during scan
            if (error.stdout) {
                return { stdout: error.stdout, stderr: error.stderr || "" };
            }
            throw error;
        });
        // Parse JSON output
        let output;
        try {
            output = JSON.parse(stdout);
        }
        catch (parseError) {
            return {
                scanner: "brakeman",
                findings: [],
                duration: Date.now() - startTime,
                success: false,
                error: "Failed to parse Brakeman JSON output",
            };
        }
        // Convert to DeterministicFindings
        const findings = output.warnings.map((warning) => {
            const cweId = WARNING_TO_CWE[warning.warning_type];
            return {
                scanner: "brakeman",
                ruleId: `brakeman:${warning.warning_code}`,
                file: warning.file.replace(projectPath + "/", ""),
                line: warning.line || 1,
                message: `${warning.warning_type}: ${warning.message}`,
                severity: mapSeverity(warning.confidence, warning.warning_type),
                confidence: 100,
                cweIds: cweId ? [cweId] : undefined,
                evidence: warning.code || undefined,
                fix: warning.link ? `See: ${warning.link}` : undefined,
                metadata: {
                    warningType: warning.warning_type,
                    warningCode: warning.warning_code,
                    checkName: warning.check_name,
                    fingerprint: warning.fingerprint,
                    brakemanConfidence: warning.confidence,
                    location: warning.location,
                    userInput: warning.user_input,
                    link: warning.link,
                },
            };
        });
        // Add errors as findings if any
        if (output.errors && output.errors.length > 0) {
            for (const error of output.errors) {
                findings.push({
                    scanner: "brakeman",
                    ruleId: "brakeman:error",
                    file: "unknown",
                    line: 1,
                    message: `Brakeman error: ${error.error}`,
                    severity: "info",
                    confidence: 100,
                    metadata: {
                        backtrace: error.backtrace.slice(0, 5),
                    },
                });
            }
        }
        return {
            scanner: "brakeman",
            findings,
            duration: Date.now() - startTime,
            success: true,
            version: availability.version,
            filesScanned: output.scan_info
                ? output.scan_info.number_of_controllers +
                    output.scan_info.number_of_models +
                    output.scan_info.number_of_templates
                : undefined,
        };
    }
    catch (error) {
        return {
            scanner: "brakeman",
            findings: [],
            duration: Date.now() - startTime,
            success: false,
            error: error instanceof Error ? error.message : "Unknown error",
        };
    }
}
/**
 * Check if a project is a Ruby on Rails project
 */
export async function detectRails(projectPath) {
    try {
        // Check for Gemfile
        await access(join(projectPath, "Gemfile"));
        // Check for Rails-specific files
        try {
            await access(join(projectPath, "config", "application.rb"));
            return true;
        }
        catch {
            // Could still be a Rails project with different structure
            try {
                await access(join(projectPath, "config", "environment.rb"));
                return true;
            }
            catch {
                // Check Gemfile for rails gem
                const { readFile } = await import("fs/promises");
                const gemfile = await readFile(join(projectPath, "Gemfile"), "utf-8");
                return gemfile.includes("gem 'rails'") || gemfile.includes('gem "rails"');
            }
        }
    }
    catch {
        return false;
    }
}
/**
 * Check if a project contains Ruby files (for general Ruby detection)
 */
export async function detectRuby(projectPath) {
    try {
        await access(join(projectPath, "Gemfile"));
        return true;
    }
    catch {
        try {
            await access(join(projectPath, "Rakefile"));
            return true;
        }
        catch {
            // Check for .rb files
            try {
                const { stdout } = await execAsync(`find "${projectPath}" -maxdepth 3 -name "*.rb" | head -1`, { timeout: 5000 });
                return stdout.trim().length > 0;
            }
            catch {
                return false;
            }
        }
    }
}
//# sourceMappingURL=brakeman.js.map