diff --git a/src/blocks/sigma/sigma_details_block.js b/src/blocks/sigma/sigma_details_block.js index d79a410..89ca272 100644 --- a/src/blocks/sigma/sigma_details_block.js +++ b/src/blocks/sigma/sigma_details_block.js @@ -15,7 +15,7 @@ const FILE_NAME = getFileName(__filename); * @param {Object} details - The rule details object containing all rule metadata * @returns {Array} Formatted Slack blocks ready for display */ -function getRuleExplanationBlocks(details) { +function getSigmaRuleDetailsBlocks(details) { logger.debug(`${FILE_NAME}: Creating rule explanation blocks for rule: ${details?.id || 'unknown'}`); if (!details) { @@ -294,5 +294,5 @@ function getRuleExplanationBlocks(details) { } module.exports = { - getRuleExplanationBlocks + getSigmaRuleDetailsBlocks }; diff --git a/src/handlers/sigma/actions/sigma_action_core.js b/src/handlers/sigma/actions/sigma_action_core.js index 7a9af17..dd79157 100644 --- a/src/handlers/sigma/actions/sigma_action_core.js +++ b/src/handlers/sigma/actions/sigma_action_core.js @@ -5,9 +5,9 @@ */ const logger = require('../../../utils/logger'); const { handleError } = require('../../../utils/error_handler'); -const { explainSigmaRule } = require('../../../services/sigma/sigma_details_service'); +const { getSigmaRuleDetails } = require('../../../services/sigma/sigma_details_service'); const { convertRuleToBackend } = require('../../../services/sigma/sigma_backend_converter'); -const { getRuleExplanationBlocks } = require('../../../blocks/sigma/sigma_details_block'); +const { getSigmaRuleDetailsBlocks } = require('../../../blocks/sigma/sigma_details_block'); const { getConversionResultBlocks } = require('../../../blocks/sigma/sigma_conversion_block'); const { SIGMA_CLI_CONFIG } = require('../../../config/appConfig'); @@ -38,8 +38,8 @@ const processRuleDetails = async (ruleId, respond, replaceOriginal = false, resp logger.info(`${FILE_NAME}: Processing details for sigma rule: ${ruleId}`); // Get Sigma rule details - logger.info(`${FILE_NAME}: Calling explainSigmaRule with ID: '${ruleId}'`); - const result = await explainSigmaRule(ruleId); + logger.info(`${FILE_NAME}: Calling getSigmaRuleDetails with ID: '${ruleId}'`); + const result = await getSigmaRuleDetails(ruleId); if (!result.success) { logger.error(`${FILE_NAME}: Rule details retrieval failed: ${result.message}`); @@ -66,7 +66,7 @@ const processRuleDetails = async (ruleId, respond, replaceOriginal = false, resp // Generate blocks let blocks; try { - blocks = getRuleExplanationBlocks(result.explanation); + blocks = getSigmaRuleDetailsBlocks(result.explanation); } catch (blockError) { await handleError(blockError, `${FILE_NAME}: Block generation`, respond, { replaceOriginal: replaceOriginal, diff --git a/src/handlers/sigma/actions/sigma_siem_actions.js b/src/handlers/sigma/actions/sigma_siem_actions.js index d446fcd..72fc9e3 100644 --- a/src/handlers/sigma/actions/sigma_siem_actions.js +++ b/src/handlers/sigma/actions/sigma_siem_actions.js @@ -5,7 +5,7 @@ */ const logger = require('../../../utils/logger'); const { handleError } = require('../../../utils/error_handler'); -const { explainSigmaRule } = require('../../../services/sigma/sigma_details_service'); +const { getSigmaRuleDetails } = require('../../../services/sigma/sigma_details_service'); const { convertRuleToBackend } = require('../../../services/sigma/sigma_backend_converter'); const { sendRuleToSiem } = require('../../../services/elastic/elastic_send_rule_to_siem_service'); const { getAllSpaces } = require('../../../services/elastic/elastic_api_service'); @@ -210,8 +210,8 @@ const registerSiemActions = (app) => { const ruleId = actionValue.replace('select_space_for_rule_', ''); // Get rule information to display in the space selection message - const explainResult = await explainSigmaRule(ruleId); - const ruleInfo = explainResult.success ? explainResult.explanation : { title: ruleId }; + const sigmaRuleDetailsResult = await getSigmaRuleDetails(ruleId); + const ruleInfo = sigmaRuleDetailsResult.success ? sigmaRuleDetailsResult.explanation : { title: ruleId }; // Generate blocks for space selection const blocks = getSpaceSelectionBlocks(ruleId, ruleInfo); diff --git a/src/handlers/sigma/sigma_details_handler.js b/src/handlers/sigma/sigma_details_handler.js index 55844b3..e3896e8 100644 --- a/src/handlers/sigma/sigma_details_handler.js +++ b/src/handlers/sigma/sigma_details_handler.js @@ -1,27 +1,34 @@ /** * sigma_details_handler.js * - * Handles Sigma rule details requests from Slack commands + * Handles Sigma rule details requests from both Slack commands and CLI * Processes requests for rule explanations */ const logger = require('../../utils/logger'); const { handleError } = require('../../utils/error_handler'); -const { explainSigmaRule } = require('../../services/sigma/sigma_details_service'); -const { processRuleDetails } = require('./actions/sigma_action_core'); -const FILE_NAME = 'sigma_details_handler.js'; +const { getSigmaRuleDetails, getSigmaRuleYaml } = require('../../services/sigma/sigma_details_service'); +const { getSigmaRuleDetailsBlocks } = require('../../blocks/sigma/sigma_details_block'); +const { formatSigmaDetails } = require('../../utils/cli_formatters'); + +const { getFileName } = require('../../utils/file_utils'); +const FILE_NAME = getFileName(__filename); + /** * Handle the sigma-details command for Sigma rules * - * @param {Object} command - The Slack command object - * @param {Function} respond - Function to send response back to Slack + * @param {Object} command - The Slack command or CLI command object + * @param {Function} respond - Function to send response back to Slack or CLI */ const handleCommand = async (command, respond) => { try { - logger.debug(`${FILE_NAME}: Processing sigma-details command: ${JSON.stringify(command.text)}`); + logger.debug(`${FILE_NAME}: Processing sigma-details command: ${command.text}`); if (!command || !command.text) { logger.warn(`${FILE_NAME}: Empty command received for sigma-details`); - await respond('Invalid command. Usage: /sigma-details [id]'); + await respond({ + text: 'Invalid command. Usage: /sigma-details [id] or "details sigma [id]"', + response_type: 'ephemeral' + }); return; } @@ -30,7 +37,10 @@ const handleCommand = async (command, respond) => { if (!ruleId) { logger.warn(`${FILE_NAME}: Missing rule ID in sigma-details command`); - await respond('Invalid command: missing rule ID. Usage: /sigma-details [id]'); + await respond({ + text: 'Invalid command: missing rule ID. Usage: /sigma-details [id] or "details sigma [id]"', + response_type: 'ephemeral' + }); return; } @@ -40,14 +50,44 @@ const handleCommand = async (command, respond) => { response_type: 'ephemeral' }); - // Use the shared processRuleDetails function from action handlers - await processRuleDetails(ruleId, respond, false, 'in_channel'); + // Get the rule explanation + const sigmaRuleDetailsResult = await getSigmaRuleDetails(ruleId); + + if (!sigmaRuleDetailsResult.success) { + logger.warn(`${FILE_NAME}: Failed to explain rule ${ruleId}: ${sigmaRuleDetailsResult.message}`); + await respond({ + text: `Error: ${sigmaRuleDetailsResult.message}`, + response_type: 'ephemeral' + }); + return; + } + + // For Slack responses, generate Block Kit blocks + let blocks; + try { + // This is for Slack - get the Block Kit UI components + blocks = getSigmaRuleDetailsBlocks(sigmaRuleDetailsResult.explanation); + } catch (blockError) { + await handleError(blockError, `${FILE_NAME}: Block generation`, respond, { + responseType: 'ephemeral', + customMessage: 'Error generating rule details view' + }); + return; + } + + // Return the response with both blocks for Slack and responseData for CLI + await respond({ + blocks: blocks, // For Slack + responseData: sigmaRuleDetailsResult.explanation, // For CLI + response_type: 'in_channel' + }); } catch (error) { await handleError(error, `${FILE_NAME}: Details command handler`, respond, { responseType: 'ephemeral' }); } }; + module.exports = { handleCommand }; \ No newline at end of file diff --git a/src/services/sigma/sigma_details_service.js b/src/services/sigma/sigma_details_service.js index e0dd364..21031a0 100644 --- a/src/services/sigma/sigma_details_service.js +++ b/src/services/sigma/sigma_details_service.js @@ -17,7 +17,7 @@ const FILE_NAME = getFileName(__filename); * @param {string} ruleId - The ID of the rule to explain * @returns {Promise} Result object with success flag and explanation or error message */ -async function explainSigmaRule(ruleId) { +async function getSigmaRuleDetails(ruleId) { if (!ruleId) { logger.warn(`${FILE_NAME}: Cannot explain rule: Missing rule ID`); return { @@ -145,6 +145,6 @@ async function getSigmaRuleYaml(ruleId) { } module.exports = { - explainSigmaRule, + getSigmaRuleDetails, getSigmaRuleYaml }; \ No newline at end of file diff --git a/src/services/sigma/sigma_stats_service.js b/src/services/sigma/sigma_stats_service.js index 20cac15..81934d1 100644 --- a/src/services/sigma/sigma_stats_service.js +++ b/src/services/sigma/sigma_stats_service.js @@ -48,7 +48,9 @@ async function getSigmaStats() { return { success: true, stats: formattedStats, - // Include raw response data for direct use by CLI + // Include raw response data for direct use by CLI. + // We have one universal function in the CLI to receive responses, + // and the CLI will then format each result differently responseData: formattedStats }; } catch (error) { diff --git a/src/utils/cli_formatters.js b/src/utils/cli_formatters.js index 49636cd..44ae36a 100644 --- a/src/utils/cli_formatters.js +++ b/src/utils/cli_formatters.js @@ -6,6 +6,36 @@ */ const chalk = require('chalk'); + +/** + * Format Sigma rule details for CLI display + * @param {Object} ruleDetails - The rule details to format + * @returns {Object} Formatted rule details for CLI display + */ +function formatSigmaDetails(ruleDetails) { + if (!ruleDetails) { + return null; + } + + // Create a flattened object for display in CLI table format + const formattedDetails = { + 'ID': ruleDetails.id || 'Unknown', + 'Title': ruleDetails.title || 'Untitled Rule', + 'Description': ruleDetails.description || 'No description provided', + 'Author': ruleDetails.author || 'Unknown author', + 'Severity': ruleDetails.severity || 'Unknown', + 'Detection': ruleDetails.detectionExplanation || 'No detection specified', + 'False Positives': Array.isArray(ruleDetails.falsePositives) ? + ruleDetails.falsePositives.join(', ') : 'None specified', + 'Tags': Array.isArray(ruleDetails.tags) ? + ruleDetails.tags.join(', ') : 'None', + 'References': Array.isArray(ruleDetails.references) ? + ruleDetails.references.join(', ') : 'None' + }; + + return formattedDetails; +} + /** * Format Sigma statistics for CLI display * @@ -92,77 +122,6 @@ function formatSigmaSearchResults(searchResults) { }; } -/** - * Format Sigma rule details for CLI display - * - * @param {Object} ruleDetails - The rule details object - * @returns {Object} Formatted details ready for CLI display - */ -function formatSigmaDetails(ruleDetails) { - if (!ruleDetails) { - return { error: 'No rule details available' }; - } - - // Filter and format the rule details for CLI display - const formattedDetails = {}; - - // Include only the most important fields for display - const fieldsToInclude = [ - 'id', 'title', 'description', 'status', 'author', - 'level', 'falsepositives', 'references', - 'created', 'modified' - ]; - - // Add detection information if available - if (ruleDetails.detection && ruleDetails.detection.condition) { - fieldsToInclude.push('detection_condition'); - formattedDetails['detection_condition'] = ruleDetails.detection.condition; - } - - // Add logsource information if available - if (ruleDetails.logsource) { - if (ruleDetails.logsource.product) { - fieldsToInclude.push('logsource_product'); - formattedDetails['logsource_product'] = ruleDetails.logsource.product; - } - - if (ruleDetails.logsource.category) { - fieldsToInclude.push('logsource_category'); - formattedDetails['logsource_category'] = ruleDetails.logsource.category; - } - - if (ruleDetails.logsource.service) { - fieldsToInclude.push('logsource_service'); - formattedDetails['logsource_service'] = ruleDetails.logsource.service; - } - } - - // Format date fields - const dateFields = ['created', 'modified']; - - for (const [key, value] of Object.entries(ruleDetails)) { - if (fieldsToInclude.includes(key)) { - // Format dates - if (dateFields.includes(key) && value) { - try { - formattedDetails[key] = new Date(value).toLocaleString(); - } catch (e) { - formattedDetails[key] = value; - } - } - // Format arrays - else if (Array.isArray(value)) { - formattedDetails[key] = value.join(', '); - } - // Default handling - else { - formattedDetails[key] = value; - } - } - } - - return formattedDetails; -} module.exports = { formatSigmaStats,