"use strict";
/**
 * Layer 2: SSRF (Server-Side Request Forgery) Detector
 * Detects user input flowing to server-side HTTP request sinks
 *
 * OWASP A01:2021 - Broken Access Control / A10:2021 - SSRF
 * CWE-918: Server-Side Request Forgery
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.detectSSRFPatterns = detectSSRFPatterns;
const context_helpers_1 = require("../utils/context-helpers");
/** User input source patterns */
const USER_INPUT_PATTERN = /(?:req|request|ctx\.request)\.(?:body|query|params|headers)(?:\.\w+|\[)/;
/** Check if file is client-side (SSRF is server-only) */
function isClientSideFile(content, filePath) {
    if (/['"]use client['"]/.test(content))
        return true;
    // .tsx/.jsx in components dir without server indicators
    if (/\/components\//.test(filePath) && !content.includes('express()') && !/['"]use server['"]/.test(content)) {
        return true;
    }
    return false;
}
/**
 * Check surrounding context for SSRF mitigations
 */
function hasSSRFMitigation(lines, lineIndex, windowSize = 15) {
    const start = Math.max(0, lineIndex - windowSize);
    const end = Math.min(lines.length, lineIndex + windowSize + 1);
    const context = lines.slice(start, end).join('\n');
    // Allowlist validation
    if (/(?:allowed|safe|valid|permitted)(?:Hosts|Domains|Urls|Origins)/i.test(context))
        return true;
    if (/allowList|allowlist|whitelist|whiteList/.test(context))
        return true;
    // URL validation with host/origin check
    if (/new\s+URL\(/.test(context) && /\.(?:hostname|host|origin)/.test(context))
        return true;
    // IP range checking
    if (/isPrivateIP|isPrivate|isInternal|isLocalhost/i.test(context))
        return true;
    if (/(?:127\.0\.0\.1|10\.\d|192\.168\.|169\.254\.)/.test(context) && /(?:block|reject|deny|forbidden)/i.test(context))
        return true;
    // Environment variable source
    if (/process\.env\./i.test(context)) {
        // Check if the URL comes from env var, not just any env var in context
        const envLineCount = lines.slice(start, end).filter(l => /process\.env\./.test(l)).length;
        const userInputCount = lines.slice(start, end).filter(l => USER_INPUT_PATTERN.test(l)).length;
        if (envLineCount > 0 && userInputCount <= 1)
            return false; // Don't suppress just because env vars exist
    }
    return false;
}
/**
 * Detect SSRF patterns in server-side code
 */
function detectSSRFPatterns(content, filePath, options) {
    const vulnerabilities = [];
    if ((0, context_helpers_1.isScannerOrFixtureFile)(filePath))
        return vulnerabilities;
    if ((0, context_helpers_1.isTestOrMockFile)(filePath))
        return vulnerabilities;
    if (isClientSideFile(content, filePath))
        return vulnerabilities;
    const lines = options?.parsed?.lines ?? content.split('\n');
    // ========== Pass 1: Direct taint — user input in HTTP sinks ==========
    const DIRECT_SSRF_PATTERNS = [
        // fetch(req.body.url), fetch(req.query.endpoint)
        /(?:fetch|got|undici\.request)\s*\(\s*(?:req|request|ctx\.request)\.(?:body|query|params|headers)(?:\.\w+|\[)/,
        // axios.get(req.body.url)
        /axios\.(?:get|post|put|delete|patch|head|options|request)\s*\(\s*(?:req|request|ctx\.request)\.(?:body|query|params)/,
        // http.get(req.body.url), https.get(...)
        /https?\.(?:get|request)\s*\(\s*(?:req|request|ctx\.request)\.(?:body|query|params)/,
        // got(req.body.url)
        /got\s*\(\s*(?:req|request|ctx\.request)\.(?:body|query|params)/,
        // Python: requests.get(request.args.get('url'))
        /requests\.(?:get|post|put|delete|patch|head|options)\s*\(\s*(?:request\.(?:args|form|json|data)\.get|request\.(?:args|form|json|data)\[)/,
        // Python: urllib.request.urlopen(request.args.get('url'))
        /urllib\.request\.urlopen\s*\(\s*(?:request\.(?:args|form|json))/,
    ];
    for (let i = 0; i < lines.length; i++) {
        const line = lines[i];
        if ((0, context_helpers_1.isComment)(line))
            continue;
        for (const pattern of DIRECT_SSRF_PATTERNS) {
            if (pattern.test(line)) {
                if (hasSSRFMitigation(lines, i))
                    continue;
                vulnerabilities.push({
                    id: `ssrf-${filePath}-${i + 1}-direct`,
                    filePath,
                    lineNumber: i + 1,
                    lineContent: line.trim(),
                    severity: 'high',
                    category: 'ssrf',
                    title: 'SSRF: User input in HTTP request',
                    description: 'User-controlled input flows directly to a server-side HTTP request. An attacker could force the server to make requests to internal services, cloud metadata endpoints (169.254.169.254), or other sensitive resources.',
                    suggestedFix: 'Validate the URL against an allowlist of permitted domains. Block requests to private IP ranges and cloud metadata endpoints.',
                    confidence: 'high',
                    layer: 2,
                });
                break;
            }
        }
    }
    // ========== Pass 2: Open redirect ==========
    const OPEN_REDIRECT_PATTERNS = [
        // res.redirect(req.query.next)
        /res\.redirect\s*\(\s*(?:req|request)\.(?:body|query|params)(?:\.\w+|\[)/,
        // redirect(req.query.returnTo)
        /(?<!\w)redirect\s*\(\s*(?:req|request)\.(?:body|query|params)(?:\.\w+|\[)/,
    ];
    for (let i = 0; i < lines.length; i++) {
        const line = lines[i];
        if ((0, context_helpers_1.isComment)(line))
            continue;
        for (const pattern of OPEN_REDIRECT_PATTERNS) {
            if (pattern.test(line)) {
                if (hasSSRFMitigation(lines, i))
                    continue;
                vulnerabilities.push({
                    id: `ssrf-${filePath}-${i + 1}-redirect`,
                    filePath,
                    lineNumber: i + 1,
                    lineContent: line.trim(),
                    severity: 'high',
                    category: 'ssrf',
                    title: 'Open redirect: User input in redirect',
                    description: 'User-controlled input is used in a server redirect. An attacker could craft a URL that redirects users to a malicious site for phishing.',
                    suggestedFix: 'Validate redirect URLs against an allowlist of permitted paths or domains. Use relative paths instead of absolute URLs.',
                    confidence: 'high',
                    layer: 2,
                });
                break;
            }
        }
    }
    // ========== Pass 3: Variable-mediated taint ==========
    // Track: const url = req.body.url; ... fetch(url)
    const taintedVars = new Map(); // varName -> lineIndex
    for (let i = 0; i < lines.length; i++) {
        const line = lines[i];
        if ((0, context_helpers_1.isComment)(line))
            continue;
        // Detect taint sources: const/let/var url = req.body.url
        const taintMatch = line.match(/(?:const|let|var)\s+(\w+)\s*=\s*(?:req|request|ctx\.request)\.(?:body|query|params|headers)(?:\.\w+|\[)/);
        if (taintMatch) {
            taintedVars.set(taintMatch[1], i);
            continue;
        }
        // Also detect destructuring: const { url } = req.body
        const destructMatch = line.match(/(?:const|let|var)\s*\{([^}]+)\}\s*=\s*(?:req|request|ctx\.request)\.(?:body|query|params)/);
        if (destructMatch) {
            const vars = destructMatch[1].split(',').map(v => v.trim().split(':')[0].trim());
            for (const v of vars) {
                if (v)
                    taintedVars.set(v, i);
            }
            continue;
        }
        // Check if tainted variable flows to HTTP sink (within 20 lines)
        if (taintedVars.size > 0) {
            const httpSinkPattern = /(?:fetch|axios\.(?:get|post|put|delete|patch|request)|got|https?\.(?:get|request)|undici\.request)\s*\(/;
            if (httpSinkPattern.test(line)) {
                for (const [varName, sourceLineIdx] of taintedVars) {
                    if (i - sourceLineIdx <= 20 && i - sourceLineIdx > 0) {
                        // Check if the variable is used in this sink call
                        const varPattern = new RegExp(`\\b${varName}\\b`);
                        if (varPattern.test(line)) {
                            if (hasSSRFMitigation(lines, i))
                                continue;
                            vulnerabilities.push({
                                id: `ssrf-${filePath}-${i + 1}-mediated`,
                                filePath,
                                lineNumber: i + 1,
                                lineContent: line.trim(),
                                severity: 'medium',
                                category: 'ssrf',
                                title: 'SSRF: Variable-mediated user input in HTTP request',
                                description: `User input assigned to variable '${varName}' (line ${sourceLineIdx + 1}) flows to an HTTP request sink. An attacker could control the request destination.`,
                                suggestedFix: 'Validate the URL against an allowlist before making the request. Block private IP ranges.',
                                confidence: 'medium',
                                layer: 2,
                                requiresAIValidation: true,
                            });
                            break;
                        }
                    }
                }
            }
            // Check for redirect sinks too
            const redirectSinkPattern = /(?:res\.redirect|redirect)\s*\(/;
            if (redirectSinkPattern.test(line)) {
                for (const [varName, sourceLineIdx] of taintedVars) {
                    if (i - sourceLineIdx <= 20 && i - sourceLineIdx > 0) {
                        const varPattern = new RegExp(`\\b${varName}\\b`);
                        if (varPattern.test(line)) {
                            if (hasSSRFMitigation(lines, i))
                                continue;
                            vulnerabilities.push({
                                id: `ssrf-${filePath}-${i + 1}-redirect-mediated`,
                                filePath,
                                lineNumber: i + 1,
                                lineContent: line.trim(),
                                severity: 'medium',
                                category: 'ssrf',
                                title: 'Open redirect: Variable-mediated user input',
                                description: `User input assigned to variable '${varName}' (line ${sourceLineIdx + 1}) flows to a redirect call.`,
                                suggestedFix: 'Validate redirect URL against an allowlist of permitted paths.',
                                confidence: 'medium',
                                layer: 2,
                                requiresAIValidation: true,
                            });
                            break;
                        }
                    }
                }
            }
        }
    }
    // ========== Pass 4: Template literal with user input in URL ==========
    for (let i = 0; i < lines.length; i++) {
        const line = lines[i];
        if ((0, context_helpers_1.isComment)(line))
            continue;
        // fetch(`${req.query.baseUrl}/api`) or fetch(`http://${req.body.host}/path`)
        const templateSinkPattern = /(?:fetch|axios\.(?:get|post|put|delete|patch|request)|got|https?\.(?:get|request))\s*\(\s*`[^`]*\$\{[^}]*(?:req|request|ctx\.request)\.(?:body|query|params)[^}]*\}/;
        if (templateSinkPattern.test(line)) {
            // Avoid duplicate with direct taint
            const alreadyFound = vulnerabilities.some(v => v.lineNumber === i + 1 && v.filePath === filePath);
            if (alreadyFound)
                continue;
            if (hasSSRFMitigation(lines, i))
                continue;
            vulnerabilities.push({
                id: `ssrf-${filePath}-${i + 1}-template`,
                filePath,
                lineNumber: i + 1,
                lineContent: line.trim(),
                severity: 'high',
                category: 'ssrf',
                title: 'SSRF: User input interpolated in HTTP request URL',
                description: 'User-controlled request data is interpolated into an HTTP request URL via template literal. An attacker could manipulate the request destination.',
                suggestedFix: 'Extract and validate the user input before constructing the URL. Use an allowlist of permitted domains.',
                confidence: 'high',
                layer: 2,
            });
        }
    }
    return vulnerabilities;
}
//# sourceMappingURL=ssrf-detection.js.map