"use strict";
/**
 * Layer 2: AI Endpoint Protection Detection
 * Detects AI/LLM endpoints without proper authentication or rate limiting
 *
 * Covers:
 * - M5.2: AI endpoints without auth/rate limiting
 * - Cost-bearing AI endpoints exposed publicly
 * - Missing rate limiting on AI routes
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.detectAIEndpointProtection = detectAIEndpointProtection;
exports.isRouteFile = isRouteFile;
exports.hasAIApiCalls = hasAIApiCalls;
exports.hasAuthentication = hasAuthentication;
exports.hasRateLimiting = hasRateLimiting;
const file_classifier_1 = require("../../parse/file-classifier");
const BASE_CONFIDENCE = 0.50;
// ============================================================================
// Context Detection
// ============================================================================
/**
 * Check if file is a route/API handler
 */
function isRouteFile(filePath) {
    const routePatterns = [
        /\/api\/.*\.(ts|js)$/i,
        /\/routes?\/.*\.(ts|js)$/i,
        /route\.(ts|js)$/i,
        /\/pages\/api\/.*\.(ts|js)$/i,
        /\/app\/.*\/route\.(ts|js)$/i,
        /\.(controller|handler)\.(ts|js)$/i,
    ];
    return routePatterns.some(p => p.test(filePath));
}
/**
 * Check if content contains AI/LLM API calls
 */
