/**
 * Logic Flaw Detector Agent
 *
 * Finds business logic vulnerabilities that pattern-based scanners miss.
 * Analyzes control flow, state management, and business rule implementations.
 *
 * Focus areas:
 * - State inconsistency: Variables mutable when should be immutable
 * - Race conditions: Check-then-act without locks
 * - Boundary conditions: Off-by-one, integer overflow
 * - Error handling: Swallowed exceptions, incomplete cleanup
 * - Trust boundaries: Client-supplied data used unsafely
 * - Business rule violations: Incorrect implementations of business logic
 *
 * @module agents/logic-flaw-detector
 */
import { readFile } from "fs/promises";
import * as path from "path";
import { glob } from "glob";
const LOGIC_PATTERNS = [
    // State Inconsistency
    {
        id: "lfd-state-001",
        title: "Mutable shared state in async context",
        pattern: /let\s+\w+\s*=[\s\S]{0,50}(?:await|\.then|setTimeout|setInterval)/g,
        severity: "medium",
        category: "state-inconsistency",
        cweIds: ["CWE-362", "CWE-820"],
        description: "Mutable variable declared before async operation may lead to race conditions.",
        impact: "Concurrent modifications can cause data corruption or unexpected behavior.",
        recommendation: "Use const or atomic operations. Consider using immutable data structures.",
        languages: ["typescript", "javascript"],
    },
    {
        id: "lfd-state-002",
        title: "Global state modification in function",
        pattern: /(?:global|window|globalThis)\.\w+\s*=(?!=)/g,
        severity: "medium",
        category: "state-inconsistency",
        cweIds: ["CWE-362"],
        description: "Function modifies global state which can cause unexpected side effects.",
        impact: "Global state mutations make code unpredictable and hard to test.",
        recommendation: "Pass state as parameters or use dependency injection.",
        languages: ["typescript", "javascript"],
    },
    // Boundary Violations
    {
        id: "lfd-boundary-001",
        title: "Array access without bounds check",
        pattern: /\[\s*(?:\w+\s*[-+]\s*\d+|\w+\s*\*\s*\w+)\s*\]/g,
        severity: "medium",
        category: "boundary-violation",
        cweIds: ["CWE-129", "CWE-787"],
        description: "Array index calculation without explicit bounds validation.",
        impact: "Out-of-bounds access can cause crashes or security vulnerabilities.",
        recommendation: "Add explicit bounds checking before array access.",
    },
    {
        id: "lfd-boundary-002",
        title: "Loop condition off-by-one risk",
        pattern: /for\s*\([^;]+;\s*\w+\s*<=\s*\w+\.length[^;]*;/g,
        severity: "medium",
        category: "boundary-violation",
        cweIds: ["CWE-193"],
        description: "Loop condition uses <= with length, potential off-by-one error.",
        impact: "Off-by-one errors can cause array out-of-bounds access.",
        recommendation: "Use < instead of <= when iterating to array length.",
        languages: ["typescript", "javascript"],
    },
    {
        id: "lfd-boundary-003",
        title: "Unchecked arithmetic in financial calculation",
        pattern: /(?:price|amount|total|balance|cost|fee|rate)\s*(?:\*|\/)(?!\s*0)/g,
        severity: "high",
        category: "boundary-violation",
        cweIds: ["CWE-190", "CWE-682"],
        description: "Financial calculation without overflow/precision checking.",
        impact: "Arithmetic errors in financial code can cause monetary losses.",
        recommendation: "Use decimal libraries (decimal.js) for financial calculations.",
    },
    // Error Handling
    {
        id: "lfd-error-001",
        title: "Empty catch block",
        pattern: /catch\s*\([^)]*\)\s*\{\s*\}/g,
        severity: "medium",
        category: "error-handling",
        cweIds: ["CWE-390"],
        description: "Exception caught but not handled, silently ignoring errors.",
        impact: "Errors are silently swallowed, making debugging difficult.",
        recommendation: "Log the error or handle it appropriately. Use error monitoring.",
    },
    {
        id: "lfd-error-002",
        title: "Catch block only logs error",
        pattern: /catch\s*\([^)]*\)\s*\{\s*(?:console\.(?:log|error)|logger\.\w+)\s*\([^)]+\)\s*;?\s*\}/g,
        severity: "low",
        category: "error-handling",
        cweIds: ["CWE-755"],
        description: "Exception caught and logged but execution continues as if successful.",
        impact: "Application may continue in an invalid state after error.",
        recommendation: "Consider re-throwing the error or returning an error result.",
    },
    {
        id: "lfd-error-003",
        title: "Missing cleanup in error path",
        pattern: /try\s*\{[\s\S]{0,500}(?:open|connect|acquire|lock)[\s\S]{0,500}\}\s*catch[\s\S]{0,200}(?!\s*(?:close|disconnect|release|unlock))/g,
        severity: "high",
        category: "error-handling",
        cweIds: ["CWE-404"],
        description: "Resource acquired in try block may not be released in catch block.",
        impact: "Resource leaks can cause denial of service or system instability.",
        recommendation: "Use try-finally or resource management patterns (using, with).",
    },
    // Trust Boundary
    {
        id: "lfd-trust-001",
        title: "User input used in object property access",
        pattern: /\[\s*(?:req|request)\.(?:body|query|params)\.\w+\s*\]/g,
        severity: "high",
        category: "trust-boundary",
        cweIds: ["CWE-915"],
        description: "User-supplied key used for object property access enables prototype pollution.",
        impact: "Attacker can access or modify unexpected object properties.",
        recommendation: "Validate user input against allowlist of permitted keys.",
        languages: ["typescript", "javascript"],
    },
    {
        id: "lfd-trust-002",
        title: "Client-provided value used for authorization",
        pattern: /(?:req|request)\.(?:body|query|params)\.(?:role|admin|permission|isAdmin|userId|user_id)/gi,
        severity: "critical",
        category: "trust-boundary",
        cweIds: ["CWE-807"],
        description: "Authorization decision based on client-provided value.",
        impact: "Attacker can elevate privileges by modifying request parameters.",
        recommendation: "Derive authorization data from session/token, never from client input.",
        languages: ["typescript", "javascript"],
    },
    {
        id: "lfd-trust-003",
        title: "Deserialization of untrusted data",
        pattern: /(?:JSON\.parse|pickle\.loads?|yaml\.load|eval)\s*\([^)]*(?:req|request|input|data|body)/gi,
        severity: "critical",
        category: "trust-boundary",
        cweIds: ["CWE-502"],
        description: "Deserializing data from untrusted source without validation.",
        impact: "Deserialization vulnerabilities can lead to remote code execution.",
        recommendation: "Validate input schema before deserialization. Use safe parsers.",
    },
    // Business Logic
    {
        id: "lfd-biz-001",
        title: "Price calculation after discount check",
        pattern: /if\s*\([^)]*discount[^)]*\)[\s\S]{0,100}(?:price|total)\s*=/gi,
        severity: "medium",
        category: "business-logic",
        cweIds: ["CWE-840"],
        description: "Discount logic may be bypassable if not validated server-side.",
        impact: "Attackers may manipulate pricing to get unauthorized discounts.",
        recommendation: "Validate all pricing calculations server-side. Log anomalies.",
    },
    {
        id: "lfd-biz-002",
        title: "Quantity/amount not validated as positive",
        pattern: /(?:quantity|amount|count)\s*[:=]\s*(?:parseInt|Number|parseFloat)\s*\([^)]+\)(?![\s\S]{0,50}(?:>|>=)\s*0)/gi,
        severity: "medium",
        category: "business-logic",
        cweIds: ["CWE-20"],
        description: "Numeric value from user input not validated as positive.",
        impact: "Negative values may cause unexpected behavior or financial loss.",
        recommendation: "Validate that quantities and amounts are positive numbers.",
    },
    // Null Safety
    {
        id: "lfd-null-001",
        title: "Optional chaining followed by method call",
        pattern: /\?\.\w+\s*\([^)]*\)\s*\./g,
        severity: "low",
        category: "null-safety",
        cweIds: ["CWE-476"],
        description: "Method called on potentially undefined result of optional chain.",
        impact: "TypeError if the optional chain returns undefined.",
        recommendation: "Add null check or continue optional chaining.",
        languages: ["typescript", "javascript"],
    },
    {
        id: "lfd-null-002",
        title: "Nullable value used without check",
        pattern: /:\s*\w+\s*\|\s*(?:null|undefined)[\s\S]{0,200}(?<!\?\.)\.\w+/g,
        severity: "medium",
        category: "null-safety",
        cweIds: ["CWE-476"],
        description: "Nullable type accessed without null check.",
        impact: "Runtime error when accessing property of null/undefined.",
        recommendation: "Add null check or use optional chaining.",
        languages: ["typescript"],
    },
    // Resource Leaks
    {
        id: "lfd-resource-001",
        title: "Event listener not removed",
        pattern: /addEventListener\s*\([^)]+\)(?![\s\S]{0,500}removeEventListener)/g,
        severity: "low",
        category: "resource-leak",
        cweIds: ["CWE-401"],
        description: "Event listener added without corresponding removal.",
        impact: "Memory leaks in long-running applications.",
        recommendation: "Remove event listeners in cleanup/unmount lifecycle.",
        languages: ["typescript", "javascript"],
    },
    {
        id: "lfd-resource-002",
        title: "Timer not cleared",
        pattern: /(?:setInterval|setTimeout)\s*\([^)]+\)(?![\s\S]{0,300}clear(?:Interval|Timeout))/g,
        severity: "low",
        category: "resource-leak",
        cweIds: ["CWE-401"],
        description: "Timer created without being cleared.",
        impact: "Timers may continue running after component/scope is destroyed.",
        recommendation: "Store timer ID and clear in cleanup.",
        languages: ["typescript", "javascript"],
    },
    // Invariant Violations
    {
        id: "lfd-inv-001",
        title: "State modified without validation",
        pattern: /this\.state\.\w+\s*=(?!=)/g,
        severity: "medium",
        category: "invariant-violation",
        cweIds: ["CWE-20"],
        description: "Direct state mutation may bypass validation invariants.",
        impact: "Object may enter invalid state, causing downstream errors.",
        recommendation: "Use setter methods with validation.",
        languages: ["typescript", "javascript"],
    },
    {
        id: "lfd-inv-002",
        title: "Enum value not exhaustively checked",
        pattern: /switch\s*\([^)]+\)\s*\{(?![\s\S]*default\s*:)/g,
        severity: "low",
        category: "invariant-violation",
        cweIds: ["CWE-478"],
        description: "Switch statement without default case may miss enum values.",
        impact: "Unhandled enum values cause unexpected behavior.",
        recommendation: "Add default case or exhaustive type checking.",
    },
];
// ============================================================================
// Analysis Functions
// ============================================================================
/**
 * Detect language from file extension
 */
