Skip to content

Repository files navigation

SigmaForge

Vendor-Agnostic Sigma Rule Generator

PythonFlaskLicenseSigma


Overview

SigmaForge is a detection rule authoring tool that generates, validates, and converts Sigma rules to six SIEM query languages plus Detection-as-Code JSON. It ships as a Flask web UI and a standalone CLI. The conversion engine is custom-built — there is no pySigma dependency, which means the Wazuh XML backend produces valid XML out of the box (pySigma has no native Wazuh backend).

Backends (SIEMConverter.convert):

Backend keyOutput format
splunkSplunk SPL with index/sourcetype prefix
elasticElastic Lucene KQL
eqlElastic EQL
sentinelMicrosoft Sentinel KQL
wazuhWazuh XML <group> / <rule> block
qradarQRadar AQL (SELECT * FROM events … LAST 24 HOURS)
dac_jsonDetection-as-Code normalized JSON

Aggregation support: Templates using aggregation conditions (count() by ... > N) emit real queries on Splunk SPL and QRadar AQL only. Elastic KQL and Elastic EQL do not implement aggregation and return placeholder text; Wazuh returns a structured {"supported": False, "reason": "aggregation_condition"} result instead of a query string (documented below under Wazuh backend specifics). Affected templates: windows_logon_brute_force, firewall_port_scan, brute_force_by_username.

Index and sourcetype defaults: generated queries use conventional index and sourcetype names (index=wineventlog, sourcetype="WinEventLog:Security" for Splunk). These are defaults, not universal — adjust the prefix to match your environment's index naming before running.

Validation: Splunk SPL output parses and executes in Splunk Enterprise. Wazuh XML output loads and parses cleanly in Wazuh 4.14.4 (wazuh-analysisd -t exits without error).

Timeframe: the timeframe field is accepted and validated but not enforced by any backend. Time windows must be set via the search or correlation schedule in the target SIEM.

Log sources (LOG_SOURCES):

KeyDescription
process_creationProcess Creation — Sysmon EID 1 / Security EID 4688
windows_securityWindows Security Event Log
sysmonSysmon Operational Log (all event types)
powershellPowerShell Script Block / Module Logging
powershell_classicWindows PowerShell (Classic) Event Log
dns_queryDNS Query Events — Sysmon EID 22
network_connectionNetwork Connection Events — Sysmon EID 3
file_eventFile Creation / Modification Events — Sysmon EID 11
registry_eventRegistry Value Set Events — Sysmon EID 13
firewallFirewall logs (vendor-agnostic)
proxyWeb proxy / HTTP logs
linux_processLinux Process Creation (auditd / sysmon-linux)
linux_authLinux Authentication Logs (/var/log/auth.log)

Screenshots

Rule Builder

Build Sigma rules visually with MITRE ATT&CK mapping and detection logic

Rule Builder

Generated Output

YAML output with Splunk SPL, Elastic KQL, EQL, Sentinel KQL, Wazuh XML, QRadar AQL, and DaC JSON conversions

Generated Output

Templates

17 pre-built detection templates covering common attack techniques

Templates

Validator

Paste any Sigma YAML for syntax checking and SIEM conversion

Validator

Rule Library

Save, load, export, and manage generated rules

Rule Library


SOC Use Case

A SOC analyst encounters an alert they want to turn into a persistent detection. The workflow in SigmaForge:

Scenario: Analyst sees Mimikatz-related activity in EDR telemetry

  1. Open the web UI Rule Builder or run the CLI generate command.
  2. Set logsource: process_creation, level critical, MITRE technique T1003.001.
  3. Add detection fields: Image|endswith=\mimikatz.exe and CommandLine|contains=sekurlsa::logonpasswords.
  4. Click Generate — the rule produces valid Sigma YAML and simultaneous output for all six backends.
  5. Copy the Wazuh XML tab output and drop it into ossec.conf or the Wazuh rules directory — the XML is ready to load, no post-processing.
  6. Copy the Splunk SPL output and save it as a saved search or correlation rule in the SIEM.
  7. Save the rule to the library via POST /api/library/save. It persists as a .yml file under rules/.

Scenario: Analyst wants to hunt from a template

python cli.py template suspicious_powershell

