"use strict";
/**
 * Layer 2: AI Schema/Tooling Validation Detection
 * Detects missing or weak validation of AI-generated structured outputs
 *
 * Covers:
 * - M5.3: Schema/tooling mismatch
 * - Unvalidated JSON parsing of AI outputs
 * - Weak schema patterns (any, permissive)
 * - Tool invocation parameters not validated
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.detectAISchemaValidation = detectAISchemaValidation;
exports.isAIContextFile = isAIContextFile;
exports.hasSchemaValidation = hasSchemaValidation;
const file_classifier_1 = require("../../parse/file-classifier");
const BASE_CONFIDENCE = 0.40;
// ============================================================================
// Context Detection
// ============================================================================
/**
 * Check if file is in an AI/LLM context
 */
function isAIContextFile(filePath, content) {
    // File path patterns
    const aiPathPatterns = [
        /\/(ai|llm|chat|openai|anthropic|agents?|tools?)\//i,
        /(ai|llm|chat|agent|tool|function).*\.(ts|js|tsx|jsx|py)$/i,
    ];
    if (aiPathPatterns.some(p => p.test(filePath))) {
        return true;
    }
    // Content patterns
    const aiContentPatterns = [
        /openai\.|anthropic\.|ChatCompletion|MessageCreate/i,
        /\.chat\.completions?\.create/i,
        /\.messages\.create/i,
        /generateText|streamText|generateObject/i,
        /tool_calls?|function_call|functionCall/i,
        /from\s+['"](?:openai|@anthropic-ai|langchain)/i,
    ];
    return aiContentPatterns.some(p => p.test(content));
}
/**
 * Check if there's schema validation in the context
 */
function hasSchemaValidation(context) {
    const validationPatterns = [
        // Zod
        /z\.(?:parse|safeParse|parseAsync)\s*\(/i,
        /\.parse\s*\([^)]*response/i,
        /zodSchema|z\.object/i,
        // Ajv
        /ajv\.(?:compile|validate)/i,
        /\.validate\s*\([^)]*schema/i,
        // Joi
        /joi\.(?:object|string|number).*\.validate/i,
        /\.validate\s*\([^)]*joi/i,
        // Yup
        /yup\.(?:object|string|number).*\.validate/i,
        // TypeBox
        /Type\.(?:Object|String|Number)/i,
        /Value\.(?:Check|Decode)/i,
        // Generic validation
        /validateSchema|schemaValidator/i,
        /JSON\.parse.*try.*catch.*schema/i,
        // OpenAI Structured Outputs
        /response_format.*json_schema/i,
        /json_schema\s*:/i,
    ];
    return validationPatterns.some(p => p.test(context));
}
/**
 * Check if there's a security-sensitive sink after parsing
 */
function hasSecuritySink(context) {
    const sinkPatterns = [
        // Execution sinks
        /eval\s*\(/i,
        /Function\s*\(/i,
        /exec\s*\(|spawn\s*\(/i,
        /child_process/i,
        // Database sinks
        /\.query\s*\(/i,
        /\.execute\s*\(/i,
        /\.raw\s*\(/i,
        /\$\{.*\}.*(?:SELECT|INSERT|UPDATE|DELETE)/i,
        // File system sinks
        /fs\.(?:readFile|writeFile|unlink|mkdir)/i,
        /readFileSync|writeFileSync/i,
        // Network sinks
        /fetch\s*\(|axios\.|request\s*\(/i,
        // DOM sinks (if applicable)
        /innerHTML|dangerouslySetInnerHTML/i,
    ];
    return sinkPatterns.some(p => p.test(context));
}
/**
 * Check if there's try-catch around the parsing
 */
function hasTryCatch(content, lineIndex) {
    const lines = content.split('\n');
    const start = Math.max(0, lineIndex - 15);
    const end = Math.min(lines.length, lineIndex + 5);
    const context = lines.slice(start, end).join('\n');
    // Simple check for try block wrapping
    const tryPattern = /try\s*\{[^}]*$/;
    const beforeContext = lines.slice(start, lineIndex).join('\n');
    return tryPattern.test(beforeContext);
}
/**
 * Get surrounding context
 */
function getSurroundingContext(content, lineIndex, windowSize = 20) {
    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');
}
/**
 * Unvalidated AI output parsing patterns
 */
const UNVALIDATED_PARSING_PATTERNS = [
    // JSON.parse on AI response content
    {
        name: 'AI response content parsed without validation',
        pattern: /JSON\.parse\s*\(\s*(?:response|result|completion|output|message)(?:\.\w+)*(?:\.content|\.text|\.message)?/gi,
        riskType: 'unvalidated_parse',
        baseSeverity: 'medium',
        description: 'AI-generated JSON parsed without schema validation. Model may return malformed or malicious structures.',
        suggestedFix: 'Validate with schema: const data = schema.parse(JSON.parse(response.content)) // using zod',
    },
    // OpenAI choices parsing
    {
        name: 'OpenAI response parsed directly',
        pattern: /JSON\.parse\s*\(\s*(?:\w+\.)?choices\[\d+\]\.message\.content/gi,
        riskType: 'unvalidated_parse',
        baseSeverity: 'medium',
        description: 'OpenAI response content parsed without validation. AI output structure should be verified.',
        suggestedFix: 'Use OpenAI Structured Outputs (response_format: { type: "json_schema", json_schema: {...} }) or validate with zod.',
    },
    // Anthropic content parsing
    {
        name: 'Anthropic response parsed directly',
        pattern: /JSON\.parse\s*\(\s*(?:\w+\.)?content\[0\]\.text/gi,
        riskType: 'unvalidated_parse',
        baseSeverity: 'medium',
        description: 'Anthropic response parsed without validation. AI output may not match expected schema.',
        suggestedFix: 'Validate response structure with zod or similar schema validation library.',
    },
    // Tool call arguments parsing
    {
        name: 'Tool call arguments parsed without validation',
        pattern: /JSON\.parse\s*\(\s*(?:\w+\.)?(?:tool_calls?|function_call)(?:\[\d+\])?\.(?:function\.)?arguments/gi,
        riskType: 'unvalidated_parse',
        baseSeverity: 'medium',
        description: 'Function calling arguments parsed without schema validation. Tool parameters should be verified.',
        suggestedFix: 'Define and validate tool argument schema: const args = toolSchema.parse(JSON.parse(toolCall.function.arguments))',
    },
    // Generic AI output parsing
    {
        name: 'AI output assigned without parsing/validation',
        pattern: /(?:const|let|var)\s+\w+\s*=\s*(?:response|completion|result)\.(?:content|text|data)\s*;?\s*(?!.*(?:parse|validate|schema))/gi,
        riskType: 'unvalidated_parse',
        baseSeverity: 'low',
        description: 'AI output assigned directly to variable. If used as structured data, validate first.',
        suggestedFix: 'If expecting JSON: const data = schema.safeParse(JSON.parse(response.content))',
    },
];
/**
 * Weak schema patterns
 */
const WEAK_SCHEMA_PATTERNS = [
    // any type for AI response
    {
        name: 'AI response typed as any',
        pattern: /(?:response|aiResponse|completion|result)\s*:\s*any\b/gi,
        riskType: 'weak_schema',
        baseSeverity: 'low',
        description: 'AI response typed as `any`. TypeScript cannot catch type errors at compile time.',
        suggestedFix: 'Define explicit interface and use runtime validation: interface AIResponse { ... }; const data: AIResponse = schema.parse(response)',
    },
    // Permissive zod schemas
    {
        name: 'Permissive zod schema for AI output',
        pattern: /z\.(?:any|unknown)\s*\(\s*\).*(?:response|output|completion)/gi,
        riskType: 'weak_schema',
        baseSeverity: 'low',
        description: 'Using z.any() or z.unknown() defeats the purpose of schema validation.',
        suggestedFix: 'Define specific schema: z.object({ field: z.string(), ... }).strict()',
    },
    // Passthrough schemas
    {
        name: 'Passthrough schema allowing extra properties',
        pattern: /z\.object\s*\([^)]+\)\.passthrough\s*\(\s*\)/gi,
        riskType: 'weak_schema',
        baseSeverity: 'info',
        description: 'Schema uses passthrough() allowing unexpected properties from AI output.',
        suggestedFix: 'Use .strict() instead to reject unexpected properties from AI responses.',
    },
    // Record<string, any>
    {
        name: 'Record<string, any> for AI data',
        pattern: /Record<string,\s*any>\s*.*(?:response|result|output|completion|aiData)/gi,
        riskType: 'weak_schema',
        baseSeverity: 'low',
        description: 'Generic Record<string, any> type used for AI output provides no type safety.',
        suggestedFix: 'Define specific interface or use zod schema for runtime validation.',
    },
    // Object type without specifics
    {
        name: 'Generic object type for AI response',
        pattern: /(?:response|completion|aiOutput)\s*:\s*(?:object|Object|{})\b/gi,
        riskType: 'weak_schema',
        baseSeverity: 'info',
        description: 'Generic object type for AI response. Consider defining specific interface.',
        suggestedFix: 'Replace with specific interface that documents expected AI output structure.',
    },
];
/**
 * Tool parameter validation issues
 */
const TOOL_PARAM_PATTERNS = [
    // Tool parameters used directly in paths
    {
        name: 'Tool parameter used in file path',
        pattern: /(?:tool|args|parameters)(?:\.\w+)*\.(?:path|filename|filepath|file).*(?:readFile|writeFile|fs\.|path\.)/gi,
        riskType: 'tool_params',
        baseSeverity: 'high',
        description: 'AI-generated file path used without validation. Path traversal attack possible.',
        suggestedFix: 'Validate path against allowlist: if (!allowedPaths.some(p => resolvedPath.startsWith(p))) throw new Error("Invalid path")',
    },
    // Tool parameters in commands
    {
        name: 'Tool parameter used in shell command',
        pattern: /(?:tool|args|parameters)(?:\.\w+)*\.(?:command|cmd|script).*(?:exec|spawn|shell)/gi,
        riskType: 'tool_params',
        baseSeverity: 'critical',
        description: 'AI-generated command executed without validation. Command injection possible.',
        suggestedFix: 'Use allowlist for permitted commands. Never execute arbitrary AI-generated commands.',
    },
    // Tool parameters in URLs
    {
        name: 'Tool parameter used in URL',
        pattern: /(?:tool|args|parameters)(?:\.\w+)*\.(?:url|endpoint|host).*(?:fetch|axios|request|http)/gi,
        riskType: 'tool_params',
        baseSeverity: 'high',
        description: 'AI-generated URL used without validation. SSRF attack possible.',
        suggestedFix: 'Validate URL against allowlist of permitted hosts before making request.',
    },
    // Tool parameters in database queries
    {
        name: 'Tool parameter used in database query',
        pattern: /(?:tool|args|parameters)(?:\.\w+)*\.(?:query|sql|table|column).*(?:\.query|\.execute|\.raw)/gi,
        riskType: 'tool_params',
        baseSeverity: 'high',
        description: 'AI-generated value used in database query. SQL injection possible.',
        suggestedFix: 'Use parameterized queries. Validate table/column names against allowlist.',
    },
    // Function call name used in switch/if
    {
        name: 'Tool/function call routed without validation',
        pattern: /(?:tool_call|function_call|functionCall)\.(?:name|function\.name).*(?:switch|if|\[.*\])/gi,
        riskType: 'tool_params',
        baseSeverity: 'medium',
        description: 'AI-selected function name used for routing. Validate against allowed functions.',
        suggestedFix: 'Check function name against allowlist: const allowedFunctions = ["search", "calculate"]; if (!allowedFunctions.includes(name)) throw',
    },
];
// ============================================================================
// Main Detection Function
// ============================================================================
/**
 * Main detection function for AI schema/tooling validation issues
 */
function detectAISchemaValidation(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 files in AI context
    if (!isAIContextFile(filePath, content)) {
        return vulnerabilities;
    }
    const lines = options?.parsed?.lines ?? content.split('\n');
    const isTestFile = (0, file_classifier_1.isTestOrMockFile)(filePath);
    // Check file-level validation presence
    const fileHasValidation = hasSchemaValidation(content);
    // Process all pattern categories
    const allPatterns = [
        ...UNVALIDATED_PARSING_PATTERNS,
        ...WEAK_SCHEMA_PATTERNS,
        ...TOOL_PARAM_PATTERNS,
    ];
    for (const pattern of allPatterns) {
        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, 20);
            // Check for local validation
            const localHasValidation = hasSchemaValidation(context);
            const localHasTryCatch = hasTryCatch(content, lineNumber - 1);
            const localHasSecuritySink = hasSecuritySink(context);
            // Calculate severity based on context
            let severity = pattern.baseSeverity;
            const notes = [];
            // Apply context-aware adjustments
            if (pattern.riskType === 'unvalidated_parse') {
                if (localHasValidation || fileHasValidation) {
                    severity = 'info';
                    notes.push('Schema validation detected nearby');
                }
                else if (localHasSecuritySink) {
                    // Elevate if used in security-sensitive context
                    if (severity === 'medium')
                        severity = 'high';
                    notes.push('Used in security-sensitive context');
                }
                else if (localHasTryCatch && !localHasSecuritySink) {
                    // Try-catch without sink is minor
                    severity = 'low';
                    notes.push('Has try-catch but no schema validation');
                }
            }
            if (pattern.riskType === 'weak_schema') {
                // Weak schemas at API boundaries are more concerning
                if (/export|handler|route|api/i.test(context)) {
                    if (severity === 'low')
                        severity = 'medium';
                    notes.push('At API boundary');
                }
            }
            if (pattern.riskType === 'tool_params') {
                // Already high/critical, check for mitigation
                if (localHasValidation) {
                    severity = severity === 'critical' ? 'medium' : 'low';
                    notes.push('Validation detected');
                }
            }
            // Downgrade test files
            if (isTestFile) {
                severity = 'info';
                notes.push('in test file');
            }
            // Build final description
            let description = pattern.description;
            if (notes.length > 0) {
                description += ` (${notes.join('; ')})`;
            }
            vulnerabilities.push({
                id: `ai-schema-${filePath}-${lineNumber}-${pattern.name.replace(/\s+/g, '-')}`,
                filePath,
                lineNumber,
                lineContent,
                severity,
                category: 'ai_schema_mismatch',
                title: pattern.name,
                description,
                suggestedFix: pattern.suggestedFix,
                confidence: severity === 'info' ? 'low' : 'medium',
                layer: 2,
                source: 'ai_code',
                requiresAIValidation: true, // Tier B - always validate with AI
                baseConfidence: BASE_CONFIDENCE,
            });
        }
    }
    return vulnerabilities;
}
//# sourceMappingURL=schema-validation.js.map