diff --git a/sigma/backends/splunk/splunk.py b/sigma/backends/splunk/splunk.py index 69c6723..4e4810b 100644 --- a/sigma/backends/splunk/splunk.py +++ b/sigma/backends/splunk/splunk.py @@ -425,9 +425,13 @@ def finalize_query_default( else: break - if prefix_parts: + remaining_query = search_query[pos:] + # If the remaining query starts with OR we would split a disjunction + # across the pipeline boundary, making the query semantically wrong. + # In that case, skip hoisting so the full disjunction stays intact in + # the trailing | search stage. + if prefix_parts and not remaining_query.lstrip().startswith("OR"): prefix = " ".join(prefix_parts) - remaining_query = search_query[pos:] query = ( prefix + deferred_part diff --git a/tests/test_backend_splunk.py b/tests/test_backend_splunk.py index a8501e1..ac5bbc7 100644 --- a/tests/test_backend_splunk.py +++ b/tests/test_backend_splunk.py @@ -330,6 +330,39 @@ def test_splunk_regex_query_explicit_or_with_add_condition(): ] +def test_splunk_disjunction_with_deferred_regex_no_search_or(): + """Regression test: a top-level disjunction where one branch carries a |re modifier + must not produce '| search OR ...' (issue #74). The query must be valid SPL and + semantically equivalent to the authored condition.""" + splunk_backend = SplunkBackend() + collection = SigmaCollection.from_yaml( + """ + title: Test + status: test + logsource: + category: test_category + product: test_product + detection: + selection_a: + fieldA|endswith: 'hdiutil.exe' + selection_b: + fieldA|endswith: 'openssl.exe' + filter_decrypt: + fieldB|re: '(?i)(?:-d|--decrypt)' + condition: selection_a or (selection_b and not filter_decrypt) + """ + ) + result = splunk_backend.convert(collection) + assert len(result) == 1 + query = result[0] + # The trailing | search clause must not start with OR + assert "\n| search OR" not in query + # The full disjunction must appear in the final | search stage + assert 'fieldA="*hdiutil.exe"' in query + assert 'fieldA="*openssl.exe"' in query + assert 'NOT fieldBCondition="true"' in query + + def test_splunk_regex_group_name_is_capped_for_long_fields(): SplunkDeferredORRegularExpression.reset() field = "msg_normalized_header_subject"