Skip to main content

sigma2sql

ci pypi python license

Run Sigma detection rules as SQL on SQLite, PostgreSQL, DuckDB and ClickHouse: hunt through log files, .evtx included, without a SIEM, convert rules for the database your logs already live in, and unit-test rules before they reach production. Full documentation: brkozer.github.io/sigma2sql.

  • All of SigmaHQ converts: the 3807 rules in its five rule folders on SQLite, DuckDB and ClickHouse, 3804 on PostgreSQL, plus all 45 count() aggregation rules SigmaHQ keeps under unsupported/ and deprecated/. 27 of the 3807 use placeholders and need your values (details). Every generated query is executed on all four databases in CI each week.
  • Checked against real events: all 477 of SigmaHQ's regression samples match as expected on SQLite, DuckDB and ClickHouse and from the original .evtx files through hunt, and on those events sigma2sql agrees with pySigma on 3076 of 3078 rules; the two differences are IPv6 addresses pySigma's text matching misses (details).
  • Correlations: event counts, distinct counts, sums, averages, percentiles and temporal sequences over a sliding window, as plain SQL.
  • Your schema: profiles map Sigma fields to your columns or nested JSON (Sysmon, EVTX exports, Elastic/Winlogbeat ECS, OCSF built in) and limit each rule to the right log source.

sigma2sql hunting through sample logs, checking rule tests and converting a rule to PostgreSQL

Install

pip install "sigma2sql[duckdb,evtx]"
# or as a standalone tool
uv tool install "sigma2sql[duckdb,evtx]"

The duckdb extra is needed for hunt and evtx for reading .evtx files; convert, test and check only need PyYAML.

Hunt through log files

git clone --depth 1 https://github.com/SigmaHQ/sigma
sigma2sql hunt sigma/rules --logs 'logs/*.evtx' --examples 1

hunt loads EVTX, JSON, JSON Lines, Parquet or CSV files into an in-memory DuckDB database, runs every rule and correlation, and lists the ones that matched, most severe first. On the examples in this repository:

$ sigma2sql hunt examples/rules --logs examples/events.jsonl
LEVEL         HITS  RULE
high             2  PowerShell started with an encoded command  (examples/rules/encoded_powershell.yml)
high             1  Brute force against one account  (examples/rules/brute_force.yml)
high             1  Office application starting a shell  (examples/rules/office_spawns_shell.yml)
medium           1  Successful RDP logon from a public address  (examples/rules/rdp_from_outside.yml)
4 of 4 rules matched 12 events (0 rules skipped)

--examples N prints matching events (or correlation results such as {"TargetUserName":"admin","window_start":"2026-11-01 03:12:01","window_end":"2026-11-01 03:12:17","value":3}), --format json gives a machine-readable report, --sarif FILE also writes a SARIF report for code scanning, and --fail-on high a non-zero exit code when a rule at that level or above fires.

.evtx files use the evtx profile unless you pass another one. The Windows profiles only run rules for product: windows, so a directory of all SigmaHQ rules can be used as it is; the rest are counted on stderr. Logs from an incident are often damaged, so everything readable is kept and the rest is reported: a cut-off file or a bad chunk gives a warning, and only a hunt that could read no event at all is an error.

Convert rules

