pySigma-backend-sqlite 1.2.4__tar.gz → 2.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,176 @@
1
+ Metadata-Version: 2.4
2
+ Name: pySigma-backend-sqlite
3
+ Version: 2.1.0
4
+ Summary: pySigma sqlite backend
5
+ License-Expression: LGPL-3.0-only
6
+ License-File: LICENSE
7
+ Author: wagga
8
+ Requires-Python: >=3.11,<4.0
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Programming Language :: Python :: 3.11
11
+ Classifier: Programming Language :: Python :: 3.12
12
+ Classifier: Programming Language :: Python :: 3.13
13
+ Classifier: Programming Language :: Python :: 3.14
14
+ Classifier: Programming Language :: Python :: 3.15
15
+ Requires-Dist: pysigma (>=2.0.0,<3.0)
16
+ Project-URL: Repository, https://github.com/SigmaHQ/pySigma-backend-sqlite
17
+ Description-Content-Type: text/markdown
18
+
19
+ ![Tests](https://github.com/wagga40/pySigma-backend-sqlite/actions/workflows/test.yml/badge.svg)
20
+ ![Coverage Badge](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/wagga40/2ec45ded898fa11f2c42bcb9d2b163cf/raw/test.json)
21
+
22
+ # pySigma SQLite Backend
23
+
24
+ Converts [Sigma](https://github.com/SigmaHQ/sigma) rules into SQLite queries. The
25
+ package `sigma.backends.sqlite` provides `sqliteBackend`, registered as the
26
+ sigma-cli target `sqlite`. It is the converter behind
27
+ [Zircolite](https://github.com/wagga40/Zircolite), and its output runs on any
28
+ SQLite table of flattened events.
29
+
30
+ Maintained by [wagga](https://github.com/wagga40/).
31
+
32
+ ## Install
33
+
34
+ ```shell
35
+ pip install pysigma-backend-sqlite
36
+ ```
37
+
38
+ Requires Python 3.11+ and [pySigma](https://github.com/SigmaHQ/pySigma) 2.x.
39
+ Backend 2.0.0 is the last release for pySigma 1.5. Correlation output needs
40
+ SQLite 3.38 or newer.
41
+
42
+ ## Quick start
43
+
44
+ ```python
45
+ from sigma.collection import SigmaCollection
46
+ from sigma.backends.sqlite import sqliteBackend
47
+
48
+ rules = SigmaCollection.from_yaml(r"""
49
+ title: Whoami execution
50
+ status: test
51
+ logsource:
52
+ category: process_creation
53
+ product: windows
54
+ detection:
55
+ selection:
56
+ Image|endswith: '\whoami.exe'
57
+ condition: selection
58
+ """)
59
+
60
+ print(sqliteBackend().convert(rules)[0])
61
+ # SELECT * FROM logs WHERE Image LIKE '%\\whoami.exe' ESCAPE '\'
62
+ ```
63
+
64
+ Processing pipelines map a logsource to concrete fields. With
65
+ `sqliteBackend(sysmon_pipeline() + windows_logsource_pipeline())` the same rule becomes:
66
+
67
+ ```sql
68
+ SELECT * FROM logs WHERE Channel='Microsoft-Windows-Sysmon/Operational' AND (EventID=1 AND Image LIKE '%\\whoami.exe' ESCAPE '\')
69
+ ```
70
+
71
+ Those pipeline packages still declare `pysigma<2`; until they are updated, install
72
+ them with `pip install --no-deps pysigma-pipeline-sysmon pysigma-pipeline-windows`.
73
+ With sigma-cli:
74
+
75
+ ```shell
76
+ sigma convert -t sqlite -f zircolite -p sysmon -p windows-logsources rules/
77
+ ```
78
+
79
+ sigma-cli 3.1 still requires pySigma 1.x, so it installs backend 2.0.0.
80
+
81
+ ## Output formats
82
+
83
+ | Format | Output |
84
+ |---|---|
85
+ | `default` | One `SELECT * FROM logs WHERE ...` statement per rule |
86
+ | `zircolite` | A JSON list with one entry per rule: Sigma metadata, the SQL, and what a consumer needs to run it. See [output format](https://github.com/wagga40/pySigma-backend-sqlite/blob/main/docs/output-format.md) |
87
+
88
+ ## Options
89
+
90
+ Pass options as constructor keywords, or from sigma-cli with `-O name=value`.
91
+ Options are read at construction; assigning attributes later skips validation
92
+ and does not change the timestamp expression.
93
+
94
+ | Option | Default | Purpose |
95
+ |---|---|---|
96
+ | `table` | `logs` | Table to query. A pipeline `setState` on `table` takes precedence |
97
+ | `collate_nocase` | `false` | Emit `field='value' COLLATE NOCASE` for tables without `NOCASE` columns |
98
+ | `max_flat_operands` | `100` | Longer AND/OR chains are nested to stay under SQLite's expression depth limit |
99
+ | `fulltext_field` | none | Column of searchable event text; enables [keyword search](https://github.com/wagga40/pySigma-backend-sqlite/blob/main/docs/keyword-search.md) |
100
+ | `timestamp_field` | `timestamp` | Event time column used by correlations |
101
+ | `timestamp_format` | `iso` | `iso`, `unix`, `unix_ms` or `unix_us` |
102
+ | `timestamp_seconds_expression` | ISO parser | SQL turning `{field}` into Unix seconds; overrides `timestamp_format` |
103
+ | `event_id_field` | `rowid` | Non-NULL, per-table unique event identifier for correlation evidence |
104
+ | `observation_end` | latest event | Unix seconds up to which absence windows can expire |
105
+
106
+ ## Generated SQL
107
+
108
+ | Modifier | SQL |
109
+ |---|---|
110
+ | `contains`, `startswith`, `endswith` | `LIKE '%value%' ESCAPE '\'` and its prefix/suffix forms |
111
+ | `all` | `AND` of the values |
112
+ | `re` | `REGEXP` (needs a registered function, see [Limitations](#limitations)) |
113
+ | `cidr` | `=` or `LIKE` prefixes of the expanded network |
114
+ | `cased` | `GLOB`; literal `*`, `?`, `[` become `[*]`, `[?]`, `[[]` |
115
+ | `fieldref` | `field1=field2`, or `LIKE` with the second field's `%` and `_` escaped |
116
+ | `exists` | `IS NOT NULL` / `IS NULL` |
117
+ | `gt`, `gte`, `lt`, `lte`, `neq` | `>`, `>=`, `<`, `<=`, negated equality |
118
+ | `minute`, `hour`, `day`, `week`, `month`, `year` | `strftime()` part |
119
+ | `base64`, `base64offset`, `wide`, `utf16*`, `windash` | Expanded by pySigma into ordinary matches |
120
+
121
+ The SQL keeps Sigma's meaning without help from the caller:
122
+
123
+ - **Negation.** Sigma reads a condition on a missing field as false; SQLite reads
124
+ it as `NULL`, and `NOT NULL` drops the row. Every `NOT` therefore wraps its
125
+ operand: `EventID=1 AND (NOT COALESCE((CommandLine LIKE '%foo%' ESCAPE '\'), 0))`.
126
+ - **Case.** `LIKE` ignores case. `=` does only on `NOCASE` columns, which is how
127
+ Zircolite builds its table; otherwise set `collate_nocase`. `cased` uses `GLOB`.
128
+ - **Booleans.** `Flag: true` matches text or integer storage: `(Flag='true' OR Flag=1)`.
129
+ - **Long lists.** Chains above `max_flat_operands` are regrouped, so rules with
130
+ thousands of values don't fail with "Expression tree is too large".
131
+ - **Field names.** SQLite keywords and names with characters other than letters,
132
+ digits and `_` are quoted with backticks. `field: null` becomes `field IS NULL`.
133
+
134
+ ## Correlations
135
+
136
+ All Sigma correlation types run in SQLite: `event_count`, `value_count`,
137
+ `value_sum`, `value_avg`, `value_median`, `value_percentile`, `temporal` and
138
+ `temporal_ordered`, including chains, aliases and extended temporal conditions.
139
+ Each result row is one alert occurrence with its window, metric and contributing
140
+ event IDs. Semantics are described in [correlations](https://github.com/wagga40/pySigma-backend-sqlite/blob/main/docs/correlations.md);
141
+ upgrading from backend 1.x is covered in the [migration guide](https://github.com/wagga40/pySigma-backend-sqlite/blob/main/docs/migration-v2.md).
142
+
143
+ ## Keyword search
144
+
145
+ Keyword (fieldless) selections need a column of searchable event text:
146
+ `sqliteBackend(fulltext_field="_sigma_fulltext")`. Without it, keyword rules raise
147
+ an error rather than guess. See [keyword search](https://github.com/wagga40/pySigma-backend-sqlite/blob/main/docs/keyword-search.md).
148
+
149
+ ## Limitations
150
+
151
+ - **REGEXP.** SQLite has no built-in `REGEXP`. Register one that is false for `NULL`:
152
+ `connection.create_function("regexp", 2, lambda p, v: v is not None and re.search(p, str(v)) is not None)`.
153
+ pySigma 2 validates `|re` patterns with RE2, so rules with lookarounds or
154
+ backreferences are rejected when parsed.
155
+ - **CIDR.** SQLite has no network type. Prefix patterns match IPv6 addresses only
156
+ in the spellings pySigma expands to.
157
+ - **Case folding** (`NOCASE`, `LIKE`) is ASCII only.
158
+ - **Timestamp parts** use `strftime()` and need ISO-8601 text; a numeric epoch is
159
+ read as a Julian day. `%W` can return week 0.
160
+ - **Field existence.** SQL `NULL` cannot tell a missing field from an explicit null.
161
+ - **Multi-field `value_count`** (a list in `condition.field`) is rejected.
162
+ - **Unknown columns.** A query naming a column the table lacks fails to prepare.
163
+ Add missing columns as `NULL` first; see [output format](https://github.com/wagga40/pySigma-backend-sqlite/blob/main/docs/output-format.md).
164
+
165
+ ## Development
166
+
167
+ ```shell
168
+ poetry install
169
+ poetry run pytest
170
+ SQLITE_MIN_BINARY=/path/to/sqlite-3.38 poetry run pytest tests/test_sqlite_minimum.py
171
+ ```
172
+
173
+ `tools/audit_corpus.py` converts and prepares a SigmaHQ checkout per profile
174
+ (coverage, not detection accuracy). `tools/benchmark_correlations.py` times
175
+ sparse and dense correlation windows.
176
+
@@ -0,0 +1,157 @@
1
+ ![Tests](https://github.com/wagga40/pySigma-backend-sqlite/actions/workflows/test.yml/badge.svg)
2
+ ![Coverage Badge](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/wagga40/2ec45ded898fa11f2c42bcb9d2b163cf/raw/test.json)
3
+
4
+ # pySigma SQLite Backend
5
+
6
+ Converts [Sigma](https://github.com/SigmaHQ/sigma) rules into SQLite queries. The
7
+ package `sigma.backends.sqlite` provides `sqliteBackend`, registered as the
8
+ sigma-cli target `sqlite`. It is the converter behind
9
+ [Zircolite](https://github.com/wagga40/Zircolite), and its output runs on any
10
+ SQLite table of flattened events.
11
+
12
+ Maintained by [wagga](https://github.com/wagga40/).
13
+
14
+ ## Install
15
+
16
+ ```shell
17
+ pip install pysigma-backend-sqlite
18
+ ```
19
+
20
+ Requires Python 3.11+ and [pySigma](https://github.com/SigmaHQ/pySigma) 2.x.
21
+ Backend 2.0.0 is the last release for pySigma 1.5. Correlation output needs
22
+ SQLite 3.38 or newer.
23
+
24
+ ## Quick start
25
+
26
+ ```python
27
+ from sigma.collection import SigmaCollection
28
+ from sigma.backends.sqlite import sqliteBackend
29
+
30
+ rules = SigmaCollection.from_yaml(r"""
31
+ title: Whoami execution
32
+ status: test
33
+ logsource:
34
+ category: process_creation
35
+ product: windows
36
+ detection:
37
+ selection:
38
+ Image|endswith: '\whoami.exe'
39
+ condition: selection
40
+ """)
41
+
42
+ print(sqliteBackend().convert(rules)[0])
43
+ # SELECT * FROM logs WHERE Image LIKE '%\\whoami.exe' ESCAPE '\'
44
+ ```
45
+
46
+ Processing pipelines map a logsource to concrete fields. With
47
+ `sqliteBackend(sysmon_pipeline() + windows_logsource_pipeline())` the same rule becomes:
48
+
49
+ ```sql
50
+ SELECT * FROM logs WHERE Channel='Microsoft-Windows-Sysmon/Operational' AND (EventID=1 AND Image LIKE '%\\whoami.exe' ESCAPE '\')
51
+ ```
52
+
53
+ Those pipeline packages still declare `pysigma<2`; until they are updated, install
54
+ them with `pip install --no-deps pysigma-pipeline-sysmon pysigma-pipeline-windows`.
55
+ With sigma-cli:
56
+
57
+ ```shell
58
+ sigma convert -t sqlite -f zircolite -p sysmon -p windows-logsources rules/
59
+ ```
60
+
61
+ sigma-cli 3.1 still requires pySigma 1.x, so it installs backend 2.0.0.
62
+
63
+ ## Output formats
64
+
65
+ | Format | Output |
66
+ |---|---|
67
+ | `default` | One `SELECT * FROM logs WHERE ...` statement per rule |
68
+ | `zircolite` | A JSON list with one entry per rule: Sigma metadata, the SQL, and what a consumer needs to run it. See [output format](https://github.com/wagga40/pySigma-backend-sqlite/blob/main/docs/output-format.md) |
69
+
70
+ ## Options
71
+
72
+ Pass options as constructor keywords, or from sigma-cli with `-O name=value`.
73
+ Options are read at construction; assigning attributes later skips validation
74
+ and does not change the timestamp expression.
75
+
76
+ | Option | Default | Purpose |
77
+ |---|---|---|
78
+ | `table` | `logs` | Table to query. A pipeline `setState` on `table` takes precedence |
79
+ | `collate_nocase` | `false` | Emit `field='value' COLLATE NOCASE` for tables without `NOCASE` columns |
80
+ | `max_flat_operands` | `100` | Longer AND/OR chains are nested to stay under SQLite's expression depth limit |
81
+ | `fulltext_field` | none | Column of searchable event text; enables [keyword search](https://github.com/wagga40/pySigma-backend-sqlite/blob/main/docs/keyword-search.md) |
82
+ | `timestamp_field` | `timestamp` | Event time column used by correlations |
83
+ | `timestamp_format` | `iso` | `iso`, `unix`, `unix_ms` or `unix_us` |
84
+ | `timestamp_seconds_expression` | ISO parser | SQL turning `{field}` into Unix seconds; overrides `timestamp_format` |
85
+ | `event_id_field` | `rowid` | Non-NULL, per-table unique event identifier for correlation evidence |
86
+ | `observation_end` | latest event | Unix seconds up to which absence windows can expire |
87
+
88
+ ## Generated SQL
89
+
90
+ | Modifier | SQL |
91
+ |---|---|
92
+ | `contains`, `startswith`, `endswith` | `LIKE '%value%' ESCAPE '\'` and its prefix/suffix forms |
93
+ | `all` | `AND` of the values |
94
+ | `re` | `REGEXP` (needs a registered function, see [Limitations](#limitations)) |
95
+ | `cidr` | `=` or `LIKE` prefixes of the expanded network |
96
+ | `cased` | `GLOB`; literal `*`, `?`, `[` become `[*]`, `[?]`, `[[]` |
97
+ | `fieldref` | `field1=field2`, or `LIKE` with the second field's `%` and `_` escaped |
98
+ | `exists` | `IS NOT NULL` / `IS NULL` |
99
+ | `gt`, `gte`, `lt`, `lte`, `neq` | `>`, `>=`, `<`, `<=`, negated equality |
100
+ | `minute`, `hour`, `day`, `week`, `month`, `year` | `strftime()` part |
101
+ | `base64`, `base64offset`, `wide`, `utf16*`, `windash` | Expanded by pySigma into ordinary matches |
102
+
103
+ The SQL keeps Sigma's meaning without help from the caller:
104
+
105
+ - **Negation.** Sigma reads a condition on a missing field as false; SQLite reads
106
+ it as `NULL`, and `NOT NULL` drops the row. Every `NOT` therefore wraps its
107
+ operand: `EventID=1 AND (NOT COALESCE((CommandLine LIKE '%foo%' ESCAPE '\'), 0))`.
108
+ - **Case.** `LIKE` ignores case. `=` does only on `NOCASE` columns, which is how
109
+ Zircolite builds its table; otherwise set `collate_nocase`. `cased` uses `GLOB`.
110
+ - **Booleans.** `Flag: true` matches text or integer storage: `(Flag='true' OR Flag=1)`.
111
+ - **Long lists.** Chains above `max_flat_operands` are regrouped, so rules with
112
+ thousands of values don't fail with "Expression tree is too large".
113
+ - **Field names.** SQLite keywords and names with characters other than letters,
114
+ digits and `_` are quoted with backticks. `field: null` becomes `field IS NULL`.
115
+
116
+ ## Correlations
117
+
118
+ All Sigma correlation types run in SQLite: `event_count`, `value_count`,
119
+ `value_sum`, `value_avg`, `value_median`, `value_percentile`, `temporal` and
120
+ `temporal_ordered`, including chains, aliases and extended temporal conditions.
121
+ Each result row is one alert occurrence with its window, metric and contributing
122
+ event IDs. Semantics are described in [correlations](https://github.com/wagga40/pySigma-backend-sqlite/blob/main/docs/correlations.md);
123
+ upgrading from backend 1.x is covered in the [migration guide](https://github.com/wagga40/pySigma-backend-sqlite/blob/main/docs/migration-v2.md).
124
+
125
+ ## Keyword search
126
+
127
+ Keyword (fieldless) selections need a column of searchable event text:
128
+ `sqliteBackend(fulltext_field="_sigma_fulltext")`. Without it, keyword rules raise
129
+ an error rather than guess. See [keyword search](https://github.com/wagga40/pySigma-backend-sqlite/blob/main/docs/keyword-search.md).
130
+
131
+ ## Limitations
132
+
133
+ - **REGEXP.** SQLite has no built-in `REGEXP`. Register one that is false for `NULL`:
134
+ `connection.create_function("regexp", 2, lambda p, v: v is not None and re.search(p, str(v)) is not None)`.
135
+ pySigma 2 validates `|re` patterns with RE2, so rules with lookarounds or
136
+ backreferences are rejected when parsed.
137
+ - **CIDR.** SQLite has no network type. Prefix patterns match IPv6 addresses only
138
+ in the spellings pySigma expands to.
139
+ - **Case folding** (`NOCASE`, `LIKE`) is ASCII only.
140
+ - **Timestamp parts** use `strftime()` and need ISO-8601 text; a numeric epoch is
141
+ read as a Julian day. `%W` can return week 0.
142
+ - **Field existence.** SQL `NULL` cannot tell a missing field from an explicit null.
143
+ - **Multi-field `value_count`** (a list in `condition.field`) is rejected.
144
+ - **Unknown columns.** A query naming a column the table lacks fails to prepare.
145
+ Add missing columns as `NULL` first; see [output format](https://github.com/wagga40/pySigma-backend-sqlite/blob/main/docs/output-format.md).
146
+
147
+ ## Development
148
+
149
+ ```shell
150
+ poetry install
151
+ poetry run pytest
152
+ SQLITE_MIN_BINARY=/path/to/sqlite-3.38 poetry run pytest tests/test_sqlite_minimum.py
153
+ ```
154
+
155
+ `tools/audit_corpus.py` converts and prepares a SigmaHQ checkout per profile
156
+ (coverage, not detection accuracy). `tools/benchmark_correlations.py` times
157
+ sparse and dense correlation windows.
@@ -1,6 +1,8 @@
1
1
  [project]
2
2
  name = "pySigma-backend-sqlite"
3
- version = "1.2.4"
3
+ version = "2.1.0"
4
+ requires-python = ">=3.11,<4.0"
5
+ dependencies = ["pysigma>=2.0.0,<3.0"]
4
6
  description = "pySigma sqlite backend"
5
7
  readme = "README.md"
6
8
  authors = [
@@ -13,10 +15,6 @@ packages = [
13
15
  { include = "sigma" }
14
16
  ]
15
17
 
16
- [tool.poetry.dependencies]
17
- python = "^3.10"
18
- pysigma = "^1"
19
-
20
18
  [tool.poetry.group.dev.dependencies]
21
19
  black = "^24.1"
22
20
  pytest = "^8.0"
@@ -25,29 +23,15 @@ coverage = "^7.4"
25
23
 
26
24
 
27
25
  [tool.pytest.ini_options]
26
+ testpaths = ["tests"]
28
27
  filterwarnings = [
29
28
  # Suppress pyparsing deprecation warning from pySigma library
30
29
  "ignore:'parseString' deprecated:DeprecationWarning",
31
30
  ]
32
31
 
33
- [tool.pdm]
34
- [tool.pdm.dev-dependencies]
35
- dev = []
36
-
37
- [tool.pdm.build]
38
- includes = ["sigma"]
39
32
  [build-system]
40
- requires = ["poetry-core>=1.8.1"]
33
+ requires = ["poetry-core>=2.0.0"]
41
34
  build-backend = "poetry.core.masonry.api"
42
35
 
43
36
  [project.urls]
44
37
  Repository = "https://github.com/SigmaHQ/pySigma-backend-sqlite"
45
-
46
- [project.group.dev.dependencies]
47
- pytest = "^8.0"
48
- pytest-cov = "^4.1"
49
- coverage = "^7.4"
50
-
51
-
52
-
53
-