/** * sigma_details_block.js * * Creates Slack Block Kit blocks for displaying Sigma rule explanations * */ const logger = require('../../utils/logger'); const { getFileName } = require('../../utils/file_utils'); const FILE_NAME = getFileName(__filename); /** * Create Slack block kit blocks for rule explanation * * @param {Object} details - The rule details object containing all rule metadata * @returns {Array} Formatted Slack blocks ready for display */ function getRuleExplanationBlocks(details) { logger.debug(`${FILE_NAME}: Creating rule explanation blocks for rule: ${details?.id || 'unknown'}`); if (!details) { logger.error('Failed to create explanation blocks: No details object provided'); return [ { type: 'section', text: { type: 'mrkdwn', text: 'Error: No explanation data provided' } } ]; } // Map severity levels to emojis for visual representation const severityConfig = { 'critical': { emoji: '🔴', text: 'Critical' }, 'high': { emoji: '🟠', text: 'High' }, 'medium': { emoji: '🟡', text: 'Medium' }, 'low': { emoji: '🟢', text: 'Low' }, 'informational': { emoji: '🔵', text: 'Info' } }; // Normalize severity to lowercase for matching const normalizedSeverity = (details.severity || '').toLowerCase(); const severityInfo = severityConfig[normalizedSeverity] || { emoji: '⚪', text: details.severity || 'Unknown' }; // Create a formatted severity indicator const severityDisplay = `${severityInfo.emoji} *${severityInfo.text}*`; logger.debug(`Rule severity: ${normalizedSeverity} (${severityDisplay})`); /** * Format tags with MITRE links where applicable * * @param {Array} tags - Array of tag strings to format * @returns {Array} Array of formatted tags with links where appropriate */ const formatTags = (tags = []) => { if (!tags || tags.length === 0 || (tags.length === 2 && tags.includes('error'))) { logger.debug('No valid tags to format'); return []; } logger.debug(`Formatting ${tags.length} tags`); return tags.map(tag => { let formattedTag = tag.trim(); let url = ''; let displayText = formattedTag; // Handle MITRE ATT&CK Technique IDs if (/^T\d{4}(\.\d{3})?$/.test(formattedTag)) { // Technique ID (e.g., T1234 or T1234.001) url = `https://attack.mitre.org/techniques/${formattedTag}/`; logger.debug(`Formatted MITRE technique: ${formattedTag}`); } // Handle MITRE ATT&CK Tactic IDs else if (/^TA\d{4}$/.test(formattedTag)) { // Tactic ID (e.g., TA0001) url = `https://attack.mitre.org/tactics/${formattedTag}/`; logger.debug(`Formatted MITRE tactic: ${formattedTag}`); } // Handle CWE IDs else if (/^S\d{4}$/.test(formattedTag)) { // CWE ID url = `https://cwe.mitre.org/data/definitions/${formattedTag.substring(1)}.html`; logger.debug(`Formatted CWE: ${formattedTag}`); } // Handle attack.* tactics else if (formattedTag.startsWith('attack.')) { const tacticName = formattedTag.substring(7); // Remove 'attack.' prefix // Handle specific techniques with T#### format if (/^t\d{4}(\.\d{3})?$/.test(tacticName)) { const techniqueId = tacticName.toUpperCase(); url = `https://attack.mitre.org/techniques/${techniqueId}/`; displayText = techniqueId; logger.debug(`Formatted MITRE technique from attack. format: ${techniqueId}`); } // Handle regular tactics else { // Map common tactics to their MITRE ATT&CK IDs const tacticMappings = { 'reconnaissance': 'TA0043', 'resourcedevelopment': 'TA0042', 'initialaccess': 'TA0001', 'execution': 'TA0002', 'persistence': 'TA0003', 'privilegeescalation': 'TA0004', 'defenseevasion': 'TA0005', 'credentialaccess': 'TA0006', 'discovery': 'TA0007', 'lateralmovement': 'TA0008', 'collection': 'TA0009', 'command-and-control': 'TA0011', 'exfiltration': 'TA0010', 'impact': 'TA0040' }; // Remove hyphens and convert to lowercase for matching const normalizedTactic = tacticName.toLowerCase().replace(/-/g, ''); if (tacticMappings[normalizedTactic]) { url = `https://attack.mitre.org/tactics/${tacticMappings[normalizedTactic]}/`; logger.debug(`Mapped tactic ${tacticName} to ${tacticMappings[normalizedTactic]}`); } else { // If we don't have a specific mapping, try a search url = `https://attack.mitre.org/search/?q=${encodeURIComponent(tacticName)}`; logger.debug(`Created search URL for unmapped tactic: ${tacticName}`); } // Format the display text with proper capitalization displayText = tacticName.replace(/-/g, ' ') .split(' ') .map(word => word.charAt(0).toUpperCase() + word.slice(1)) .join(' '); } } // Handle CVE IDs else if (/^CVE-\d{4}-\d{4,}$/i.test(formattedTag)) { url = `https://nvd.nist.gov/vuln/detail/${formattedTag.toUpperCase()}`; displayText = formattedTag.toUpperCase(); logger.debug(`Formatted CVE: ${displayText}`); } if (url) { return `<${url}|${displayText}>`; } else { return displayText; } }); }; // Define header based on title - check if it contains error messages const isErrorMessage = details.title.toLowerCase().includes('error') || details.title.toLowerCase().includes('missing'); if (isErrorMessage) { logger.warn(`Rule appears to have errors: ${details.title}`); } // Start with header block const blocks = [ { type: 'header', text: { type: 'plain_text', text: details.title || 'Rule Explanation', emoji: true } }, { type: 'context', elements: [ { type: 'mrkdwn', text: `ID: ${details.id || 'Unknown'}` } ] }, { type: 'section', fields: [ { type: 'mrkdwn', text: `*Severity:* ${severityDisplay}` }, { type: 'mrkdwn', text: `*Author:* ${details.author || 'Unknown'}` } ] } ]; // Add divider for visual separation blocks.push({ type: 'divider' }); // Add description section blocks.push({ type: 'section', text: { type: 'mrkdwn', text: `*Description:*\n${details.description || 'No description available'}` } }); // Detection explanation section - only add if not an error case or has useful detection info if (!isErrorMessage || details.detectionExplanation !== 'Content missing') { blocks.push({ type: 'section', text: { type: 'mrkdwn', text: `*What This Rule Detects:*\n${details.detectionExplanation || 'No detection information available'}` } }); } // False positives section - only add if not an error case with N/A values if (details.falsePositives && !details.falsePositives.includes('N/A - Content missing')) { const fpItems = Array.isArray(details.falsePositives) ? details.falsePositives.map(item => `• ${item}`).join('\n') : `• ${details.falsePositives}`; blocks.push({ type: 'section', text: { type: 'mrkdwn', text: `*Possible False Positives:*\n${fpItems || 'None specified'}` } }); } // Add tags if they exist and are formatted const formattedTags = formatTags(details.tags); if (formattedTags.length > 0) { logger.debug(`Added ${formattedTags.length} formatted tags to the block`); blocks.push({ type: 'context', elements: [ { type: 'mrkdwn', text: `*Tags:* ${formattedTags.join(' | ')}` } ] }); } // If this is an error message, add a troubleshooting section if (isErrorMessage) { blocks.push({ type: 'section', text: { type: 'mrkdwn', text: ':warning: *Troubleshooting:*\nThis rule appears to have issues in the database. You may want to check the rule import process or run a database maintenance task to fix this issue.' } }); } // Add action buttons for better interactivity blocks.push({ type: 'actions', elements: [ { type: 'button', text: { type: 'plain_text', text: 'View YAML', emoji: true }, action_id: 'view_yaml', value: `view_yaml_${details.id}` }, { type: 'button', text: { type: 'plain_text', text: 'Convert to SIEM Rule', emoji: true }, action_id: 'convert_rule_to_siem', value: `convert_rule_to_siem_${details.id}` }, ] }); // Add a divider at the end blocks.push({ type: 'divider' }); logger.debug(`Created ${blocks.length} blocks for rule explanation`); return blocks; } module.exports = { getRuleExplanationBlocks };