/**
 * Binary Analysis Scanner
 *
 * Detects vulnerabilities in compiled code, native Node.js addons, and shared libraries.
 * Checks for security hardening flags (RELRO, NX, PIE, CANARY) and identifies
 * potentially vulnerable native modules.
 *
 * Detection targets:
 * - Node.js native addons (binding.gyp, *.node)
 * - Shared libraries (*.so, *.dll, *.dylib)
 * - Rust FFI (Cargo.toml with cdylib)
 * - Go CGO (import "C" in .go files)
 */
import { exec } from "child_process";
import { promisify } from "util";
import * as fs from "fs/promises";
import * as path from "path";
import { glob } from "glob";
const execAsync = promisify(exec);
// CWE mappings for binary security issues
const BINARY_CWES = {
    "missing-canary": ["CWE-121"], // Stack-based Buffer Overflow
    "missing-nx": ["CWE-119"], // Improper Restriction of Operations within Bounds
    "missing-pie": ["CWE-120"], // Buffer Copy without Checking Size
    "partial-relro": ["CWE-134"], // Use of Externally-Controlled Format String
    "missing-relro": ["CWE-134"],
    "missing-fortify": ["CWE-120", "CWE-787"], // Out-of-bounds Write
    "unsafe-rpath": ["CWE-426"], // Untrusted Search Path
};
/**
 * Check if binary analysis tools are available
 */
export async function checkBinaryAnalysisAvailable() {
    const results = {
        checksec: false,
        nm: false,
        objdump: false,
        file: false,
    };
    // Check for checksec (Python version or bash script)
    try {
        await execAsync("checksec --version 2>/dev/null || checksec.sh --version 2>/dev/null");
        results.checksec = true;
    }
    catch {
        // checksec not available
    }
    // Check for nm
    try {
        await execAsync("nm --version");
        results.nm = true;
    }
    catch {
        // nm not available
    }
    // Check for objdump
    try {
        await execAsync("objdump --version");
        results.objdump = true;
    }
    catch {
        // objdump not available
    }
    // Check for file command
    try {
        await execAsync("file --version");
        results.file = true;
    }
    catch {
        // file not available
    }
    return results;
}
/**
 * Detect native modules and binaries in the project
 */
export async function detectNativeModules(projectPath) {
    const targets = [];
    const absolutePath = path.resolve(projectPath);
    // Node.js native addon patterns
    const patterns = [
        { glob: "**/binding.gyp", type: "binding-gyp" },
        { glob: "**/*.node", type: "node-addon" },
        { glob: "**/*.so", type: "shared-lib" },
        { glob: "**/*.dylib", type: "shared-lib" },
        { glob: "**/*.dll", type: "shared-lib" },
    ];
    for (const pattern of patterns) {
        try {
            const matches = await glob(pattern.glob, {
                cwd: absolutePath,
                ignore: ["**/node_modules/.pnpm/**", "**/node_modules/.cache/**"],
                nodir: true,
            });
            for (const match of matches) {
                targets.push({
                    path: path.join(absolutePath, match),
                    type: pattern.type,
                    name: path.basename(match),
                });
            }
        }
        catch {
            // Pattern not found or glob error
        }
    }
    // Check for Rust FFI (cdylib in Cargo.toml)
    try {
        const cargoFiles = await glob("**/Cargo.toml", {
            cwd: absolutePath,
            ignore: ["**/node_modules/**", "**/target/**"],
        });
        for (const cargoFile of cargoFiles) {
            const content = await fs.readFile(path.join(absolutePath, cargoFile), "utf-8");
            if (content.includes("cdylib") || content.includes("crate-type") && content.includes("dylib")) {
                targets.push({
                    path: path.join(absolutePath, cargoFile),
                    type: "rust-ffi",
                    name: path.basename(path.dirname(cargoFile)),
                });
            }
        }
    }
    catch {
        // No Rust projects
    }
    // Check for Go CGO
    try {
        const goFiles = await glob("**/*.go", {
            cwd: absolutePath,
            ignore: ["**/vendor/**", "**/node_modules/**"],
        });
        for (const goFile of goFiles) {
            const content = await fs.readFile(path.join(absolutePath, goFile), "utf-8");
            if (content.includes('import "C"') || content.includes("import (\n\t\"C\"")) {
                targets.push({
                    path: path.join(absolutePath, goFile),
                    type: "go-cgo",
                    name: path.basename(goFile),
                });
                break; // One CGO detection per project is enough
            }
        }
    }
    catch {
        // No Go projects
    }
    return targets;
}
/**
 * Parse checksec output for a binary
 */
