From ad6b108d3feb6b92eec8d6f4a091c165118fcd19 Mon Sep 17 00:00:00 2001 From: Charlotte Croce Date: Sat, 19 Apr 2025 14:02:30 -0400 Subject: [PATCH 1/3] add os and category labels to details and conversion slack blocks --- src/blocks/sigma/sigma_conversion_block.js | 20 +++++++++ src/blocks/sigma/sigma_details_block.js | 24 +++++++++- .../sigma/sigma_search_results_block.js | 17 ++++++- src/handlers/sigma/sigma_search_handler.js | 5 ++- src/services/sigma/sigma_details_service.js | 45 ++++++++++--------- 5 files changed, 84 insertions(+), 27 deletions(-) diff --git a/src/blocks/sigma/sigma_conversion_block.js b/src/blocks/sigma/sigma_conversion_block.js index 8190e6f..3932a9f 100644 --- a/src/blocks/sigma/sigma_conversion_block.js +++ b/src/blocks/sigma/sigma_conversion_block.js @@ -40,6 +40,13 @@ function getConversionResultBlocks(conversionResult) { format: 'siem_rule_ndjson' }; + // Extract logsource information or use defaults + const logsource = rule.logsource || {}; + const product = logsource.product || 'N/A'; + const category = logsource.category || 'N/A'; + + logger.debug(`${FILE_NAME}: Logsource info - Product: ${product}, Category: ${category}`); + // Truncate output if it's too long for Slack let output = conversionResult.output || ''; const maxOutputLength = 2900; // Slack has a limit of ~3000 chars in a code block @@ -66,6 +73,19 @@ function getConversionResultBlocks(conversionResult) { text: `*Rule ID:* ${rule.id}\n*Description:* ${rule.description}` } }, + { + type: 'section', + fields: [ + { + type: 'mrkdwn', + text: `*OS/Product:* ${product}` + }, + { + type: 'mrkdwn', + text: `*Category:* ${category}` + } + ] + }, { type: 'section', text: { diff --git a/src/blocks/sigma/sigma_details_block.js b/src/blocks/sigma/sigma_details_block.js index 89ca272..ccf175b 100644 --- a/src/blocks/sigma/sigma_details_block.js +++ b/src/blocks/sigma/sigma_details_block.js @@ -193,6 +193,28 @@ function getSigmaRuleDetailsBlocks(details) { } ]; + // Get logsource information from the details + const logsource = details.logsource || {}; + const product = logsource.product || 'N/A'; + const category = logsource.category || 'N/A'; + + logger.debug(`${FILE_NAME}: Logsource info - Product: ${product}, Category: ${category}`); + + // Add logsource information section after severity/author + blocks.push({ + type: 'section', + fields: [ + { + type: 'mrkdwn', + text: `*OS/Product:* ${product}` + }, + { + type: 'mrkdwn', + text: `*Category:* ${category}` + } + ] + }); + // Add divider for visual separation blocks.push({ type: 'divider' }); @@ -295,4 +317,4 @@ function getSigmaRuleDetailsBlocks(details) { module.exports = { getSigmaRuleDetailsBlocks -}; +}; \ No newline at end of file diff --git a/src/blocks/sigma/sigma_search_results_block.js b/src/blocks/sigma/sigma_search_results_block.js index e118c17..6ab7aef 100644 --- a/src/blocks/sigma/sigma_search_results_block.js +++ b/src/blocks/sigma/sigma_search_results_block.js @@ -78,12 +78,25 @@ const getSearchResultBlocks = (keyword, results, pagination = {}) => { const ruleId = safeRule.id || 'unknown'; logger.debug(`${FILE_NAME}: Adding result #${index + 1}: ${ruleId} - ${safeRule.title || 'Untitled'}`); - // Combine rule information and action button into a single line + // Get OS emoji based on product + const getOsEmoji = (product) => { + if (!product) return ''; + + const productLower = product.toLowerCase(); + if (productLower.includes('windows')) return ':window: '; + if (productLower.includes('mac') || productLower.includes('apple')) return ':apple: '; + if (productLower.includes('linux')) return ':penguin: '; + return ''; + }; + + const osEmoji = getOsEmoji(safeRule.logsource && safeRule.logsource.product); + + // Rule information and action button - with OS emoji before title and no ID field blocks.push({ "type": "section", "text": { "type": "mrkdwn", - "text": `*${safeRule.title || 'Untitled Rule'}*\nID: \`${ruleId}\`` + "text": `*${osEmoji}${safeRule.title || 'Untitled Rule'}*` }, "accessory": { "type": "button", diff --git a/src/handlers/sigma/sigma_search_handler.js b/src/handlers/sigma/sigma_search_handler.js index f6d9e17..2932b8d 100644 --- a/src/handlers/sigma/sigma_search_handler.js +++ b/src/handlers/sigma/sigma_search_handler.js @@ -4,7 +4,7 @@ * Handles Sigma rule search requests from Slack commands */ -const { searchSigmaRules, searchSigmaRulesComplex } = require('../../services/sigma/sigma_search_service'); +const { searchSigmaRules, searchSigmaRulesComplex, searchAndConvertRules } = require('../../services/sigma/sigma_search_service'); const logger = require('../../utils/logger'); const { handleError } = require('../../utils/error_handler'); const { getSearchResultBlocks } = require('../../blocks/sigma/sigma_search_results_block'); @@ -69,7 +69,7 @@ const handleCommand = async (command, respond) => { }); // Search for rules using the service function with pagination - const searchResult = await searchSigmaRules(keyword, page, pageSize); + const searchResult = await searchAndConvertRules(keyword, page, pageSize); logger.debug(`${FILE_NAME}: Search result status: ${searchResult.success}`); logger.debug(`${FILE_NAME}: Found ${searchResult.results?.length || 0} results out of ${searchResult.pagination?.totalResults || 0} total matches`); logger.debug(`${FILE_NAME}: About to generate blocks for search results`); @@ -240,6 +240,7 @@ const handleComplexSearch = async (command, respond) => { ); // Replace the header to indicate it's a complex search + // TODO: should be moved to dedicated block file if (blocks && blocks.length > 0) { blocks[0] = { type: "header", diff --git a/src/services/sigma/sigma_details_service.js b/src/services/sigma/sigma_details_service.js index 21031a0..021611b 100644 --- a/src/services/sigma/sigma_details_service.js +++ b/src/services/sigma/sigma_details_service.js @@ -25,15 +25,15 @@ async function getSigmaRuleDetails(ruleId) { message: 'Missing rule ID' }; } - + logger.info(`${FILE_NAME}: Running diagnostics for rule: ${ruleId}`); logger.info(`${FILE_NAME}: Explaining rule ${ruleId}`); - + try { // Run diagnostics on the rule content first const diagnosticResult = await debugRuleContent(ruleId); logger.debug(`${FILE_NAME}: Diagnostic result: ${JSON.stringify(diagnosticResult || {})}`); - + // Convert the rule ID to a structured object const conversionResult = await convertSigmaRule(ruleId); if (!conversionResult.success) { @@ -43,9 +43,9 @@ async function getSigmaRuleDetails(ruleId) { message: conversionResult.message || `Failed to parse rule with ID ${ruleId}` }; } - + const rule = conversionResult.rule; - + // Extra safety check if (!rule) { logger.error(`${FILE_NAME}: Converted rule is null for ID ${ruleId}`); @@ -54,7 +54,7 @@ async function getSigmaRuleDetails(ruleId) { message: `Failed to process rule with ID ${ruleId}` }; } - + // Create a simplified explanation with safe access to properties const explanation = { id: rule.id || ruleId, @@ -62,27 +62,28 @@ async function getSigmaRuleDetails(ruleId) { description: rule.description || 'No description provided', author: rule.author || 'Unknown author', severity: rule.level || 'Unknown', + logsource: rule.logsource || {}, // Add this line to include logsource info detectionExplanation: extractDetectionCondition(rule), falsePositives: Array.isArray(rule.falsepositives) ? rule.falsepositives : - typeof rule.falsepositives === 'string' ? [rule.falsepositives] : - ['None specified'], + typeof rule.falsepositives === 'string' ? [rule.falsepositives] : + ['None specified'], tags: Array.isArray(rule.tags) ? rule.tags : [], references: Array.isArray(rule.references) ? rule.references : [] }; - + logger.info(`${FILE_NAME}: Successfully explained rule ${ruleId}`); logger.debug(`${FILE_NAME}: Explanation properties: ${Object.keys(explanation).join(', ')}`); - - return { - success: true, - explanation + + return { + success: true, + explanation }; } catch (error) { logger.error(`${FILE_NAME}: Error explaining rule: ${error.message}`); logger.debug(`${FILE_NAME}: Error stack: ${error.stack}`); - return { - success: false, - message: `Error explaining rule: ${error.message}` + return { + success: false, + message: `Error explaining rule: ${error.message}` }; } } @@ -102,13 +103,13 @@ async function getSigmaRuleYaml(ruleId) { message: 'Missing rule ID' }; } - + logger.info(`${FILE_NAME}: Getting YAML content for rule: ${ruleId}`); - + try { // Get YAML content from database const yamlResult = await getRuleYamlContent(ruleId); - + if (!yamlResult.success) { logger.warn(`${FILE_NAME}: Failed to retrieve YAML for rule ${ruleId}: ${yamlResult.message}`); return { @@ -116,7 +117,7 @@ async function getSigmaRuleYaml(ruleId) { message: yamlResult.message || `Failed to retrieve YAML for rule with ID ${ruleId}` }; } - + // Add extra safety check for content if (!yamlResult.content) { logger.warn(`${FILE_NAME}: YAML content is empty for rule ${ruleId}`); @@ -126,9 +127,9 @@ async function getSigmaRuleYaml(ruleId) { warning: 'YAML content is empty for this rule' }; } - + logger.debug(`${FILE_NAME}: Successfully retrieved YAML content with length: ${yamlResult.content.length}`); - + // Return the YAML content return { success: true, From 977dd7e6d3848d971855b819a5d9560d2d43f565 Mon Sep 17 00:00:00 2001 From: Charlotte Croce Date: Sat, 19 Apr 2025 14:15:12 -0400 Subject: [PATCH 2/3] fixed emoji pagation issue --- .gitignore | 1 + .../sigma/sigma_search_results_block.js | 15 +---- .../sigma/actions/sigma_view_actions.js | 4 +- src/utils/os_emojis.js | 65 +++++++++++++++++++ 4 files changed, 71 insertions(+), 14 deletions(-) create mode 100644 src/utils/os_emojis.js diff --git a/.gitignore b/.gitignore index 19ad939..4608f5e 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ fylgja.yml slack.yml sigma.db sigma-repo/ +.VSCodeCounter \ No newline at end of file diff --git a/src/blocks/sigma/sigma_search_results_block.js b/src/blocks/sigma/sigma_search_results_block.js index 6ab7aef..c459375 100644 --- a/src/blocks/sigma/sigma_search_results_block.js +++ b/src/blocks/sigma/sigma_search_results_block.js @@ -8,6 +8,7 @@ const logger = require('../../utils/logger'); const { getFileName } = require('../../utils/file_utils'); +const { getProductEmoji } = require('../../utils/os_emojis'); const FILE_NAME = getFileName(__filename); /** @@ -78,18 +79,8 @@ const getSearchResultBlocks = (keyword, results, pagination = {}) => { const ruleId = safeRule.id || 'unknown'; logger.debug(`${FILE_NAME}: Adding result #${index + 1}: ${ruleId} - ${safeRule.title || 'Untitled'}`); - // Get OS emoji based on product - const getOsEmoji = (product) => { - if (!product) return ''; - - const productLower = product.toLowerCase(); - if (productLower.includes('windows')) return ':window: '; - if (productLower.includes('mac') || productLower.includes('apple')) return ':apple: '; - if (productLower.includes('linux')) return ':penguin: '; - return ''; - }; - - const osEmoji = getOsEmoji(safeRule.logsource && safeRule.logsource.product); + // Get product emoji + const osEmoji = getProductEmoji(safeRule.logsource && safeRule.logsource.product); // Rule information and action button - with OS emoji before title and no ID field blocks.push({ diff --git a/src/handlers/sigma/actions/sigma_view_actions.js b/src/handlers/sigma/actions/sigma_view_actions.js index d896f31..09d07f3 100644 --- a/src/handlers/sigma/actions/sigma_view_actions.js +++ b/src/handlers/sigma/actions/sigma_view_actions.js @@ -6,7 +6,7 @@ const logger = require('../../../utils/logger'); const { handleError } = require('../../../utils/error_handler'); const { getSigmaRuleYaml } = require('../../../services/sigma/sigma_details_service'); -const { searchSigmaRules } = require('../../../services/sigma/sigma_search_service'); +const { searchSigmaRules, searchAndConvertRules } = require('../../../services/sigma/sigma_search_service'); const { getYamlViewBlocks } = require('../../../blocks/sigma/sigma_view_yaml_block'); const { getSearchResultBlocks } = require('../../../blocks/sigma/sigma_search_results_block'); const { processRuleDetails } = require('./sigma_action_core'); @@ -62,7 +62,7 @@ const handlePaginationAction = async (body, ack, respond) => { logger.info(`${FILE_NAME}: Processing pagination request for "${keyword}" (page ${page}, size ${pageSize})`); // Perform the search with the new pagination parameters - const searchResult = await searchSigmaRules(keyword, page, pageSize); + const searchResult = await searchAndConvertRules(keyword, page, pageSize); if (!searchResult.success) { logger.error(`${FILE_NAME}: Search failed during pagination: ${searchResult.message}`); diff --git a/src/utils/os_emojis.js b/src/utils/os_emojis.js new file mode 100644 index 0000000..4d135e9 --- /dev/null +++ b/src/utils/os_emojis.js @@ -0,0 +1,65 @@ +/** + * os_emojis.js + * + * Provides emoji mappings for different products/platforms in Sigma rules + */ + +/** + * Get the appropriate emoji for a product + * @param {string} product - The product/platform name + * @returns {string} - The corresponding emoji string + */ +const getProductEmoji = (product) => { + if (!product) return ''; + + const productLower = product.toLowerCase(); + + // Mapping of products to their respective emojis + const emojiMap = { + 'aws': ':cloud:', + 'azure': ':cloud:', + 'bitbucket': ':bucket:', + 'cisco': ':satellite_antenna:', + 'django': ':snake:', + 'dns': ':globe_with_meridians:', + 'fortios': ':shield:', + 'gcp': ':cloud:', + 'github': ':octocat:', + 'huawei': ':satellite_antenna:', + 'juniper': ':satellite_antenna:', + 'jvm': ':coffee:', + 'kubernetes': ':wheel_of_dharma:', + 'linux': ':penguin:', + 'm365': ':envelope:', + 'macos': ':apple:', + 'modsecurity': ':shield:', + 'nodejs': ':green_heart:', + 'okta': ':key:', + 'onelogin': ':key:', + 'opencanary': ':bird:', + 'paloalto': ':shield:', + 'python': ':snake:', + 'qualys': ':mag:', + 'rpc_firewall': ':fire_extinguisher:', + 'ruby_on_rails': ':gem:', + 'spring': ':leaves:', + 'sql': ':floppy_disk:', + 'velocity': ':zap:', + 'windows': ':window:', + 'zeek': ':eyes:' + }; + + // Check if the product is directly in our map + for (const [key, emoji] of Object.entries(emojiMap)) { + if (productLower.includes(key)) { + return emoji + ' '; + } + } + + // Default emoji for unknown products + return ':computer: '; + }; + + module.exports = { + getProductEmoji + }; \ No newline at end of file From 96892cb94b629b3e6773b1ebc49a69c0dd16b06b Mon Sep 17 00:00:00 2001 From: Charlotte Croce Date: Sat, 19 Apr 2025 14:18:22 -0400 Subject: [PATCH 3/3] fix command-and-control tag link --- src/blocks/sigma/sigma_details_block.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/blocks/sigma/sigma_details_block.js b/src/blocks/sigma/sigma_details_block.js index ccf175b..5ac5217 100644 --- a/src/blocks/sigma/sigma_details_block.js +++ b/src/blocks/sigma/sigma_details_block.js @@ -112,7 +112,7 @@ function getSigmaRuleDetailsBlocks(details) { 'discovery': 'TA0007', 'lateralmovement': 'TA0008', 'collection': 'TA0009', - 'command-and-control': 'TA0011', + 'commandandcontrol': 'TA0011', 'exfiltration': 'TA0010', 'impact': 'TA0040' };