CLI formatting. no tables just tabs. and colors
This commit is contained in:
parent
282f6b74b6
commit
8c725be8a1
4 changed files with 260 additions and 108 deletions
|
@ -3,27 +3,6 @@
|
||||||
*
|
*
|
||||||
* Interactive CLI interface
|
* Interactive CLI interface
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const readline = require('readline');
|
|
||||||
// Import chalk with compatibility for both ESM and CommonJS
|
|
||||||
let chalk;
|
|
||||||
try {
|
|
||||||
// First try CommonJS import (chalk v4.x)
|
|
||||||
chalk = require('chalk');
|
|
||||||
} catch (e) {
|
|
||||||
// If that fails, provide a fallback implementation
|
|
||||||
chalk = {
|
|
||||||
blue: (text) => text,
|
|
||||||
green: (text) => text,
|
|
||||||
red: (text) => text,
|
|
||||||
yellow: (text) => text,
|
|
||||||
cyan: (text) => text,
|
|
||||||
white: (text) => text,
|
|
||||||
dim: (text) => text,
|
|
||||||
hex: () => (text) => text
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const { parseCommand } = require('./lang/command_parser');
|
const { parseCommand } = require('./lang/command_parser');
|
||||||
const logger = require('./utils/logger');
|
const logger = require('./utils/logger');
|
||||||
const sigmaSearchHandler = require('./handlers/sigma/sigma_search_handler');
|
const sigmaSearchHandler = require('./handlers/sigma/sigma_search_handler');
|
||||||
|
@ -34,6 +13,58 @@ const { handleCommand: handleAlerts } = require('./handlers/alerts/alerts_handle
|
||||||
const { handleCommand: handleCase } = require('./handlers/case/case_handler');
|
const { handleCommand: handleCase } = require('./handlers/case/case_handler');
|
||||||
const { handleCommand: handleConfig } = require('./handlers/config/config_handler');
|
const { handleCommand: handleConfig } = require('./handlers/config/config_handler');
|
||||||
const { handleCommand: handleStats } = require('./handlers/stats/stats_handler');
|
const { handleCommand: handleStats } = require('./handlers/stats/stats_handler');
|
||||||
|
const readline = require('readline');
|
||||||
|
|
||||||
|
|
||||||
|
const colors = {
|
||||||
|
// ANSI color codes
|
||||||
|
reset: '\x1b[0m',
|
||||||
|
bright: '\x1b[1m',
|
||||||
|
dim: '\x1b[2m',
|
||||||
|
underscore: '\x1b[4m',
|
||||||
|
blink: '\x1b[5m',
|
||||||
|
reverse: '\x1b[7m',
|
||||||
|
hidden: '\x1b[8m',
|
||||||
|
|
||||||
|
// Foreground colors
|
||||||
|
black: '\x1b[30m',
|
||||||
|
red: '\x1b[31m',
|
||||||
|
green: '\x1b[32m',
|
||||||
|
yellow: '\x1b[33m',
|
||||||
|
blue: '\x1b[34m',
|
||||||
|
magenta: '\x1b[35m',
|
||||||
|
cyan: '\x1b[36m',
|
||||||
|
white: '\x1b[37m',
|
||||||
|
|
||||||
|
// Background colors
|
||||||
|
bgBlack: '\x1b[40m',
|
||||||
|
bgRed: '\x1b[41m',
|
||||||
|
bgGreen: '\x1b[42m',
|
||||||
|
bgYellow: '\x1b[43m',
|
||||||
|
bgBlue: '\x1b[44m',
|
||||||
|
bgMagenta: '\x1b[45m',
|
||||||
|
bgCyan: '\x1b[46m',
|
||||||
|
bgWhite: '\x1b[47m'
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create simple color functions
|
||||||
|
const colorize = {
|
||||||
|
blue: (text) => `${colors.blue}${text}${colors.reset}`,
|
||||||
|
green: (text) => `${colors.green}${text}${colors.reset}`,
|
||||||
|
red: (text) => `${colors.red}${text}${colors.reset}`,
|
||||||
|
yellow: (text) => `${colors.yellow}${text}${colors.reset}`,
|
||||||
|
cyan: (text) => `${colors.cyan}${text}${colors.reset}`,
|
||||||
|
white: (text) => `${colors.white}${text}${colors.reset}`,
|
||||||
|
dim: (text) => `${colors.dim}${text}${colors.reset}`,
|
||||||
|
};
|
||||||
|
|
||||||
|
const tableColors = {
|
||||||
|
header: colorize.cyan,
|
||||||
|
key: colorize.blue,
|
||||||
|
statsKey: colorize.yellow,
|
||||||
|
count: colorize.green,
|
||||||
|
dim: colorize.dim
|
||||||
|
};
|
||||||
|
|
||||||
// Import CLI formatters
|
// Import CLI formatters
|
||||||
const {
|
const {
|
||||||
|
@ -91,9 +122,17 @@ function completer(line) {
|
||||||
'sigma stats',
|
'sigma stats',
|
||||||
'stats sigma',
|
'stats sigma',
|
||||||
'search sigma rules where title contains',
|
'search sigma rules where title contains',
|
||||||
'search rules where tags include',
|
'search sigma where title contains',
|
||||||
'search rules where logsource.category ==',
|
'search sigma rules where tags include',
|
||||||
'search rules where modified after',
|
'search sigma where tags include',
|
||||||
|
'search sigma rules where logsource.category ==',
|
||||||
|
'search sigma where logsource.category ==',
|
||||||
|
'search sigma rules where modified after',
|
||||||
|
'search sigma where modified after',
|
||||||
|
'search sigma rules where author is',
|
||||||
|
'search sigma where author is',
|
||||||
|
'search sigma rules where level is',
|
||||||
|
'search sigma where level is',
|
||||||
'help',
|
'help',
|
||||||
'exit',
|
'exit',
|
||||||
'quit',
|
'quit',
|
||||||
|
@ -163,8 +202,9 @@ function normalizeAndWrap(text, maxWidth) {
|
||||||
return lines;
|
return lines;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Format CLI output similar to MySQL
|
* Format CLI output
|
||||||
* @param {Object} data The data to format
|
* @param {Object} data The data to format
|
||||||
* @param {string} type The type of data (results, details, stats)
|
* @param {string} type The type of data (results, details, stats)
|
||||||
*/
|
*/
|
||||||
|
@ -174,105 +214,133 @@ function formatOutput(data, type) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log();
|
||||||
|
|
||||||
|
// word wrapping function
|
||||||
|
function normalizeAndWrap(text, maxWidth) {
|
||||||
|
if (text === undefined || text === null) return [''];
|
||||||
|
|
||||||
|
// Convert to string and normalize newlines
|
||||||
|
const normalized = String(text).replace(/\n/g, ' ');
|
||||||
|
|
||||||
|
// If text fits in one line, return it
|
||||||
|
if (normalized.length <= maxWidth) return [normalized];
|
||||||
|
|
||||||
|
const words = normalized.split(' ');
|
||||||
|
const lines = [];
|
||||||
|
let currentLine = '';
|
||||||
|
|
||||||
|
for (const word of words) {
|
||||||
|
// Skip empty words
|
||||||
|
if (!word) continue;
|
||||||
|
|
||||||
|
// Check if adding this word would exceed max width
|
||||||
|
if ((currentLine.length + word.length + (currentLine ? 1 : 0)) > maxWidth) {
|
||||||
|
// Push current line if not empty
|
||||||
|
if (currentLine) {
|
||||||
|
lines.push(currentLine);
|
||||||
|
currentLine = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle words longer than maxWidth by splitting them
|
||||||
|
if (word.length > maxWidth) {
|
||||||
|
let remaining = word;
|
||||||
|
while (remaining.length > 0) {
|
||||||
|
const chunk = remaining.substring(0, maxWidth);
|
||||||
|
lines.push(chunk);
|
||||||
|
remaining = remaining.substring(maxWidth);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
currentLine = word;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Add word to current line
|
||||||
|
currentLine = currentLine ? `${currentLine} ${word}` : word;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the last line if not empty
|
||||||
|
if (currentLine) {
|
||||||
|
lines.push(currentLine);
|
||||||
|
}
|
||||||
|
|
||||||
|
return lines.length ? lines : [''];
|
||||||
|
}
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'search_results':
|
case 'search_results':
|
||||||
// Search results table format remains the same
|
// Header
|
||||||
console.log('\n+-------+----------------------+------------------+-------------+');
|
console.log(
|
||||||
console.log('| ID | Title | Author | Level |');
|
tableColors.header(
|
||||||
console.log('+-------+----------------------+------------------+-------------+');
|
'#'.padEnd(5) +
|
||||||
|
'Title'.padEnd(32) +
|
||||||
|
'OS/Product'.padEnd(15) +
|
||||||
|
'ID'.padEnd(13)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
if (data.results && data.results.length > 0) {
|
if (data.results && data.results.length > 0) {
|
||||||
data.results.forEach(rule => {
|
data.results.forEach((rule, index) => {
|
||||||
const id = (rule.id || '').padEnd(5).substring(0, 5);
|
const num = (index + 1).toString().padEnd(5);
|
||||||
const title = (rule.title || '').padEnd(20).substring(0, 20);
|
const title = (rule.title || '').substring(0, 32).padEnd(32);
|
||||||
const author = (rule.author || 'Unknown').padEnd(16).substring(0, 16);
|
|
||||||
const level = (rule.level || 'medium').padEnd(11).substring(0, 11);
|
|
||||||
|
|
||||||
console.log(`| ${id} | ${title} | ${author} | ${level} |`);
|
// Extract OS/Product from tags or logsource if available
|
||||||
|
let osProduct = 'Unknown';
|
||||||
|
if (rule.tags && Array.isArray(rule.tags)) {
|
||||||
|
// Look for os tags like windows, linux, macos
|
||||||
|
const osTags = rule.tags.filter(tag =>
|
||||||
|
['windows', 'linux', 'macos', 'unix', 'azure', 'aws', 'gcp'].includes(tag.toLowerCase())
|
||||||
|
);
|
||||||
|
if (osTags.length > 0) {
|
||||||
|
osProduct = osTags[0].charAt(0).toUpperCase() + osTags[0].slice(1);
|
||||||
|
}
|
||||||
|
} else if (rule.logsource && rule.logsource.product) {
|
||||||
|
osProduct = rule.logsource.product;
|
||||||
|
}
|
||||||
|
|
||||||
|
osProduct = osProduct.substring(0, 15).padEnd(15);
|
||||||
|
const id = (rule.id || '').substring(0, 13).padEnd(13);
|
||||||
|
|
||||||
|
console.log(`${num}${title}${osProduct}${id}`);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
console.log('| No results found |');
|
console.log(tableColors.dim('No results found'));
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('+-------+----------------------+------------------+-------------+');
|
console.log(tableColors.count(`${data.totalCount || 0} rows in set`));
|
||||||
console.log(`${data.totalCount || 0} rows in set`);
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'details':
|
case 'details':
|
||||||
// Set a fixed width for the entire table
|
const detailsKeyWidth = 24;
|
||||||
const sigmaDetailsKeyWidth = 22;
|
const detailsValueWidth = 50;
|
||||||
const sigmaDetailsValueWidth = 50;
|
|
||||||
|
|
||||||
// Create the table borders
|
|
||||||
const detailsHeaderLine = '╔' + '═'.repeat(sigmaDetailsKeyWidth) + '╦' + '═'.repeat(sigmaDetailsValueWidth) + '╗';
|
|
||||||
const sigmaDetailsDividerLine = '╠' + '═'.repeat(sigmaDetailsKeyWidth) + '╬' + '═'.repeat(sigmaDetailsValueWidth) + '╣';
|
|
||||||
const sigmaDetailsRowSeparator = '╟' + '─'.repeat(sigmaDetailsKeyWidth) + '╫' + '─'.repeat(sigmaDetailsValueWidth) + '╢';
|
|
||||||
const sigmaDetailsFooterLine = '╚' + '═'.repeat(sigmaDetailsKeyWidth) + '╩' + '═'.repeat(sigmaDetailsValueWidth) + '╝';
|
|
||||||
|
|
||||||
console.log('\n' + detailsHeaderLine);
|
|
||||||
console.log(`║ ${'Field'.padEnd(sigmaDetailsKeyWidth - 2)} ║ ${'Value'.padEnd(sigmaDetailsValueWidth - 2)} ║`);
|
|
||||||
console.log(sigmaDetailsDividerLine);
|
|
||||||
|
|
||||||
// Track whether we need to add a row separator
|
|
||||||
let isFirstRow = true;
|
|
||||||
|
|
||||||
for (const [key, value] of Object.entries(data)) {
|
for (const [key, value] of Object.entries(data)) {
|
||||||
if (typeof value !== 'object' || value === null) {
|
if (typeof value !== 'object' || value === null) {
|
||||||
// Add separator between rows (but not before the first row)
|
const formattedKey = tableColors.key(key.padEnd(detailsKeyWidth - 2));
|
||||||
if (!isFirstRow) {
|
|
||||||
console.log(sigmaDetailsRowSeparator);
|
|
||||||
}
|
|
||||||
isFirstRow = false;
|
|
||||||
|
|
||||||
const formattedKey = key.padEnd(sigmaDetailsKeyWidth - 2);
|
|
||||||
|
|
||||||
// Handle wrapping
|
// Handle wrapping
|
||||||
const lines = normalizeAndWrap(value, sigmaDetailsValueWidth - 2);
|
const lines = normalizeAndWrap(value, detailsValueWidth);
|
||||||
|
|
||||||
// Print first line with the key
|
// Print first line with the key
|
||||||
console.log(`║ ${formattedKey} ║ ${lines[0].padEnd(sigmaDetailsValueWidth - 2)} ║`);
|
console.log(`${formattedKey} ${lines[0]}`);
|
||||||
|
|
||||||
// Print additional lines if there are any
|
// Print additional lines if there are any
|
||||||
for (let i = 1; i < lines.length; i++) {
|
for (let i = 1; i < lines.length; i++) {
|
||||||
console.log(`║ ${' '.repeat(sigmaDetailsKeyWidth - 2)} ║ ${lines[i].padEnd(sigmaDetailsValueWidth - 2)} ║`);
|
console.log(`${' '.repeat(detailsKeyWidth)} ${lines[i]}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(sigmaDetailsFooterLine);
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'stats':
|
case 'stats':
|
||||||
// Set column widths
|
const statsMetricWidth = 25;
|
||||||
const sigmaStatsMetricWidth = 25;
|
|
||||||
const sigmaStatsValueWidth = 26;
|
|
||||||
|
|
||||||
// Create the table borders
|
|
||||||
const sigmaStatsHeaderLine = '╔' + '═'.repeat(sigmaStatsMetricWidth) + '╦' + '═'.repeat(sigmaStatsValueWidth) + '╗';
|
|
||||||
const sigmaStatsDividerLine = '╠' + '═'.repeat(sigmaStatsMetricWidth) + '╬' + '═'.repeat(sigmaStatsValueWidth) + '╣';
|
|
||||||
const sigmaStatsRowSeparator = '╟' + '─'.repeat(sigmaStatsMetricWidth) + '╫' + '─'.repeat(sigmaStatsValueWidth) + '╢';
|
|
||||||
const sigmaStatsFooterLine = '╚' + '═'.repeat(sigmaStatsMetricWidth) + '╩' + '═'.repeat(sigmaStatsValueWidth) + '╝';
|
|
||||||
|
|
||||||
console.log('\n' + sigmaStatsHeaderLine);
|
|
||||||
console.log(`║ ${'Metric'.padEnd(sigmaStatsMetricWidth - 2)} ║ ${'Value'.padEnd(sigmaStatsValueWidth - 2)} ║`);
|
|
||||||
console.log(sigmaStatsDividerLine);
|
|
||||||
|
|
||||||
// Track whether we need to add a row separator
|
|
||||||
let statsIsFirstRow = true;
|
|
||||||
|
|
||||||
for (const [key, value] of Object.entries(data)) {
|
for (const [key, value] of Object.entries(data)) {
|
||||||
// Add separator between rows (but not before the first row)
|
const formattedKey = tableColors.statsKey(key.padEnd(statsMetricWidth - 2));
|
||||||
if (!statsIsFirstRow) {
|
const formattedValue = String(value || '');
|
||||||
console.log(sigmaStatsRowSeparator);
|
|
||||||
}
|
|
||||||
statsIsFirstRow = false;
|
|
||||||
|
|
||||||
const formattedKey = key.padEnd(sigmaStatsMetricWidth - 2);
|
console.log(`${formattedKey} ${formattedValue}`);
|
||||||
const formattedValue = String(value || '').padEnd(sigmaStatsValueWidth - 2);
|
|
||||||
|
|
||||||
console.log(`║ ${formattedKey} ║ ${formattedValue} ║`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(sigmaStatsFooterLine);
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
@ -330,7 +398,7 @@ async function processCommand(input) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Special case for simple search
|
// Special case for simple search
|
||||||
if (input.trim().match(/^search\s+sigma\s+(.+)$/i)) {
|
if (input.trim().match(/^search\s+sigma\s+(.+)$/i) && !input.trim().toLowerCase().includes('where') && !input.trim().toLowerCase().includes('with')) {
|
||||||
const keyword = input.trim().match(/^search\s+sigma\s+(.+)$/i)[1];
|
const keyword = input.trim().match(/^search\s+sigma\s+(.+)$/i)[1];
|
||||||
|
|
||||||
// Add to command history
|
// Add to command history
|
||||||
|
@ -364,6 +432,42 @@ async function processCommand(input) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Special case for complex search
|
||||||
|
const complexSearchMatch = input.trim().match(/^search\s+sigma\s+(rules\s+|detections\s+)?(where|with)\s+(.+)$/i);
|
||||||
|
if (complexSearchMatch) {
|
||||||
|
const complexQuery = complexSearchMatch[3];
|
||||||
|
|
||||||
|
// Add to command history
|
||||||
|
commandHistory.push(input);
|
||||||
|
historyIndex = commandHistory.length;
|
||||||
|
|
||||||
|
// Create fake command object
|
||||||
|
const command = {
|
||||||
|
text: complexQuery,
|
||||||
|
user_id: 'cli_user',
|
||||||
|
user_name: 'cli_user',
|
||||||
|
command: '/fylgja',
|
||||||
|
channel_id: 'cli',
|
||||||
|
channel_name: 'cli'
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create custom respond function
|
||||||
|
const respond = createRespondFunction('complexSearch', 'sigma', [complexQuery]);
|
||||||
|
|
||||||
|
console.log(`Executing: module=sigma, action=complexSearch, params=[${complexQuery}]`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await sigmaSearchHandler.handleComplexSearch(command, respond);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error: ${error.message}`);
|
||||||
|
logger.error(`${FILE_NAME}: Command execution error: ${error.message}`);
|
||||||
|
logger.debug(`${FILE_NAME}: Error stack: ${error.stack}`);
|
||||||
|
rl.prompt();
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Add to command history
|
// Add to command history
|
||||||
commandHistory.push(input);
|
commandHistory.push(input);
|
||||||
historyIndex = commandHistory.length;
|
historyIndex = commandHistory.length;
|
||||||
|
@ -475,14 +579,32 @@ async function processCommand(input) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a custom respond function for handling results
|
* Custom respond function for handling results
|
||||||
* @param {string} action The action being performed
|
* @param {string} action The action being performed
|
||||||
* @param {string} module The module being used
|
* @param {string} module The module being used
|
||||||
* @param {Array} params The parameters for the action
|
* @param {Array} params The parameters for the action
|
||||||
* @returns {Function} A respond function for handling results
|
* @returns {Function} A respond function for handling results
|
||||||
*/
|
*/
|
||||||
function createRespondFunction(action, module, params) {
|
function createRespondFunction(action, module, params) {
|
||||||
|
// Keep track of whether we're waiting for results
|
||||||
|
let isWaitingForResults = false;
|
||||||
|
|
||||||
return async (response) => {
|
return async (response) => {
|
||||||
|
// Check if this is a progress message
|
||||||
|
const isProgressMessage =
|
||||||
|
typeof response === 'object' &&
|
||||||
|
response.text &&
|
||||||
|
!response.responseData &&
|
||||||
|
(response.text.includes('moment') ||
|
||||||
|
response.text.includes('searching') ||
|
||||||
|
response.text.includes('processing'));
|
||||||
|
|
||||||
|
if (isProgressMessage) {
|
||||||
|
console.log(response.text);
|
||||||
|
isWaitingForResults = true;
|
||||||
|
return; // Don't show prompt after progress messages
|
||||||
|
}
|
||||||
|
|
||||||
if (typeof response === 'string') {
|
if (typeof response === 'string') {
|
||||||
console.log(response);
|
console.log(response);
|
||||||
rl.prompt();
|
rl.prompt();
|
||||||
|
@ -519,6 +641,8 @@ function createRespondFunction(action, module, params) {
|
||||||
console.log('Command completed successfully.');
|
console.log('Command completed successfully.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reset waiting state and show prompt after results
|
||||||
|
isWaitingForResults = false;
|
||||||
rl.prompt();
|
rl.prompt();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -540,8 +664,10 @@ Advanced Sigma Search Commands:
|
||||||
- search sigma where tags include privilege_escalation - Search by tags
|
- search sigma where tags include privilege_escalation - Search by tags
|
||||||
- search sigma where logsource.category == "process_creation" - Search by log source
|
- search sigma where logsource.category == "process_creation" - Search by log source
|
||||||
- search sigma where modified after 2024-01-01 - Search by modification date
|
- search sigma where modified after 2024-01-01 - Search by modification date
|
||||||
|
- search sigma rules where title contains "ransomware" - Alternative syntax
|
||||||
|
- search sigma rules where tags include privilege_escalation - Alternative syntax
|
||||||
|
|
||||||
|
CLI Commands:
|
||||||
- exit or quit - Exit the CLI
|
- exit or quit - Exit the CLI
|
||||||
- clear - Clear the terminal screen
|
- clear - Clear the terminal screen
|
||||||
- help - Display this help text
|
- help - Display this help text
|
||||||
|
@ -549,7 +675,6 @@ Advanced Sigma Search Commands:
|
||||||
|
|
||||||
console.log(helpText);
|
console.log(helpText);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start the CLI application
|
* Start the CLI application
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -25,13 +25,28 @@ const commandPatterns = [
|
||||||
},
|
},
|
||||||
// Sigma search patterns
|
// Sigma search patterns
|
||||||
{
|
{
|
||||||
name: 'sigma-search',
|
name: 'sigma-search-complex-1',
|
||||||
regex: /^(search|find)\s+(sigma\s+)?(rules|detections)?\s*(where|with)\s+(.+)$/i,
|
regex: /^(search|find)\s+sigma\s+rules?\s*(where|with)\s+(.+)$/i,
|
||||||
action: 'complexSearch',
|
action: 'complexSearch',
|
||||||
module: 'sigma',
|
module: 'sigma',
|
||||||
params: [5] // complex query conditions in capturing group 5
|
params: [4] // complex query conditions in capturing group 4
|
||||||
|
},
|
||||||
|
// Alternate form without "rules"
|
||||||
|
{
|
||||||
|
name: 'sigma-search-complex-2',
|
||||||
|
regex: /^(search|find)\s+sigma\s+(where|with)\s+(.+)$/i,
|
||||||
|
action: 'complexSearch',
|
||||||
|
module: 'sigma',
|
||||||
|
params: [3] // complex query conditions in capturing group 3
|
||||||
|
},
|
||||||
|
// Simple keyword search pattern
|
||||||
|
{
|
||||||
|
name: 'sigma-search-simple',
|
||||||
|
regex: /^(search|find)\s+sigma\s+(.+)$/i,
|
||||||
|
action: 'search',
|
||||||
|
module: 'sigma',
|
||||||
|
params: [2] // keyword is in capturing group 2
|
||||||
},
|
},
|
||||||
|
|
||||||
// Sigma create patterns
|
// Sigma create patterns
|
||||||
{
|
{
|
||||||
name: 'sigma-create',
|
name: 'sigma-create',
|
||||||
|
|
0
src/logo.js
Normal file
0
src/logo.js
Normal file
|
@ -6,6 +6,7 @@
|
||||||
*/
|
*/
|
||||||
const chalk = require('chalk');
|
const chalk = require('chalk');
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wraps text at specified length
|
* Wraps text at specified length
|
||||||
* @param {string} text - Text to wrap
|
* @param {string} text - Text to wrap
|
||||||
|
@ -146,16 +147,27 @@ function formatSigmaSearchResults(searchResults) {
|
||||||
|
|
||||||
// Return a structure with results and meta info
|
// Return a structure with results and meta info
|
||||||
return {
|
return {
|
||||||
results: searchResults.results.map(rule => ({
|
results: searchResults.results.map(rule => {
|
||||||
id: rule.id || '',
|
// Get logsource.product field
|
||||||
title: wrapText(rule.title || '', 60), // Use narrower width for table columns
|
let osProduct = 'N/A';
|
||||||
author: rule.author || 'Unknown',
|
|
||||||
level: rule.level || 'medium'
|
// Only use logsource.product if it exists
|
||||||
})),
|
if (rule.logsource && rule.logsource.product) {
|
||||||
|
osProduct = rule.logsource.product;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: rule.id || '',
|
||||||
|
title: wrapText(rule.title || '', 60), // Use narrower width for table columns
|
||||||
|
author: rule.author || 'Unknown',
|
||||||
|
level: rule.level || 'medium',
|
||||||
|
osProduct: osProduct,
|
||||||
|
tags: rule.tags || [] // Include the original tags for potential reference
|
||||||
|
};
|
||||||
|
}),
|
||||||
totalCount: searchResults.totalCount || 0
|
totalCount: searchResults.totalCount || 0
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
formatSigmaStats,
|
formatSigmaStats,
|
||||||
formatSigmaSearchResults,
|
formatSigmaSearchResults,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue