"use strict";
/**
 * Layer 2: Log Injection Detector
 * Detects unsanitized user input flowing into log statements
 *
 * OWASP A09:2021 - Security Logging and Monitoring Failures
 * CWE-117: Improper Output Neutralization for Logs
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.detectLogInjection = detectLogInjection;
const context_helpers_1 = require("../utils/context-helpers");
/** Log sink patterns */
const LOG_SINK_PATTERNS = [
    /console\.(?:log|info|warn|error|debug|trace)\s*\(/,
    /logger\.(?:log|info|warn|error|debug|trace|fatal|silly|verbose)\s*\(/,
    /(?:winston|pino|bunyan|log4js|signale)\.(?:log|info|warn|error|debug)\s*\(/,
    /log\.(?:info|warn|error|debug|trace|fatal)\s*\(/,
];
/** User-controlled request data patterns */
const USER_INPUT_PATTERNS = [
    /(?:req|request|ctx\.request)\.(?:body|query|params)(?:\.\w+|\[['"][^'"]+['"]\])/,
    /(?:req|request)\.headers(?:\.\w+|\[['"][^'"]+['"]\])/,
];
/** Error/catch variable names to exclude */
const ERROR_VAR_PATTERN = /\b(?:err|error|e|ex|exception)\b/;
/** Patterns that are NOT user input (internal/app data) */
const NOT_USER_INPUT_PATTERNS = [
    // Error objects in catch blocks
    /\bcatch\s*\(\s*(?:err|error|e|ex|exception)\b/,
    // Internal IDs
    /(?:user|session|tenant|org|account)\.id\b/,
    /(?:userId|sessionId|tenantId|orgId|accountId)\b/,
    // Internal state
    /\.length\b/,
    /\.count\b/,
    /\.size\b/,
    /\.status\b/,
];
/** Middleware logging patterns to skip entirely */
const MIDDLEWARE_LOGGING = /(?:app|server|router)\.use\s*\(\s*(?:morgan|pino|express-winston|express\.logger)/;
/** Sanitization patterns near log line */
const SANITIZATION_PATTERNS = [
    /\.replace\s*\(\s*\/\[\\r\\n\]/,
    /sanitize\s*\(/,
    /escape\s*\(/,
    /encodeURI(?:Component)?\s*\(/,
    /stripNewlines?\s*\(/,
    /\.replace\s*\(\s*\/[\r\n]/,
];
/**
 * Check if a log line contains user input (not just error objects)
 */
function containsUserInput(line, lines, lineIndex) {
    // Check for req.headers in log
    if (/(?:req|request)\.headers/.test(line)) {
        return { found: true, type: 'header' };
    }
    // Check for req.body/query/params in log
    if (/(?:req|request|ctx\.request)\.(?:body|query|params)(?:\.\w+|\[)/.test(line)) {
        return { found: true, type: 'body' };
    }
    // Check for spread of request body: console.log({ ...req.body })
    if (/\.\.\.\s*(?:req|request|ctx\.request)\.(?:body|query|params)/.test(line)) {
        return { found: true, type: 'spread' };
    }
    // Check for JSON.stringify(req.body) in log
    if (/JSON\.stringify\s*\(\s*(?:req|request|ctx\.request)\.(?:body|query|params)/.test(line)) {
        return { found: true, type: 'stringify' };
    }
    // Check for template literal with user input
    if (/`[^`]*\$\{[^}]*(?:req|request|ctx\.request)\.(?:body|query|params)[^}]*\}/.test(line)) {
        return { found: true, type: 'body' };
    }
    // Multi-line log calls: check ±3 lines
    for (let offset = -3; offset <= 3; offset++) {
        if (offset === 0)
            continue;
        const idx = lineIndex + offset;
        if (idx < 0 || idx >= lines.length)
            continue;
        const nearLine = lines[idx];
        if (USER_INPUT_PATTERNS.some(p => p.test(nearLine))) {
            // Only flag if the nearby line is part of the same log call (open paren)
            const fromStart = Math.min(lineIndex, idx);
            const toEnd = Math.max(lineIndex, idx);
            const block = lines.slice(fromStart, toEnd + 1).join('\n');
            // Check for unclosed paren indicating multi-line call
            const openParens = (block.match(/\(/g) || []).length;
            const closeParens = (block.match(/\)/g) || []).length;
            if (openParens > closeParens) {
                if (/(?:req|request)\.headers/.test(nearLine)) {
                    return { found: true, type: 'header' };
                }
                return { found: true, type: 'body' };
            }
        }
    }
    return { found: false, type: 'body' };
}
/**
 * Check if the log line is just error logging in a catch block
 */
function isErrorLogging(line, lines, lineIndex) {
    // Direct error object logging: console.error(err), console.error(error)
    if (/console\.(?:error|warn|log)\s*\(\s*(?:['"][^'"]*['"],?\s*)?(?:err|error|e|ex|exception)\s*\)/.test(line)) {
        return true;
    }
    // console.error('message', error) pattern
    if (/console\.(?:error|warn|log)\s*\(\s*['"][^'"]*['"],\s*(?:err|error|e|ex|exception)\s*\)/.test(line)) {
        return true;
    }
    // error.message logging
    if (/(?:err|error|e|ex|exception)\.(?:message|stack|name|code)\b/.test(line)) {
        return true;
    }
    // logger.error('msg', { error })
    if (/logger\.(?:error|warn)\s*\(.*(?:err|error|e)\b/.test(line) && !USER_INPUT_PATTERNS.some(p => p.test(line))) {
        return true;
    }
    // Check if we're inside a catch block
    for (let j = lineIndex - 1; j >= Math.max(0, lineIndex - 5); j--) {
        if (/\bcatch\s*\(/.test(lines[j])) {
            // We're in a catch block — check if the logged value is the error var
            const catchMatch = lines[j].match(/catch\s*\(\s*(\w+)/);
            if (catchMatch) {
                const errorVar = catchMatch[1];
                if (new RegExp(`\\b${errorVar}\\b`).test(line)) {
                    return true;
                }
            }
        }
    }
    return false;
}
/**
 * Check for sanitization near the log line
 */
function hasSanitization(lines, lineIndex) {
    const start = Math.max(0, lineIndex - 3);
    const end = Math.min(lines.length, lineIndex + 4);
    const context = lines.slice(start, end).join('\n');
    return SANITIZATION_PATTERNS.some(p => p.test(context));
}
/**
 * Detect log injection patterns
 */
function detectLogInjection(content, filePath, options) {
    const vulnerabilities = [];
    if ((0, context_helpers_1.isScannerOrFixtureFile)(filePath))
        return vulnerabilities;
    if ((0, context_helpers_1.isTestOrMockFile)(filePath))
        return vulnerabilities;
    // Skip Morgan/express middleware logging (intentional request logging)
    if (MIDDLEWARE_LOGGING.test(content))
        return vulnerabilities;
    const lines = options?.parsed?.lines ?? content.split('\n');
    for (let i = 0; i < lines.length; i++) {
        const line = lines[i];
        if ((0, context_helpers_1.isComment)(line))
            continue;
        // Check if this line is a log sink
        const isLogSink = LOG_SINK_PATTERNS.some(p => p.test(line));
        if (!isLogSink)
            continue;
        // Skip error logging (not user input)
        if (isErrorLogging(line, lines, i))
            continue;
        // Check if user input flows to this log call
        const userInput = containsUserInput(line, lines, i);
        if (!userInput.found)
            continue;
        // Skip if sanitization is nearby
        if (hasSanitization(lines, i))
            continue;
        // Determine severity
        let severity = 'low';
        let title = 'Log injection: User input in log statement';
        let description = '';
        if (userInput.type === 'header') {
            severity = 'medium';
            title = 'Log injection: HTTP headers in log statement';
            description =
                'HTTP request headers are logged without sanitization. Headers can contain CRLF sequences that forge log entries or inject malicious content into log processing tools.';
        }
        else if (userInput.type === 'spread' || userInput.type === 'stringify') {
            severity = 'low';
            title = 'Log injection: Entire request body logged';
            description =
                'Entire request body is logged, which may contain unsanitized user input. This can lead to log forging, log injection, or sensitive data exposure in logs.';
        }
        else {
            severity = 'low';
            title = 'Log injection: Request data in log statement';
            description =
                'User-controlled request data flows into a log statement. Unsanitized input can forge log entries or inject newlines to create misleading log records.';
        }
        vulnerabilities.push({
            id: `loginj-${filePath}-${i + 1}`,
            filePath,
            lineNumber: i + 1,
            lineContent: line.trim(),
            severity,
            category: 'log_injection',
            title,
            description,
            suggestedFix: 'Sanitize user input before logging (strip newlines and control characters). Use structured logging (JSON format) to prevent log forging.',
            confidence: severity === 'medium' ? 'high' : 'medium',
            layer: 2,
            requiresAIValidation: severity !== 'medium',
        });
    }
    return vulnerabilities;
}
//# sourceMappingURL=log-injection.js.map