Technical
Sigma rule import.
Loupe ships 35 SigmaHQ-curated rules at v1.0, drawn from a pinned upstream commit, converted at build time by a Python script, and bundled as JSON inside the app. The runtime is YAML-parser-free. Every rule that didn't fit Loupe's predicate vocabulary at v1.0 is logged with a reason — listed below — and queued for v1.x conversion.
v1.0 snapshot
The bundle is pinned to SigmaHQ commit 34c5d66c (snapshot Apr 29 2026). The exact commit SHA is recorded in Resources/sigma/MANIFEST.json inside every shipped Loupe.app — auditors verify the rule vintage by reading that file.
Subtrees imported: linux/, web/, network/, category/. Other subtrees (windows/, cloud/, macos/) are skipped wholesale because Loupe doesn't ingest those log types.
What gets converted
Two detection shapes pass conversion at v1.0:
- Pure keyword detections with
condition: keywords. The keyword list becomes a case-insensitive alternation regex ((?i)(kw1|kw2|kw3)) on Loupe'sRule.Predicates.messageRegex. - Single-block selections with regex modifier (
field|re: pattern). The pattern is wrapped with case-insensitive flag and passed through unchanged. Direct, lossless conversion.
ATT&CK tags from the upstream rule (attack.t1110.001 etc.) are resolved at runtime against the bundled MITRE ATT&CK database (see MITRE ATT&CK integration).
Why only 35 rules at v1.0
Of the in-scope SigmaHQ rules, the converter emitted 35 JSON files and skipped the rest with logged reasons. The breakdown:
| Skip reason | Rules | Why |
|---|---|---|
| complex-condition | 104 | Sigma rules using AND / OR / NOT operators or aggregations like "1 of selection*". Loupe's Rule.Predicates is a single regex + severity floor; flattening these to a regex risks false positives. |
| multi-field-selection | 90 | Selection blocks with two or more fields (e.g., cs-method + cs-uri-query). Translatable to lookahead-regex but lossy on fields whose values appear elsewhere in a log line. v1.x candidate. |
| out-of-scope-logsource | 38 | Rules targeting Windows EVTX, cloud-provider audit logs, or other formats Loupe does not ingest. Skipped wholesale at the logsource → Loupe-category mapping step. |
| field-specific-not-generic | 32 | Single-field selections targeting structured log fields (e.g., cs-uri-query|contains) that don't map cleanly onto Loupe's text-line predicate. Convertible with field-aware tooling; v1.x. |
| unsupported-field-shape | 12 | Field shapes Loupe's parser library doesn't recognize (CIDR ranges, timeframe windows, count operators). |
| non-block-selection | 2 | Selections expressed as something other than a YAML block — list-shaped or scalar-shaped detections that the converter can't safely interpret. |
| keywords-not-strings | 1 | A keyword detection where the keywords list contains non-string elements. Likely a YAML authoring quirk in the upstream rule. |
Conservative-by-design: keyword-only conversion is high-fidelity. Field-aware conversion against text-line log formats has false-positive risk that erodes the trust-thin bar. Better to ship 35 rules that fire correctly than 300 that produce noise. v1.x adds field-aware conversion to lift the count.
Reproducibility
Converter source: Scripts/sigma-import.py. Re-run with a different SigmaHQ checkout via python3 Scripts/sigma-import.py /path/to/sigma --clean. The script prints the same skip-reason breakdown shown above. Bundle output: Sources/Loupe/Resources/sigma/sigma_*.json (35 files) + MANIFEST.json (commit SHA + snapshot date + count). Loader test: Tests/LoupeTests/SigmaBundleTests.swift (9 tests asserting every bundled rule loads, every regex compiles, no ID collisions with the hand-rolled rules).