This prints the Sigma YAML and Splunk/Elastic/EQL/Sentinel conversions for a pre-built PowerShell detection (encoded commands, download cradles, AMSI bypass — all in one rule with three OR'd selection groups).

Scenario: Analyst receives a Sigma rule from the community and needs to push it to Wazuh

python cli.py validate community_rule.yml
python cli.py convert community_rule.yml --backend wazuh --rule-id 100500 --group-name sigma_rules

SigmaValidator.validate() checks required fields (title, logsource, detection), level/status values, field modifiers, and condition references before conversion.


Architecture

SigmaForge/
├── app.py # Flask web application and REST API
├── cli.py # CLI — six subcommands
├── src/
│ └── sigma_engine.py # Core engine (~2,000 lines)
└── templates/
└── index.html # Single-page web UI (four tabs)

Core classes (src/sigma_engine.py)

SigmaRule (dataclass) Represents a complete Sigma detection rule. Key fields: title, description, log_source_key, detection (dict), level, status, author, mitre_techniques, falsepositives, rule_id (auto-UUID), date (auto-today).

Methods:

  • to_yaml() — serializes to Sigma-spec YAML via yaml.dump
  • to_dict() — serializes to JSON-safe dict
  • get_logsource() — resolves log_source_key to a logsource block
  • get_mitre_tags() — generates attack.<tactic> and attack.tXXXX tag list

SigmaValidator (static)

  • validate(rule_yaml: str) → dict — returns {"valid": bool, "errors": [], "warnings": []}
  • Required fields: title, logsource, detection
  • Valid levels: informational, low, medium, high, critical
  • Valid statuses: stable, test, experimental, deprecated, unsupported
  • Valid field modifiers: contains, startswith, endswith, base64, base64offset, utf16le, utf16be, wide, re, cidr, all, gt, gte, lt, lte, fieldref, expand, windash

SIEMConverter (static)

  • convert(rule_yaml, backend, rule_id=100001, group_name="sigma_rules") → str | dictdict only for the wazuh backend's known-unsupported cases ({"supported": False, "reason": <code>})
  • _build_field_query(field_name, values, backend, negate, field_map) — translates a single field with modifiers to the backend's syntax
  • _parse_condition(condition, selections, backend) — resolves selection references, handles boolean operators and aggregation conditions (count() by field > N)
  • _build_aggregation(base_query, count_field, group_field, operator, threshold, backend) — generates stats/summarize/aggregation syntax per backend
  • _get_source_prefix(logsource, backend) — emits index/sourcetype/category prefix per backend

Wazuh backend specifics:

  • WAZUH_FIELD_MAP — decoder-scoped field maps: windows_security, windows_sysmon, windows_eventchannel, linux_auth, linux_audit, linux_syslog
  • Emits <group><rule><field> elements; OR conditions produce multiple <rule> siblings; NOT conditions produce negate="yes" on <field>
  • <mitre><id> block requires Wazuh 4.2+
  • Aggregation conditions (conditions containing |), parenthesised sub-expressions, and conditions resolving to no field selections are not supported natively — convert() returns {"supported": False, "reason": <code>} instead of a query string, and app.py/cli.py map the reason code to a static, human-written message (never exception text — see CodeQL py/stack-trace-exposure)
  • rule_id clamped to 1–999,999; group_name validated against ^[A-Za-z0-9._-]{1,64}$

Helper functions:

  • build_rule_from_form(data: dict) → SigmaRule — builds a SigmaRule from web form/API JSON
  • build_rule_from_template(template_key: str) → SigmaRule — instantiates a SigmaRule from RULE_TEMPLATES

Flask routes (app.py)

MethodEndpointFunction
GET/index() — serves index.html
POST/api/generateapi_generate() — build + validate + convert all backends
GET/api/template/<key>api_template() — load pre-built template
POST/api/validateapi_validate() — validate YAML only
POST/api/convertapi_convert() — convert to one backend
POST/api/library/saveapi_save_rule() — write .yml to rules/
GET/api/library/listapi_list_rules() — list rules/
GET/api/library/load/<file>api_load_rule() — load and convert
DELETE/api/library/delete/<file>api_delete_rule() — delete from rules/
GET/api/library/exportapi_export_library() — JSON bundle of all rules
GET/api/log-sourcesapi_log_sources() — return LOG_SOURCES
GET/api/mitreapi_mitre() — return MITRE_ATTACK_MAP + TACTIC_IDS
GET/api/templatesapi_templates() — return RULE_TEMPLATES summary

Request bodies are capped at 50 KB (_MAX_RULE_YAML_BYTES). File paths under rules/ are sanitized via secure_filename and checked against path traversal before read/write.

Web UI (templates/index.html)

Single-page app with four tabs: Rule Builder, Templates, Validator, Rule Library. All backend output tabs (Splunk SPL, Elastic KQL, Elastic EQL, Sentinel KQL, Wazuh XML, QRadar AQL, DaC JSON) render in the same page on generate.


Quick Start

Web UI

git clone https://github.com/Rootless-Ghost/SigmaForge.git
cd SigmaForge
python -m venv venv
source venv/bin/activate # Linux/Mac
venv\Scripts\activate # Windows
pip install -r requirements.txt
python app.py
# Open http://localhost:5000

Note: Do not expose on a shared network without adding authentication — the library endpoints read and write files on disk.

Docker (standalone)

docker build -t sigmaforge .
docker run -p 5000:5000 sigmaforge

Open http://localhost:5000

CLI

generate — build a rule from arguments

python cli.py generate \
--title "Suspicious CMD Execution" \
--logsource process_creation \
--level high \
--field "Image|endswith=\\cmd.exe" \
--field "ParentImage|endswith=\\excel.exe,\\winword.exe" \
--mitre T1059.003 \
--backend splunk \
--output suspicious_cmd.yml
FlagShortDescription
--title-tRule title
--description-dRule description
--logsource-lLog source key (e.g. process_creation)
--levelinformational / low / medium / high / critical
--statusexperimental / test / stable
--authorRule author
--field-fDetection field — format field|modifier=value; repeatable
--condition-cDetection condition (default: selection)
--mitre-mComma-separated MITRE technique IDs
--falsepositivesComma-separated false positive descriptions
--backend-bsplunk / elastic / eql / sentinel / wazuh / qradar / dac_json
--rule-idWazuh rule ID (integer, default 100001)
--group-nameWazuh group name (default sigma_rules)
--output-oOutput file path (.yml)

validate — check a Sigma YAML file

python cli.py validate my_rule.yml

convert — convert an existing rule file to a target backend

python cli.py convert my_rule.yml --backend wazuh --rule-id 100200 --group-name sigma_rules
python cli.py convert my_rule.yml --backend qradar
python cli.py convert my_rule.yml --backend dac_json

template — generate from a pre-built template

python cli.py template suspicious_powershell
python cli.py template mimikatz_execution --output mimikatz.yml

templates — list all available templates with level and technique IDs

python cli.py templates

logsources — list all available log source keys and their fields

python cli.py logsources

Running tests

Test dependencies (pytest) are kept separate from requirements.txt, which is runtime-only and installed into the Docker image.

pip install -r requirements-dev.txt
python -m pytest

tests/test_conversion.py parametrizes every RULE_TEMPLATES key across all seven backends (splunk, elastic, eql, sentinel, wazuh, qradar, dac_json), asserting SIEMConverter.convert() returns a non-empty string — except Wazuh on the three aggregation-condition templates (windows_logon_brute_force, firewall_port_scan, brute_force_by_username), where it asserts a {"supported": False, "reason": "aggregation_condition"} result is returned instead. It also asserts every template validates via SigmaValidator, and locks in the aggregation-handling fix with regression checks against the Splunk/Sentinel output of those three templates.


MITRE ATT&CK Coverage

MITRE_ATTACK_MAP in sigma_engine.py covers 110+ technique and sub-technique IDs across 13 tactics. TACTIC_IDS maps each tactic name to its TA number.

Tactic coverage

TacticTA IDExample techniques in map
ReconnaissanceTA0043T1595, T1592, T1589
Initial AccessTA0001T1566, T1190, T1133, T1078, T1195
ExecutionTA0002T1059 (and .001–.007), T1053, T1047, T1203, T1569
PersistenceTA0003T1547, T1136, T1543, T1505, T1098
Privilege EscalationTA0004T1055, T1068, T1548
Defense EvasionTA0005T1562, T1070, T1027, T1036, T1218, T1112, T1140, T1564
Credential AccessTA0006T1003 (and .001–.003), T1110, T1555, T1558, T1552
DiscoveryTA0007T1087, T1082, T1083, T1057, T1018, T1046, T1135
Lateral MovementTA0008T1021 (and .001–.004, .006), T1570 — a concrete rule for T1021.006 (WinRM child process of wsmprovhost.exe) ships in rules/winrm_child_process_wsmprovhost.yml
CollectionTA0009T1005, T1560, T1074, T1113, T1115, T1119
Command & ControlTA0011T1071, T1105, T1090, T1572, T1573, T1095, T1219
ExfiltrationTA0010T1041, T1048, T1567, T1537
ImpactTA0040T1486, T1485, T1489, T1490, T1491, T1498, T1529

Tags are generated by SigmaRule.get_mitre_tags() in Sigma format: attack.<tactic> and attack.tXXXX_XXX. When a rule is converted to Wazuh XML, technique IDs are emitted as <mitre><id>T####</id></mitre> inside the <rule> block.

Pre-built templates and their technique mappings

Template keyNameLevelTechnique
suspicious_powershellSuspicious PowerShell ExecutionhighT1059.001
mimikatz_executionMimikatz Credential DumpingcriticalT1003.001
suspicious_scheduled_taskSuspicious Scheduled Task CreationmediumT1053.005
windows_logon_brute_forceMultiple Failed Logon AttemptsmediumT1110
event_log_clearingWindows Event Log ClearedhighT1070.001
suspicious_dns_querySuspicious DNS Query to Known Malicious TLDmediumT1071.004
lolbin_executionLOLBin Suspicious ExecutionmediumT1218
firewall_port_scanPotential Port Scan DetectedmediumT1046
proxy_suspicious_user_agentSuspicious User Agent in Proxy LogsmediumT1071.001
registry_persistenceRegistry Run Key PersistencemediumT1547.001
network_connection_suspicious_portOutbound Connection to Non-Standard PortlowT1095
linux_reverse_shellLinux Reverse Shell DetectedcriticalT1059.004
winrm_child_processChild Process of WinRM Provider Host (wsmprovhost.exe)highT1021.006
brute_force_by_usernameBrute Force - Multiple Failed Logons by UsermediumT1110
off_hours_successful_logonOff-Hours Successful LogonmediumT1078
privilege_escalation_group_membershipPrivilege Escalation via Privileged Group Membership ChangehighT1098
account_lockoutAccount LockoutlowT1110.001

Integration with Nebula Forge

SigmaForge occupies the Detect phase in the Nebula Forge pipeline and has two primary integration points.

SigmaForge ↔ EndpointForge (closed-loop validation)

EndpointForge runs on the monitored host and exports findings as Wazuh-formatted log entries via WazuhExporter. SigmaForge generates the Wazuh XML rules that Wazuh uses to alert on those findings.

The loop:

  1. Author — write a detection rule in SigmaForge targeting a specific technique (e.g. T1547.001 — Registry Run Key persistence).
  2. Deploy — use cli.py convert rule.yml --backend wazuh --rule-id 100300 --group-name sigma_rules to produce Wazuh XML; drop the output into the Wazuh rules directory.
  3. Trigger — run a persistence check in EndpointForge (POST /api/scan/persistence or POST /api/scan/registry). EndpointForge's WazuhExporter.export_findings() writes findings to the Wazuh log path.
  4. Validate — confirm Wazuh fires the rule against the exported telemetry. If the rule does not fire, the detection gap feeds back into SigmaForge for tuning.
  5. Iterate — adjust the rule's field conditions in SigmaForge, re-export the Wazuh XML, re-validate.

The Wazuh field maps in WAZUH_FIELD_MAP (windows_sysmon, windows_security, linux_auth, etc.) are designed to match the field paths that Wazuh decoders produce from real agent data. This means a SigmaForge-generated Wazuh rule referencing win.eventdata.commandLine will match actual Sysmon EventID 1 output from a Wazuh-enrolled Windows agent.

Home lab target: Wazuh server at <YOUR_WAZUH_IP> (v4.14.4), Windows Agent running SwiftOnSecurity Sysmon config.

SigmaForge ↔ SIREN (incident documentation)

When a SigmaForge rule fires and an incident is declared, the detection rule metadata feeds directly into a SIREN (IncidentReport) entry:

  • The rule title and description become the incident description.
  • MITRE technique IDs (mitre_techniques list) map to the SIREN recommendations or timeline source field.
  • The Sigma level field (high, critical) aligns with SIREN's SeverityLevel enum for consistent severity scoring across the detection-to-report pipeline.

SigmaForge in the full pipeline

SigmaForge (Detect)
│
├── Wazuh XML → Wazuh SIEM → alert fires
│
├── EndpointForge telemetry validates rule in lab
│ (closed-loop: gap found → return to SigmaForge)
│
└── Rule fires in production
│
└── SIREN (Report) — IR report with technique context

Project Structure

SigmaForge/
├── app.py # Flask web application and REST API
├── cli.py # CLI — generate / validate / convert / template / templates / logsources
├── requirements.txt
├── src/
│ ├── __init__.py
│ └── sigma_engine.py # SigmaRule, SigmaValidator, SIEMConverter, RULE_TEMPLATES, LOG_SOURCES
├── templates/
│ └── index.html # Single-page web UI (Rule Builder, Templates, Validator, Rule Library)
├── static/
│ ├── css/style.css
│ └── js/app.js
├── rules/ # Saved rule library (.yml files)
│ └── winrm_child_process_wsmprovhost.yml # Seed rule — T1021.006 WinRM child-process detection (process_creation, high)
├── SECURITY.md
└── LICENSE

License

This project is licensed under the MIT License — see the LICENSE file for details.

About

Vendor-agnostic Sigma rule generator with a custom conversion engine (no pySigma dependency). Six SIEM backends — Splunk SPL, Elastic KQL/EQL, Sentinel KQL, Wazuh XML, QRadar AQL — plus Detection-as-Code JSON. 17 templates, MITRE ATT&CK auto-tagging, 145-case test suite. Wazuh XML validated against a live 4.14.4 manager.

Topics

Resources

Security policy

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages