"use strict";
/**
 * Category-Based Filtering
 *
 * Enables CI to fail only on specific vulnerability categories,
 * allowing gradual rollout (e.g., "only block prompt injection")
 * and fine-grained control over which findings are blocking.
 *
 * @example
 * // Fail only on AI-related and secret categories
 * --fail-on-categories ai-*,secrets-*
 *
 * @example
 * // Combined with severity
 * --fail-on high --fail-on-categories ai-*
 * // Only fail on high+ AI findings
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.ALL_CATEGORIES = exports.CATEGORY_GROUPS = void 0;
exports.normalizeCategory = normalizeCategory;
exports.expandCategoryPattern = expandCategoryPattern;
exports.matchesAnyCategory = matchesAnyCategory;
exports.shouldFailOnCategories = shouldFailOnCategories;
exports.getMatchingCategories = getMatchingCategories;
exports.parseCategoryList = parseCategoryList;
exports.validateCategories = validateCategories;
exports.getAvailableCategoryGroups = getAvailableCategoryGroups;
exports.getCategoryGroupCounts = getCategoryGroupCounts;
const parsed_file_1 = require("./utils/parsed-file");
/**
 * Category group definitions for wildcard expansion
 *
 * These groups allow users to specify broad categories like "ai-*"
 * which expand to all AI-related vulnerability categories.
 */
exports.CATEGORY_GROUPS = {
    'ai-*': [
        'ai_pattern',
        'ai_prompt_injection',
        'ai_unsafe_execution',
        'ai_overpermissive_tool',
        'ai_rag_exfiltration',
        'ai_endpoint_unprotected',
        'ai_schema_mismatch',
        'ai_package_hallucination',
        'ai_rag_corpus_poisoning',
        'ai_rag_pii_leakage',
        'ai_mcp_tool_poisoning',
        'ai_mcp_credential_issue',
        'ai_mcp_confused_deputy',
        'ai_mcp_description_injection',
        'ai_mcp_server_shadowing',
        'ai_mcp_config_secrets',
        'ai_mcp_config_permissions',
        'ai_rag_query_injection',
        'ai_rag_embedding_poisoning',
        'ai_rag_chunk_injection',
        'ai_package_typosquat',
        'ai_package_malicious',
        'ai_unsafe_model_load',
        'ai_unverified_model',
        'ai_unsafe_finetuning',
        'ai_excessive_agency',
    ],
    'secrets-*': [
        'hardcoded_secret',
        'high_entropy_string',
        'sensitive_variable',
    ],
    'owasp-*': [
        'sql_injection',
        'xss',
        'command_injection',
        'missing_auth',
        'security_bypass',
        'insecure_config',
        'cors_misconfiguration',
        'data_exposure',
        'weak_crypto',
    ],
};
/**
 * All known valid category names for validation
 */
exports.ALL_CATEGORIES = [
    'hardcoded_secret',
    'high_entropy_string',
    'sensitive_variable',
    'security_bypass',
    'dangerous_function',
    'sql_injection',
    'xss',
    'command_injection',
    'insecure_config',
    'missing_auth',
    'suspicious_package',
    'cors_misconfiguration',
    'root_container',
    'dangerous_file',
    'ai_pattern',
    'sensitive_url',
    'weak_crypto',
    'data_exposure',
    'ai_prompt_injection',
    'ai_unsafe_execution',
    'ai_overpermissive_tool',
    'ai_rag_exfiltration',
    'ai_endpoint_unprotected',
    'ai_schema_mismatch',
    'ai_package_hallucination',
    'ai_rag_corpus_poisoning',
    'ai_rag_pii_leakage',
    'ai_mcp_tool_poisoning',
    'ai_mcp_credential_issue',
    'ai_mcp_confused_deputy',
    'ai_mcp_description_injection',
    'ai_mcp_server_shadowing',
    'ai_mcp_config_secrets',
    'ai_mcp_config_permissions',
    'ai_rag_query_injection',
    'ai_rag_embedding_poisoning',
    'ai_rag_chunk_injection',
    'ai_package_typosquat',
    'ai_package_malicious',
    'ai_unsafe_model_load',
    'ai_unverified_model',
    'ai_unsafe_finetuning',
    'ai_excessive_agency',
];
/**
 * Normalize category name for comparison
 * - Converts to lowercase
 * - Converts hyphens to underscores
 * - Trims whitespace
 *
 * @example
 * normalizeCategory('SQL-Injection') // 'sql_injection'
 * normalizeCategory('high_entropy_string') // 'high_entropy_string'
 */
