pySigma Elasticsearch Backend
This is the Elasticsearch backend for pySigma. It provides the package sigma.backends.elasticsearch with the LuceneBackend class.
It supports the following output formats:
- default: Lucene queries.
- dsl_lucene: DSL with embedded Lucene queries.
- eql: Elastic Event Query Language queries.
- kibana_ndjson: Kibana NDJSON with Lucene queries.
Further, it contains the following processing pipelines in sigma.pipelines.elasticsearch:
- ecs_windows in windows submodule: ECS mapping for Windows event logs ingested with Winlogbeat.
- ecs_windows_old in windows submodule: ECS mapping for Windows event logs ingested with Winlogbeat <= 6.x.
- ecs_zeek_beats in zeek submodule: Zeek ECS mapping from Elastic.
- ecs_zeek_corelight in zeek submodule: Zeek ECS mapping from Corelight.
- zeek_raw in zeek submodule: Zeek raw JSON log field naming.
- ecs_kubernetes in kubernetes submodule: ECS mapping for Kubernetes audit logs ingested with Kubernetes integration.
- ecs_macos_esf in macos submodule: ECS mapping for macOS Endpoint Security Framework (ESF) events.
This backend is currently maintained by:
Further maintainers required! Send a message to Thomas if you want to co-maintain this backend.
Formats vs. Query Post Processing
While trying to support the minimum compatible output the built-in formats can't fits everyones needs. This gap is filled by a feature called "query post processing" available since pysigma v0.10.
For further information please read "Introducing Query Post-Processing and Output Finalization to Processing Pipelines".
Lucene Kibana NDJSON
Instead of using the format -t lucene -f kibana_ndjson you can also use the following query postprocessing pipeline
to get the same output or use this as a starting point for your own customizations.
# lucene-kibana-ndjson.yml
postprocessing:
- type: template
template: |+
{"id": "{{ rule.id }}", "type": "search", "attributes": {"title": "SIGMA - {{ rule.title }}", "description": "{{ rule.description }}", "hits": 0, "columns": [], "sort": ["@timestamp", "desc"], "version": 1, "kibanaSavedObjectMeta": {"searchSourceJSON": "{\"index\": \"beats-*\", \"filter\": [], \"highlight\": {\"pre_tags\": [\"@kibana-highlighted-field@\"], \"post_tags\": [\"@/kibana-highlighted-field@\"], \"fields\": {\"*\": {}}, \"require_field_match\": false, \"fragment_size\": 2147483647}, \"query\": {\"query_string\": {\"query\": \"{{ query }}\", \"analyze_wildcard\": true}}}"}}, "references": [{"id": "beats-*", "name": "kibanaSavedObjectMeta.searchSourceJSON.index", "type": "index-pattern"}]}
Use this pipeline with: -t lucene -p lucene-kibana-ndjson.yml but now without -f kibana_ndjson.
Lucene Kibana SIEM Rule
Instead of using the format -t lucene -f siem_rule you can also use the following query postprocessing pipeline
to get the same output or use this as a starting point for your own customizations.
# lucene-kibana-siemrule.yml
vars:
index_names:
- "apm-*-transaction*"
- "auditbeat-*"
- "endgame-*"
- "filebeat-*"
- "logs-*"
- "packetbeat-*"
- "traces-apm*"
- "winlogbeat-*"
- "-*elastic-cloud-logs-*"
schedule_interval: 5
schedule_interval_unit: m
postprocessing:
- type: template
template: |+
{
"name": "SIGMA - {{ rule.title }}",
"consumer": "siem",
"enabled": true,
"throttle": null,
"schedule": {
"interval": "{{ pipeline.vars.schedule_interval }}{{ pipeline.vars.schedule_interval_unit }}"
},
"params": {
"author": [
{% if rule.author is string -%}
"{{rule.author}}"
{% else %}
{% for a in rule.author -%}
"{{ a }}"{% if not loop.last %},{%endif%}
{% endfor -%}
{% endif -%}
],
"description": "{{ rule.description }}",
"ruleId": "{{ rule.id }}",
"falsePositives": {{ rule.falsepositives }},
"from": "now-{{ pipeline.vars.schedule_interval }}{{ pipeline.vars.schedule_interval_unit }}",
"immutable": false,
"license": "DRL",
"outputIndex": "",
"meta": {
"from": "1m"
},
"maxSignals": 100,
"riskScore": (
self.severity_risk_mapping[rule.level.name]
if rule.level is not None
else 21
),
"riskScoreMapping": [],
"severity": (
str(rule.level.name).lower() if rule.level is not None else "low"
),
"severityMapping": [],
"threat": list(self.finalize_output_threat_model(rule.tags)),
"to": "now",
"references": {{ rule.references |tojson(indent=6)}},
"version": 1,
"exceptionsList": [],
"relatedIntegrations": [],
"requiredFields": [],
"setup": "",
"type": "query",
"language": "lucene",
"index": {{ pipeline.vars.index_names | tojson(indent=6)}},
"query": "{{ query }}",
"filters": []
},
"rule_type_id": "siem.queryRule",
"tags": [
{% for n in rule.tags -%}
"{{ n.namespace }}-{{ n.name }}"{% if not loop.last %},{%endif%}
{% endfor -%}
],
"notify_when": "onActiveAlert",
"actions": []
}
Use this pipeline with: -t lucene -p lucene-kibana-siemrule.yml but now without -f kibana_ndjson.
EQL siem_rule_ndjson
vars:
schedule_interval: 5
schedule_interval_unit: m
postprocessing:
- type: template
template: |+
{%- set tags = [] -%}
{% for n in rule.tags %}
{%- set tag_string = n.namespace ~ '-' ~ n.name -%}
{%- set tags=tags.append(tag_string) -%}
{% endfor %}
{%- set rule_data = {
"name": rule.title,
"id": rule.id | lower,
"author": [rule.author] if rule.author is string else rule.author or "",
"description": rule.description if rule.description else "empty description",
"references": rule.references,
"enabled": true,
"interval": pipeline.vars.schedule_interval|string ~ pipeline.vars.schedule_interval_unit,
"from": "now-" ~ pipeline.vars.schedule_interval|string ~ pipeline.vars.schedule_interval_unit,
"rule_id": rule.id | lower,
"false_positives": rule.falsepositives,
"immutable": false,
"output_index": "",
"meta": {
"from": "1m"
},
"risk_score": rule.custom_attributes.risk_score | default(21),
"severity": rule.level.name | string | lower if rule.level is not none else 'low',
"threat": rule.custom_attributes.threat | default([]),
"severity_mapping": [],
"to": "now",
"version": 1,
"max_signals": 100,
"exceptions_list": [],
"setup": "",
"type": "eql",
"note": "",
"license": "DRL",
"language": "eql",
"query": query,
"tags": tags,
"index": pipeline.state.index,
"actions": [],
"related_integrations": [],
"required_fields": [],
"risk_score_mapping": []
}
-%}
{{ rule_data | tojson }}
Use this pipeline with: -t eql -p eql-siemrule-ndjson.yml but now without -f siem_rule_ndjson.
The output can be imported directly into Kibana as a Detection Rule.
ESQL siem_rule_ndjson
vars:
schedule_interval: 5
schedule_interval_unit: m
postprocessing:
- type: template
template: |+
{%- set tags = [] -%}
{% for n in rule.tags %}
{%- set tag_string = n.namespace ~ '-' ~ n.name -%}
{%- set tags=tags.append(tag_string) -%}
{% endfor %}
{%- set rule_data = {
"name": rule.title,
"id": rule.id | lower,
"author": [rule.author] if rule.author is string else rule.author,
"description": rule.description,
"references": rule.references,
"enabled": true,
"interval": pipeline.vars.schedule_interval|string ~ pipeline.vars.schedule_interval_unit,
"from": "now-" ~ pipeline.vars.schedule_interval|string ~ pipeline.vars.schedule_interval_unit,
"rule_id": rule.id | lower,
"false_positives": rule.falsepositives,
"immutable": false,
"output_index": "",
"meta": {
"from": "1m"
},
"risk_score": backend.severity_risk_mapping[rule.level.name] if rule.level is not none else 21,
"severity": rule.level.name | string | lower if rule.level is not none else "low",
"severity_mapping": [],
"threat": backend.finalize_output_threat_model(rule.tags) | list,
"to": "now",
"version": 1,
"max_signals": 100,
"exceptions_list": [],
"setup": "",
"type": "esql",
"note": "",
"license": "DRL",
"language": "esql",
"index": pipeline.vars.index_names | list,
"query": query,
"tags": tags,
"actions": [],
"related_integrations": [],
"required_fields": [],
"risk_score_mapping": []
}
-%}
{{ rule_data | tojson }}
Use this pipeline with: -t esql -p esql-siemrule-ndjson.yml but now without -f siem_rule_ndjson.
The output can be imported directly into Kibana as a Detection Rule.
Lucene siem_rule_ndjson
To be continued...
Known Limitations
ES|QL Correlation Rules: Static Time Boundaries
ES|QL correlation rules (event count, value count, and temporal correlation types) use DATE_TRUNC() to assign events to fixed, epoch-aligned time buckets. This means that the timespan window in a correlation rule is aligned to clock boundaries rather than being a true sliding window.
Consequence: A correlation rule with a timespan of 5m only matches if all relevant events appear within the same clock-aligned 5-minute interval (e.g. 17:15:00–17:20:00 or 17:20:00–17:25:00, where the end is exclusive). If events straddle a boundary — some falling in one bucket and some in the next — neither bucket may independently meet the detection threshold, resulting in a false negative.
Example: Five SSH authentication failures spanning the 17:19–17:20 boundary would be split into two buckets (4 events + 1 event). A rule requiring 5 distinct usernames would not fire, even though all 5 events occurred within a 5-minute window.
This behavior is described in the Sigma correlation rules specification and is an intentional trade-off, as ES|QL does not natively support true sliding-window aggregations. Users authoring correlation rules should be aware that detections may be missed when attack activity straddles a clock-aligned bucket boundary.
Metadata
Release files for pySigma-backend-elasticsearch 2.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| pysigma_backend_elasticsearch-2.1.1.tar.gz | 35.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pysigma_backend_elasticsearch-2.1.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 77.8 kB
Release files / pysigma_backend_elasticsearch-2.1.1.tar.gz
| Download URL | pysigma_backend_elasticsearch-2.1.1.tar.gz |
|---|---|
| Size | 35.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
0cfb6b8e7892591c3bc83ef4dea4c4ef5cf8fe9e53e27dc3b0e9112bb3427fa6
|
|
BLAKE2b-256 checksum How to use checksums |
3e12b38da5ea8877210ccbf1d6748bf243961274219e879a37fdbccb3ca46067
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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 Aug 10, 2026.
Transparency logRelease files / pysigma_backend_elasticsearch-2.1.1-py3-none-any.whl
| Download URL | pysigma_backend_elasticsearch-2.1.1-py3-none-any.whl |
|---|---|
| Size | 42.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
0bb514ae02445d2053d530bd732c8747ffac9184246d45f3fa2669d0862a5af4
|
|
BLAKE2b-256 checksum How to use checksums |
ca918cfc1d040a206c9f9c30b6d1322c0cb2cb44fb6e986659c777fa1345ae24
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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 Aug 10, 2026.
Transparency log