create NLP command for details
This commit is contained in:
parent
31d6296c6e
commit
181eade8c4
6 changed files with 348 additions and 10 deletions
29
src/app.js
29
src/app.js
|
@ -3,7 +3,7 @@
|
||||||
*
|
*
|
||||||
* Main application file for Fylgja Slack bot
|
* Main application file for Fylgja Slack bot
|
||||||
* Initializes the Slack Bolt app with custom ExpressReceiver Registers command handlers
|
* Initializes the Slack Bolt app with custom ExpressReceiver Registers command handlers
|
||||||
*
|
* Now supports the universal /fylgja command
|
||||||
*/
|
*/
|
||||||
const { App, ExpressReceiver } = require('@slack/bolt');
|
const { App, ExpressReceiver } = require('@slack/bolt');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
|
@ -13,16 +13,14 @@ const { SIGMA_CLI_PATH, SIGMA_CLI_CONFIG, SLACK_CONFIG } = require('./config/app
|
||||||
const { getFileName } = require('./utils/file_utils');
|
const { getFileName } = require('./utils/file_utils');
|
||||||
const FILE_NAME = getFileName(__filename);
|
const FILE_NAME = getFileName(__filename);
|
||||||
|
|
||||||
// Import individual command handlers
|
// Import the unified fylgja command handler
|
||||||
|
const fylgjaCommandHandler = require('./handlers/fylgja_command_handler');
|
||||||
|
|
||||||
const sigmaDetailsHandler = require('./handlers/sigma/sigma_details_handler');
|
const sigmaDetailsHandler = require('./handlers/sigma/sigma_details_handler');
|
||||||
const sigmaSearchHandler = require('./handlers/sigma/sigma_search_handler');
|
const sigmaSearchHandler = require('./handlers/sigma/sigma_search_handler');
|
||||||
const sigmaCreateHandler = require('./handlers/sigma/sigma_create_handler');
|
const sigmaCreateHandler = require('./handlers/sigma/sigma_create_handler');
|
||||||
// Import the action registry
|
// Import the action registry
|
||||||
const sigmaActionRegistry = require('./handlers/sigma/actions/sigma_action_registry');
|
const sigmaActionRegistry = require('./handlers/sigma/actions/sigma_action_registry');
|
||||||
//const configCommand = require('./commands/config/index.js');
|
|
||||||
//const alertsCommand = require('./commands/alerts/index.js');
|
|
||||||
//const caseCommand = require('./commands/case/index.js');
|
|
||||||
//const statsCommand = require('./commands/stats/index.js');
|
|
||||||
|
|
||||||
// Verify sigma-cli is installed
|
// Verify sigma-cli is installed
|
||||||
if (!fs.existsSync(SIGMA_CLI_PATH)) {
|
if (!fs.existsSync(SIGMA_CLI_PATH)) {
|
||||||
|
@ -49,8 +47,23 @@ const app = new App({
|
||||||
receiver: expressReceiver
|
receiver: expressReceiver
|
||||||
});
|
});
|
||||||
|
|
||||||
// Register individual command handlers for all sigma commands
|
// Register the unified fylgja command handler
|
||||||
logger.info('Registering command handlers');
|
logger.info('Registering unified fylgja command handler');
|
||||||
|
app.command('/fylgja', async ({ command, ack, respond }) => {
|
||||||
|
try {
|
||||||
|
await ack();
|
||||||
|
logger.info(`Received fylgja command: ${command.text}`);
|
||||||
|
await fylgjaCommandHandler.handleCommand(command, respond);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`Error handling fylgja command: ${error.message}`);
|
||||||
|
logger.debug(`Error stack: ${error.stack}`);
|
||||||
|
await respond({
|
||||||
|
text: `An error occurred: ${error.message}`,
|
||||||
|
response_type: 'ephemeral'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
// Register sigma command handlers directly
|
// Register sigma command handlers directly
|
||||||
app.command('/sigma-create', async ({ command, ack, respond }) => {
|
app.command('/sigma-create', async ({ command, ack, respond }) => {
|
||||||
|
|
142
src/handlers/fylgja_command_handler.js
Normal file
142
src/handlers/fylgja_command_handler.js
Normal file
|
@ -0,0 +1,142 @@
|
||||||
|
/**
|
||||||
|
* fylgja_command_handler.js
|
||||||
|
*
|
||||||
|
* Unified command handler for the Fylgja Slack bot.
|
||||||
|
* Processes natural language commands and routes to appropriate handlers.
|
||||||
|
*/
|
||||||
|
const logger = require('../utils/logger');
|
||||||
|
const { handleError } = require('../utils/error_handler');
|
||||||
|
const FILE_NAME = 'fylgja_command_handler.js';
|
||||||
|
|
||||||
|
// Import command handlers
|
||||||
|
const sigmaDetailsHandler = require('./sigma/sigma_details_handler');
|
||||||
|
const sigmaSearchHandler = require('./sigma/sigma_search_handler');
|
||||||
|
const sigmaCreateHandler = require('./sigma/sigma_create_handler');
|
||||||
|
const sigmaStatsHandler = require('./sigma/sigma_stats_handler');
|
||||||
|
|
||||||
|
// Import language processing utilities
|
||||||
|
const commandParser = require('../lang/command_parser');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle the universal fylgja command
|
||||||
|
*
|
||||||
|
* @param {Object} command - The Slack command object
|
||||||
|
* @param {Function} respond - Function to send response back to Slack
|
||||||
|
*/
|
||||||
|
const handleCommand = async (command, respond) => {
|
||||||
|
try {
|
||||||
|
if (!command || !command.text) {
|
||||||
|
logger.warn(`${FILE_NAME}: Empty command received for fylgja`);
|
||||||
|
await respond({
|
||||||
|
text: 'Please provide a command. Try `/fylgja help` for available commands.',
|
||||||
|
response_type: 'ephemeral'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info(`${FILE_NAME}: Processing fylgja command: ${command.text}`);
|
||||||
|
|
||||||
|
// Parse the natural language command
|
||||||
|
const parsedCommand = await commandParser.parseCommand(command.text);
|
||||||
|
|
||||||
|
if (!parsedCommand.success) {
|
||||||
|
logger.warn(`${FILE_NAME}: Failed to parse command: ${command.text}`);
|
||||||
|
await respond({
|
||||||
|
text: `I couldn't understand that command. ${parsedCommand.message || ''}`,
|
||||||
|
response_type: 'ephemeral'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Route to the appropriate handler based on the parsed command
|
||||||
|
await routeCommand(parsedCommand.command, command, respond);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
await handleError(error, `${FILE_NAME}: Command handler`, respond, {
|
||||||
|
responseType: 'ephemeral'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Route the command to the appropriate handler
|
||||||
|
*
|
||||||
|
* @param {Object} parsedCommand - The parsed command object
|
||||||
|
* @param {Object} originalCommand - The original Slack command object
|
||||||
|
* @param {Function} respond - Function to send response back to Slack
|
||||||
|
*/
|
||||||
|
const routeCommand = async (parsedCommand, originalCommand, respond) => {
|
||||||
|
const { action, module, params } = parsedCommand;
|
||||||
|
|
||||||
|
// Create a modified command object with the extracted parameters
|
||||||
|
const modifiedCommand = {
|
||||||
|
...originalCommand,
|
||||||
|
text: params.join(' ')
|
||||||
|
};
|
||||||
|
|
||||||
|
// Log the routing decision
|
||||||
|
logger.debug(`${FILE_NAME}: Routing command - Action: ${action}, Module: ${module}, Params: ${JSON.stringify(params)}`);
|
||||||
|
|
||||||
|
// Route to the appropriate handler
|
||||||
|
switch (`${module}:${action}`) {
|
||||||
|
case 'sigma:details':
|
||||||
|
case 'sigma:explain':
|
||||||
|
await sigmaDetailsHandler.handleCommand(modifiedCommand, respond);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'sigma:search':
|
||||||
|
await sigmaSearchHandler.handleCommand(modifiedCommand, respond);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'sigma:create':
|
||||||
|
await sigmaCreateHandler.handleCommand(modifiedCommand, respond);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'sigma:stats':
|
||||||
|
await sigmaStatsHandler.handleCommand(modifiedCommand, respond);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'help:general':
|
||||||
|
await showHelp(respond);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
logger.warn(`${FILE_NAME}: Unknown command combination: ${module}:${action}`);
|
||||||
|
await respond({
|
||||||
|
text: `I don't know how to ${action} in ${module}. Try \`/fylgja help\` for available commands.`,
|
||||||
|
response_type: 'ephemeral'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show help information
|
||||||
|
*
|
||||||
|
* @param {Function} respond - Function to send response back to Slack
|
||||||
|
*/
|
||||||
|
const showHelp = async (respond) => {
|
||||||
|
await respond({
|
||||||
|
text: "Here are some example commands you can use with Fylgja:",
|
||||||
|
blocks: [
|
||||||
|
{
|
||||||
|
type: "section",
|
||||||
|
text: {
|
||||||
|
type: "mrkdwn",
|
||||||
|
text: "*Fylgja Commands*\nHere are some example commands you can use:"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "section",
|
||||||
|
text: {
|
||||||
|
type: "mrkdwn",
|
||||||
|
text: "• `/fylgja explain rule from sigma where id=<rule_id>`\n• `/fylgja search sigma for <query>`\n• `/fylgja create rule in sigma with <parameters>`\n• `/fylgja show stats for sigma`"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
response_type: 'ephemeral'
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
handleCommand
|
||||||
|
};
|
|
@ -7,7 +7,7 @@
|
||||||
const logger = require('../../utils/logger');
|
const logger = require('../../utils/logger');
|
||||||
const { handleError } = require('../../utils/error_handler');
|
const { handleError } = require('../../utils/error_handler');
|
||||||
const { getSigmaStats } = require('../../services/sigma/sigma_stats_service');
|
const { getSigmaStats } = require('../../services/sigma/sigma_stats_service');
|
||||||
const { getStatsBlocks } = require('../../blocks/sigma_stats_block');
|
const { getStatsBlocks } = require('../../blocks/sigma/sigma_stats_block');
|
||||||
|
|
||||||
const { getFileName } = require('../../utils/file_utils');
|
const { getFileName } = require('../../utils/file_utils');
|
||||||
const FILE_NAME = getFileName(__filename);
|
const FILE_NAME = getFileName(__filename);
|
||||||
|
|
105
src/lang/command_parser.js
Normal file
105
src/lang/command_parser.js
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
/**
|
||||||
|
* command_parser.js
|
||||||
|
*
|
||||||
|
* Provides functionality for parsing commands for the Fylgja bot
|
||||||
|
*/
|
||||||
|
const logger = require('../utils/logger');
|
||||||
|
const FILE_NAME = 'command_parser.js';
|
||||||
|
|
||||||
|
// Import language patterns and synonyms
|
||||||
|
const commandPatterns = require('./command_patterns');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse a natural language command into a structured command object
|
||||||
|
*
|
||||||
|
* @param {string} commandText - The natural language command text
|
||||||
|
* @returns {Promise<Object>} Result object with success flag and parsed command or error message
|
||||||
|
*/
|
||||||
|
const parseCommand = async (commandText) => {
|
||||||
|
try {
|
||||||
|
logger.debug(`${FILE_NAME}: Parsing command: ${commandText}`);
|
||||||
|
|
||||||
|
if (!commandText || typeof commandText !== 'string') {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: 'Empty or invalid command.'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert to lowercase for case-insensitive matching
|
||||||
|
const normalizedCommand = commandText.toLowerCase().trim();
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
// Handle help command separately
|
||||||
|
if (normalizedCommand === 'help') {
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
command: {
|
||||||
|
action: 'general',
|
||||||
|
module: 'help',
|
||||||
|
params: []
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to match command against known patterns
|
||||||
|
for (const pattern of commandPatterns) {
|
||||||
|
const match = matchPattern(normalizedCommand, pattern);
|
||||||
|
if (match) {
|
||||||
|
logger.debug(`${FILE_NAME}: Command matched pattern: ${pattern.name}`);
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
command: match
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we reach here, no pattern matched
|
||||||
|
logger.warn(`${FILE_NAME}: No pattern matched for command: ${commandText}`);
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: "I couldn't understand that command. Try `/fylgja help` for examples."
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`${FILE_NAME}: Error parsing command: ${error.message}`);
|
||||||
|
logger.debug(`${FILE_NAME}: Error stack: ${error.stack}`);
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: `Error parsing command: ${error.message}`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Match a command against a pattern
|
||||||
|
*
|
||||||
|
* @param {string} command - The normalized command text
|
||||||
|
* @param {Object} pattern - The pattern object to match against
|
||||||
|
* @returns {Object|null} Parsed command object or null if no match
|
||||||
|
*/
|
||||||
|
const matchPattern = (command, pattern) => {
|
||||||
|
// Check if the command matches the regex pattern
|
||||||
|
const match = pattern.regex.exec(command);
|
||||||
|
if (!match) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract parameters based on the pattern's parameter mapping
|
||||||
|
const params = [];
|
||||||
|
for (const paramIndex of pattern.params) {
|
||||||
|
if (match[paramIndex]) {
|
||||||
|
params.push(match[paramIndex].trim());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the structured command
|
||||||
|
return {
|
||||||
|
action: pattern.action,
|
||||||
|
module: pattern.module,
|
||||||
|
params
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
parseCommand
|
||||||
|
};
|
78
src/lang/command_patterns.js
Normal file
78
src/lang/command_patterns.js
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
/**
|
||||||
|
* command_patterns.js
|
||||||
|
*
|
||||||
|
* Defines pattern matching rules for natural language commands
|
||||||
|
* Each pattern includes a regex and mapping for parameter extraction
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Command patterns array
|
||||||
|
* Each pattern object contains:
|
||||||
|
* - name: A descriptive name for the pattern
|
||||||
|
* - regex: A regular expression to match the command
|
||||||
|
* - action: The action to perform (e.g., details, search)
|
||||||
|
* - module: The module to use (e.g., sigma, alerts)
|
||||||
|
* - params: Array of capturing group indices to extract parameters
|
||||||
|
*/
|
||||||
|
const commandPatterns = [
|
||||||
|
// Sigma details patterns
|
||||||
|
{
|
||||||
|
name: 'sigma-details-direct',
|
||||||
|
regex: /^(explain|get|show|display|details|info|about)\s+(rule|detection)\s+(from\s+)?sigma\s+(where\s+)?(id=|id\s+is\s+|with\s+id\s+)(.+)$/i,
|
||||||
|
action: 'details',
|
||||||
|
module: 'sigma',
|
||||||
|
params: [6] // rule ID is in capturing group 6
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'sigma-details-simple',
|
||||||
|
regex: /^(details|explain)\s+(.+)$/i,
|
||||||
|
action: 'details',
|
||||||
|
module: 'sigma',
|
||||||
|
params: [2] // rule ID is in capturing group 2
|
||||||
|
},
|
||||||
|
|
||||||
|
// Sigma search patterns
|
||||||
|
{
|
||||||
|
name: 'sigma-search',
|
||||||
|
regex: /^(search|find|look\s+for)\s+(rules|detections)?\s*(in|from)?\s*sigma\s+(for|where|with)?\s+(.+)$/i,
|
||||||
|
action: 'search',
|
||||||
|
module: 'sigma',
|
||||||
|
params: [5] // search query is in capturing group 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'sigma-search-simple',
|
||||||
|
regex: /^(search|find)\s+(.+)$/i,
|
||||||
|
action: 'search',
|
||||||
|
module: 'sigma',
|
||||||
|
params: [2] // search query is in capturing group 2
|
||||||
|
},
|
||||||
|
|
||||||
|
// Sigma create patterns
|
||||||
|
{
|
||||||
|
name: 'sigma-create',
|
||||||
|
regex: /^(create|new|add)\s+(rule|detection)\s+(in|to|for)?\s*sigma\s+(with|using)?\s+(.+)$/i,
|
||||||
|
action: 'create',
|
||||||
|
module: 'sigma',
|
||||||
|
params: [5] // creation parameters in capturing group 5
|
||||||
|
},
|
||||||
|
|
||||||
|
// Sigma stats patterns
|
||||||
|
{
|
||||||
|
name: 'sigma-stats',
|
||||||
|
regex: /^(stats|statistics|metrics|counts)\s+(for|about|on|of)?\s*sigma$/i,
|
||||||
|
action: 'stats',
|
||||||
|
module: 'sigma',
|
||||||
|
params: []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'sigma-stats-show',
|
||||||
|
regex: /^(show|get|display)\s+(stats|statistics|metrics|counts)\s+(for|about|on|of)?\s*sigma$/i,
|
||||||
|
action: 'stats',
|
||||||
|
module: 'sigma',
|
||||||
|
params: []
|
||||||
|
}
|
||||||
|
|
||||||
|
// Additional command patterns for other modules can be added here
|
||||||
|
];
|
||||||
|
|
||||||
|
module.exports = commandPatterns;
|
Loading…
Add table
Add a link
Reference in a new issue