"use strict";
/**
 * Layer 2: BYOK (Bring Your Own Key) Pattern Detection
 * Distinguishes per-user BYOK flows from central/shared key storage
 * BYOK is often a feature, not a vulnerability - severity depends on context
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.detectBYOKPatterns = detectBYOKPatterns;
const file_classifier_1 = require("../../parse/file-classifier");
const middleware_detector_1 = require("../../model/middleware-detector");
const BASE_CONFIDENCE_STORAGE = 0.50;
const BASE_CONFIDENCE_TRANSIENT = 0.25;
/**
 * Check if line contains example/placeholder API key patterns
 */
function isExampleCredential(line) {
    const examplePatterns = [
        /your[-_]?api[-_]?key/i,
        /example[-_]?api[-_]?key/i,
        /sample[-_]?key/i,
        /replace[-_]?with/i,
        /placeholder/i,
        /your[-_]?secret/i,
        /example[-_]?secret/i,
        /<.*api.*key.*>/i, // <YOUR_API_KEY>
        /\[.*api.*key.*\]/i, // [API_KEY]
        /xxx+/i,
        /demo[-_]?key/i,
        /test[-_]?key/i,
        /fake[-_]?key/i,
        /mock[-_]?key/i,
    ];
    return examplePatterns.some(p => p.test(line));
}
const BYOK_PATTERNS = [
    // OpenAI API key patterns
    {
        name: 'User-provided OpenAI API key',
        pattern: /(?:openai|api).*key.*(?:req\.|body\.|params\.|input\.|user)|(?:req\.|body\.|params\.).*(?:openai|api).*key/gi,
        keyType: 'openai',
        description: 'User-provided OpenAI API key detected',
    },
    {
        name: 'OpenAI key from request',
        pattern: /new\s+OpenAI\s*\(\s*\{[^}]*apiKey\s*:\s*(?:req|body|params|input)/gi,
        keyType: 'openai',
        description: 'OpenAI client initialized with user-provided key',
    },
    // Anthropic API key patterns
    {
        name: 'User-provided Anthropic API key',
        pattern: /(?:anthropic|claude).*key.*(?:req\.|body\.|params\.|input\.|user)|(?:req\.|body\.|params\.).*(?:anthropic|claude).*key/gi,
        keyType: 'anthropic',
        description: 'User-provided Anthropic API key detected',
    },
    {
        name: 'Anthropic key from request',
        pattern: /new\s+Anthropic\s*\(\s*\{[^}]*apiKey\s*:\s*(?:req|body|params|input)/gi,
        keyType: 'anthropic',
        description: 'Anthropic client initialized with user-provided key',
    },
    // Generic AI key patterns
    {
        name: 'User-provided AI API key',
        pattern: /(?:ai|llm|model).*(?:api)?.*key.*(?:req\.|body\.|params\.|input\.)/gi,
        keyType: 'generic_ai',
        description: 'User-provided AI API key detected',
    },
    // Generic key patterns (more cautious)
    {
        name: 'API key from user input',
        pattern: /apiKey\s*[=:]\s*(?:req|body|params|input)\./gi,
        keyType: 'generic',
        description: 'API key received from user input',
    },
];
/**
 * Check if file has explicit authentication checks
 */
function hasExplicitAuthCheck(content) {
    const authPatterns = [
        // Common auth check functions
        /getCurrentUser(Id)?\s*\(/i,
        /getSession\s*\(/i,
        /getServerSession\s*\(/i,
        /auth\s*\(\s*\)/i,
        /requireAuth\s*\(/i,
        /verifyToken\s*\(/i,
        /isAuthenticated/i,
        /checkAuth\s*\(/i,
        /withAuth\s*\(/i,
        // Request user checks
        /req\.user|request\.user/i,
        /context\.user/i,
        // Supabase auth
        /supabase\.auth\.getUser/i,
        /supabase\.auth\.getSession/i,
        // NextAuth
        /getServerSession\s*\(/i,
        /useSession\s*\(/i,
        // Clerk
        /currentUser\s*\(/i,
        /auth\(\)\.protect/i,
    ];
    return authPatterns.some(p => p.test(content));
}
/**
 * Check if key appears to be stored to database
 */
function isKeyStoredToDatabase(content, lineNumber) {
    const lines = content.split('\n');
    const startLine = Math.max(0, lineNumber - 5);
    const endLine = Math.min(lines.length, lineNumber + 15);
    const context = lines.slice(startLine, endLine).join('\n');
    // Patterns indicating database storage operations
    // NOTE: We use specific patterns to avoid false positives with API calls like
    // openai.chat.completions.create() or anthropic.messages.create()
    const storagePatterns = [
        /\.insert\s*\(/i,
        /\.update\s*\(/i,
        /\.upsert\s*\(/i,
        /\.save\s*\(/i,
        /INSERT\s+INTO/i,
        /UPDATE\s+.*SET/i,
        // ORM-specific patterns - must have table/model context
        /prisma\.\w+\.create\s*\(/i,
        /supabase\.from\(.*\)\.(?:insert|update|upsert)/i,
        /db\.\w+\.create\s*\(/i,
        /knex\(.*\)\.insert/i,
        /\.collection\(.*\)\.(?:insertOne|updateOne|save)/i, // MongoDB
        /sequelize\..*\.create\s*\(/i,
        // localStorage/sessionStorage (client-side storage)
        /localStorage\.setItem/i,
        /sessionStorage\.setItem/i,
    ];
    // Exclude AI SDK patterns that use .create() for API calls, not storage
    const aiApiPatterns = [
        /openai\..*\.create/i,
        /anthropic\..*\.create/i,
        /\.chat\.completions\.create/i,
        /\.messages\.create/i,
        /\.completions\.create/i,
        /\.embeddings\.create/i,
    ];
    const hasStoragePattern = storagePatterns.some(p => p.test(context));
    const hasAiApiPattern = aiApiPatterns.some(p => p.test(context));
    // Only consider it storage if we have storage patterns but NOT AI API patterns
    // If both are present, check if storage patterns are separate from AI patterns
    if (hasStoragePattern && hasAiApiPattern) {
        // Check if there's a clear database storage operation separate from AI calls
        // Look for keywords like 'user', 'key', 'credential' near storage patterns
        return /(?:insert|update|upsert|save).*(?:apiKey|api_key|key|credential)/i.test(context) ||
            /(?:apiKey|api_key|key|credential).*(?:insert|update|upsert|save)/i.test(context);
    }
    return hasStoragePattern;
}
/**
 * Check if key is only used transiently (same request, not stored)
 */
function isTransientKeyUsage(content, lineNumber) {
    const lines = content.split('\n');
    const startLine = Math.max(0, lineNumber - 3);
    const endLine = Math.min(lines.length, lineNumber + 20);
    const context = lines.slice(startLine, endLine).join('\n');
    // Transient usage patterns - key used immediately then discarded
    const transientPatterns = [
        /new\s+(?:OpenAI|Anthropic)\s*\(/i, // Immediate client creation
        /\.create\s*\(\s*\{[^}]*messages/i, // Immediate API call
        /\.completions\.create/i,
        /\.messages\.create/i,
        /await\s+(?:openai|anthropic|client)\./i,
    ];
    // If we see API calls but no storage, it's transient
    const hasApiCalls = transientPatterns.some(p => p.test(context));
    const hasStorage = isKeyStoredToDatabase(content, lineNumber);
    return hasApiCalls && !hasStorage;
}
/**
 * Check if key appears to be logged (bad practice)
 */
function isKeyLogged(content, lineNumber) {
    const lines = content.split('\n');
    const startLine = Math.max(0, lineNumber - 3);
    const endLine = Math.min(lines.length, lineNumber + 10);
    const context = lines.slice(startLine, endLine).join('\n');
    const loggingPatterns = [
        /console\.(log|info|debug|warn|error)\s*\([^)]*(?:apiKey|api_key|key)/i,
        /logger\.(log|info|debug|warn|error)\s*\([^)]*(?:apiKey|api_key|key)/i,
        /\.(log|info|debug|warn|error)\s*\(.*(?:apiKey|api_key|key)/i,
    ];
    return loggingPatterns.some(p => p.test(context));
}
/**
 * Detect BYOK patterns and classify their risk level
 */
function detectBYOKPatterns(content, filePath, middlewareConfig, options) {
    const vulnerabilities = [];
    // Skip scanner/fixture files to avoid self-detection
    if ((0, file_classifier_1.isScannerOrFixtureFile)(filePath))
        return vulnerabilities;
    const lines = options?.parsed?.lines ?? content.split('\n');
    const isTestFile = (0, file_classifier_1.isTestOrMockFile)(filePath);
    // Skip example/demo files entirely - they contain placeholder credentials by design
    if ((0, file_classifier_1.isExampleFile)(filePath)) {
        return vulnerabilities;
    }
    // Check route protection
    const routePath = (0, middleware_detector_1.getRoutePathFromFile)(filePath);
    const middlewareProtection = routePath && middlewareConfig
        ? (0, middleware_detector_1.isRouteProtectedByMiddleware)(routePath, middlewareConfig)
        : { isProtected: false, reason: '' };
    // Check for user scoping patterns in the file
    const userScoping = (0, middleware_detector_1.detectUserScopingPatterns)(content);
    // Check for explicit auth in file content (more reliable than middleware detection)
    const hasExplicitAuth = hasExplicitAuthCheck(content);
    lines.forEach((line, index) => {
        if ((0, file_classifier_1.isComment)(line))
            return;
        // Skip lines with example/placeholder credentials
        if (isExampleCredential(line) || (0, file_classifier_1.isPlaceholderValue)('', line)) {
            return;
        }
        for (const pattern of BYOK_PATTERNS) {
            const regex = new RegExp(pattern.pattern.source, pattern.pattern.flags);
            if (regex.test(line)) {
                // Determine context
                const isAuthenticated = middlewareProtection.isProtected || hasExplicitAuth;
                const isUserScoped = userScoping.hasUserScoping;
                const isStoredCentrally = isKeyStoredToDatabase(content, index);
                const isTransient = isTransientKeyUsage(content, index);
                // Determine severity based on context
                let severity;
                let description;
                let suggestedFix;
                let baseConfidence;
                if (isAuthenticated && isTransient) {
                    // Authenticated and transient - this is the IDEAL BYOK pattern
                    // Check if key is being logged (bad practice)
                    const keyLogged = isKeyLogged(content, index);
                    if (keyLogged) {
                        severity = 'low';
                        description = `BYOK feature detected: ${pattern.description}. Keys are used transiently (good!) but appear to be logged (avoid logging API keys, even in debug).`;
                        suggestedFix = 'Remove logging of API keys. Best practices: (1) Validate API key format, (2) Add per-user rate limiting.';
                        baseConfidence = BASE_CONFIDENCE_STORAGE;
                    }
                    else {
                        // IDEAL PATTERN: Authenticated + transient + no logging = no issue
                        // Skip emitting a finding entirely for the ideal case
                        continue;
                    }
                }
                else if (!isAuthenticated && isTransient) {
                    // Unauthenticated but transient - still lower risk than storage
                    severity = 'low';
                    description = `BYOK feature detected: ${pattern.description}. Keys are used transiently (not stored). Consider adding authentication or rate limiting.`;
                    suggestedFix = 'Consider adding authentication. If intentionally public: add rate limiting, key format validation, and usage tracking.';
                    baseConfidence = BASE_CONFIDENCE_TRANSIENT;
                }
                else if (!isAuthenticated && isStoredCentrally) {
                    // Unauthenticated AND storing keys - this is the real risk
                    severity = 'medium';
                    description = `${pattern.description}. This endpoint appears to lack authentication AND stores keys. This could allow unauthorized key storage.`;
                    suggestedFix = 'Add authentication. Ensure stored keys are scoped by user_id with proper access controls.';
                    baseConfidence = BASE_CONFIDENCE_STORAGE;
                }
                else if (isStoredCentrally && !isUserScoped) {
                    // Authenticated but keys stored without user scoping - medium risk
                    severity = 'medium';
                    description = `${pattern.description}. Keys appear to be stored centrally without user-scoping, which could lead to cross-tenant key access.`;
                    suggestedFix = 'Ensure stored keys are scoped by user_id. Add proper access controls to prevent users from accessing other users\' keys.';
                    baseConfidence = BASE_CONFIDENCE_STORAGE;
                }
                else if (isAuthenticated && isUserScoped) {
                    // Authenticated and user-scoped with storage - generally okay
                    severity = 'info';
                    description = `${pattern.description}. Route is authenticated and operations appear user-scoped. If keys are stored, consider encryption at rest.`;
                    suggestedFix = 'If storing keys: consider encryption at rest. Add rate limiting to prevent cost abuse.';
                    baseConfidence = BASE_CONFIDENCE_STORAGE;
                }
                else {
                    // Authenticated but unclear scoping - needs review, but low priority
                    severity = 'info';
                    description = `${pattern.description}. Route is authenticated. This appears to be a BYOK feature.`;
                    suggestedFix = 'Verify user-scoping for stored keys. Add rate limiting for cost control.';
                    baseConfidence = BASE_CONFIDENCE_TRANSIENT;
                }
                // Downgrade test files
                if (isTestFile) {
                    severity = 'info';
                    description = `${description} (in test file)`;
                }
                vulnerabilities.push({
                    id: `byok-${filePath}-${index + 1}-${pattern.name}`,
                    filePath,
                    lineNumber: index + 1,
                    lineContent: line.trim(),
                    severity,
                    category: 'ai_pattern',
                    title: `BYOK: ${pattern.name}`,
                    description,
                    suggestedFix,
                    confidence: isTestFile ? 'low' : 'medium',
                    layer: 2,
                    source: 'ai_code',
                    baseConfidence,
                });
                break; // One finding per line
            }
        }
    });
    return vulnerabilities;
}
//# sourceMappingURL=byok-patterns.js.map