Architecture¶
Cargo workspace¶
Le projet est un cargo workspace de 11 packages (2 crates binaires + 9 bibliothèques) :
sigmacatch/
├── Cargo.toml # Racine workspace
├── crates/
│ ├── sigmacatch-config/ # Config YAML + parsing CLI + custom_channels.yaml + diagnostics git dry-run
│ ├── sigmacatch-logger/ # Abonnement tracing à deux couches (stderr info + fichier journal rolling debug)
│ ├── sigmacatch-rule/ # SigmahqRules : chargement de règles (parse_sigma_yaml), filtre, dédupe, remove_id + SigmaRuleExt (techniques ATT&CK)
│ ├── sigmacatch-detection/ # Wrapper DetectionEngine + pipelines embarquées (windows.yml, flatten_winevt.yml) + channel_resolver
│ ├── input-windows-channels/ # Collecteur Winevt multi-channel (cfg(windows))
│ ├── sigmacatch-regression/ # SigmahqRegression (get_sigma_id, add, retire), InfoYml, triplet
│ ├── sigmacatch-types/ # Types partagés : Event, Alert, RegressionHeader + parsing XML + tables de mapping logsource
│ ├── sigmacatch-repo/ # wrapper grit-lib + SigmaRepo + opérations git
│ └── input-evtx/ # Parser fichiers EVTX → Event
├── sigmacatch/ # Binaire + orchestration
│ └── src/
│ └── main.rs # Config + init repo + boucle continue + process_and_generate + commit/push
└── tools/ # Outils de dev (hors du crate principal)
└── src/
├── check_dry_run.rs # Diagnostics git (token/fork/API/info-refs/état repo)
├── check_channels.rs # Résout et liste les channels Windows collectés
├── list_rules.rs # Liste les règles chargées (techniques, lien ART)
├── check_filter.rs # Valide SigmaFilterConfig contre les vraies règles Sigma (comptage ground-truth)
├── check_evtx.rs # Validation batch du moteur Sigma contre les données .evtx
├── get_atomic.rs # Génère run_atomic.ps (Invoke-AtomicTest) pour les règles sans regression data
└── coverage.rs # Statistiques de couverture des règles (locales + branches en attente)
Arborescence¶
sigmacatch/src/
└── main.rs # Binaire : orchestration, boucle continue, process_and_generate
tools/src/
├── check_dry_run.rs # Outil de diagnostics git (config.yaml + dry_run_git)
├── check_channels.rs # Outil de résolution de channels (filtre config.yaml)
├── list_rules.rs # Outil de listing des règles (filtre config.yaml)
├── check_filter.rs # Outil de validation des filtres (pas d'args CLI, charge ./sigma lui-même)
├── check_evtx.rs # Outil de validation batch (exit 1 sur entrée vide / aucun match)
├── get_atomic.rs # Outil de génération run_atomic.ps (règles sans regression data)
└── coverage.rs # Outil de stats de couverture (règles locales + branches remote en attente)
Il n'y a pas de config.rs / logger.rs / repo.rs dans le binaire — ces modules ont été
déplacés dans les crates sigmacatch-config, sigmacatch-logger et sigmacatch-repo.
Graphe de dépendances¶
sigmacatch ──┬── sigmacatch-config (Config, CliArgs, diagnostics dry-run)
├── sigmacatch-logger (init tracing)
├── sigmacatch-rule (SigmahqRules : load/filter/remove_id)
├── sigmacatch-detection (DetectionEngine : pipelines + bloom + LogSourceExtractor + resolve_channels)
├── input-windows-channels (EventCollector : Winevt multi-channel)
├── sigmacatch-regression (SigmahqRegression : skip set + génération triplet)
├── sigmacatch-types (Event, Alert, RegressionHeader, Product, EventProducer, parsing XML)
└── sigmacatch-repo (SigmaRepo, wrapper grit-lib)
tools ──┬── sigmacatch-rule (SigmahqRules + SigmaFilterConfig)
├── sigmacatch-detection (DetectionEngine)
├── sigmacatch-regression (SigmahqRegression)
└── input-evtx (parse_evtx_bytes)
sigmacatch-detection dépend de sigmacatch-rule + sigmacatch-types + rsigma-eval.
input-windows-channels dépend de sigmacatch-types pour les types partagés et les tables de
mapping logsource. sigmacatch-regression dépend de sigmacatch-types. sigmacatch-rule
dépend de rsigma-parser. sigmacatch-config dépend de sigmacatch-repo + sigmacatch-rule.
Pipeline (boucle continue)¶
1. parse_args() + Config::load_with_cli("config.yaml", cli)
2. init_logger(verbose) → tracing (stderr `error` par défaut, `info` avec `-v`, fichier debug)
3. ensure_dirs() → dossier repo sigma + logs/
4. SigmaRepo init (remote_url = fork, branche de travail, token) → init() [clone/fetch]
└── set_git_operations(offline, contrib) → set_working_branch() → check_remote_working_branch() (garde sur branche du même jour)
5. SigmahqRegression::new() → charge les info.yml existants depuis ./sigma/regression_data
└── existing_rules = regression.get_sigma_id() ∪ sigma_repo.pending_regression_rule_ids() (branches remote sigmacatch/* en attente) → HashSet<Uuid> (vide avec --all-rules)
6. SigmahqRules::new() → chargement + dédupe ; remove_id() par règle skipée
└── filter(SigmaFilterConfig { product, min_status, min_level, author, max_rule_size }) ; 0 règles → bail
7. custom_map = load_custom_channel_mapping("custom_channels.yaml")
8. DetectionEngine::new(&rules) (pipelines + bloom + LogSourceExtractor)
└── cycle_channels = engine.resolve_channels(&custom_map) ; 0 channels → warn + return
9. output_base = <sigma_repo_path>/regression_data ; clean_partial_artifacts()
10. EventCollector::new(cycle_channels).run(tx, stop) → task tokio
11. Boucle : tokio::select!
├── shutdown_rx (Ctrl+C) → break
├── event depuis rx → engine.put_events(vec![event])
└── generate_interval (30s) → process_and_generate() → upload_regression() si fichiers
12. Flush final : drain des events restants → process_and_generate() → upload_regression() (commit par règle) → push unique si contrib
process_and_generate() :
engine.process_events() → get_alerts()
├── alerts vides → return (pas de log "evaluation complete")
├── log stats (events_processed, matches_found, alerts_count)
└── pour chaque alert : regression.add(&alert) → Option<Vec<String>>
├── None si règle déjà retirée / pas d'id valide / info.yml existant
└── Some(files) → écrit le triplet + regression_tests_path + retire la règle
└── règles retirées → rules.remove_id() → engine.reload_rules() (un seul reload batch)
↓
retourne batches: Vec<(Uuid, Vec<String>)> (règles générées + fichiers écrits)
↓
upload_regression() → upload_rule_batches() (dans sigmacatch-repo)
├── un commit par règle : "🧪 test: add regression data for rule {rule_id}"
├── échec commit/push → rollback de la branche locale vers le tip pré-batch
└── UN SEUL push si git.contrib: true (sinon commits locaux) → message PR
Notes de conception¶
- Skip set =
HashSet<Uuid>depuisSigmahqRegression::get_sigma_id()(info.yml existants + données valides) ∪SigmaRepo::pending_regression_rule_ids()(arbres des branches remotesigmacatch/*: PR en attente non mergés — une VM fraîche ne recapture pas leurs données), construit une seule fois au démarrage.--all-rulesle désactive. Après génération, une règle est retirée et le moteur est rechargé en un seul batch (engine.reload_rules). Les règles dont les données commitées sont invalides (EVTX vide) sont exclues du skip set → régénérées. - Output toujours dans le repo sigma :
<sigma_repo_path>/regression_data/<rule_rel_path>/(tripletinfo.yml+<rule_id>.json+<rule_id>.evtx), commité sur le fork sicontrib(commits locaux sinon). - Collecteur observable : les channels inexistants sont exclus une fois pour toutes sur
ERROR_EVT_CHANNEL_NOT_FOUND(un seulerror!) ; chaque channel vivant log « initial query OK » puis un heartbeat « still alive » (60s) ;warn!quand des events sont fetchés mais perdus au render/parse.
Les détails du skip set et les décisions de conception clés sont dans
architecture-reference.md.