function hasAIApiCalls(content) {
    const aiPatterns = [
        // OpenAI
        /openai\.chat\.completions?\.create/i,
        /openai\.completions?\.create/i,
        /openai\.embeddings?\.create/i,
        // Anthropic
        /anthropic\.messages\.create/i,
        /anthropic\.completions?\.create/i,
        // Vercel AI SDK
        /\bgenerateText\s*\(/i,
        /\bstreamText\s*\(/i,
        /\bgenerateObject\s*\(/i,
        /\bstreamObject\s*\(/i,
        // Generic patterns
        /\.create\s*\(\s*\{[^}]*model\s*:/i,
        /ChatCompletion|MessageCreate/i,
        // LangChain
        /\.invoke\s*\([^)]*\)/i,
        /ChatOpenAI|ChatAnthropic|ChatModel/i,
        // Other providers
        /replicate\.run/i,
        /cohere\.(?:generate|chat)/i,
        /mistral\.chat/i,
    ];
    return aiPatterns.some(p => p.test(content));
}
/**
 * Check if there's authentication in the route
 */
function hasAuthentication(content) {
    const authPatterns = [
        // Session/user checks
        /getSession|getServerSession|getCurrentUser/i,
        /auth\(\)|requireAuth|withAuth|ensureAuth/i,
        /verifyToken|validateToken|checkToken/i,
        /req\.user|request\.user|context\.user/i,
        /isAuthenticated|checkAuth/i,
        // Header checks
        /Authorization.*Bearer/i,
        /headers\[['"`]authorization['"`]\]/i,
        /getHeader\(['"`]authorization['"`]\)/i,
        // API key validation
        /apiKey|api_key|x-api-key/i,
        // Clerk/NextAuth/Auth0
        /currentUser\(\)|auth\(\)\.protect/i,
        /getAuth\(\)|clerkClient/i,
        // User ID extraction (implies auth)
        /userId|user\.id|currentUserId/i,
        // 401/403 responses
        /status\(401\)|status\(403\)/i,
        /Unauthorized|Forbidden/i,
    ];
    return authPatterns.some(p => p.test(content));
}
/**
 * Check if there's rate limiting
 */
function hasRateLimiting(content) {
    const rateLimitPatterns = [
        // Rate limit middleware/libraries
        /rateLimit|rateLimiter/i,
        /express-rate-limit/i,
        /upstash.*ratelimit|@upstash\/ratelimit/i,
        /rate-limiter-flexible/i,
        // Custom rate limiting
        /throttle|Throttle/i,
        /requestsPerMinute|requestsPerHour/i,
        /maxRequests|requestLimit/i,
        // Rate limit headers
        /X-RateLimit|x-ratelimit/i,
        /Retry-After/i,
        // 429 responses
        /status\(429\)|\.status === 429/i,
        /Too Many Requests|TooManyRequests/i,
        // Sliding window/token bucket
        /slidingWindow|tokenBucket|fixedWindow/i,
    ];
    return rateLimitPatterns.some(p => p.test(content));
}
/**
 * Check if this is a BYOK (Bring Your Own Key) endpoint
 * BYOK endpoints have lower risk since user pays for their own usage
 */
function isBYOKEndpoint(content) {
    const byokPatterns = [
        /req\.body\.(?:apiKey|api_key|openaiKey|anthropicKey)/i,
        /request\.(?:apiKey|api_key|openaiKey|anthropicKey)/i,
        /userApiKey|user_api_key|clientApiKey/i,
        /['"`](?:apiKey|api_key|openai_key|anthropic_key)['"`]\s*:/i,
        // Headers with user's key
        /headers\[['"`]x-openai-key['"`]\]/i,
        /headers\[['"`]x-api-key['"`]\]/i,
        // Destructuring from request body
        /const\s*\{\s*[^}]*apiKey[^}]*\}\s*=\s*await\s*(?:request|req)\.json\(\)/i,
        /const\s*\{\s*[^}]*api_key[^}]*\}\s*=\s*await\s*(?:request|req)\.json\(\)/i,
        // OpenAI/Anthropic client with user-provided key
        /new\s+OpenAI\s*\(\s*\{\s*apiKey\s*[,}]/i,
        /new\s+Anthropic\s*\(\s*\{\s*apiKey\s*[,}]/i,
    ];
    return byokPatterns.some(p => p.test(content));
}
/**
 * Check if route is protected by middleware (from config)
 */
function isProtectedByMiddleware(filePath, middlewareConfig) {
    if (!middlewareConfig?.protectedPaths)
        return false;
    // Extract route path from file path
    const routePath = filePath
        .replace(/.*\/(pages|app)\/api/, '/api')
        .replace(/.*\/routes?/, '')
        .replace(/\/route\.(ts|js)$/, '')
        .replace(/\.(ts|js)$/, '')
        .replace(/\[([^\]]+)\]/g, ':$1');
    return middlewareConfig.protectedPaths.some(p => routePath.startsWith(p));
}
/**
 * Check if this is an internal/admin-only route
 */
function isInternalRoute(filePath, content) {
    const internalPatterns = [
        /\/internal\//i,
        /\/admin\//i,
        /\/_internal\//i,
        // Content checks
        /adminOnly|internalOnly|isAdmin/i,
        /process\.env\.INTERNAL_SECRET/i,
    ];
    return internalPatterns.some(p => p.test(filePath) || p.test(content));
}
/**
 * Get surrounding context
 */
function getSurroundingContext(content, lineIndex, windowSize = 50) {
    const lines = content.split('\n');
    const start = Math.max(0, lineIndex - windowSize);
    const end = Math.min(lines.length, lineIndex + windowSize);
    return lines.slice(start, end).join('\n');
}
/**
 * Route handler patterns by framework
 */
const ROUTE_HANDLER_PATTERNS = [
    // Next.js App Router
    {
        name: 'Next.js App Router AI endpoint',
        pattern: /export\s+(?:async\s+)?function\s+(GET|POST|PUT|PATCH|DELETE)\s*\(/gi,
        framework: 'nextjs_app',
        description: 'Next.js App Router endpoint with AI API calls lacks protection.',
        suggestedFix: 'Add authentication check at start of handler. Consider using middleware for auth and rate limiting.',
    },
    // Next.js Pages Router
    {
        name: 'Next.js Pages API route',
        pattern: /export\s+default\s+(?:async\s+)?function\s*(?:\w+\s*)?\([^)]*req/gi,
        framework: 'nextjs_pages',
        description: 'Next.js Pages API route with AI calls lacks protection.',
        suggestedFix: 'Add authentication: const session = await getServerSession(req, res, authOptions); if (!session) return res.status(401).json({error: "Unauthorized"})',
    },
    // Express
    {
        name: 'Express AI route',
        pattern: /(?:app|router)\.(get|post|put|patch|delete)\s*\(\s*['"`][^'"`]+['"`]\s*,/gi,
        framework: 'express',
        description: 'Express route with AI API calls lacks middleware protection.',
        suggestedFix: 'Add authentication and rate limiting middleware: app.post("/api/chat", authMiddleware, rateLimiter, handler)',
    },
    // Fastify
    {
        name: 'Fastify AI route',
        pattern: /fastify\.(get|post|put|patch|delete)\s*\(\s*['"`][^'"`]+['"`]/gi,
        framework: 'fastify',
        description: 'Fastify route with AI calls lacks protection.',
        suggestedFix: 'Add preHandler hooks for auth and rate limiting: { preHandler: [authenticate, rateLimit] }',
    },
    // Generic handler exports
    {
        name: 'API handler with AI calls',
        pattern: /export\s+(?:const|async\s+function)\s+(?:handler|apiHandler)\s*[=:]/gi,
        framework: 'generic',
        description: 'API handler with AI calls may lack protection.',
        suggestedFix: 'Ensure authentication and rate limiting are applied before AI API calls.',
    },
];
/**
 * Main detection function for AI endpoint protection issues
 */
function detectAIEndpointProtection(content, filePath, options = {}) {
    const vulnerabilities = [];
    // Skip non-applicable files
    if ((0, file_classifier_1.isScannerOrFixtureFile)(filePath))
        return vulnerabilities;
    if ((0, file_classifier_1.isDocumentationFile)(filePath))
        return vulnerabilities;
    // Only scan route files that have AI API calls
    if (!isRouteFile(filePath) || !hasAIApiCalls(content)) {
        return vulnerabilities;
    }
    const lines = options?.parsed?.lines ?? content.split('\n');
    const isTestFile = (0, file_classifier_1.isTestOrMockFile)(filePath);
    const isExample = (0, file_classifier_1.isExampleDirectory)(filePath);
    // Check file-level protection
    const fileHasAuth = hasAuthentication(content);
    const fileHasRateLimit = hasRateLimiting(content);
    const isByok = isBYOKEndpoint(content);
    const isInternal = isInternalRoute(filePath, content);
    const isMiddlewareProtected = isProtectedByMiddleware(filePath, options.middlewareConfig);
    // Scan for route handler patterns
    for (const pattern of ROUTE_HANDLER_PATTERNS) {
        const regex = new RegExp(pattern.pattern.source, pattern.pattern.flags);
        let match;
        while ((match = regex.exec(content)) !== null) {
            const lineNumber = content.substring(0, match.index).split('\n').length;
            const lineContent = lines[lineNumber - 1]?.trim() || '';
            // Skip comments
            if ((0, file_classifier_1.isComment)(lineContent))
                continue;
            // Get surrounding context
            const context = getSurroundingContext(content, lineNumber - 1, 50);
            // Check for local protection patterns (within handler)
            const handlerHasAuth = hasAuthentication(context);
            const handlerHasRateLimit = hasRateLimiting(context);
            // Calculate severity based on protection status
            let severity;
            const notes = [];
            if (isMiddlewareProtected || isInternal) {
                severity = 'info';
                notes.push(isMiddlewareProtected ? 'Protected by middleware' : 'Internal route');
            }
            else if (handlerHasAuth || fileHasAuth) {
                if (handlerHasRateLimit || fileHasRateLimit) {
                    // Has both auth and rate limiting
                    severity = 'info';
                    notes.push('Has authentication and rate limiting');
                }
                else {
                    // Has auth but no rate limiting
                    severity = 'low';
                    notes.push('Has authentication but missing rate limiting');
                }
            }
            else if (handlerHasRateLimit || fileHasRateLimit) {
                // Has rate limiting but no auth
                severity = 'medium';
                notes.push('Has rate limiting but missing authentication');
            }
            else {
                // No protection at all
                severity = 'high';
                notes.push('Missing authentication and rate limiting');
            }
            // BYOK endpoints have lower risk - user provides own API key
            if (isByok && severity !== 'info') {
                // BYOK with auth = info (user pays for own usage, access controlled)
                // BYOK without auth = low (potential abuse but user still pays)
                if (handlerHasAuth || fileHasAuth) {
                    severity = 'info';
                    notes.push('BYOK endpoint with authentication - user pays for their own usage');
                }
                else {
                    severity = 'low';
                    notes.push('BYOK endpoint without auth - potential abuse but user provides API key');
                }
            }
            // Downgrade test files
            if (isTestFile) {
                severity = 'info';
                notes.push('in test file');
            }
            // Downgrade example/demo directories - these are tutorials, not production code
            if (isExample && severity !== 'info') {
                severity = 'info';
                notes.push('in example/demo directory - tutorial code');
            }
            // Build description
            let description = pattern.description;
            if (notes.length > 0) {
                description += ` (${notes.join('; ')})`;
            }
            // Determine suggested fix based on what's missing
            let suggestedFix = pattern.suggestedFix;
            if (handlerHasAuth && !handlerHasRateLimit) {
                suggestedFix = 'Add rate limiting to prevent cost abuse: npm install express-rate-limit or use Upstash ratelimit for serverless.';
            }
            vulnerabilities.push({
                id: `ai-endpoint-${filePath}-${lineNumber}-${pattern.name.replace(/\s+/g, '-')}`,
                filePath,
                lineNumber,
                lineContent,
                severity,
                category: 'ai_endpoint_unprotected',
                title: pattern.name,
                description,
                suggestedFix,
                confidence: severity === 'info' ? 'low' : 'medium',
                layer: 2,
                source: 'ai_code',
                requiresAIValidation: severity !== 'info',
                baseConfidence: BASE_CONFIDENCE,
            });
            // Only report one finding per file (file-level issue)
            break;
        }
        // Only process if we found a route handler (avoid duplicate patterns)
        if (vulnerabilities.length > 0)
            break;
    }
    return vulnerabilities;
}
//# sourceMappingURL=endpoint-protection.js.map