"use strict";
/**
 * DOM/XSS Detection
 *
 * Detection logic for XSS-related patterns like innerHTML, dangerouslySetInnerHTML,
 * and document.write.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.isStyleElementInnerHTML = isStyleElementInnerHTML;
exports.isStaticHTMLContent = isStaticHTMLContent;
exports.hasDOMPurifySanitization = hasDOMPurifySanitization;
exports.isLLMPromptContext = isLLMPromptContext;
exports.isTrustedLibraryHTMLOutput = isTrustedLibraryHTMLOutput;
exports.isStaticBootstrapScript = isStaticBootstrapScript;
/**
 * Check if innerHTML is being used on a style element (CSS injection is not XSS)
 */
function isStyleElementInnerHTML(lineContent, content, lineNumber, lines) {
    const _lines = lines ?? content.split('\n');
    // Direct style element patterns on the line
    const stylePatterns = [
        /stylesheet\.innerHTML/i, // stylesheet.innerHTML = ...
        /styleElement\.innerHTML/i, // styleElement.innerHTML = ...
        /styleEl\.innerHTML/i, // styleEl.innerHTML = ...
        /style\.innerHTML/i, // style.innerHTML = ...
        /\.style\b.*\.innerHTML/i, // element.style.innerHTML
        /createElement\s*\(\s*['"`]style['"`]\s*\)/i, // createElement('style')
    ];
    if (stylePatterns.some(p => p.test(lineContent))) {
        return true;
    }
    // Check surrounding context for style element creation
    const contextStart = Math.max(0, lineNumber - 10);
    const contextEnd = lineNumber;
    const contextBefore = _lines.slice(contextStart, contextEnd).join('\n');
    // Look for style element creation that flows into innerHTML
    const styleCreationPatterns = [
        /document\.createElement\s*\(\s*['"`]style['"`]\s*\)/i,
        /\.appendChild\s*\([^)]*style/i,
        /const\s+\w*(style|stylesheet)\w*\s*=/i,
        /let\s+\w*(style|stylesheet)\w*\s*=/i,
        /var\s+\w*(style|stylesheet)\w*\s*=/i,
    ];
    return styleCreationPatterns.some(p => p.test(contextBefore));
}
/**
 * Check if innerHTML/dangerouslySetInnerHTML uses static content only
 */
function isStaticHTMLContent(lineContent, content, lineNumber, lines) {
    const _lines = lines ?? content.split('\n');
    // Get surrounding context (5 lines before and after)
    const contextStart = Math.max(0, lineNumber - 6);
    const contextEnd = Math.min(_lines.length, lineNumber + 5);
    const context = _lines.slice(contextStart, contextEnd).join('\n');
    // Static HTML indicators - string literals only
    const staticIndicators = [
        /innerHTML\s*=\s*['"][^'"]*['"]/, // innerHTML = "static string" (single line)
        /innerHTML\s*=\s*`[^`]*`/, // innerHTML = `static template` (single line, no ${})
        /dangerouslySetInnerHTML\s*=\s*\{\s*\{\s*__html:\s*['"`]/, // React static string
    ];
    // Check for multi-line template literals assigned to innerHTML
    // Look for: innerHTML = ` at the start
    const multilineTemplateStart = /innerHTML\s*=\s*`/;
    if (multilineTemplateStart.test(lineContent)) {
        // Find the closing backtick in subsequent lines
        let templateContent = '';
        let foundClosing = false;
        for (let i = lineNumber - 1; i < _lines.length && i < lineNumber + 50; i++) {
            templateContent += _lines[i] + '\n';
            if (_lines[i].includes('`') && i > lineNumber - 1) {
                foundClosing = true;
                break;
            }
        }
        // If template has no ${...} interpolations, it's static
        if (foundClosing && !/\$\{[^}]*\}/.test(templateContent)) {
            return true;
        }
    }
    // Dynamic content indicators (red flags)
    const dynamicIndicators = [
        /\$\{[^}]*\}/, // Template interpolation ${...}
        /innerHTML\s*=.*\+/, // String concatenation with +
        /innerHTML\s*\+=\s*/, // Append operation
        /\breq\.|\.params|\.query|\.body/, // User input (req.params, req.query, req.body)
        /\bprops\./, // Component props
        /\bstate\./, // Component state
        /\.value\b/, // Input value
        /dangerouslySetInnerHTML\s*=\s*\{\s*\{\s*__html:\s*[^'"`]/, // React dynamic
    ];
    const isStatic = staticIndicators.some(p => p.test(lineContent));
    const isDynamic = dynamicIndicators.some(p => p.test(context));
    return isStatic && !isDynamic;
}
/**
 * Check if dangerouslySetInnerHTML is used with DOMPurify sanitization
 */
function hasDOMPurifySanitization(lineContent, content, lineNumber, lines) {
    const _lines = lines ?? content.split('\n');
    const contextStart = Math.max(0, lineNumber - 10);
    const contextEnd = Math.min(_lines.length, lineNumber + 5);
    const context = _lines.slice(contextStart, contextEnd).join('\n');
    // DOMPurify sanitization patterns
    const sanitizationPatterns = [
        /DOMPurify\.sanitize/i,
        /sanitize\s*\(/i,
        /purify\s*\(/i,
        /xss\s*\(/i,
        /clean\s*\(/i,
        /sanitizeHtml/i,
        /escapeHtml/i,
        /sanitized/i,
        /purified/i,
    ];
    return sanitizationPatterns.some(p => p.test(context));
}
/**
 * Check if data flows to an LLM prompt rather than a DOM sink
 * LLM prompts are NOT XSS - they're prompt injection (different risk profile)
 */
function isLLMPromptContext(lineContent, content, filePath) {
    // File path indicators of AI/LLM code
    const aiFilePatterns = [
        /\/(ai|llm|chat|openai|anthropic|gpt|claude)\//i,
        /\/(assistants?|agents?|prompts?)\//i,
        /(chat|ai|llm|prompt|assistant).*\.(ts|js|tsx|jsx)$/i,
    ];
    if (aiFilePatterns.some(p => p.test(filePath))) {
        return true;
    }
    // Content patterns suggesting LLM API usage
    const llmApiPatterns = [
        /\.create\s*\(\s*\{[^}]*messages\s*:/i, // OpenAI/Anthropic SDK
        /openai|anthropic|claude|gpt-4|gpt-3/i, // AI service mentions
        /\bprompt\s*[=:+]/i, // prompt assignment
        /\bsystemPrompt|userPrompt|assistantPrompt/i, // Prompt variables
        /completion|chat\.create|messages\.create/i, // API calls
        /\bmessages\s*:\s*\[/i, // Messages array
        /role:\s*['"`](user|assistant|system)['"`]/i, // Message roles
    ];
    // Check the line and surrounding context
    const _lines = content.split('\n');
    const lineIndex = _lines.findIndex(l => l === lineContent || l.includes(lineContent.trim()));
    const startLine = Math.max(0, lineIndex - 10);
    const endLine = Math.min(_lines.length, lineIndex + 10);
    const context = _lines.slice(startLine, endLine).join('\n');
    return llmApiPatterns.some(p => p.test(lineContent) || p.test(context));
}
/**
 * Check if innerHTML uses output from trusted HTML rendering libraries
 * Libraries like Shiki, highlight.js, marked, etc. produce sanitized HTML
 */
function isTrustedLibraryHTMLOutput(lineContent, content, lineNumber, lines) {
    const _lines = lines ?? content.split('\n');
    const contextStart = Math.max(0, lineNumber - 15);
    const contextEnd = Math.min(_lines.length, lineNumber + 5);
    const context = _lines.slice(contextStart, contextEnd).join('\n');
    // Trusted HTML rendering library patterns
    const trustedLibraryPatterns = [
        // Syntax highlighting
        /\bshiki\b/i,
        /\bcodeToHtml\s*\(/i, // Shiki's codeToHtml()
        /\bhighlight(?:er)?\.highlight/i, // highlight.js
        /\bhljs\.highlight/i,
        /\bPrism\.highlight/i, // Prism.js
        /\bPrismJS/i,
        // Markdown rendering
        /\bmarked\s*\(/i, // marked library
        /\bmarkdownIt/i, // markdown-it
        /\bremark/i, // remark
        /\brehype/i, // rehype
        /\bMDX/i,
        /\bserialize\s*\(.*mdx/i, // next-mdx-remote
        /\bcompileMDX/i,
        // Rich text editors (output is sanitized)
        /\bTiptap/i,
        /\bProseMirror/i,
        /\bQuill/i,
        /\bSlate/i,
        /\bLexical/i,
        /\bDraft(?:JS)?/i,
        /\.getHTML\s*\(\)/i, // Editor getHTML() output
        // React components that handle sanitization
        /\brenderToString\s*\(/i, // Server-rendered React
        /\brenderToStaticMarkup\s*\(/i,
        // Code formatting/display libraries
        /\bprettier/i,
        /\bbeautify/i,
        // SVG rendering
        /\bcanvg/i,
        /\.toSVG\s*\(/i,
    ];
    // Also check imports at top of file
    const fullContent = content.substring(0, 2000); // First 2000 chars for imports
    return (trustedLibraryPatterns.some(p => p.test(lineContent)) ||
        trustedLibraryPatterns.some(p => p.test(context)) ||
        trustedLibraryPatterns.some(p => p.test(fullContent)));
}
/**
 * Check if this is a static bootstrap script (e.g., localStorage theme reader)
 * These are very low risk even with dangerouslySetInnerHTML
 */
function isStaticBootstrapScript(_lineContent, content, lineNumber, lines) {
    const _lines = lines ?? content.split('\n');
    const contextStart = Math.max(0, lineNumber - 10);
    const contextEnd = Math.min(_lines.length, lineNumber + 5);
    const context = _lines.slice(contextStart, contextEnd).join('\n');
    // Bootstrap script indicators (reading from localStorage, setting attributes)
    const bootstrapPatterns = [
        /localStorage\.getItem/i,
        /document\.documentElement\.setAttribute/i,
        /data-(theme|font|mode)/i,
        /classList\.(add|remove|toggle)/i,
        /\.dataset\./i,
    ];
    // Dangerous patterns that disqualify as safe bootstrap
    const dangerousPatterns = [
        /\$\{.*\}/, // Template interpolation
        /\+\s*[a-zA-Z]/, // String concatenation with variable
        /innerHTML\s*=\s*[a-zA-Z]/, // innerHTML set to variable directly
        /fetch\s*\(/, // Network requests
        /\.(query|params|body)/, // User input
        /location\.(search|hash)/, // URL parameters
        /document\.cookie/, // Cookie access
    ];
    const hasBootstrapPatterns = bootstrapPatterns.some(p => p.test(context));
    const hasDangerousPatterns = dangerousPatterns.some(p => p.test(context));
    return hasBootstrapPatterns && !hasDangerousPatterns;
}
//# sourceMappingURL=dom-xss.js.map