"use strict";
/**
 * Layer 2: MCP (Model Context Protocol) Security Detection
 * Detects security issues in MCP tool implementations
 *
 * Background: MCP enables AI agents to call external tools. Security risks include:
 * - Tool Poisoning: External content returned without validation (CVE-2025-6514)
 * - Credential Issues: Credentials in tool parameters/responses
 * - Confused Deputy: Operations without proper user context
 *
 * Reference: https://modelcontextprotocol.io, 13,000+ MCP servers deployed
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.detectMCPSecurity = detectMCPSecurity;
const context_helpers_1 = require("../utils/context-helpers");
// ============================================================================
// Context Detection
// ============================================================================
/**
 * Check if file is an MCP server/tool file based on imports and patterns
 */
function isMCPFile(content, filePath) {
    // Import patterns for MCP SDK
    const mcpImportPatterns = [
        /@modelcontextprotocol\/sdk/i,
        /from\s+['"]mcp['"]/i,
        /from\s+['"]@mcp\//i,
        /McpServer/i,
        /mcp\.server/i,
        /server\.tool\s*\(/i,
        /@server\.tool/i,
    ];
    if (mcpImportPatterns.some(p => p.test(content))) {
        return true;
    }
    // Path patterns
    const mcpPathPatterns = [
        /\/mcp\//i,
        /mcp[-_]?server/i,
        /mcp[-_]?tools?/i,
    ];
    return mcpPathPatterns.some(p => p.test(filePath));
}
/**
 * Check if line/context has content sanitization
 */
function hasContentSanitization(context) {
    const sanitizationPatterns = [
        /sanitize|DOMPurify|purify/i,
        /escapeHtml|escape_html|html\.escape/i,
        /strip(?:Tags|Html|Scripts)/i,
        /validate(?:Content|Input|Schema)/i,
        /zod\.parse|schema\.parse|safeParse/i,
        /filterHtml|cleanHtml/i,
        /ALLOWED_TAGS/i,
        // Safe return patterns - returning only safe fields
        /\.map\s*\([^)]*\{\s*id|title|name|summary\s*:/i,
        // Static content patterns
        /loadStaticDocs|staticContent|publicData/i,
        // Pure computation
        /mathjs\.evaluate|calculate/i,
    ];
    return sanitizationPatterns.some(p => p.test(context));
}
/**
 * Check if the return is for a safe/static data source
 */
function isSafeDataSource(context) {
    const safePatterns = [
        // Static/public data
        /(?:static|public)(?:Data|Docs|Content)/i,
        // Mathematical operations
        /mathjs|calculate|compute/i,
        // Internal API with server-side auth
        /process\.env\.INTERNAL|SERVER_SIDE/i,
        // User's own data explicitly
        /findByUser|getByUser|user\.(?:files|documents|records)/i,
        // Returns only safe fields like id, name, title
        /return\s*\{[^}]*:\s*\{[^}]*(?:only|safe|id|title|name)[^}]*\}/i,
    ];
    return safePatterns.some(p => p.test(context));
}
/**
 * Check if tool has user context access
 */
function hasUserContext(context) {
    const userContextPatterns = [
        /context\.user/i,
        /context\.userId/i,
        /context\.session/i,
        /context\.auth/i,
        /getCurrentUser/i,
        /request\.user/i,
        /req\.user/i,
        /user\.id/i,
        /userId/i,
        /session\.user/i,
        /auth\(\)/i,
        /tenantId/i,
        /tenant\.id/i,
        /orgId/i,
    ];
    return userContextPatterns.some(p => p.test(context));
}
/**
 * Check if there's an authorization check in context
 */
function hasAuthorizationCheck(context) {
    const authCheckPatterns = [
        /if\s*\([^)]*\.ownerId\s*[!=]==?\s*/i,
        /if\s*\([^)]*userId\s*[!=]==?\s*/i,
        /if\s*\([^)]*tenantId\s*[!=]==?\s*/i,
        /Not\s*authorized/i,
        /Forbidden/i,
        /checkPermission/i,
        /checkAccess/i,
        /canAccess/i,
        /hasPermission/i,
        /isAuthorized/i,
        /throw.*Error.*auth/i,
    ];
    return authCheckPatterns.some(p => p.test(context));
}
/**
 * Get surrounding context for analysis
 */
function getSurroundingContext(content, lineIndex, windowSize = 30) {
    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');
}
/**
 * Tool Poisoning Patterns
 * Detect tools that return external content without validation
 */
const TOOL_POISONING_PATTERNS = [
    // Raw HTTP response content (JS and Python)
    {
        name: 'Raw HTTP response in tool',
        pattern: /(?:return|=>)\s*[{(]\s*[{"]?[^}]*(?:content|body|text|html)['"]\s*[:=]\s*(?:await\s+)?(?:response|res)\.(?:text|json|body)/gi,
        category: 'tool_poisoning',
        baseSeverity: 'high',
        description: 'MCP tool returns raw HTTP response content without sanitization. External content could contain prompt injection payloads.',
        suggestedFix: 'Sanitize external content before returning: return { content: sanitize(response.text()) }',
    },
    // Raw fetch result
    {
        name: 'Fetch result returned directly',
        pattern: /return\s*[{(]\s*[{"]?[^}]*[:=]\s*await\s+fetch\([^)]+\)\.(?:text|json)\(\)/gi,
        category: 'tool_poisoning',
        baseSeverity: 'high',
        description: 'Fetch result returned directly in tool response. Content may contain malicious instructions.',
        suggestedFix: 'Validate and sanitize fetch results before including in response.',
    },
    // Database query results (JS)
    {
        name: 'Raw database content in response',
        pattern: /return\s*\{[^}]*(?:data|results?|rows|documents?|items?)\s*:\s*(?:await\s+)?(?:db|database|client|collection|query)\.(?:query|find|search|execute)/gi,
        category: 'tool_poisoning',
        baseSeverity: 'medium',
        description: 'Database query results returned without filtering. Stored content could be poisoned.',
        suggestedFix: 'Validate and sanitize database content. Consider returning only safe fields.',
    },
    // Database query results (Python)
    {
        name: 'Raw database content in Python response',
        pattern: /return\s*\{[^}]*["'](?:data|results?|documents?)["']\s*:\s*(?:await\s+)?(?:db|database|results)[\.\[]/gi,
        category: 'tool_poisoning',
        baseSeverity: 'medium',
        description: 'Database query results returned without filtering in Python MCP tool.',
        suggestedFix: 'Validate and sanitize database content. Consider returning only safe fields.',
    },
    // File content
    {
        name: 'File content returned without validation',
        pattern: /return\s*[{(]\s*[{"]?[^}]*content['"]\s*[:=]\s*(?:await\s+)?(?:fs|file|readFile|readFileSync)/gi,
        category: 'tool_poisoning',
        baseSeverity: 'high',
        description: 'File content returned without validation. Files could contain malicious instructions.',
        suggestedFix: 'Validate file content and type. Sanitize before returning to the model.',
    },
    // Email content
    {
        name: 'Email content in response',
        pattern: /return\s*[{(]\s*[{"]?[^}]*(?:body|content|text)['"]\s*[:=]\s*(?:email|message|mail)\.(?:body|content|text|html)/gi,
        category: 'tool_poisoning',
        baseSeverity: 'high',
        description: 'Email content returned to model. Emails are common vectors for prompt injection.',
        suggestedFix: 'Sanitize email content. Strip HTML, scripts, and instruction-like patterns.',
    },
    // RSS/feed content
    {
        name: 'RSS/feed content in response',
        pattern: /return\s*[{(]\s*[{"]?[^}]*(?:items?|entries?|feed)['"]\s*[:=]\s*(?:feed|rss|parser)\.(?:items?|entries?|parse)/gi,
        category: 'tool_poisoning',
        baseSeverity: 'medium',
        description: 'RSS/feed content returned without filtering. Feed titles and descriptions could be poisoned.',
        suggestedFix: 'Sanitize feed content. Filter to safe fields only (id, title summary).',
    },
    // Generic raw content return (JS)
    {
        name: 'Raw content in tool response',
        pattern: /server\.tool\s*\([^)]+,\s*async[^{]+\{[^}]*return\s*\{[^}]*:\s*(?:await\s+)?response\.text\(\)/gi,
        category: 'tool_poisoning',
        baseSeverity: 'high',
        description: 'MCP tool returns raw text content from external source.',
        suggestedFix: 'Add content sanitization layer before returning external content.',
    },
    // Python httpx response text
    {
        name: 'Raw HTTP response in Python tool',
        pattern: /return\s*\{[^}]*["']content["']\s*:\s*(?:await\s+)?response\.text/gi,
        category: 'tool_poisoning',
        baseSeverity: 'high',
        description: 'Python MCP tool returns raw HTTP response content.',
        suggestedFix: 'Sanitize external content before returning to the model.',
    },
    // Variable-based: HTTP response assigned then returned
    {
        name: 'HTTP response variable in MCP tool',
        pattern: /(?:const|let|var)\s+\w+\s*=\s*(?:await\s+)?response\.text\(\)[^}]+return\s*\{[^}]*content/gis,
        category: 'tool_poisoning',
        baseSeverity: 'high',
        description: 'HTTP response text stored in variable and returned. External content could be poisoned.',
        suggestedFix: 'Sanitize the content before returning: const sanitized = sanitize(html)',
    },
    // Variable-based: File read assigned then returned
    {
        name: 'File read variable in MCP tool',
        pattern: /(?:const|let|var)\s+\w+\s*=\s*(?:await\s+)?(?:fs\.readFile|readFile)[^}]+return\s*\{[^}]*content/gis,
        category: 'tool_poisoning',
        baseSeverity: 'high',
        description: 'File content stored in variable and returned. File content could contain malicious instructions.',
        suggestedFix: 'Validate and sanitize file content before returning.',
    },
    // Database query result in return (shorthand property)
    {
        name: 'Database query in MCP return',
        pattern: /(?:const|let|var)\s+(?:results?|data|rows)\s*=\s*(?:await\s+)?(?:db|database|client)\.(?:query|find|search)[^}]+return\s*\{[^}]*(?:data|results?|rows)/gis,
        category: 'tool_poisoning',
        baseSeverity: 'medium',
        description: 'Database query results returned in MCP tool. Stored content could be poisoned.',
        suggestedFix: 'Validate and sanitize database content before returning.',
    },
    // Email body returned
    {
        name: 'Email body in MCP return',
        pattern: /(?:email|message|mail)\s*=\s*(?:await)?[^}]+return\s*\{[^}]*body\s*:\s*(?:email|message|mail)\.body/gis,
        category: 'tool_poisoning',
        baseSeverity: 'high',
        description: 'Email body content returned in MCP tool. Emails are common prompt injection vectors.',
        suggestedFix: 'Sanitize email content. Strip HTML and instruction-like patterns.',
    },
    // Feed/RSS items returned
    {
        name: 'RSS/feed items in MCP return',
        pattern: /(?:feed|rss)\s*=\s*(?:await)?[^}]+return\s*\{[^}]*items?\s*:\s*(?:feed|rss)\.items?/gis,
        category: 'tool_poisoning',
        baseSeverity: 'medium',
        description: 'RSS/feed items returned in MCP tool. Feed content could be poisoned.',
        suggestedFix: 'Sanitize feed content. Filter to safe fields only.',
    },
];
/**
 * Credential Issue Patterns
 * Detect credentials in tool parameters or responses
 */
const CREDENTIAL_PATTERNS = [
    // API key in parameter
    {
        name: 'API key in tool parameter',
        pattern: /server\.tool\s*\([^)]+,\s*async\s*\(\s*\{[^}]*(?:apiKey|api_key|token|secret|password|privateKey|private_key|accessToken|access_token|authToken|auth_token)/gi,
        category: 'credential_issue',
        baseSeverity: 'high',
        description: 'Tool accepts credentials as parameter. Credentials should not flow through the model.',
        suggestedFix: 'Use server-side credential storage. Remove credential parameter and use environment variables or secret manager.',
    },
    // Python decorator with credentials
    {
        name: 'Python tool with credential parameter',
        pattern: /@server\.tool[^)]*\)\s*(?:async\s+)?def\s+\w+\s*\([^)]*(?:api_key|token|secret|password|private_key|access_token|auth_token)/gi,
        category: 'credential_issue',
        baseSeverity: 'high',
        description: 'Python MCP tool accepts credentials as parameter.',
        suggestedFix: 'Use server-side credential management. Do not pass secrets through tool parameters.',
    },
    // Returning credentials in response
    {
        name: 'Credentials in tool response',
        pattern: /return\s*\{[^}]*(?:apiKey|api_key|token|password|secret|privateKey|private_key|accessToken|access_token|refreshToken|refresh_token|jwt)\s*:/gi,
        category: 'credential_issue',
        baseSeverity: 'critical',
        description: 'Tool response includes credentials. Exposing secrets to the model is dangerous.',
        suggestedFix: 'Never return credentials in tool responses. Return success status or user-safe identifiers only.',
    },
    // Connection string in parameter
    {
        name: 'Connection string in tool parameter',
        pattern: /server\.tool\s*\([^)]+,\s*async\s*\(\s*\{[^}]*(?:connectionString|connection_string|dsn|dbUrl|db_url|databaseUrl|database_url)/gi,
        category: 'credential_issue',
        baseSeverity: 'high',
        description: 'Database connection string passed as tool parameter. Connection strings contain credentials.',
        suggestedFix: 'Use server-side database configuration. Do not accept connection strings as parameters.',
    },
    // Environment secrets in response
    {
        name: 'Environment secrets in response',
        pattern: /return\s*\{[^}]*:\s*process\.env\.(?:.*(?:KEY|SECRET|TOKEN|PASSWORD|CREDENTIAL))/gi,
        category: 'credential_issue',
        baseSeverity: 'critical',
        description: 'Environment secrets returned in tool response.',
        suggestedFix: 'Never return environment secrets. Use them server-side only.',
    },
];
/**
 * Confused Deputy Patterns
 * Detect operations without proper user context
 */
const CONFUSED_DEPUTY_PATTERNS = [
    // Data operation without user context
    {
        name: 'Data deletion without user context',
        pattern: /server\.tool\s*\([^)]+,\s*async\s*\(\s*\{[^}]*(?:Id|id)\s*\}[^)]*\)\s*(?:=>|:)[^{]*\{[^}]*(?:\.delete|\.remove|\.destroy)\s*\(/gi,
        category: 'confused_deputy',
        baseSeverity: 'high',
        description: 'Tool deletes data using only an ID parameter without user context verification.',
        suggestedFix: 'Add user context parameter and verify ownership: if (record.ownerId !== context.user.id) throw new Error("Unauthorized")',
    },
    // Update operation without auth check
    {
        name: 'Data update without authorization',
        pattern: /server\.tool\s*\([^)]+,\s*async\s*\(\s*\{[^}]*(?:Id|id)[^}]*data[^}]*\}[^)]*\)[^{]*\{[^}]*(?:\.update|\.set|\.save)\s*\(/gi,
        category: 'confused_deputy',
        baseSeverity: 'high',
        description: 'Tool updates data without verifying the user owns the record.',
        suggestedFix: 'Validate user ownership before update. Add authorization check.',
    },
    // Reading user-specific data without context
    {
        name: 'User data access without context',
        pattern: /server\.tool\s*\([^)]+(?:user|file|record|document|message)[^)]+,\s*async\s*\(\s*\{[^}]*(?:Id|id)\s*\}/gi,
        category: 'confused_deputy',
        baseSeverity: 'medium',
        description: 'Tool accesses user-specific data with only an ID. Missing user context verification.',
        suggestedFix: 'Add user context and verify access permissions for the requested resource.',
    },
    // Admin/privileged operation without auth
    {
        name: 'Privileged operation without authorization',
        pattern: /server\.tool\s*\([^)]+(?:admin|grant|revoke|elevate|promote)[^)]*,\s*async/gi,
        category: 'confused_deputy',
        baseSeverity: 'critical',
        description: 'Privileged/admin tool without visible authorization check.',
        suggestedFix: 'Add strict authorization check. Verify caller has admin privileges before executing.',
    },
    // Send email/message as user
    {
        name: 'Send message without identity verification',
        pattern: /server\.tool\s*\([^)]+(?:send|email|message)[^)]+,\s*async\s*\(\s*\{[^}]*(?:from|sender)[^}]*\}/gi,
        category: 'confused_deputy',
        baseSeverity: 'high',
        description: 'Tool sends messages with a \'from\' parameter. Should use authenticated user identity.',
        suggestedFix: 'Use context.user for sender identity. Do not allow arbitrary \'from\' values.',
    },
    // Cross-tenant data access
    {
        name: 'Organization/tenant data without scope',
        pattern: /server\.tool\s*\([^)]+(?:org|organization|tenant|workspace)[^)]+,\s*async\s*\(\s*\{[^}]*(?:Id|id)\s*\}/gi,
        category: 'confused_deputy',
        baseSeverity: 'high',
        description: 'Tool accesses organization data by ID without tenant context verification.',
        suggestedFix: 'Verify tenant membership: if (org.id !== context.user.tenantId) throw new Error("Unauthorized")',
    },
    // Python tool without context
    {
        name: 'Python tool data operation without user',
        pattern: /@server\.tool[^)]*\)\s*(?:async\s+)?def\s+(?:delete|update|remove|create)_\w+\s*\(\s*(?:\w+_)?id\s*:/gi,
        category: 'confused_deputy',
        baseSeverity: 'medium',
        description: 'Python MCP tool performs data operation with only an ID parameter.',
        suggestedFix: 'Add user context parameter and validate authorization.',
    },
];
/**
 * Tool Description Injection Patterns
 * Detect prompt injection risks in MCP tool descriptions/metadata
 */
const DESCRIPTION_INJECTION_PATTERNS = [
    // Dynamic description from variable/input (JS template literals)
    {
        name: 'Dynamic tool description from variable',
        pattern: /description\s*:\s*[`'"].*\$\{.*(?:user|req|input|param|config).*\}.*[`'"]/gi,
        category: 'description_injection',
        baseSeverity: 'high',
        description: 'Tool description constructed from user input or external variables. Malicious content could manipulate AI behavior.',
        suggestedFix: 'Use static descriptions only. Never include user input in tool descriptions.',
    },
    // Description concatenated with user input
    {
        name: 'Tool description with user input concatenation',
        pattern: /description\s*:\s*(?:["'][^"']*["']\s*\+\s*)?(?:user|req|input|param|options)\./gi,
        category: 'description_injection',
        baseSeverity: 'high',
        description: 'Tool description concatenated with user-controlled values. Could inject prompt manipulation instructions.',
        suggestedFix: 'Use static descriptions. If dynamic content is needed, sanitize and validate strictly.',
    },
    // Injection keywords in tool descriptions
    {
        name: 'Injection keywords in tool description',
        pattern: /description\s*:\s*["'`][^"'`]*(?:ignore\s*(?:previous|above|all)|bypass|override|system\s*prompt|disregard|forget)[^"'`]*["'`]/gi,
        category: 'description_injection',
        baseSeverity: 'critical',
        description: 'Tool description contains prompt injection keywords. This could manipulate AI behavior.',
        suggestedFix: 'Remove manipulation keywords from description. Use neutral, factual descriptions.',
    },
    // Tool name from untrusted source
    {
        name: 'Dynamic tool name from config/options',
        pattern: /(?:registerTool|server\.tool|addTool)\s*\(\s*(?:config|options|params|settings)\s*\[?\s*['".]?\s*(?:name|tool)/gi,
        category: 'description_injection',
        baseSeverity: 'high',
        description: 'Tool name derived from configuration or options. Attackers could shadow legitimate tools.',
        suggestedFix: 'Use hardcoded tool names. Validate against an allowlist if dynamic names are required.',
    },
    // Python dynamic description
    {
        name: 'Python tool with dynamic description',
        pattern: /@server\.tool\s*\(\s*name\s*=\s*(?:f["']|["'].*\{)/gi,
        category: 'description_injection',
        baseSeverity: 'high',
        description: 'Python MCP tool with f-string or formatted description. Could include injected content.',
        suggestedFix: 'Use static string literals for tool names and descriptions.',
    },
    // Description from database/storage
    {
        name: 'Tool description from storage',
        pattern: /description\s*:\s*(?:await\s+)?(?:db|database|storage|cache|redis)\.(?:get|read|fetch)/gi,
        category: 'description_injection',
        baseSeverity: 'medium',
        description: 'Tool description loaded from storage. Stored content could be poisoned.',
        suggestedFix: 'Use static descriptions. If dynamic descriptions are required, validate and sanitize thoroughly.',
    },
];
/**
 * Cross-Server Tool Shadowing Patterns
 * Detect malicious MCP servers overriding legitimate tools
 */
const SERVER_SHADOWING_PATTERNS = [
    // Server config from environment/user input
    {
        name: 'MCP server config from environment',
        pattern: /(?:MCP_SERVERS?|mcpServers?)\s*[=:]\s*(?:JSON\.parse\s*\(\s*)?process\.env/gi,
        category: 'server_shadowing',
        baseSeverity: 'medium',
        description: 'MCP server configuration loaded from environment variables. Ensure proper validation.',
        suggestedFix: 'Validate server URLs against an allowlist. Use explicit server configuration in code.',
    },
    // Server URLs from user input
    {
        name: 'MCP server URL from user input',
        pattern: /(?:server(?:Url|URL|Uri)|endpoint)\s*:\s*(?:req\.|user\.|input\.|params\.|body\.)/gi,
        category: 'server_shadowing',
        baseSeverity: 'high',
        description: 'MCP server URL derived from user input. Attackers could point to malicious servers.',
        suggestedFix: 'Use hardcoded server URLs or validate against a strict allowlist.',
    },
    // Dynamic server registration from config
    {
        name: 'Dynamic MCP server registration',
        pattern: /(?:for|forEach)\s*\([^)]*\)\s*(?:=>|\{)\s*[^}]*(?:register|connect|add)(?:Server|MCP)/gi,
        category: 'server_shadowing',
        baseSeverity: 'medium',
        description: 'MCP servers registered dynamically from configuration. Tool shadowing risk.',
        suggestedFix: 'Register servers explicitly. Implement tool name conflict detection.',
    },
    // Server list from JSON parse
    {
        name: 'MCP servers from parsed JSON',
        pattern: /servers\s*=\s*JSON\.parse\s*\(\s*(?:req\.|user|input|localStorage|sessionStorage)/gi,
        category: 'server_shadowing',
        baseSeverity: 'high',
        description: 'MCP server list parsed from user-controlled data. Could inject malicious servers.',
        suggestedFix: 'Define servers in code. If dynamic loading is needed, validate against an allowlist.',
    },
    // Server config override
    {
        name: 'MCP server config override pattern',
        pattern: /Object\.assign\s*\([^)]*(?:server|mcp)Config[^)]*,\s*(?:req\.|user\.|options\.)/gi,
        category: 'server_shadowing',
        baseSeverity: 'medium',
        description: 'MCP server configuration being overridden with user-provided values.',
        suggestedFix: 'Validate and sanitize configuration overrides. Use allowlist for permitted settings.',
    },
];
/**
 * Phase 5 Task 5: MCP Schema Validation Patterns
 * Detect MCP tools that use arguments without schema validation
 */
const SCHEMA_VALIDATION_PATTERNS = [
    // MCP tool using args directly without validation (JS)
    {
        name: 'MCP tool without input validation',
        pattern: /server\.tool\s*\([^)]+,\s*async\s*\(\s*(?:args|params|input)\s*\)\s*(?:=>|:)[^{]*\{(?![\s\S]{0,100}(?:schema\.parse|safeParse|validate|zod|yup|joi|superstruct|ajv|\.parse\())/gi,
        category: 'schema_bypass',
        baseSeverity: 'medium',
        description: 'MCP tool uses arguments directly without schema validation. Malformed or malicious input could cause unexpected behavior.',
        suggestedFix: 'Validate inputs with a schema: const validated = schema.parse(args); return runCommand(validated.command)',
    },
    // MCP tool accessing args properties without validation
    {
        name: 'MCP tool args used without validation',
        pattern: /server\.tool\s*\([^)]+,\s*async\s*\(\s*(?:args|params)\s*\)[^{]*\{[^}]*(?:args|params)\.(?:command|query|path|url|file|data|input|content|sql|script|code)(?![\s\S]{0,50}(?:validated|parsed|sanitized))/gi,
        category: 'schema_bypass',
        baseSeverity: 'high',
        description: 'MCP tool uses potentially dangerous argument properties directly. Input validation required.',
        suggestedFix: 'Validate dangerous inputs: const { command } = commandSchema.parse(args)',
    },
    // Python MCP tool without type/validation
    {
        name: 'Python MCP tool without validation',
        pattern: /@server\.tool[^)]*\)\s*(?:async\s+)?def\s+\w+\s*\(\s*(?:args|params|kwargs|\*\*)\s*(?::\s*dict)?\s*\)(?![\s\S]{0,50}(?:pydantic|validate|TypedDict|dataclass))/gi,
        category: 'schema_bypass',
        baseSeverity: 'medium',
        description: 'Python MCP tool accepts dict/kwargs without type validation. Use Pydantic or TypedDict.',
        suggestedFix: 'Use Pydantic model: def tool_name(args: MyInputModel) or validate with TypedDict',
    },
    // Args spread into function call
    {
        name: 'MCP tool args spread into call',
        pattern: /(?:runCommand|exec|spawn|query|execute|fetch)\s*\(\s*\.\.\.(?:args|params|input)/gi,
        category: 'schema_bypass',
        baseSeverity: 'high',
        description: 'MCP tool arguments spread directly into function call. All fields pass through unvalidated.',
        suggestedFix: 'Validate and destructure specific fields: const { field1, field2 } = schema.parse(args); fn(field1, field2)',
    },
    // Dynamic property access on args
    {
        name: 'Dynamic property access on MCP args',
        pattern: /(?:args|params|input)\s*\[\s*(?:key|prop|field|name)\s*\]/gi,
        category: 'schema_bypass',
        baseSeverity: 'medium',
        description: 'Dynamic property access on MCP tool arguments. Could access unintended properties.',
        suggestedFix: 'Use explicit destructuring with validation: const { expectedField } = schema.parse(args)',
    },
];
/**
 * Phase 6 Task 3: MCP Tool Result Injection Patterns
 * Detect MCP tool results directly interpolated into prompts without sanitization
 */
const RESULT_INJECTION_PATTERNS = [
    // MCP result interpolated into prompt template literal
    {
        name: 'MCP result in prompt template',
        pattern: /`[^`]*\$\{[^}]*(?:tool|mcp|result|toolResult|mcpResult)[^}]*\}[^`]*`\s*(?:\+\s*)?(?:system|prompt|message|instruction)/gi,
        category: 'tool_poisoning',
        baseSeverity: 'high',
        description: 'MCP tool results interpolated into prompts could contain injection payloads from external sources.',
        suggestedFix: 'Sanitize MCP tool results before including in prompts. Use structured data extraction: const safeData = extractSafeFields(toolResult)',
    },
    // Tool result concatenated with system prompt
    {
        name: 'Tool result concatenated with prompt',
        pattern: /(?:systemPrompt|prompt|message|instruction)\s*(?:\+|\.concat)\s*(?:toolResult|mcpResult|result|tool\.result|mcp\.result)/gi,
        category: 'tool_poisoning',
        baseSeverity: 'high',
        description: 'Tool results concatenated with prompts. External content in results could manipulate model behavior.',
        suggestedFix: 'Sanitize tool results before concatenation. Consider using delimiters: prompt + "\\n---DATA---\\n" + sanitize(result)',
    },
    // Tool result in messages array
    {
        name: 'Raw tool result in messages',
        pattern: /messages\s*(?:\.push|:\s*\[)[^;]*content\s*:\s*(?:toolResult|mcpResult|result|tool\.result)(?!\.sanitized|\.safe)/gi,
        category: 'tool_poisoning',
        baseSeverity: 'medium',
        description: 'Raw tool results added to message content. Results from external tools could contain injection payloads.',
        suggestedFix: 'Sanitize or structure tool results: messages.push({ content: sanitizeForPrompt(toolResult) })',
    },
    // Tool result used as context without processing
    {
        name: 'Tool result as unprocessed context',
        pattern: /context\s*[:=]\s*(?:toolResult|mcpResult|result|tool\.(?:output|result))(?!\s*\.|\.sanitize|\.filter)/gi,
        category: 'tool_poisoning',
        baseSeverity: 'medium',
        description: 'Tool result assigned directly as context. External content should be processed before use.',
        suggestedFix: 'Process and validate tool results: const context = processToolResult(result)',
    },
    // Spread tool result into prompt data
    {
        name: 'Tool result spread into prompt',
        pattern: /\{[^}]*\.\.\.(?:toolResult|mcpResult|result|tool\.result)[^}]*\}\s*(?:as|:|\s+(?:prompt|message|context))/gi,
        category: 'tool_poisoning',
        baseSeverity: 'high',
        description: 'Tool result spread into prompt data. All fields from external tool pass through.',
        suggestedFix: 'Extract specific fields: const { safeField1, safeField2 } = validateToolResult(result)',
    },
    // JSON stringify tool result into prompt
    {
        name: 'JSON stringified tool result in prompt',
        pattern: /JSON\.stringify\s*\(\s*(?:toolResult|mcpResult|result|tool\.result)\s*\)[^;]*(?:prompt|message|context|instruction)/gi,
        category: 'tool_poisoning',
        baseSeverity: 'medium',
        description: 'Tool result JSON-stringified into prompt. Serialized content could contain injection payloads.',
        suggestedFix: 'Filter tool result before stringification: JSON.stringify(filterSafeFields(result))',
    },
    // Format tool result for LLM
    {
        name: 'Unvalidated tool result formatting',
        pattern: /format(?:Tool|Result|Output)?\s*\(\s*(?:toolResult|mcpResult|result|tool\.result)\s*\)(?![\s\S]{0,30}(?:sanitize|validate|filter))/gi,
        category: 'tool_poisoning',
        baseSeverity: 'medium',
        description: 'Tool result formatted without validation. Formatting function should include sanitization.',
        suggestedFix: 'Include sanitization in formatting: formatToolResult(sanitize(result))',
    },
];
/**
 * Phase 5 Task 6: Human-in-the-Loop for Destructive Operations
 * Detect destructive operations without confirmation mechanism
 */
const DESTRUCTIVE_OPS_PATTERNS = [
    // File deletion without confirmation
    {
        name: 'MCP file deletion without confirmation',
        pattern: /server\.tool\s*\([^)]+(?:delete|remove|unlink|rm)[^)]+,\s*async[^{]*\{(?![\s\S]{0,100}(?:confirm|approved|needsConfirmation|requireApproval|humanInLoop))[^}]*(?:fs\.rm|fs\.unlink|unlinkSync|rmSync|remove|rimraf)/gi,
        category: 'missing_hitl',
        baseSeverity: 'high',
        description: 'MCP tool performs file deletion without confirmation mechanism. Destructive operations should require human approval.',
        suggestedFix: 'Add confirmation: if (!args.confirmed) { return { needsConfirmation: true, action: "delete", path: args.path } }',
    },
    // Database deletion without confirmation
    {
        name: 'MCP database deletion without confirmation',
        pattern: /server\.tool\s*\([^)]+(?:delete|drop|truncate|remove)[^)]+,\s*async[^{]*\{(?![\s\S]{0,100}(?:confirm|approved|needsConfirmation))[^}]*(?:\.delete|\.drop|\.truncate|\.destroy|DELETE\s+FROM|DROP\s+TABLE)/gi,
        category: 'missing_hitl',
        baseSeverity: 'high',
        description: 'MCP tool performs database deletion without confirmation. Data loss risk.',
        suggestedFix: 'Require confirmation for destructive DB operations: if (!args.confirmed) return { needsConfirmation: true }',
    },
    // Recursive directory deletion
    {
        name: 'MCP recursive deletion without confirmation',
        pattern: /(?:fs\.rm|rimraf|rmdir)\s*\([^)]*,\s*\{\s*recursive\s*:\s*true/gi,
        category: 'missing_hitl',
        baseSeverity: 'critical',
        description: 'Recursive directory deletion in MCP tool. High risk of unintended data loss.',
        suggestedFix: 'Add explicit confirmation with path display: if (!args.confirmed) return { needsConfirmation: true, message: `Delete ${path} and all contents?` }',
    },
    // Shell command execution without confirmation
    {
        name: 'MCP shell execution without confirmation',
        pattern: /server\.tool\s*\([^)]+(?:exec|run|shell|command)[^)]+,\s*async[^{]*\{(?![\s\S]{0,100}(?:confirm|approved|needsConfirmation))[^}]*(?:exec|spawn|execSync|spawnSync)\s*\(/gi,
        category: 'missing_hitl',
        baseSeverity: 'high',
        description: 'MCP tool executes shell commands without confirmation. Dangerous commands could be executed.',
        suggestedFix: 'Require confirmation for shell commands: if (!args.confirmed) return { needsConfirmation: true, command: args.command }',
    },
    // Send/publish operations without confirmation
    {
        name: 'MCP send operation without confirmation',
        pattern: /server\.tool\s*\([^)]+(?:send|publish|broadcast|notify)[^)]+,\s*async[^{]*\{(?![\s\S]{0,100}(?:confirm|approved|draft))[^}]*(?:\.send|\.publish|sendEmail|sendMessage)/gi,
        category: 'missing_hitl',
        baseSeverity: 'medium',
        description: 'MCP tool sends messages/emails without confirmation. Could send unintended communications.',
        suggestedFix: 'Add draft/confirmation: if (!args.confirmed) return { needsConfirmation: true, preview: messageContent }',
    },
    // Payment/transaction operations
    {
        name: 'MCP payment without confirmation',
        pattern: /server\.tool\s*\([^)]+(?:pay|charge|transfer|transaction)[^)]+,\s*async[^{]*\{(?![\s\S]{0,100}(?:confirm|approved|needsConfirmation))/gi,
        category: 'missing_hitl',
        baseSeverity: 'critical',
        description: 'MCP tool processes payments without confirmation. Financial operations require human approval.',
        suggestedFix: 'Always require confirmation for financial operations: if (!args.confirmed) return { needsConfirmation: true, amount, recipient }',
    },
    // API key/secret deletion
    {
        name: 'MCP credential deletion without confirmation',
        pattern: /server\.tool\s*\([^)]+(?:delete|revoke|remove)[^)]*(?:key|token|secret|credential)[^)]+,\s*async[^{]*\{(?![\s\S]{0,100}(?:confirm|approved))/gi,
        category: 'missing_hitl',
        baseSeverity: 'high',
        description: 'MCP tool deletes credentials without confirmation. Could cause service disruption.',
        suggestedFix: 'Require explicit confirmation: if (!args.confirmed) return { needsConfirmation: true, warning: "This will revoke access" }',
    },
];
// ============================================================================
// Main Detection Function
// ============================================================================
/**
 * Map internal category to vulnerability category
 */
function mapCategory(internal) {
    switch (internal) {
        case 'tool_poisoning':
            return 'ai_mcp_tool_poisoning';
        case 'credential_issue':
            return 'ai_mcp_credential_issue';
        case 'confused_deputy':
            return 'ai_mcp_confused_deputy';
        case 'description_injection':
            return 'ai_mcp_description_injection';
        case 'server_shadowing':
            return 'ai_mcp_server_shadowing';
        case 'schema_bypass':
            return 'ai_mcp_tool_poisoning'; // Schema bypass leads to tool poisoning risks
        case 'missing_hitl':
            return 'ai_excessive_agency'; // Missing human-in-the-loop is excessive agency
    }
}
/**
 * Main detection function for MCP security issues
 */
function detectMCPSecurity(content, filePath, options) {
    const vulnerabilities = [];
    // Skip non-applicable files
    if ((0, context_helpers_1.isScannerOrFixtureFile)(filePath))
        return vulnerabilities;
    if ((0, context_helpers_1.isDocumentationFile)(filePath))
        return vulnerabilities;
    // Only scan MCP-related files
    if (!isMCPFile(content, filePath)) {
        return vulnerabilities;
    }
    const lines = options?.parsed?.lines ?? content.split('\n');
    const isTestFile = (0, context_helpers_1.isTestOrMockFile)(filePath);
    const isExample = (0, context_helpers_1.isExampleDirectory)(filePath);
    const isLibrary = (0, context_helpers_1.isLibraryCode)(filePath);
    // Process all pattern categories
    const allPatterns = [
        ...TOOL_POISONING_PATTERNS,
        ...CREDENTIAL_PATTERNS,
        ...CONFUSED_DEPUTY_PATTERNS,
        ...DESCRIPTION_INJECTION_PATTERNS,
        ...SERVER_SHADOWING_PATTERNS,
        // Phase 5: New detection patterns
        ...SCHEMA_VALIDATION_PATTERNS,
        ...DESTRUCTIVE_OPS_PATTERNS,
        // Phase 6: MCP result injection
        ...RESULT_INJECTION_PATTERNS,
    ];
    // Track findings to avoid duplicates
    const seenFindings = new Set();
    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, context_helpers_1.isComment)(lineContent))
                continue;
            // Create dedup key
            const dedupKey = `${filePath}:${lineNumber}:${pattern.category}`;
            if (seenFindings.has(dedupKey))
                continue;
            seenFindings.add(dedupKey);
            // Get surrounding context for analysis
            const context = getSurroundingContext(content, lineNumber - 1, 30);
            // Calculate severity based on context
            let severity = pattern.baseSeverity;
            let description = pattern.description;
            const notes = [];
            // Apply context-aware severity adjustments
            if (pattern.category === 'tool_poisoning') {
                // Check for content sanitization
                if (hasContentSanitization(context)) {
                    severity = 'info';
                    notes.push('Content sanitization detected');
                }
                // Check for safe data source
                else if (isSafeDataSource(context)) {
                    severity = 'info';
                    notes.push('Safe/static data source detected');
                }
                // Check for user context (their own data)
                else if (hasUserContext(context)) {
                    // Has user context - might be returning user's own data
                    if (severity === 'high')
                        severity = 'medium';
                    notes.push('User context present - may be returning user\'s own data');
                }
            }
            if (pattern.category === 'confused_deputy') {
                // Check for user context
                if (hasUserContext(context)) {
                    // User context present - check for auth
                    if (hasAuthorizationCheck(context)) {
                        severity = 'info';
                        notes.push('Authorization check detected');
                    }
                    else {
                        // Has user but no auth check - lower severity
                        if (severity === 'high')
                            severity = 'medium';
                        if (severity === 'critical')
                            severity = 'high';
                        notes.push('User context present but no authorization check');
                    }
                }
            }
            // Credential issues are always serious, but check context
            if (pattern.category === 'credential_issue') {
                // Check if it's returning the credential
                if (pattern.name.includes('response') || pattern.name.includes('return')) {
                    // Returning credentials is always critical/high
                }
                else if (hasUserContext(context)) {
                    // Parameter with user context - still bad but slightly less severe
                    if (severity === 'high')
                        severity = 'medium';
                    notes.push('User context present but credentials still in parameters');
                }
            }
            // Description injection - check for input sanitization
            if (pattern.category === 'description_injection') {
                // Check for sanitization or validation before description
                if (/sanitize|validate|escape|filter|strip/i.test(context)) {
                    severity = 'low';
                    notes.push('Input sanitization detected nearby');
                }
                // Check for static/constant descriptions
                if (/const\s+\w+\s*=\s*["'`][^"'`]+["'`]\s*;?\s*$/m.test(context)) {
                    // Likely a constant being used
                    severity = 'info';
                    notes.push('May be using constant description');
                }
            }
            // Server shadowing - check for allowlist validation
            if (pattern.category === 'server_shadowing') {
                // Check for allowlist/whitelist validation
                if (/allowlist|whitelist|ALLOWED_SERVERS|validServers|trustedServers/i.test(context)) {
                    severity = 'info';
                    notes.push('Server allowlist detected');
                }
                // Check for URL validation
                if (/validate.*url|url.*validate|isValidUrl|checkUrl/i.test(context)) {
                    severity = 'low';
                    notes.push('URL validation detected');
                }
            }
            // Downgrade test files
            if (isTestFile) {
                severity = 'info';
                notes.push('in test file');
            }
            // Downgrade example/demo directories
            if (isExample && severity !== 'info') {
                severity = 'info';
                notes.push('in example/demo directory');
            }
            // Downgrade library code
            if (isLibrary && severity !== 'info') {
                severity = 'info';
                notes.push('library code');
            }
            // Build final description
            if (notes.length > 0) {
                description += ` (${notes.join('; ')})`;
            }
            vulnerabilities.push({
                id: `ai-mcp-${filePath}-${lineNumber}-${pattern.name.replace(/\s+/g, '-')}`,
                filePath,
                lineNumber,
                lineContent,
                severity,
                category: mapCategory(pattern.category),
                title: pattern.name,
                description,
                suggestedFix: pattern.suggestedFix,
                confidence: severity === 'info' ? 'low' : 'medium',
                layer: 2,
                requiresAIValidation: severity !== 'info' && severity !== 'low',
            });
        }
    }
    return vulnerabilities;
}
//# sourceMappingURL=ai-mcp-security.js.map