/**
 * Rust Security Scanner
 *
 * Scans Rust projects for security vulnerabilities using
 * cargo-audit and clippy.
 *
 * @module scanners/rust
 */
import { exec } from "child_process";
import { promisify } from "util";
import { access } from "fs/promises";
import { join } from "path";
const execAsync = promisify(exec);
export async function checkCargoAuditAvailable() {
    try {
        const { stdout } = await execAsync("cargo audit --version", { timeout: 10000 });
        return {
            scanner: "cargo-audit",
            available: true,
            version: stdout.trim(),
        };
    }
    catch {
        return {
            scanner: "cargo-audit",
            available: false,
            error: "cargo-audit not found. Install with: cargo install cargo-audit",
        };
    }
}
export async function checkClippyAvailable() {
    try {
        const { stdout } = await execAsync("cargo clippy --version", { timeout: 10000 });
        return {
            scanner: "clippy",
            available: true,
            version: stdout.trim(),
        };
    }
    catch {
        return {
            scanner: "clippy",
            available: false,
            error: "clippy not found. Install with: rustup component add clippy",
        };
    }
}
function mapAuditSeverity(severity) {
    switch (severity.toLowerCase()) {
        case "critical":
            return "critical";
        case "high":
            return "high";
        case "medium":
        case "moderate":
            return "medium";
        default:
            return "low";
    }
}
export async function runCargoAudit(projectPath, options) {
    const startTime = Date.now();
    try {
        const availability = await checkCargoAuditAvailable();
        if (!availability.available) {
            return {
                scanner: "cargo-audit",
                findings: [],
                duration: Date.now() - startTime,
                success: false,
                error: availability.error,
            };
        }
        const { stdout } = await execAsync(`cd "${projectPath}" && cargo audit --json`, {
            timeout: options?.timeout || 120000,
            maxBuffer: 10 * 1024 * 1024,
        }).catch((error) => {
            if (error.stdout) {
                return { stdout: error.stdout, stderr: error.stderr || "" };
            }
            throw error;
        });
        const output = JSON.parse(stdout);
        const findings = [];
        for (const vuln of output.vulnerabilities.list) {
            findings.push({
                scanner: "cargo-audit",
                ruleId: `cargo-audit:${vuln.advisory.id}`,
                file: "Cargo.lock",
                line: 1,
                message: `${vuln.advisory.title} in ${vuln.package.name}@${vuln.package.version}`,
                severity: mapAuditSeverity(vuln.advisory.severity),
                confidence: 100,
                metadata: {
                    package: vuln.package.name,
                    version: vuln.package.version,
                    advisoryUrl: vuln.advisory.url,
                    patchedVersions: vuln.versions.patched,
                    cvss: vuln.advisory.cvss,
                    description: vuln.advisory.description,
                },
            });
        }
        // Add warnings for unmaintained packages
        for (const warn of output.warnings.unmaintained || []) {
            findings.push({
                scanner: "cargo-audit",
                ruleId: `cargo-audit:${warn.advisory.id}`,
                file: "Cargo.lock",
                line: 1,
                message: `Unmaintained package: ${warn.package.name}@${warn.package.version} - ${warn.advisory.title}`,
                severity: "low",
                confidence: 100,
                metadata: {
                    package: warn.package.name,
                    version: warn.package.version,
                    type: "unmaintained",
                },
            });
        }
        return {
            scanner: "cargo-audit",
            findings,
            duration: Date.now() - startTime,
            success: true,
            version: availability.version,
        };
    }
    catch (error) {
        return {
            scanner: "cargo-audit",
            findings: [],
            duration: Date.now() - startTime,
            success: false,
            error: error instanceof Error ? error.message : "Unknown error",
        };
    }
}
export async function runClippy(projectPath, options) {
    const startTime = Date.now();
    try {
        const availability = await checkClippyAvailable();
        if (!availability.available) {
            return {
                scanner: "clippy",
                findings: [],
                duration: Date.now() - startTime,
                success: false,
                error: availability.error,
            };
        }
        const { stdout, stderr } = await execAsync(`cd "${projectPath}" && cargo clippy --message-format=json -- -W clippy::all -W clippy::pedantic -W clippy::nursery 2>&1`, {
            timeout: options?.timeout || 300000,
            maxBuffer: 50 * 1024 * 1024,
        }).catch((error) => {
            if (error.stdout) {
                return { stdout: error.stdout, stderr: error.stderr || "" };
            }
            throw error;
        });
        const findings = [];
        const lines = stdout.split("\n").filter((l) => l.trim());
        for (const line of lines) {
            try {
                const msg = JSON.parse(line);
                if (msg.reason === "compiler-message" && msg.message && msg.message.spans?.length > 0) {
                    const primarySpan = msg.message.spans.find((s) => s.is_primary) || msg.message.spans[0];
                    // Only include security-relevant lints
                    const code = msg.message.code?.code || "";
                    const isSecurityRelevant = code.includes("unsafe") ||
                        code.includes("panic") ||
                        code.includes("unwrap") ||
                        code.includes("expect") ||
                        code.includes("transmute") ||
                        msg.message.level === "error";
                    if (isSecurityRelevant) {
                        findings.push({
                            scanner: "clippy",
                            ruleId: `clippy:${code}`,
                            file: primarySpan.file_name.replace(projectPath + "/", ""),
                            line: primarySpan.line_start,
                            endLine: primarySpan.line_end,
                            column: primarySpan.column_start,
                            message: msg.message.message,
                            severity: msg.message.level === "error" ? "high" : "medium",
                            confidence: 100,
                            evidence: primarySpan.text?.[0]?.text,
                        });
                    }
                }
            }
            catch {
                // Skip non-JSON lines
            }
        }
        return {
            scanner: "clippy",
            findings,
            duration: Date.now() - startTime,
            success: true,
            version: availability.version,
        };
    }
    catch (error) {
        return {
            scanner: "clippy",
            findings: [],
            duration: Date.now() - startTime,
            success: false,
            error: error instanceof Error ? error.message : "Unknown error",
        };
    }
}
export async function runRustScanners(projectPath, options) {
    const startTime = Date.now();
    const [auditResult, clippyResult] = await Promise.all([
        runCargoAudit(projectPath, options),
        runClippy(projectPath, options),
    ]);
    const findings = [...auditResult.findings, ...clippyResult.findings];
    const success = auditResult.success || clippyResult.success;
    return {
        scanner: "rust",
        findings,
        duration: Date.now() - startTime,
        success,
        error: !success ? "No Rust scanners available" : undefined,
    };
}
export async function detectRust(projectPath) {
    try {
        await access(join(projectPath, "Cargo.toml"));
        return true;
    }
    catch {
        return false;
    }
}
//# sourceMappingURL=rust.js.map