function detectLanguage(file) {
    const ext = path.extname(file).toLowerCase();
    const langMap = {
        ".ts": "typescript",
        ".tsx": "typescript",
        ".js": "javascript",
        ".jsx": "javascript",
        ".py": "python",
        ".go": "go",
        ".java": "java",
    };
    return langMap[ext] || "unknown";
}
/**
 * Extract code snippet around a line
 */
function extractSnippet(content, lineNumber, context = 2) {
    const lines = content.split("\n");
    const start = Math.max(0, lineNumber - context - 1);
    const end = Math.min(lines.length, lineNumber + context);
    return lines.slice(start, end).join("\n");
}
/**
 * Find line number from character index
 */
function findLineNumber(content, index) {
    return content.slice(0, index).split("\n").length;
}
/**
 * Check if a finding is in a test file
 */
function isTestFile(file) {
    return (file.includes(".test.") ||
        file.includes(".spec.") ||
        file.includes("__tests__") ||
        file.includes("/test/") ||
        file.includes("/tests/"));
}
/**
 * Run logic flaw detection on a single file
 */
function analyzeFile(file, content, categories) {
    const findings = [];
    const language = detectLanguage(file);
    // Skip test files
    if (isTestFile(file)) {
        return findings;
    }
    let findingId = 1;
    for (const pattern of LOGIC_PATTERNS) {
        // Filter by category if specified
        if (categories.length > 0 && !categories.includes(pattern.category)) {
            continue;
        }
        // Filter by language if specified
        if (pattern.languages && !pattern.languages.includes(language)) {
            continue;
        }
        // Reset regex
        pattern.pattern.lastIndex = 0;
        let match;
        while ((match = pattern.pattern.exec(content)) !== null) {
            const lineNumber = findLineNumber(content, match.index);
            const snippet = extractSnippet(content, lineNumber);
            findings.push({
                id: `lfd-${file.slice(-10).replace(/[^a-z0-9]/gi, "")}-${String(findingId++).padStart(3, "0")}`,
                title: pattern.title,
                description: pattern.description,
                severity: pattern.severity,
                confidence: 75,
                category: pattern.category,
                file,
                line: lineNumber,
                codeSnippet: snippet,
                impact: pattern.impact,
                cweIds: pattern.cweIds,
                recommendation: pattern.recommendation,
            });
        }
    }
    return findings;
}
// ============================================================================
// Main Entry Point
// ============================================================================
/**
 * Run logic flaw detection on a project
 */
