#!/usr/bin/env python3 """ SIGMA CLI - CVE-SIGMA Auto Generator Command Line Interface A CLI tool for processing CVEs and generating SIGMA detection rules in a file-based directory structure. Author: CVE-SIGMA Auto Generator """ import click import asyncio import os import sys import json from typing import Optional, List from pathlib import Path from datetime import datetime # Add parent directories to path for imports sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'backend')) sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'core')) # Import CLI command modules from commands.process_commands import ProcessCommands from commands.generate_commands import GenerateCommands from commands.search_commands import SearchCommands from commands.stats_commands import StatsCommands from commands.export_commands import ExportCommands from commands.migrate_commands import MigrateCommands # Global CLI configuration class Config: def __init__(self): self.base_dir = Path.cwd() self.cves_dir = self.base_dir / "cves" self.templates_dir = self.base_dir / "backend" / "templates" self.reports_dir = self.base_dir / "reports" self.config_file = Path.home() / ".sigma-cli" / "config.yaml" # Ensure directories exist self.cves_dir.mkdir(exist_ok=True) self.reports_dir.mkdir(exist_ok=True) (Path.home() / ".sigma-cli").mkdir(exist_ok=True) pass_config = click.make_pass_decorator(Config, ensure=True) @click.group() @click.option('--verbose', '-v', is_flag=True, help='Enable verbose output') @click.option('--config', '-c', type=click.Path(), help='Path to configuration file') @click.pass_context def cli(ctx, verbose, config): """ SIGMA CLI - CVE-SIGMA Auto Generator A command line tool for processing CVEs and generating SIGMA detection rules. Rules are stored in a file-based directory structure organized by year and CVE-ID. """ ctx.ensure_object(Config) if verbose: click.echo("Verbose mode enabled") if config: ctx.obj.config_file = Path(config) # Initialize logging import logging level = logging.DEBUG if verbose else logging.INFO logging.basicConfig(level=level, format='%(asctime)s - %(levelname)s - %(message)s') # Process commands @cli.group() @pass_config def process(config): """Process CVEs and generate SIGMA rules""" pass @process.command('year') @click.argument('year', type=int) @click.option('--method', '-m', multiple=True, type=click.Choice(['template', 'llm', 'hybrid', 'all']), default=['template'], help='Rule generation method(s)') @click.option('--force', '-f', is_flag=True, help='Force regeneration of existing rules') @click.option('--batch-size', '-b', default=50, help='Batch size for processing') @pass_config def process_year(config, year, method, force, batch_size): """Process all CVEs for a specific year""" cmd = ProcessCommands(config) asyncio.run(cmd.process_year(year, method, force, batch_size)) @process.command('cve') @click.argument('cve_id') @click.option('--method', '-m', multiple=True, type=click.Choice(['template', 'llm', 'hybrid', 'all']), default=['template'], help='Rule generation method(s)') @click.option('--force', '-f', is_flag=True, help='Force regeneration of existing rules') @pass_config def process_cve(config, cve_id, method, force): """Process a specific CVE""" cmd = ProcessCommands(config) asyncio.run(cmd.process_cve(cve_id, method, force)) @process.command('bulk') @click.option('--start-year', default=2022, help='Starting year for bulk processing') @click.option('--end-year', default=datetime.now().year, help='Ending year for bulk processing') @click.option('--method', '-m', multiple=True, type=click.Choice(['template', 'llm', 'hybrid', 'all']), default=['template'], help='Rule generation method(s)') @click.option('--batch-size', '-b', default=50, help='Batch size for processing') @pass_config def process_bulk(config, start_year, end_year, method, batch_size): """Bulk process all CVEs across multiple years""" cmd = ProcessCommands(config) asyncio.run(cmd.process_bulk(start_year, end_year, method, batch_size)) @process.command('incremental') @click.option('--days', '-d', default=7, help='Process CVEs modified in the last N days') @click.option('--method', '-m', multiple=True, type=click.Choice(['template', 'llm', 'hybrid', 'all']), default=['template'], help='Rule generation method(s)') @pass_config def process_incremental(config, days, method): """Process recently modified CVEs""" cmd = ProcessCommands(config) asyncio.run(cmd.process_incremental(days, method)) # Generate commands @cli.group() @pass_config def generate(config): """Generate SIGMA rules for existing CVEs""" pass @generate.command('cve') @click.argument('cve_id') @click.option('--method', '-m', type=click.Choice(['template', 'llm', 'hybrid', 'all']), default='template', help='Rule generation method') @click.option('--provider', '-p', type=click.Choice(['openai', 'anthropic', 'ollama']), help='LLM provider for LLM-based generation') @click.option('--model', help='Specific model to use') @click.option('--force', '-f', is_flag=True, help='Force regeneration of existing rules') @pass_config def generate_cve(config, cve_id, method, provider, model, force): """Generate SIGMA rules for a specific CVE""" cmd = GenerateCommands(config) asyncio.run(cmd.generate_cve(cve_id, method, provider, model, force)) @generate.command('regenerate') @click.option('--year', type=int, help='Regenerate rules for specific year') @click.option('--method', '-m', type=click.Choice(['template', 'llm', 'hybrid', 'all']), default='all', help='Rule generation method') @click.option('--filter-quality', type=click.Choice(['excellent', 'good', 'fair']), help='Only regenerate rules for CVEs with specific PoC quality') @pass_config def generate_regenerate(config, year, method, filter_quality): """Regenerate existing SIGMA rules""" cmd = GenerateCommands(config) asyncio.run(cmd.regenerate_rules(year, method, filter_quality)) # Search commands @cli.group() @pass_config def search(config): """Search CVEs and SIGMA rules""" pass @search.command('cve') @click.argument('pattern') @click.option('--year', type=int, help='Search within specific year') @click.option('--severity', type=click.Choice(['low', 'medium', 'high', 'critical']), help='Filter by severity') @click.option('--has-poc', is_flag=True, help='Only show CVEs with PoC data') @click.option('--has-rules', is_flag=True, help='Only show CVEs with generated rules') @click.option('--limit', '-l', default=20, help='Limit number of results') @pass_config def search_cve(config, pattern, year, severity, has_poc, has_rules, limit): """Search for CVEs by pattern""" cmd = SearchCommands(config) asyncio.run(cmd.search_cves(pattern, year, severity, has_poc, has_rules, limit)) @search.command('rules') @click.argument('pattern') @click.option('--rule-type', help='Filter by rule type (e.g., process, network, file)') @click.option('--method', type=click.Choice(['template', 'llm', 'hybrid']), help='Filter by generation method') @click.option('--limit', '-l', default=20, help='Limit number of results') @pass_config def search_rules(config, pattern, rule_type, method, limit): """Search for SIGMA rules by pattern""" cmd = SearchCommands(config) asyncio.run(cmd.search_rules(pattern, rule_type, method, limit)) # Statistics commands @cli.group() @pass_config def stats(config): """Generate statistics and reports""" pass @stats.command('overview') @click.option('--year', type=int, help='Statistics for specific year') @click.option('--output', '-o', type=click.Path(), help='Save output to file') @pass_config def stats_overview(config, year, output): """Generate overview statistics""" cmd = StatsCommands(config) asyncio.run(cmd.overview(year, output)) @stats.command('poc') @click.option('--year', type=int, help='PoC statistics for specific year') @pass_config def stats_poc(config, year): """Generate PoC coverage statistics""" cmd = StatsCommands(config) asyncio.run(cmd.poc_stats(year)) @stats.command('rules') @click.option('--year', type=int, help='Rule statistics for specific year') @click.option('--method', type=click.Choice(['template', 'llm', 'hybrid']), help='Filter by generation method') @pass_config def stats_rules(config, year, method): """Generate rule generation statistics""" cmd = StatsCommands(config) asyncio.run(cmd.rule_stats(year, method)) # Export commands @cli.group() @pass_config def export(config): """Export rules in various formats""" pass @export.command('sigma') @click.argument('output_dir', type=click.Path()) @click.option('--year', type=int, help='Export rules for specific year') @click.option('--format', type=click.Choice(['yaml', 'json']), default='yaml', help='Output format') @click.option('--method', type=click.Choice(['template', 'llm', 'hybrid']), help='Filter by generation method') @pass_config def export_sigma(config, output_dir, year, format, method): """Export SIGMA rules to a directory""" cmd = ExportCommands(config) asyncio.run(cmd.export_sigma_rules(output_dir, year, format, method)) @export.command('metadata') @click.argument('output_file', type=click.Path()) @click.option('--year', type=int, help='Export metadata for specific year') @click.option('--format', type=click.Choice(['json', 'csv']), default='json', help='Output format') @pass_config def export_metadata(config, output_file, year, format): """Export CVE metadata""" cmd = ExportCommands(config) asyncio.run(cmd.export_metadata(output_file, year, format)) # Migration commands (for transitioning from web app) @cli.group() @pass_config def migrate(config): """Migration utilities for transitioning from web application""" pass @migrate.command('from-database') @click.option('--database-url', help='Database URL to migrate from') @click.option('--batch-size', '-b', default=100, help='Batch size for migration') @click.option('--dry-run', is_flag=True, help='Show what would be migrated without doing it') @pass_config def migrate_from_database(config, database_url, batch_size, dry_run): """Migrate data from existing database to file structure""" cmd = MigrateCommands(config) asyncio.run(cmd.migrate_from_database(database_url, batch_size, dry_run)) @migrate.command('validate') @click.option('--year', type=int, help='Validate specific year') @pass_config def migrate_validate(config, year): """Validate migrated data integrity""" cmd = MigrateCommands(config) asyncio.run(cmd.validate_migration(year)) # Utility commands @cli.command() @pass_config def version(config): """Show version information""" click.echo("SIGMA CLI v1.0.0") click.echo("CVE-SIGMA Auto Generator - File-based Edition") @cli.command() @pass_config def config_init(config): """Initialize CLI configuration""" config_data = { 'base_dir': str(config.base_dir), 'api_keys': { 'nvd_api_key': '', 'github_token': '', 'openai_api_key': '', 'anthropic_api_key': '' }, 'llm_settings': { 'default_provider': 'ollama', 'default_model': 'llama3.2', 'ollama_base_url': 'http://localhost:11434' }, 'processing': { 'default_batch_size': 50, 'default_methods': ['template'] } } config.config_file.parent.mkdir(exist_ok=True) with open(config.config_file, 'w') as f: import yaml yaml.dump(config_data, f, default_flow_style=False) click.echo(f"Configuration initialized at {config.config_file}") click.echo("Please edit the configuration file to add your API keys and preferences.") if __name__ == '__main__': cli()