$ sigma2sql convert examples/rules/office_spawns_shell.yml -d postgres --json-column data
-- Office application starting a shell (8a7f3e91-2c44-4b8e-b0f6-3e5d9c1a7b02)
SELECT * FROM "events" WHERE (("data"->>'ParentImage' ILIKE '%\\winword.exe' ESCAPE '\' OR ...)
  AND ("data"->>'Image' ILIKE '%\\cmd.exe' ESCAPE '\' OR ...));

A whole directory works the same way, e.g. sigma2sql convert sigma/rules -d postgres -t security.events --json-column event --profile ecs-winlogbeat. Without --json-column each Sigma field is a column. Unsupported rules are listed on stderr with the reason, and the command exits non-zero.

Option Meaning
-d, --dialect sqlite (default), postgres, duckdb or clickhouse
-t, --table table name, schema.table allowed (default events)
--json-column read fields from this JSON / JSONB column
--keyword-column column searched by keyword rules (in JSON mode the whole document)
-p, --profile built-in profile or a YAML file (see below)
-m, --mapping YAML map from Sigma field to your column or key, applied over the profile
--placeholders YAML file with the values of %name% placeholders (see below)
--nested dotted names are paths into nested JSON
--timestamp-field, --timestamp-format event time for correlations (iso, epoch_s, epoch_ms)

Correlations

Sigma correlation rules and the older | count() by ... > N syntax both become SQL. With a timespan, each event closes a window over the events of the same group before it; the window's events are found with a self-join, which works the same on every database and allows distinct counts:

title: Brute force against one account
correlation:
  type: event_count          # also value_count, value_sum, value_avg, value_median,
                             # value_percentile, temporal
  rules: [failed_logon]
  group-by: [TargetUserName]
  timespan: 5m
  condition: {gte: 3}

Each result row holds the group values, the window's start and end (first_seen and last_seen without a timespan) and the counted value. Rules referenced by a correlation are not reported on their own unless it sets generate: true. temporal_ordered is not supported yet.

Placeholders

Rules with the expand modifier leave a value for you to fill in, such as your domain controllers. Give the values in a YAML file; a list means any of them:

dc_ip_addresses: [10.0.0.10, 10.0.0.11]
Workstations: "WS-*"
sigma2sql convert rules/ --placeholders values.yml

A rule with a placeholder you did not provide is skipped and named on stderr, rather than converted into something that never matches or always does.

Filters

Sigma filters keep your exceptions out of the rules, so rules can be updated without losing them. Pass filter files along with the rules; hunt and convert apply them, and a filter that reaches no rule gives a warning:

title: Not on build servers
logsource: {product: windows, category: process_creation}
filter:
  rules: [5f4b9d6e-5a3f-4c8c-9d1e-2b7a8c6e1f30]    # ids or names, or `any`
  build: {Computer|startswith: build-}
  condition: not build
sigma2sql hunt rules/ filters/ --logs 'logs/*.evtx'

check and test load one rule file at a time, so filters in other files do not apply. A filter in the same file as the rule does.

Profiles

A profile describes where your events keep Sigma's fields and which events each log source covers. sigma2sql profiles lists the built-in ones:

Profile For
sysmon Sysmon events with their own field names; each category limited to its event ID
evtx EVTX exported to JSON by evtx_dump or python-evtx (Event.System, Event.EventData)
ecs-winlogbeat Windows events shipped by Winlogbeat in Elastic Common Schema
ocsf OCSF 1.x events, time in epoch milliseconds

Your own profile is a small YAML file:

nested: true
timestamp: "@timestamp"
product: windows                                # optional: `hunt` runs only these rules
default_field: "winlog.event_data.{field}"      # fallback for unmapped fields
fields:
  Image: process.executable
  EventID: [event.code, winlog.event_id]         # first one present wins
logsources:
  - match: {product: windows, category: process_creation}
    table: process_events                        # optional: route to a table
    where: {event.code: "1"}                     # optional: extra condition

Without a profile, rules are not limited by log source: a network rule can then match a process event that happens to have the same field values. The built-in profiles cover the common Windows sources; for other data, add logsources entries.

Test rules

Put example events next to a rule, one JSON object per line:

rules/office_spawns_shell.yml
rules/office_spawns_shell.match.jsonl     # every event must match
rules/office_spawns_shell.nomatch.jsonl   # no event may match
$ sigma2sql check examples/rules --require-tests
ok   examples/rules/brute_force.yml
ok   examples/rules/encoded_powershell.yml
ok   examples/rules/office_spawns_shell.yml
ok   examples/rules/rdp_from_outside.yml
4 passed, 0 failed, 0 without tests

A failure names the rule, the file and the event's position in it, e.g. after adding an Outlook-spawned cmd.exe to the match file of a rule that only watches Word, Excel and PowerPoint:

FAIL rules/office_spawns_shell.yml: Office application starting a shell: missed office_spawns_shell.match.jsonl event(s) 1

check also loads rules without test files, so a rule that cannot be loaded or converted, or an invalid regular expression, fails the run. A rule path that does not exist or holds no rule files exits with code 2, also next to a good path.

The same check as a GitHub Action:

- uses: brkozer/sigma2sql@v0.3.1
  with:
    path: rules
    profile: sysmon          # optional
    require-tests: "true"    # optional
    sarif: rules.sarif       # optional, for github/codeql-action/upload-sarif

sigma2sql test RULE EVENTS.jsonl prints the events (or correlation results) a single rule produces.

Supported Sigma features

Feature SQLite PostgreSQL DuckDB ClickHouse
maps, lists, keywords (with all, re and string modifiers) ✓ ✓ ✓ ✓
and, or, not, 1 of x*, all of x*, them, condition lists ✓ ✓ ✓ ✓
wildcards and escapes, contains, startswith, endswith, all, cased, windash, neq ✓ ✓ ✓ ✓
base64, base64offset, utf16, utf16le, utf16be, wide ✓ ✓ ✓ ✓
re with i, m, s REGEXP ¹ ~ ² regexp_matches ³ match ³
cidr (IPv4 and IPv6, any notation) cidr_match() ¹ inet <<= cidr_match() ¹ isIPAddressInRange
lt, lte, gt, gte, exists, fieldref, null ✓ ✓ ✓ ✓ ⁴
minute, hour, day, week, month, year (UTC, ISO weeks) ✓ ✓ ✓ ✓
expand placeholders, filters ✓ ✓ ✓ ✓
correlations, count() aggregations ✓ ¹ ✓ ✓ ✓ ⁵
temporal_ordered, temporal with a boolean condition – – – –

¹ Not built into the database. hunt, test and check register them; when you run the SQL elsewhere, register regexp(pattern, value), cidr_match(value, network) and, for percentiles, the aggregate sigma_percentile(value, fraction) yourself (sigma2sql.runner.connect() returns a SQLite connection with all three).
² PostgreSQL rejects repetition counts above 255; those three rules are reported as unsupported rather than silently changed. Without s or m, . still matches a newline there. Values such as - in an address or number field are skipped with pg_input_is_valid, so cidr, comparisons and correlations need PostgreSQL 16 or newer.
³ RE2: no lookaround or backreferences; such rules are reported as unsupported.
⁴ Without --json-column, a missing value is only NULL in Nullable columns.
⁵ Tested on ClickHouse 25.8. Correlations with group-by need IS NOT DISTINCT FROM in JOIN ON. One SigmaHQ rule, a list of driver hashes, is larger than the default max_query_size of 256 KiB and needs that setting raised.

Matching is case-insensitive by default (ASCII in SQLite, as its LIKE and NOCASE are); a number in a rule matches a value in a JSON column whether it is stored as 4624 or "4624", and not on a missing field is true, as Sigma intends.

How it works

flowchart LR
    Y[rule files] --> L[loader<br/>rules, correlations,<br/>count aggregations]
    L --> C[condition parser]
    L --> D[search compiler<br/>modifiers, wildcards,<br/>base64, windash]
    C --> E[match tree]
    D --> E
    P[profile<br/>fields, logsources,<br/>timestamp] --> R
    E --> R[renderer<br/>SQLite · PostgreSQL · DuckDB · ClickHouse]
    R --> Q[SQL]
    R --> K[correlation SQL<br/>self-join windows]
    Q --> H[hunt / test / check]
    K --> H

Library use

from pathlib import Path

from sigma2sql import Target, load_rule_file, to_query
from sigma2sql.profile import apply_profile, load_profile

rule = load_rule_file(Path("rule.yml"))
target = apply_profile(
    Target("postgres", "logs.windows", json_column="event"), load_profile("evtx")
)
print(to_query(rule, target))

Development

uv sync
uv run pytest                                               # SQLite and DuckDB
SIGMA2SQL_PG_DSN=postgresql://postgres:test@localhost/postgres uv run pytest   # + PostgreSQL
SIGMA2SQL_CH_URL=http://localhost:8123/ uv run pytest                          # + ClickHouse
uv run python scripts/check_sigmahq.py sigma/rules --dsn ... --clickhouse ... [--profile evtx]
uv run python scripts/check_regression.py sigma --clickhouse ... --evtx
uv run --with pysigma --with pysigma-backend-sqlite python scripts/diff_pysigma.py sigma
uv run python scripts/demo_svg.py docs/demo.svg

The behaviour tests in tests/cases.py and tests/test_correlation.py run unchanged on all four databases. The example rules and events in examples/ were written for this project.

License

MIT

Metadata

Release files for sigma2sql 0.3.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for sigma2sql 0.3.1
File Size Uploaded Provenance Actions
sigma2sql-0.3.1.tar.gz 68.3 kB With provenance Details for sigma2sql-0.3.1.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for sigma2sql 0.3.1
File Interpreter ABI Platform Actions
sigma2sql-0.3.1-py3-none-any.whl Python 3 none any Details for sigma2sql-0.3.1-py3-none-any.whl

Total release size: 115.6 kB

Release files / sigma2sql-0.3.1.tar.gz

Download URL sigma2sql-0.3.1.tar.gz
Size 68.3 kB
Tags Source
SHA-256 checksum
How to use checksums
a6c0694156d28954ce583a00f24a4741b2ba426539ca4a63328832e2bb4d99b5
BLAKE2b-256 checksum
How to use checksums
9dfab26391c20992c8f034ef6eac65927336d71786074151d5c4374a2415de47
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 6, 2026.

Transparency log

Release files / sigma2sql-0.3.1-py3-none-any.whl

Download URL sigma2sql-0.3.1-py3-none-any.whl
Size 47.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
5b86a08edd2965a4e00ae0bb1d89dd54f0b833b1ad7a0b1a06dbc69895c6c5c3
BLAKE2b-256 checksum
How to use checksums
d373beacef9a46e01dc786bfef60ddf945e684fcd884c04ef562014ecd53fb20
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 6, 2026.

Transparency log

Security for sigma2sql 0.3.1

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

Both files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for sigma2sql-0.3.1.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 6, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page