function parseChecksecOutput(output) {
    const flags = {
        relro: "none",
        nx: false,
        pie: false,
        canary: false,
        fortify: false,
        rpath: false,
        runpath: false,
    };
    // Parse checksec output (handles both JSON and text formats)
    const lowerOutput = output.toLowerCase();
    // RELRO
    if (lowerOutput.includes("full relro")) {
        flags.relro = "full";
    }
    else if (lowerOutput.includes("partial relro")) {
        flags.relro = "partial";
    }
    // NX (No eXecute)
    if (lowerOutput.includes("nx enabled") || lowerOutput.includes("nx:true") || lowerOutput.includes('"nx": true')) {
        flags.nx = true;
    }
    // PIE (Position Independent Executable)
    if (lowerOutput.includes("pie enabled") || lowerOutput.includes("pie:true") || lowerOutput.includes('"pie": true')) {
        flags.pie = true;
    }
    // Stack Canary
    if (lowerOutput.includes("canary found") || lowerOutput.includes("canary:true") || lowerOutput.includes('"canary": true')) {
        flags.canary = true;
    }
    // Fortify
    if (lowerOutput.includes("fortify") && !lowerOutput.includes("no fortify")) {
        flags.fortify = true;
    }
    // RPATH/RUNPATH (potential security issue if set)
    if (lowerOutput.includes("rpath:") && !lowerOutput.includes("no rpath")) {
        flags.rpath = true;
    }
    if (lowerOutput.includes("runpath:") && !lowerOutput.includes("no runpath")) {
        flags.runpath = true;
    }
    return flags;
}
/**
 * Run checksec on a binary file
 */
async function runChecksecOnBinary(binaryPath) {
    try {
        // Try JSON output first (newer checksec versions)
        const { stdout } = await execAsync(`checksec --file="${binaryPath}" --output=json 2>/dev/null || checksec --file="${binaryPath}" 2>/dev/null`, {
            timeout: 10000,
        });
        return parseChecksecOutput(stdout);
    }
    catch {
        return null;
    }
}
/**
 * Analyze binary using nm/objdump as fallback
 */
async function analyzeWithNm(binaryPath) {
    const flags = {};
    try {
        const { stdout } = await execAsync(`nm -D "${binaryPath}" 2>/dev/null | head -100`, {
            timeout: 10000,
        });
        // Check for stack protector symbols (indicates canary)
        if (stdout.includes("__stack_chk_fail") || stdout.includes("__stack_chk_guard")) {
            flags.canary = true;
        }
        // Check for fortify symbols
        if (stdout.includes("__fortify_fail") || stdout.includes("_chk@")) {
            flags.fortify = true;
        }
    }
    catch {
        // nm failed
    }
    return flags;
}
/**
 * Generate findings from security flags analysis
 */
function generateFindingsFromFlags(target, flags, projectPath) {
    const findings = [];
    const relativePath = path.relative(projectPath, target.path);
    // Missing stack canary (critical for native addons)
    if (!flags.canary) {
        findings.push({
            scanner: "binary-analysis",
            ruleId: "binary:missing-stack-canary",
            file: relativePath,
            line: 0,
            column: 0,
            message: `Native module "${target.name}" compiled without stack canary protection. This makes buffer overflow exploitation easier.`,
            severity: "high",
            confidence: 100,
            category: "binary-security",
            cweIds: BINARY_CWES["missing-canary"],
            evidence: `Security flags: canary=${flags.canary}`,
            metadata: {
                targetType: target.type,
                protections: flags,
            },
        });
    }
    // Missing NX (executable stack)
    if (!flags.nx) {
        findings.push({
            scanner: "binary-analysis",
            ruleId: "binary:missing-nx",
            file: relativePath,
            line: 0,
            column: 0,
            message: `Native module "${target.name}" has executable stack (NX disabled). Attackers can execute shellcode on the stack.`,
            severity: "high",
            confidence: 100,
            category: "binary-security",
            cweIds: BINARY_CWES["missing-nx"],
            evidence: `Security flags: nx=${flags.nx}`,
            metadata: {
                targetType: target.type,
                protections: flags,
            },
        });
    }
    // Missing PIE
    if (!flags.pie && target.type !== "shared-lib") {
        findings.push({
            scanner: "binary-analysis",
            ruleId: "binary:missing-pie",
            file: relativePath,
            line: 0,
            column: 0,
            message: `Native module "${target.name}" not compiled as position-independent executable (PIE). ASLR bypass may be easier.`,
            severity: "medium",
            confidence: 100,
            category: "binary-security",
            cweIds: BINARY_CWES["missing-pie"],
            evidence: `Security flags: pie=${flags.pie}`,
            metadata: {
                targetType: target.type,
                protections: flags,
            },
        });
    }
    // Partial or missing RELRO
    if (flags.relro === "none") {
        findings.push({
            scanner: "binary-analysis",
            ruleId: "binary:missing-relro",
            file: relativePath,
            line: 0,
            column: 0,
            message: `Native module "${target.name}" compiled without RELRO protection. GOT overwrite attacks are possible.`,
            severity: "high",
            confidence: 100,
            category: "binary-security",
            cweIds: BINARY_CWES["missing-relro"],
            evidence: `Security flags: relro=${flags.relro}`,
            metadata: {
                targetType: target.type,
                protections: flags,
            },
        });
    }
    else if (flags.relro === "partial") {
        findings.push({
            scanner: "binary-analysis",
            ruleId: "binary:partial-relro",
            file: relativePath,
            line: 0,
            column: 0,
            message: `Native module "${target.name}" uses partial RELRO. Full RELRO provides better protection against GOT overwrites.`,
            severity: "low",
            confidence: 100,
            category: "binary-security",
            cweIds: BINARY_CWES["partial-relro"],
            evidence: `Security flags: relro=${flags.relro}`,
            metadata: {
                targetType: target.type,
                protections: flags,
            },
        });
    }
    // Unsafe RPATH/RUNPATH
    if (flags.rpath || flags.runpath) {
        findings.push({
            scanner: "binary-analysis",
            ruleId: "binary:unsafe-rpath",
            file: relativePath,
            line: 0,
            column: 0,
            message: `Native module "${target.name}" has RPATH/RUNPATH set. This could allow library injection attacks.`,
            severity: "medium",
            confidence: 80,
            category: "binary-security",
            cweIds: BINARY_CWES["unsafe-rpath"],
            evidence: `Security flags: rpath=${flags.rpath}, runpath=${flags.runpath}`,
            metadata: {
                targetType: target.type,
                protections: flags,
            },
        });
    }
    return findings;
}
/**
 * Generate findings for binding.gyp files (source-level analysis)
 */
async function analyzeBindingGyp(target, projectPath) {
    const findings = [];
    const relativePath = path.relative(projectPath, target.path);
    try {
        const content = await fs.readFile(target.path, "utf-8");
        // Check for unsafe compiler flags
        if (content.includes("-fno-stack-protector")) {
            findings.push({
                scanner: "binary-analysis",
                ruleId: "binary:disabled-stack-protector",
                file: relativePath,
                line: findLineNumber(content, "-fno-stack-protector"),
                column: 0,
                message: "Stack protector explicitly disabled in binding.gyp. This removes buffer overflow protection.",
                severity: "critical",
                confidence: 100,
                category: "binary-security",
                cweIds: ["CWE-121"],
                evidence: "Found: -fno-stack-protector",
                metadata: { targetType: target.type },
            });
        }
        // Check for executable stack flag
        if (content.includes("-z execstack")) {
            findings.push({
                scanner: "binary-analysis",
                ruleId: "binary:executable-stack-flag",
                file: relativePath,
                line: findLineNumber(content, "-z execstack"),
                column: 0,
                message: "Executable stack explicitly enabled in binding.gyp. Shellcode can be executed on the stack.",
                severity: "critical",
                confidence: 100,
                category: "binary-security",
                cweIds: ["CWE-119"],
                evidence: "Found: -z execstack",
                metadata: { targetType: target.type },
            });
        }
        // Check for disabled RELRO
        if (content.includes("-z norelro")) {
            findings.push({
                scanner: "binary-analysis",
                ruleId: "binary:disabled-relro",
                file: relativePath,
                line: findLineNumber(content, "-z norelro"),
                column: 0,
                message: "RELRO explicitly disabled in binding.gyp. GOT overwrite attacks are possible.",
                severity: "high",
                confidence: 100,
                category: "binary-security",
                cweIds: ["CWE-134"],
                evidence: "Found: -z norelro",
                metadata: { targetType: target.type },
            });
        }
        // Check for disabled FORTIFY_SOURCE
        if (content.includes("-D_FORTIFY_SOURCE=0") || content.includes("-U_FORTIFY_SOURCE")) {
            findings.push({
                scanner: "binary-analysis",
                ruleId: "binary:disabled-fortify",
                file: relativePath,
                line: findLineNumber(content, "_FORTIFY_SOURCE"),
                column: 0,
                message: "FORTIFY_SOURCE disabled in binding.gyp. Buffer overflow detection is disabled.",
                severity: "high",
                confidence: 100,
                category: "binary-security",
                cweIds: ["CWE-120", "CWE-787"],
                evidence: "Found: FORTIFY_SOURCE disabled",
                metadata: { targetType: target.type },
            });
        }
    }
    catch {
        // Could not read binding.gyp
    }
    return findings;
}
/**
 * Find line number of a pattern in content
 */
