// engine/auditor-security/error-checks.ts — Error handling and async promise checks
// Includes OWASP A10:2025 — Mishandling of Exceptional Conditions (SPEC-029 TD-P15-03)
/**
 * OWASP A10:2025 — Mishandling of Exceptional Conditions.
 * Detects: swallowed exceptions, exposed stack traces, unchecked return values,
 * and missing timeouts on async operations.
 * Returns a list of human-readable issue descriptions (not AuditFinding objects)
 * so this function can be used standalone without a file context.
 */
export function checkOwaspA10Conditions(content) {
    const issues = [];
    // 1. Swallowed exceptions — catch block with no body or only whitespace
    if (/catch\s*\(\s*\w*\s*\)\s*\{\s*\}/.test(content) || /catch\s*\{\s*\}/.test(content)) {
        issues.push('Swallowed exception: empty catch block silently discards errors (OWASP A10)');
    }
    // 2. Stack traces exposed in production responses
    if (/(?:res|response|ctx|context)\s*\.\s*(?:send|json|write)\s*\([^)]*(?:stack|\.stack)\b/i.test(content)) {
        issues.push('Stack trace exposure: error.stack sent in HTTP response — reveals internals to clients (OWASP A10)');
    }
    // 3. Unchecked return values — result discarded without null/error check
    //    Detect patterns like: const result = dangerousOp(); result.doSomething() without null-check
    if (/const\s+\w+\s*=\s*(?:JSON\.parse|parseInt|parseFloat|Number)\s*\([^)]*\);\s*\n[^\n]*\./.test(content)) {
        issues.push('Unchecked return value: result of parsing operation used without null/NaN check (OWASP A10)');
    }
    // 4. Missing timeout on async/fetch operations
    const hasFetch = /\bfetch\s*\(/.test(content) || /\baxios\b/.test(content) || /\bhttpClient\b/.test(content);
    const hasTimeout = /timeout\s*[:=]|AbortSignal\.timeout|setTimeout\s*\(/.test(content);
    if (hasFetch && !hasTimeout) {
        issues.push('Missing timeout: async HTTP operation has no timeout configured — risks indefinite hang (OWASP A10)');
    }
    return issues;
}
export function checkErrorHandling(file, content, lines, ext) {
    const findings = [];
    const emptyCatch = content.matchAll(/catch\s*\([^)]*\)\s*\{\s*\}/g);
    for (const match of emptyCatch) {
        const lineNum = content.substring(0, match.index).split('\n').length;
        findings.push({
            file,
            line: lineNum,
            category: 'error-handling',
            severity: 'error',
            rule: 'empty-catch',
            message: 'Empty catch block silently swallows errors.',
            suggestion: 'At minimum, log the error. Consider re-throwing or handling gracefully.',
        });
    }
    for (let i = 0; i < lines.length; i++) {
        const catchLine = lines[i];
        /* v8 ignore next 2 */
        if (catchLine === undefined) {
            continue;
        }
        if (/catch\s*\(/.exec(catchLine)) {
            const body = [];
            let braceCount = 0;
            for (let j = i; j < Math.min(i + 10, lines.length); j++) {
                const bodyLine = lines[j];
                /* v8 ignore next 2 */
                if (bodyLine === undefined) {
                    continue;
                }
                body.push(bodyLine);
                braceCount += (bodyLine.match(/\{/g) ?? []).length;
                braceCount -= (bodyLine.match(/\}/g) ?? []).length;
                if (braceCount === 0 && j > i) {
                    break;
                }
            }
            const bodyStr = body.join('\n');
            if (/console\.(log|error)/.exec(bodyStr) && !/throw|return|reject/.exec(bodyStr)) {
                findings.push({
                    file,
                    line: i + 1,
                    category: 'error-handling',
                    severity: 'warning',
                    rule: 'catch-only-logs',
                    message: 'Catch block only logs the error without proper handling.',
                    suggestion: 'Consider returning an error response, retrying, or propagating the error.',
                });
            }
        }
    }
    if (['.ts', '.tsx', '.js', '.jsx'].includes(ext)) {
        const asyncFunctions = content.matchAll(/async\s+(?:function\s+)?(\w+)/g);
        for (const match of asyncFunctions) {
            const fnName = match[1];
            const fnIndex = match.index;
            const surroundingCode = content.substring(fnIndex, Math.min(fnIndex + 500, content.length));
            if (!surroundingCode.includes('try') && !surroundingCode.includes('.catch')) {
                findings.push({
                    file,
                    category: 'error-handling',
                    severity: 'info',
                    rule: 'unhandled-async',
                    message: `Async function "${fnName}" may have unhandled promise rejections.`,
                    suggestion: 'Wrap async operations in try-catch or use .catch() for error handling.',
                });
            }
        }
    }
    return findings;
}
//# sourceMappingURL=error-checks.js.map