rename explain-sigma-rules to sigma-rule-details

This commit is contained in:
Charlotte Croce 2025-04-19 12:44:45 -04:00
parent 519c87fb04
commit 657a33a189
7 changed files with 96 additions and 95 deletions

View file

@ -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
};

View file

@ -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,

View file

@ -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);

View file

@ -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
};

View file

@ -17,7 +17,7 @@ const FILE_NAME = getFileName(__filename);
* @param {string} ruleId - The ID of the rule to explain
* @returns {Promise<Object>} 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
};

View file

@ -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) {

View file

@ -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,