export async function runLogicFlawDetector(projectPath, config) {
    const absolutePath = path.resolve(projectPath);
    const findings = [];
    let filesAnalyzed = 0;
    // Get files to analyze
    let files;
    if (config.focusFiles && config.focusFiles.length > 0) {
        files = config.focusFiles;
    }
    else {
        const patterns = ["**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx", "**/*.py", "**/*.go", "**/*.java"];
        const ignore = [
            "**/node_modules/**",
            "**/vendor/**",
            "**/dist/**",
            "**/build/**",
            "**/*.test.*",
            "**/*.spec.*",
            "**/test/**",
            "**/tests/**",
            "**/__tests__/**",
        ];
        files = await glob(patterns, {
            cwd: absolutePath,
            ignore,
            nodir: true,
        });
    }
    // Limit files based on depth
    const maxFiles = config.maxFilesToAnalyze ||
        (config.analysisDepth === "quick" ? 30 :
            config.analysisDepth === "standard" ? 75 : 150);
    const selectedFiles = files.slice(0, maxFiles);
    const categories = config.categories || [];
    // Analyze each file
    for (const file of selectedFiles) {
        const fullPath = path.join(absolutePath, file);
        try {
            const content = await readFile(fullPath, "utf-8");
            const fileFindings = analyzeFile(file, content, categories);
            findings.push(...fileFindings);
            filesAnalyzed++;
        }
        catch {
            // Skip unreadable files
        }
    }
    // Generate recommendations
    const recommendations = [];
    const categoryCounts = {};
    for (const finding of findings) {
        categoryCounts[finding.category] = (categoryCounts[finding.category] || 0) + 1;
    }
    if (categoryCounts["trust-boundary"]) {
        recommendations.push(`${categoryCounts["trust-boundary"]} trust boundary issues found. Implement strict input validation at all API boundaries.`);
    }
    if (categoryCounts["error-handling"]) {
        recommendations.push(`${categoryCounts["error-handling"]} error handling issues found. Review exception handling patterns and ensure proper cleanup.`);
    }
    if (categoryCounts["boundary-violation"]) {
        recommendations.push(`${categoryCounts["boundary-violation"]} boundary violations found. Add explicit bounds checking for array access and arithmetic.`);
    }
    if (categoryCounts["business-logic"]) {
        recommendations.push(`${categoryCounts["business-logic"]} business logic issues found. Add server-side validation for all business rules.`);
    }
    const detectedCategories = Object.keys(categoryCounts);
    return {
        filesAnalyzed,
        findings,
        categories: detectedCategories,
        recommendations,
    };
}
/**
 * Convert logic flaw findings to certification findings
 */
export function logicFlawToFindings(result) {
    return result.findings.map((f) => ({
        id: f.id,
        severity: f.severity,
        category: mapLogicCategory(f.category),
        description: `${f.title}: ${f.description}`,
        evidence: `File: ${f.file}:${f.line}\nCode: ${f.codeSnippet.slice(0, 200)}\nImpact: ${f.impact}`,
        confidence: f.confidence,
        verifications: [],
        created_at: new Date().toISOString(),
        scanner_source: "logic-flaw-detector",
        metadata: {
            cweIds: f.cweIds,
            recommendation: f.recommendation,
            relatedFunctions: f.relatedFunctions,
        },
    }));
}
/**
 * Map logic flaw category to finding category
 */
function mapLogicCategory(category) {
    const mapping = {
        "state-inconsistency": "logic-flaw",
        "race-condition": "race-condition",
        "boundary-violation": "input-validation",
        "error-handling": "error-handling",
        "trust-boundary": "input-validation",
        "business-logic": "logic-flaw",
        "null-safety": "code-quality",
        "resource-leak": "resource-management",
        "invariant-violation": "logic-flaw",
    };
    return mapping[category] || "code-quality";
}
//# sourceMappingURL=logic-flaw-detector.js.map