function findLineNumber(content, pattern) {
    const lines = content.split("\n");
    for (let i = 0; i < lines.length; i++) {
        if (lines[i].includes(pattern)) {
            return i + 1;
        }
    }
    return 0;
}
/**
 * Main entry point: Run binary analysis on a project
 */
export async function runBinaryAnalysis(projectPath, options) {
    const startTime = Date.now();
    const findings = [];
    try {
        // 1. Detect native modules and binaries
        const targets = await detectNativeModules(projectPath);
        if (targets.length === 0) {
            return {
                scanner: "binary-analysis",
                findings: [],
                duration: Date.now() - startTime,
                success: true,
                metadata: {
                    message: "No native modules or binaries detected",
                    targetsScanned: 0,
                },
            };
        }
        // 2. Check tool availability
        const tools = await checkBinaryAnalysisAvailable();
        const hasChecksec = tools.checksec;
        const hasNm = tools.nm;
        // 3. Analyze each target
        for (const target of targets) {
            // Source-level analysis for binding.gyp
            if (target.type === "binding-gyp") {
                const gypFindings = await analyzeBindingGyp(target, projectPath);
                findings.push(...gypFindings);
                continue;
            }
            // Binary analysis for compiled files
            if (target.type === "node-addon" || target.type === "shared-lib") {
                let flags = null;
                // Try checksec first
                if (hasChecksec) {
                    flags = await runChecksecOnBinary(target.path);
                }
                // Fallback to nm analysis
                if (!flags && hasNm) {
                    const partialFlags = await analyzeWithNm(target.path);
                    flags = {
                        relro: "none", // Cannot determine with nm alone
                        nx: true, // Assume enabled (conservative)
                        pie: true, // Assume enabled (conservative)
                        canary: partialFlags.canary ?? false,
                        fortify: partialFlags.fortify ?? false,
                        rpath: false,
                        runpath: false,
                    };
                }
                // Generate findings from flags
                if (flags) {
                    const flagFindings = generateFindingsFromFlags(target, flags, projectPath);
                    findings.push(...flagFindings);
                }
                else {
                    // No tools available - warn about unanalyzed binary
                    findings.push({
                        scanner: "binary-analysis",
                        ruleId: "binary:unanalyzed",
                        file: path.relative(projectPath, target.path),
                        line: 0,
                        column: 0,
                        message: `Native module "${target.name}" could not be analyzed. Install checksec or nm for security flag verification.`,
                        severity: "info",
                        confidence: 100,
                        category: "binary-security",
                        cweIds: [],
                        evidence: "No binary analysis tools available",
                        metadata: { targetType: target.type },
                    });
                }
            }
            // Rust FFI and Go CGO get informational findings
            if (target.type === "rust-ffi") {
                findings.push({
                    scanner: "binary-analysis",
                    ruleId: "binary:rust-ffi-detected",
                    file: path.relative(projectPath, target.path),
                    line: 0,
                    column: 0,
                    message: `Rust FFI library detected (${target.name}). Review unsafe blocks and C interop boundaries.`,
                    severity: "info",
                    confidence: 100,
                    category: "binary-security",
                    cweIds: [],
                    evidence: "Cargo.toml contains cdylib crate-type",
                    metadata: { targetType: target.type },
                });
            }
            if (target.type === "go-cgo") {
                findings.push({
                    scanner: "binary-analysis",
                    ruleId: "binary:go-cgo-detected",
                    file: path.relative(projectPath, target.path),
                    line: 0,
                    column: 0,
                    message: `Go CGO usage detected (${target.name}). C interop may introduce memory safety issues.`,
                    severity: "info",
                    confidence: 100,
                    category: "binary-security",
                    cweIds: [],
                    evidence: 'Found: import "C"',
                    metadata: { targetType: target.type },
                });
            }
        }
        return {
            scanner: "binary-analysis",
            findings,
            duration: Date.now() - startTime,
            success: true,
            metadata: {
                targetsScanned: targets.length,
                toolsAvailable: tools,
            },
        };
    }
    catch (error) {
        return {
            scanner: "binary-analysis",
            findings,
            duration: Date.now() - startTime,
            success: false,
            error: error instanceof Error ? error.message : "Unknown error",
        };
    }
}
//# sourceMappingURL=binary-analysis.js.map