function normalizeCategory(category) {
    return category
        .toLowerCase()
        .trim()
        .replace(/-/g, '_');
}
/**
 * Check if a string is a valid wildcard pattern
 * Valid wildcards end with '-*' or '_*'
 */
function isWildcardPattern(pattern) {
    return pattern.endsWith('-*') || pattern.endsWith('_*');
}
/**
 * Expand a wildcard pattern or single category to a list of categories
 *
 * @param pattern - Category name or wildcard (e.g., 'sql_injection', 'ai-*')
 * @returns Array of matching categories
 *
 * @example
 * expandCategoryPattern('ai-*') // Returns all ai_* categories
 * expandCategoryPattern('sql_injection') // Returns ['sql_injection']
 * expandCategoryPattern('unknown-*') // Returns []
 */
function expandCategoryPattern(pattern) {
    const normalized = normalizeCategory(pattern);
    // Check for wildcard patterns
    if (isWildcardPattern(normalized)) {
        // Normalize the wildcard pattern (both ai-* and ai_* should work)
        const normalizedWildcard = normalized.replace('_*', '-*');
        // Look up in category groups
        const expanded = exports.CATEGORY_GROUPS[normalizedWildcard];
        if (expanded) {
            return [...expanded];
        }
        // Unknown wildcard - return empty
        return [];
    }
    // Single category - validate and return
    // Handle both hyphenated and underscored versions
    const normalizedCategory = normalized;
    if (exports.ALL_CATEGORIES.includes(normalizedCategory)) {
        return [normalizedCategory];
    }
    // Try hyphenated version converted to underscore
    const underscored = normalized.replace(/-/g, '_');
    if (exports.ALL_CATEGORIES.includes(underscored)) {
        return [underscored];
    }
    return [];
}
/**
 * Check if a category matches any pattern in the filter list
 *
 * @param category - The vulnerability category to check
 * @param patterns - Array of category patterns (names or wildcards)
 * @returns true if the category matches any pattern
 *
 * @example
 * matchesAnyCategory('ai_prompt_injection', ['ai-*']) // true
 * matchesAnyCategory('sql_injection', ['ai-*']) // false
 * matchesAnyCategory('sql_injection', ['sql_injection', 'xss']) // true
 */
function matchesAnyCategory(category, patterns) {
    if (!patterns || patterns.length === 0) {
        return false;
    }
    const normalizedCategory = normalizeCategory(category);
    for (const pattern of patterns) {
        const expanded = expandCategoryPattern(pattern);
        // Check if category is in the expanded list
        if (expanded.some(c => normalizeCategory(c) === normalizedCategory)) {
            return true;
        }
    }
    return false;
}
// severityRank imported from utils/parsed-file
/**
 * Check if vulnerabilities should cause failure based on category filter
 *
 * When both category patterns and severity threshold are provided,
 * BOTH conditions must match for a finding to cause failure.
 *
 * @param vulnerabilities - List of vulnerabilities to check
 * @param categoryPatterns - Category patterns to filter on
 * @param severityThreshold - Optional severity threshold (both must match)
 * @returns true if any vulnerability matches and should cause failure
 *
 * @example
 * // Only fail on AI findings
 * shouldFailOnCategories(vulns, ['ai-*'])
 *
 * @example
 * // Only fail on HIGH+ AI findings
 * shouldFailOnCategories(vulns, ['ai-*'], 'high')
 */
function shouldFailOnCategories(vulnerabilities, categoryPatterns, severityThreshold) {
    if (!vulnerabilities || vulnerabilities.length === 0) {
        return false;
    }
    if (!categoryPatterns || categoryPatterns.length === 0) {
        return false;
    }
    const thresholdRank = severityThreshold ? (0, parsed_file_1.severityRank)(severityThreshold) : 0;
    for (const vuln of vulnerabilities) {
        // Check if category matches any pattern
        if (!matchesAnyCategory(vuln.category, categoryPatterns)) {
            continue;
        }
        // If no severity threshold specified, any matching category triggers failure
        if (!severityThreshold) {
            return true;
        }
        // Check if severity meets threshold
        if ((0, parsed_file_1.severityRank)(vuln.severity) >= thresholdRank) {
            return true;
        }
    }
    return false;
}
/**
 * Get the categories that matched the filter from vulnerabilities
 * Useful for error messages showing which categories caused failure
 */
function getMatchingCategories(vulnerabilities, categoryPatterns, severityThreshold) {
    if (!vulnerabilities || vulnerabilities.length === 0) {
        return [];
    }
    if (!categoryPatterns || categoryPatterns.length === 0) {
        return [];
    }
    const thresholdRank = severityThreshold ? (0, parsed_file_1.severityRank)(severityThreshold) : 0;
    const matched = new Set();
    for (const vuln of vulnerabilities) {
        // Check if category matches any pattern
        if (!matchesAnyCategory(vuln.category, categoryPatterns)) {
            continue;
        }
        // If no severity threshold, or severity meets threshold
        if (!severityThreshold || (0, parsed_file_1.severityRank)(vuln.severity) >= thresholdRank) {
            matched.add(vuln.category);
        }
    }
    return Array.from(matched);
}
/**
 * Parse comma-separated category string into array
 *
 * @param input - Comma-separated category string
 * @returns Array of trimmed category patterns
 *
 * @example
 * parseCategoryList('ai-*, secrets-*') // ['ai-*', 'secrets-*']
 * parseCategoryList('sql_injection, xss') // ['sql_injection', 'xss']
 */
function parseCategoryList(input) {
    if (!input || typeof input !== 'string') {
        return [];
    }
    return input
        .split(',')
        .map(s => s.trim())
        .filter(s => s.length > 0);
}
/**
 * Validate category names, separating valid from invalid
 *
 * @param categories - Array of category patterns to validate
 * @returns Object with valid and invalid category arrays
 *
 * @example
 * validateCategories(['ai-*', 'sql_injection', 'fake_category'])
 * // { valid: ['ai-*', 'sql_injection'], invalid: ['fake_category'] }
 */
function validateCategories(categories) {
    const valid = [];
    const invalid = [];
    for (const category of categories) {
        const normalized = normalizeCategory(category);
        // Check if it's a valid wildcard
        if (isWildcardPattern(normalized)) {
            const normalizedWildcard = normalized.replace('_*', '-*');
            if (exports.CATEGORY_GROUPS[normalizedWildcard]) {
                valid.push(category);
            }
            else {
                invalid.push(category);
            }
            continue;
        }
        // Check if it's a valid category name
        const expanded = expandCategoryPattern(category);
        if (expanded.length > 0) {
            valid.push(category);
        }
        else {
            invalid.push(category);
        }
    }
    return { valid, invalid };
}
/**
 * Get a human-readable list of available category groups
 * Useful for help text and error messages
 */
function getAvailableCategoryGroups() {
    return Object.keys(exports.CATEGORY_GROUPS);
}
/**
 * Get the count of categories in each group
 * Useful for documentation and help text
 */
function getCategoryGroupCounts() {
    const counts = {};
    for (const [group, categories] of Object.entries(exports.CATEGORY_GROUPS)) {
        counts[group] = categories.length;
    }
    return counts;
}
//# sourceMappingURL=category-filter.js.map