Initial commit: KQL ↔ SDL PowerQuery proof of equivalence

This commit is contained in:
marc
2026-06-01 09:57:14 +02:00
commit 23cbaa9c08
91 changed files with 5966 additions and 0 deletions
+12
View File
@@ -0,0 +1,12 @@
event_type events uniq_ts collision_loss%
----------------------------------------------------------------------
AuditLogs 12 12 0.0%
AzureActivity 6 6 0.0%
CommonSecurityLog 84 84 0.0%
DeviceFileEvents 9 1 88.9%
OfficeActivity 203 203 0.0%
SecurityEvent 61 53 13.1%
SigninLogs 69 45 34.8%
ThreatIntelIndicators 1 1 0.0%
----------------------------------------------------------------------
TOTAL 445 405
+41
View File
@@ -0,0 +1,41 @@
/Users/marc.chisinevski/.venvs/azcli/lib/python3.9/site-packages/urllib3/__init__.py:35: NotOpenSSLWarning: urllib3 v2 only supports OpenSSL 1.1.1+, currently the 'ssl' module is compiled with 'LibreSSL 2.8.3'. See: https://github.com/urllib3/urllib3/issues/3020
warnings.warn(
================================================================================
Local JSONL event_type counts
================================================================================
AuditLogs 12
AzureActivity 6
CommonSecurityLog 84
DeviceFileEvents 9
OfficeActivity 203
SecurityEvent 61
SigninLogs 69
ThreatIntelIndicators 1
TOTAL 445
================================================================================
Step 2: ingesting 5 marker-tagged CSL events (loss-probe-1780246494)
================================================================================
addEvents -> {"bytesCharged": 0, "status": "success"}
waiting 10 s for indexing ...
probe query -> matching=0.0, rows=[]
================================================================================
Step 3: full bulk ingest of every event in JSONL
================================================================================
ingest_jsonl reports 445 events sent
waiting 20 s for indexing ...
================================================================================
Step 4: SDL counts by event_type
================================================================================
event_type local SDL loss%
------------------------------------------------------------
AuditLogs 12 0 100%
AzureActivity 6 1 83%
CommonSecurityLog 84 1 99%
DeviceFileEvents 9 0 100%
OfficeActivity 203 1 100%
SecurityEvent 61 0 100%
SigninLogs 69 0 100%
ThreatIntelIndicators 1 0 100%
+31
View File
@@ -0,0 +1,31 @@
/Users/marc.chisinevski/.venvs/azcli/lib/python3.9/site-packages/urllib3/__init__.py:35: NotOpenSSLWarning: urllib3 v2 only supports OpenSSL 1.1.1+, currently the 'ssl' module is compiled with 'LibreSSL 2.8.3'. See: https://github.com/urllib3/urllib3/issues/3020
warnings.warn(
================================================================================
Local JSONL event_type counts
================================================================================
AuditLogs 12
AzureActivity 6
CommonSecurityLog 84
DeviceFileEvents 9
OfficeActivity 203
SecurityEvent 61
SigninLogs 69
ThreatIntelIndicators 1
TOTAL 445
================================================================================
Step 2: ingesting 5 marker-tagged CSL events (loss-probe-1780246593)
================================================================================
addEvents -> {"bytesCharged": 0, "status": "success"}
waiting 10 s for indexing ...
probe query -> matching=0.0, rows=[]
================================================================================
Step 3: full bulk ingest of every event in JSONL
================================================================================
Traceback (most recent call last):
File "/Users/marc.chisinevski/.codeium/windsurf/s1-claude-skills/kql-to-pq/harness/debug_ingest_loss.py", line 78, in <module>
sent = ingest_jsonl(JSONL)
File "/Users/marc.chisinevski/.codeium/windsurf/s1-claude-skills/kql-to-pq/harness/sdl_client.py", line 85, in ingest_jsonl
raise RuntimeError(f"addEvents rejected batch: {r}")
RuntimeError: addEvents rejected batch: {'bytesCharged': 0, 'status': 'success'}
+32
View File
@@ -0,0 +1,32 @@
/Users/marc.chisinevski/.venvs/azcli/lib/python3.9/site-packages/urllib3/__init__.py:35: NotOpenSSLWarning: urllib3 v2 only supports OpenSSL 1.1.1+, currently the 'ssl' module is compiled with 'LibreSSL 2.8.3'. See: https://github.com/urllib3/urllib3/issues/3020
warnings.warn(
[sdl_client] session = kql-proof-6612b627-0405-4e09-8883-3edc1ee74960
================================================================================
Local JSONL event_type counts
================================================================================
AuditLogs 12
AzureActivity 6
CommonSecurityLog 84
DeviceFileEvents 9
OfficeActivity 203
SecurityEvent 61
SigninLogs 69
ThreatIntelIndicators 1
TOTAL 445
================================================================================
Step 2: ingesting 5 marker-tagged CSL events (loss-probe-1780246645)
================================================================================
addEvents -> {"bytesCharged": 0, "status": "success"}
waiting 10 s for indexing ...
probe query -> matching=0.0, rows=[]
================================================================================
Step 3: full bulk ingest of every event in JSONL
================================================================================
Traceback (most recent call last):
File "/Users/marc.chisinevski/.codeium/windsurf/s1-claude-skills/kql-to-pq/harness/debug_ingest_loss.py", line 78, in <module>
sent = ingest_jsonl(JSONL)
File "/Users/marc.chisinevski/.codeium/windsurf/s1-claude-skills/kql-to-pq/harness/sdl_client.py", line 91, in ingest_jsonl
raise RuntimeError(f"addEvents rejected batch: {r}")
RuntimeError: addEvents rejected batch: {'bytesCharged': 0, 'status': 'success'}
+42
View File
@@ -0,0 +1,42 @@
/Users/marc.chisinevski/.venvs/azcli/lib/python3.9/site-packages/urllib3/__init__.py:35: NotOpenSSLWarning: urllib3 v2 only supports OpenSSL 1.1.1+, currently the 'ssl' module is compiled with 'LibreSSL 2.8.3'. See: https://github.com/urllib3/urllib3/issues/3020
warnings.warn(
[sdl_client] session = kql-proof-0587d7de-8f44-4e70-ad21-c79fae1d4821
================================================================================
Local JSONL event_type counts
================================================================================
AuditLogs 12
AzureActivity 6
CommonSecurityLog 84
DeviceFileEvents 9
OfficeActivity 203
SecurityEvent 61
SigninLogs 69
ThreatIntelIndicators 1
TOTAL 445
================================================================================
Step 2: ingesting 5 marker-tagged CSL events (loss-probe-1780246719)
================================================================================
addEvents -> {"bytesCharged": 0, "status": "success"}
waiting 10 s for indexing ...
probe query (1h) -> matching=0.0, rows=[]
================================================================================
Step 3: full bulk ingest of every event in JSONL
================================================================================
ingest_jsonl reports 445 events sent
waiting 20 s for indexing ...
================================================================================
Step 4: SDL counts by event_type
================================================================================
event_type local SDL loss%
------------------------------------------------------------
AuditLogs 12 0 100%
AzureActivity 6 1 83%
CommonSecurityLog 84 1 99%
DeviceFileEvents 9 0 100%
OfficeActivity 203 1 100%
SecurityEvent 61 0 100%
SigninLogs 69 0 100%
ThreatIntelIndicators 1 0 100%
+30
View File
@@ -0,0 +1,30 @@
/Users/marc.chisinevski/.venvs/azcli/lib/python3.9/site-packages/urllib3/__init__.py:35: NotOpenSSLWarning: urllib3 v2 only supports OpenSSL 1.1.1+, currently the 'ssl' module is compiled with 'LibreSSL 2.8.3'. See: https://github.com/urllib3/urllib3/issues/3020
warnings.warn(
================================================================================
# any serverHost=kql-proof
query: serverHost='kql-proof' | columns event_type, UserPrincipalName, ts_epoch_ms | limit 5
status=success matching=0.0 rows=0 took=8.7s
================================================================================
# count by event_type
query: serverHost='kql-proof' | group n=count() by event_type
status=success matching=0.0 rows=0 took=7.0s
================================================================================
# SigninLogs by user
query: serverHost='kql-proof' event_type='SigninLogs' | group n=count() by UserPrincipalName
status=success matching=0.0 rows=0 took=7.4s
================================================================================
# SigninLogs min/max ts_epoch_ms
query: serverHost='kql-proof' event_type='SigninLogs' | group mn=min(ts_epoch_ms), mx=max(ts_epoch_ms), n=count()
status=success matching=0.0 rows=0 took=4.1s
================================================================================
# recent SigninLogs (no time filter)
query: serverHost='kql-proof' event_type='SigninLogs' Location='RU' | columns UserPrincipalName, Location | limit 10
status=success matching=0.0 rows=0 took=3.7s
================================================================================
# SecurityEvent EventID column type
query: serverHost='kql-proof' event_type='SecurityEvent' | columns EventID, NewProcessName | limit 5
status=success matching=0.0 rows=0 took=3.3s
================================================================================
# Audit OperationName
query: serverHost='kql-proof' event_type='AuditLogs' | columns OperationName | limit 10
status=success matching=0.0 rows=0 took=3.5s
+52
View File
@@ -0,0 +1,52 @@
/Users/marc.chisinevski/.venvs/azcli/lib/python3.9/site-packages/urllib3/__init__.py:35: NotOpenSSLWarning: urllib3 v2 only supports OpenSSL 1.1.1+, currently the 'ssl' module is compiled with 'LibreSSL 2.8.3'. See: https://github.com/urllib3/urllib3/issues/3020
warnings.warn(
================================================================================
# event_type=SigninLogs 7d (no serverHost) (start=7d)
q: event_type='SigninLogs' | columns UserPrincipalName | limit 5
status=success matching=5.0 rows=5 took=3.8s
{'UserPrincipalName': 'dave@contoso.com'}
{'UserPrincipalName': 'dave@contoso.com'}
{'UserPrincipalName': 'bob@contoso.com'}
{'UserPrincipalName': 'bob@contoso.com'}
{'UserPrincipalName': 'carol@contoso.com'}
================================================================================
# event_type=SigninLogs 1h (start=1h)
q: event_type='SigninLogs' | columns UserPrincipalName, ts_epoch_ms | limit 5
status=success matching=0.0 rows=0 took=2.0s
================================================================================
# UserPrincipalName matching contoso (start=1d)
q: UserPrincipalName='alice@contoso.com' | columns event_type, UserPrincipalName | limit 5
status=success matching=5.0 rows=5 took=3.8s
{'event_type': 'SigninLogs', 'UserPrincipalName': 'alice@contoso.com'}
{'event_type': 'SigninLogs', 'UserPrincipalName': 'alice@contoso.com'}
{'event_type': 'SigninLogs', 'UserPrincipalName': 'alice@contoso.com'}
{'event_type': 'SigninLogs', 'UserPrincipalName': 'alice@contoso.com'}
{'event_type': 'SigninLogs', 'UserPrincipalName': 'alice@contoso.com'}
================================================================================
# anything from xdr tenant 1h (start=1h)
q: * | columns event_type, serverHost, logfile | limit 5
status=error/client/badParam matching=None rows=0 took=0.6s
ERROR: {"message": "invalid query: Don't understand [*] -- try enclosing it in quotes", "status": "error/client/badParam"}
================================================================================
# logfile contains kql-proof (start=7d)
q: logfile contains 'kql-proof' | columns event_type | limit 5
status=success matching=5.0 rows=5 took=3.7s
{'event_type': 'SigninLogs'}
{'event_type': 'SigninLogs'}
{'event_type': 'SigninLogs'}
{'event_type': 'SigninLogs'}
{'event_type': 'AuditLogs'}
================================================================================
# contoso.com in attrs (start=1d)
q: Identity contains 'contoso.com' | columns event_type, Identity | limit 5
status=success matching=5.0 rows=5 took=1.7s
{'event_type': 'SigninLogs', 'Identity': 'alice@contoso.com'}
{'event_type': 'SigninLogs', 'Identity': 'alice@contoso.com'}
{'event_type': 'SigninLogs', 'Identity': 'frank@contoso.com'}
{'event_type': 'SigninLogs', 'Identity': 'alice@contoso.com'}
{'event_type': 'SigninLogs', 'Identity': 'frank@contoso.com'}
================================================================================
# test: count any events tenant-wide 5m (start=5m)
q: * | group n=count()
status=error/client/badParam matching=None rows=0 took=0.6s
ERROR: {"message": "invalid query: Don't understand [*] -- try enclosing it in quotes", "status": "error/client/badParam"}
+21
View File
@@ -0,0 +1,21 @@
/Users/marc.chisinevski/.venvs/azcli/lib/python3.9/site-packages/urllib3/__init__.py:35: NotOpenSSLWarning: urllib3 v2 only supports OpenSSL 1.1.1+, currently the 'ssl' module is compiled with 'LibreSSL 2.8.3'. See: https://github.com/urllib3/urllib3/issues/3020
warnings.warn(
[sdl_client] session = kql-proof-3d7aacda-a300-47c8-854d-2049dee8cfc0
Sending 9 events at ages [0.5, 5, 30, 60, 120, 240, 360, 720, 1440] min
addEvents -> {"bytesCharged": 0, "status": "success"}
Waiting 12 s ...
Querying probe '68e13aef' over last 48h ...
matching=1.0
age_min sent queryable
0.5 yes NO
5 yes NO
30 yes NO
60 yes NO
120 yes NO
240 yes NO
360 yes NO
720 yes NO
1440 yes YES
+36
View File
@@ -0,0 +1,36 @@
/Users/marc.chisinevski/.venvs/azcli/lib/python3.9/site-packages/urllib3/__init__.py:35: NotOpenSSLWarning: urllib3 v2 only supports OpenSSL 1.1.1+, currently the 'ssl' module is compiled with 'LibreSSL 2.8.3'. See: https://github.com/urllib3/urllib3/issues/3020
warnings.warn(
[sdl_client] session = kql-proof-64201002-a39c-4fc1-81db-eb0c568f6e81
[sdl_client] session = kql-proof-8cc119dc-7036-4bd1-adf3-24e9bc0686af
age= 0.5 min session=24e9bc0686af addEvents={'bytesCharged': 0, 'status': 'success'}
[sdl_client] session = kql-proof-d4a3b00d-d2ac-4cdd-9510-973003bc3055
age= 5 min session=973003bc3055 addEvents={'bytesCharged': 0, 'status': 'success'}
[sdl_client] session = kql-proof-1f101b96-1831-4db7-bf9d-ff704b97ba93
age= 30 min session=ff704b97ba93 addEvents={'bytesCharged': 0, 'status': 'success'}
[sdl_client] session = kql-proof-cd83a4c0-b91d-4ebb-b5e8-e8d08a9be2b5
age= 60 min session=e8d08a9be2b5 addEvents={'bytesCharged': 0, 'status': 'success'}
[sdl_client] session = kql-proof-488737cf-f9b9-4732-9037-ef21a726ec07
age= 120 min session=ef21a726ec07 addEvents={'bytesCharged': 0, 'status': 'success'}
[sdl_client] session = kql-proof-e7df8519-ff5d-472b-949c-dc1eb4dec8e1
age= 240 min session=dc1eb4dec8e1 addEvents={'bytesCharged': 0, 'status': 'success'}
[sdl_client] session = kql-proof-0a8ac536-a781-4b53-8a5f-a362f268c2d2
age= 480 min session=a362f268c2d2 addEvents={'bytesCharged': 0, 'status': 'success'}
[sdl_client] session = kql-proof-efe9ed8a-207f-4a7a-84f5-abd2c070a178
age= 720 min session=abd2c070a178 addEvents={'bytesCharged': 0, 'status': 'success'}
[sdl_client] session = kql-proof-d55d5ce5-e164-4c92-859a-681c29acb4cf
age= 1440 min session=681c29acb4cf addEvents={'bytesCharged': 0, 'status': 'success'}
Waiting 12 s ...
Query matching=1.0
age_min queryable
0.5 NO
5 NO
30 NO
60 NO
120 NO
240 NO
480 NO
720 NO
1440 YES
+33
View File
@@ -0,0 +1,33 @@
/Users/marc.chisinevski/.venvs/azcli/lib/python3.9/site-packages/urllib3/__init__.py:35: NotOpenSSLWarning: urllib3 v2 only supports OpenSSL 1.1.1+, currently the 'ssl' module is compiled with 'LibreSSL 2.8.3'. See: https://github.com/urllib3/urllib3/issues/3020
warnings.warn(
[sdl_client] session = kql-proof-c0d2d10b-5952-4900-8dff-0875dd805fe9
Latest proof_run_id from log: run-a9174a254e)
================================================================================
# any event for this run
q: proof_run_id='run-a9174a254e)' | group n=count()
status=success matching=0.0 took=2.0s
================================================================================
# by event_type for this run
q: proof_run_id='run-a9174a254e)' | group n=count() by event_type
status=success matching=0.0 took=4.7s
================================================================================
# all kql-proof logfile (any run)
q: logfile contains 'kql-proof' | group n=count() by event_type
status=success matching=906.0 took=4.7s
{'event_type': 'AuditLogs', 'n': 24}
{'event_type': 'AzureActivity', 'n': 13}
{'event_type': 'CommonSecurityLog', 'n': 181}
{'event_type': 'DeviceFileEvents', 'n': 18}
{'event_type': 'OfficeActivity', 'n': 407}
{'event_type': 'SecurityEvent', 'n': 123}
{'event_type': 'SigninLogs', 'n': 138}
{'event_type': 'ThreatIntelIndicators', 'n': 2}
================================================================================
# rule 1 raw query that errors
q: proof_run_id='run-a9174a254e)' event_type='SigninLogs' | filter ts_epoch_ms >= 0 | group LocationCount = estimate_distinct(Location), LocationList = group_unique_values(Location), LogonCount = count() by UserPrincipalName, AppDisplayName | filter LocationCount >= 3
status=error/client/badParam matching=None took=0.8s
ERROR: {"message": "invalid query: Unknown function 'group_unique_values'", "status": "error/client/badParam"}
+42
View File
@@ -0,0 +1,42 @@
/Users/marc.chisinevski/.venvs/azcli/lib/python3.9/site-packages/urllib3/__init__.py:35: NotOpenSSLWarning: urllib3 v2 only supports OpenSSL 1.1.1+, currently the 'ssl' module is compiled with 'LibreSSL 2.8.3'. See: https://github.com/urllib3/urllib3/issues/3020
warnings.warn(
[sdl_client] session = kql-proof-64b584b2-b3db-4478-be7c-995df8785351
Latest proof_run_id from log: run-5abed53e35
================================================================================
# any event for this run
q: proof_run_id='run-5abed53e35' | group n=count()
status=success matching=445.0 took=3.6s
{'n': 445}
================================================================================
# by event_type for this run
q: proof_run_id='run-5abed53e35' | group n=count() by event_type
status=success matching=445.0 took=2.5s
{'event_type': 'AuditLogs', 'n': 12}
{'event_type': 'AzureActivity', 'n': 6}
{'event_type': 'CommonSecurityLog', 'n': 84}
{'event_type': 'DeviceFileEvents', 'n': 9}
{'event_type': 'OfficeActivity', 'n': 203}
{'event_type': 'SecurityEvent', 'n': 61}
{'event_type': 'SigninLogs', 'n': 69}
{'event_type': 'ThreatIntelIndicators', 'n': 1}
================================================================================
# all kql-proof logfile (any run)
q: logfile contains 'kql-proof' | group n=count() by event_type
status=success matching=1351.0 took=2.1s
{'event_type': 'AuditLogs', 'n': 36}
{'event_type': 'AzureActivity', 'n': 19}
{'event_type': 'CommonSecurityLog', 'n': 265}
{'event_type': 'DeviceFileEvents', 'n': 27}
{'event_type': 'OfficeActivity', 'n': 610}
{'event_type': 'SecurityEvent', 'n': 184}
{'event_type': 'SigninLogs', 'n': 207}
{'event_type': 'ThreatIntelIndicators', 'n': 3}
================================================================================
# rule 1 raw query that errors
q: proof_run_id='run-5abed53e35' event_type='SigninLogs' | filter ts_epoch_ms >= 0 | group LocationCount = estimate_distinct(Location), LocationList = group_unique_values(Location), LogonCount = count() by UserPrincipalName, AppDisplayName | filter LocationCount >= 3
status=error/client/badParam matching=None took=0.7s
ERROR: {"message": "invalid query: Unknown function 'group_unique_values'", "status": "error/client/badParam"}
+85
View File
@@ -0,0 +1,85 @@
/Users/marc.chisinevski/.venvs/azcli/lib/python3.9/site-packages/urllib3/__init__.py:35: NotOpenSSLWarning: urllib3 v2 only supports OpenSSL 1.1.1+, currently the 'ssl' module is compiled with 'LibreSSL 2.8.3'. See: https://github.com/urllib3/urllib3/issues/3020
warnings.warn(
[sdl_client] session = kql-proof-2987fb30-b85c-4fd7-a8fb-8e6d33f0f46e
=== Payload (3 events) ===
[
{
"ts": "1780217792000000000",
"sev": 3,
"thread": "T1",
"attrs": {
"event_type": "SigninLogs",
"TimeGenerated": "2026-05-31T08:56:32.000Z",
"ts_epoch_ms": 1780217792000,
"UserPrincipalName": "alice@contoso.com",
"Identity": "alice@contoso.com",
"AppDisplayName": "Office 365 Exchange Online",
"ResultType": 0,
"IPAddress": "10.0.0.20",
"Location": "US",
"LocationDetails_country": "US",
"LocationDetails_state": "HQ",
"LocationDetails_city": "HQ",
"UserAgent": "Mozilla/5.0 (Windows NT 10.0) Edge/120.0",
"DeviceDetail_os": "Windows 10",
"probe": "18ac4061_0"
}
},
{
"ts": "1780220192000000000",
"sev": 3,
"thread": "T1",
"attrs": {
"event_type": "SigninLogs",
"TimeGenerated": "2026-05-31T09:36:32.000Z",
"ts_epoch_ms": 1780220192000,
"UserPrincipalName": "alice@contoso.com",
"Identity": "alice@contoso.com",
"AppDisplayName": "Office 365 Exchange Online",
"ResultType": 0,
"IPAddress": "10.0.0.21",
"Location": "US",
"LocationDetails_country": "US",
"LocationDetails_state": "HQ",
"LocationDetails_city": "HQ",
"UserAgent": "Mozilla/5.0 (Windows NT 10.0) Edge/120.0",
"DeviceDetail_os": "Windows 10",
"probe": "18ac4061_1"
}
},
{
"ts": "1780222592000000000",
"sev": 3,
"thread": "T1",
"attrs": {
"event_type": "SigninLogs",
"TimeGenerated": "2026-05-31T10:16:32.000Z",
"ts_epoch_ms": 1780222592000,
"UserPrincipalName": "alice@contoso.com",
"Identity": "alice@contoso.com",
"AppDisplayName": "Office 365 Exchange Online",
"ResultType": 0,
"IPAddress": "10.0.0.22",
"Location": "US",
"LocationDetails_country": "US",
"LocationDetails_state": "HQ",
"LocationDetails_city": "HQ",
"UserAgent": "Mozilla/5.0 (Windows NT 10.0) Edge/120.0",
"DeviceDetail_os": "Windows 10",
"probe": "18ac4061_2"
}
}
]
=== Submitting (probe prefix=18ac4061) ===
addEvents -> {"bytesCharged": 0, "status": "success"}
Waiting 12 s for indexing ...
Query: probe contains '18ac4061' | columns event_type, probe, ts_epoch_ms | limit 10
Result -> matching=0.0
real_now_ms = 1780246993092
event ts_ms=1780217792000 age=486.68 min attrs.event_type=SigninLogs
event ts_ms=1780220192000 age=446.68 min attrs.event_type=SigninLogs
event ts_ms=1780222592000 age=406.68 min attrs.event_type=SigninLogs
+16
View File
@@ -0,0 +1,16 @@
/Users/marc.chisinevski/.venvs/azcli/lib/python3.9/site-packages/urllib3/__init__.py:35: NotOpenSSLWarning: urllib3 v2 only supports OpenSSL 1.1.1+, currently the 'ssl' module is compiled with 'LibreSSL 2.8.3'. See: https://github.com/urllib3/urllib3/issues/3020
warnings.warn(
[sdl_client] session = kql-proof-828613c9-4ba7-458e-8e80-dae7ee6557f8
=== Sending 7 probe events ===
addEvents -> {"bytesCharged": 0, "status": "success"}
Waiting 12 s for indexing ...
=== Per-case verification ===
A_minimal_2_attrs matching=1.0 status=OK -> [['CommonSecurityLog', '759b315a_A']]
B_one_int_attr matching=1.0 status=OK -> [['CommonSecurityLog', '759b315a_B']]
C_one_negative_int matching=1.0 status=OK -> [['CommonSecurityLog', '759b315a_C']]
D_with_special_chars matching=1.0 status=OK -> [['CommonSecurityLog', '759b315a_D']]
E_with_backslashes matching=1.0 status=OK -> [['SecurityEvent', '759b315a_E']]
F_realistic_csl_via_clean matching=1.0 status=OK -> [['CommonSecurityLog', '759b315a_F']]
G_realistic_csl_with_None matching=1.0 status=OK -> [['CommonSecurityLog', '759b315a_G']]
+39
View File
@@ -0,0 +1,39 @@
/Users/marc.chisinevski/.venvs/azcli/lib/python3.9/site-packages/urllib3/__init__.py:35: NotOpenSSLWarning: urllib3 v2 only supports OpenSSL 1.1.1+, currently the 'ssl' module is compiled with 'LibreSSL 2.8.3'. See: https://github.com/urllib3/urllib3/issues/3020
warnings.warn(
[sdl_client] session = kql-proof-7a2a3e4a-a6b7-4a15-bcfc-a25476b595a9
RUN = run-9c02f87a3f
RECENT_MS = 1780240728000
================================================================================
# rule 4 exact
q: proof_run_id='run-9c02f87a3f' event_type='SigninLogs' | filter ts_epoch_ms >= 1780240728000 | group LocationCount = estimate_distinct(Location), DistinctSourceIp = estimate_distinct(IPAddress), LogonC
status=success matching=39.0 rows=9
{'AppDisplayName': 'Azure Portal', 'UserPrincipalName': 'dave@contoso.com', 'LocationCount': 1.0, 'DistinctSourceIp': 1.0, 'LogonCount': 1}
{'AppDisplayName': 'Azure Portal', 'UserPrincipalName': 'eve@contoso.com', 'LocationCount': 10.0, 'DistinctSourceIp': 10.0, 'LogonCount': 10}
{'AppDisplayName': 'Microsoft Teams', 'UserPrincipalName': 'bob@contoso.com', 'LocationCount': 4.0, 'DistinctSourceIp': 4.0, 'LogonCount': 4}
{'AppDisplayName': 'Office 365 Exchange Online', 'UserPrincipalName': 'alice@contoso.com', 'LocationCount': 1.0, 'DistinctSourceIp': 1.0, 'LogonCount': 4}
{'AppDisplayName': 'Office 365 Exchange Online', 'UserPrincipalName': 'bob@contoso.com', 'LocationCount': 1.0, 'DistinctSourceIp': 1.0, 'LogonCount': 4}
{'AppDisplayName': 'Office 365 Exchange Online', 'UserPrincipalName': 'carol@contoso.com', 'LocationCount': 1.0, 'DistinctSourceIp': 1.0, 'LogonCount': 4}
{'AppDisplayName': 'Office 365 Exchange Online', 'UserPrincipalName': 'dave@contoso.com', 'LocationCount': 1.0, 'DistinctSourceIp': 1.0, 'LogonCount': 4}
{'AppDisplayName': 'Office 365 Exchange Online', 'UserPrincipalName': 'eve@contoso.com', 'LocationCount': 1.0, 'DistinctSourceIp': 1.0, 'LogonCount': 4}
================================================================================
# rule 4 without ts filter
q: proof_run_id='run-9c02f87a3f' event_type='SigninLogs' | group LocationCount = estimate_distinct(Location), DistinctSourceIp = estimate_distinct(IPAddress), LogonCount = count() by AppDisplayName, User
status=success matching=69.0 rows=13
{'AppDisplayName': 'Azure Portal', 'UserPrincipalName': 'dave@contoso.com', 'LocationCount': 1.0, 'DistinctSourceIp': 1.0, 'LogonCount': 1}
{'AppDisplayName': 'Azure Portal', 'UserPrincipalName': 'eve@contoso.com', 'LocationCount': 10.0, 'DistinctSourceIp': 10.0, 'LogonCount': 10}
{'AppDisplayName': 'Microsoft Teams', 'UserPrincipalName': 'alice@contoso.com', 'LocationCount': 1.0, 'DistinctSourceIp': 3.0, 'LogonCount': 3}
{'AppDisplayName': 'Microsoft Teams', 'UserPrincipalName': 'bob@contoso.com', 'LocationCount': 4.0, 'DistinctSourceIp': 7.0, 'LogonCount': 7}
{'AppDisplayName': 'Microsoft Teams', 'UserPrincipalName': 'carol@contoso.com', 'LocationCount': 1.0, 'DistinctSourceIp': 3.0, 'LogonCount': 3}
{'AppDisplayName': 'Microsoft Teams', 'UserPrincipalName': 'dave@contoso.com', 'LocationCount': 1.0, 'DistinctSourceIp': 3.0, 'LogonCount': 3}
{'AppDisplayName': 'Microsoft Teams', 'UserPrincipalName': 'eve@contoso.com', 'LocationCount': 1.0, 'DistinctSourceIp': 3.0, 'LogonCount': 3}
{'AppDisplayName': 'Office 365 Exchange Online', 'UserPrincipalName': 'alice@contoso.com', 'LocationCount': 2.0, 'DistinctSourceIp': 4.0, 'LogonCount': 7}
================================================================================
# show 5 SigninLogs columns
q: proof_run_id='run-9c02f87a3f' event_type='SigninLogs' | columns AppDisplayName, UserPrincipalName, Location, IPAddress, ts_epoch_ms | limit 5
status=success matching=5.0 rows=5
{'AppDisplayName': 'Office 365 Exchange Online', 'UserPrincipalName': 'alice@contoso.com', 'Location': 'US', 'IPAddress': '10.0.0.20', 'ts_epoch_ms': 1780219128000}
{'AppDisplayName': 'Office 365 Exchange Online', 'UserPrincipalName': 'alice@contoso.com', 'Location': 'US', 'IPAddress': '10.0.0.21', 'ts_epoch_ms': 1780221528000}
{'AppDisplayName': 'Office 365 Exchange Online', 'UserPrincipalName': 'alice@contoso.com', 'Location': 'US', 'IPAddress': '10.0.0.22', 'ts_epoch_ms': 1780223928000}
{'AppDisplayName': 'Microsoft Teams', 'UserPrincipalName': 'alice@contoso.com', 'Location': 'US', 'IPAddress': '10.0.0.20', 'ts_epoch_ms': 1780219128000}
{'AppDisplayName': 'Microsoft Teams', 'UserPrincipalName': 'alice@contoso.com', 'Location': 'US', 'IPAddress': '10.0.0.21', 'ts_epoch_ms': 1780221528000}
+41
View File
@@ -0,0 +1,41 @@
/Users/marc.chisinevski/.venvs/azcli/lib/python3.9/site-packages/urllib3/__init__.py:35: NotOpenSSLWarning: urllib3 v2 only supports OpenSSL 1.1.1+, currently the 'ssl' module is compiled with 'LibreSSL 2.8.3'. See: https://github.com/urllib3/urllib3/issues/3020
warnings.warn(
[sdl_client] session = kql-proof-e6ab5a8c-7c7a-4c90-ab9f-898f88b4ddb0
run_id = run-20b5bcb16f
================================================================================
# show 3 SigninLogs with ts_epoch_ms
q: proof_run_id='run-20b5bcb16f' event_type='SigninLogs' | columns ts_epoch_ms, UserPrincipalName | limit 3
status=success matching=3.0
{'ts_epoch_ms': 1780218888000, 'UserPrincipalName': 'alice@contoso.com'}
{'ts_epoch_ms': 1780221288000, 'UserPrincipalName': 'alice@contoso.com'}
{'ts_epoch_ms': 1780223688000, 'UserPrincipalName': 'alice@contoso.com'}
================================================================================
# count where ts_epoch_ms exists (any)
q: proof_run_id='run-20b5bcb16f' ts_epoch_ms=* | group n=count()
status=success matching=445.0
{'n': 445}
================================================================================
# count where ts_epoch_ms > number
q: proof_run_id='run-20b5bcb16f' | filter ts_epoch_ms > 1000000000000 | group n=count()
status=success matching=445.0
{'n': 445}
================================================================================
# count where ts_epoch_ms (as string) > '0'
q: proof_run_id='run-20b5bcb16f' | filter ts_epoch_ms > '0' | group n=count()
status=success matching=445.0
{'n': 445}
================================================================================
# count where ts_epoch_ms >= NOW-2h numeric
q: proof_run_id='run-20b5bcb16f' | filter ts_epoch_ms >= 1780240661498 | group n=count()
status=success matching=309.0
{'n': 309}
================================================================================
# min/max ts_epoch_ms aggregate
q: proof_run_id='run-20b5bcb16f' | group mn=min(ts_epoch_ms), mx=max(ts_epoch_ms), n=count()
status=success matching=445.0
{'mn': 1780218888000.0, 'mx': 1780244028000.0, 'n': 445}
================================================================================
# event_type filter alone
q: proof_run_id='run-20b5bcb16f' event_type='SigninLogs' | group n=count()
status=success matching=69.0
{'n': 69}
+29
View File
@@ -0,0 +1,29 @@
==================================================================
Step 1/5 Verify .gitignore covers secrets
==================================================================
config.json is gitignored ✓
reports/* gitignored ✓
==================================================================
Step 2/5 Scan all candidate-tracked files for secrets
==================================================================
No JWTs in any to-be-committed file ✓
==================================================================
Step 3/5 Initialise fresh git repo inside /Users/marc.chisinevski/.codeium/windsurf/s1-claude-skills/kql-to-pq
==================================================================
d874181 Initial commit: KQL ↔ SDL PowerQuery proof of equivalence
84 files staged
==================================================================
Step 4/5 Add remote https://github.com/marcredhat/kql.git
==================================================================
remote: origin https://github.com/marcredhat/kql.git (fetch)
==================================================================
Step 5/5 Push (force) to origin main
==================================================================
To https://github.com/marcredhat/kql.git
+ 31d2c23...d874181 main -> main (forced update)
branch 'main' set up to track 'origin/main'.
✓ Published to https://github.com/marcredhat/kql.git
+139
View File
@@ -0,0 +1,139 @@
==================================================================
STEP 1/5 Regenerate deterministic sample dataset
==================================================================
NOW = 2026-05-31T18:27:24+00:00
BASELINE = 2026-05-31T10:27:24+00:00 .. 2026-05-31T16:27:24+00:00
RECENT = 2026-05-31T16:27:24+00:00 .. 2026-05-31T18:27:24+00:00
Wrote 445 events -> /Users/marc.chisinevski/.codeium/windsurf/s1-claude-skills/kql-to-pq/sample_data/events.jsonl
Wrote anchor -> /Users/marc.chisinevski/.codeium/windsurf/s1-claude-skills/kql-to-pq/sample_data/time_anchor.json
==================================================================
STEP 2/5 Export KQL and PowerQuery files (with anti-pattern scan)
==================================================================
✓ Exported 17 rules to kql/ and pq/
(RECENT_MS = 1780244844000 = 2026-05-31T16:27:24+00:00)
KQL files:
01_anomalous_signin_location_increase.kql
02_rare_audit_activity_by_app.kql
03_azure_rare_subscription_ops.kql
04_daily_signin_location_trend.kql
05_daily_network_traffic_per_source.kql
06_daily_process_execution_trend.kql
07_rare_user_agent_by_app.kql
08_network_ioc_match.kql
09_new_processes_24h.kql
10_sharepoint_anomaly.kql
11_palo_alto_beacon.kql
12_suspicious_windows_logon_off_hours.kql
13_insider_threat_sensitive_files.kql
14_priv_escalation.kql
15_slow_brute_force.kql
16_suspicious_travel.kql
17_daily_baseline_new_locations.kql
PQ files:
01_anomalous_signin_location_increase.pq
02_rare_audit_activity_by_app.pq
03_azure_rare_subscription_ops.pq
04_daily_signin_location_trend.pq
05_daily_network_traffic_per_source.pq
06_daily_process_execution_trend.pq
07_rare_user_agent_by_app.pq
08_network_ioc_match.pq
09_new_processes_24h.pq
10_sharepoint_anomaly.pq
11_palo_alto_beacon.pq
12_suspicious_windows_logon_off_hours.pq
13_insider_threat_sensitive_files.pq
14_priv_escalation.pq
15_slow_brute_force.pq
16_suspicious_travel.pq
17_daily_baseline_new_locations.pq
==================================================================
STEP 3/5 Ingest sample dataset to SDL + execute PowerQueries
==================================================================
Loaded 445 events
Local reference: 39 total fired rows across 17 rules
/Users/marc.chisinevski/.venvs/azcli/lib/python3.9/site-packages/urllib3/__init__.py:35: NotOpenSSLWarning: urllib3 v2 only supports OpenSSL 1.1.1+, currently the 'ssl' module is compiled with 'LibreSSL 2.8.3'. See: https://github.com/urllib3/urllib3/issues/3020
warnings.warn(
[sdl_client] session = kql-proof-86c4db1e-a4bd-42a8-addf-f71be31b8161
Ingested 445 events to SDL (proof_run_id=run-cf5fd1cd08)
Waiting for SDL indexing ... 445 ✓ ready
scope = proof_run_id='run-cf5fd1cd08'
RECENT_MS = 1780244844000 (2026-05-31T16:27:24+00:00)
NOW = 2026-05-31T18:27:24+00:00
[ 1/17] 01_anomalous_signin_location_increase -> 2 rows matching=39.0 (1.8s, success)
[ 2/17] 02_rare_audit_activity_by_app -> 2 rows matching=2.0 (2.1s, success)
[ 3/17] 03_azure_rare_subscription_ops -> 1 rows matching=6.0 (2.5s, success)
[ 4/17] 04_daily_signin_location_trend -> 9 rows matching=39.0 (2.4s, success)
[ 5/17] 05_daily_network_traffic_per_source -> 3 rows matching=64.0 (3.4s, success)
[ 6/17] 06_daily_process_execution_trend -> 5 rows matching=5.0 (3.2s, success)
[ 7/17] 07_rare_user_agent_by_app -> 1 rows matching=15.0 (2.1s, success)
[ 8/17] 08_network_ioc_match -> 2 rows matching=61.0 (5.3s, success)
[ 9/17] 09_new_processes_24h -> 1 rows matching=1.0 (3.2s, success)
[10/17] 10_sharepoint_anomaly -> 1 rows matching=200.0 (2.2s, success)
[11/17] 11_palo_alto_beacon -> 1 rows matching=64.0 (2.3s, success)
[12/17] 12_suspicious_windows_logon_off_hours -> 1 rows matching=1.0 (2.4s, success)
[13/17] 13_insider_threat_sensitive_files -> 3 rows matching=9.0 (5.1s, success)
[14/17] 14_priv_escalation -> 1 rows matching=1.0 (3.0s, success)
[15/17] 15_slow_brute_force -> 1 rows matching=24.0 (3.2s, success)
[16/17] 16_suspicious_travel -> 2 rows matching=15.0 (2.9s, success)
[17/17] 17_daily_baseline_new_locations -> 3 rows matching=15.0 (2.4s, success)
Wrote /Users/marc.chisinevski/.codeium/windsurf/s1-claude-skills/kql-to-pq/reports/PROOF.md
==================================================================
STEP 4/5 Side-by-side comparison summary
==================================================================
Rule Ref rows SDL rows Status
--------------------------------------------------------------------------------
01_anomalous_signin_location_increase 2 2 OK
02_rare_audit_activity_by_app 2 2 OK
03_azure_rare_subscription_ops 1 1 OK
04_daily_signin_location_trend 9 9 OK
05_daily_network_traffic_per_source 3 3 OK
06_daily_process_execution_trend 5 5 OK
07_rare_user_agent_by_app 2 1 OK
08_network_ioc_match 2 2 OK
09_new_processes_24h 1 1 OK
10_sharepoint_anomaly 1 1 OK
11_palo_alto_beacon 1 1 OK
12_suspicious_windows_logon_off_hours 1 1 OK
13_insider_threat_sensitive_files 3 3 OK
14_priv_escalation 1 1 OK
15_slow_brute_force 1 1 OK
16_suspicious_travel 2 2 OK
17_daily_baseline_new_locations 2 3 OK
--------------------------------------------------------------------------------
OK: 17 EMPTY: 0 ERROR: 0
Full report: reports/PROOF.md
==================================================================
STEP 5/5 Verify each pq/*.pq runs cleanly on SDL as-written
(proof that pasted-as-is queries return status=success)
==================================================================
/Users/marc.chisinevski/.venvs/azcli/lib/python3.9/site-packages/urllib3/__init__.py:35: NotOpenSSLWarning: urllib3 v2 only supports OpenSSL 1.1.1+, currently the 'ssl' module is compiled with 'LibreSSL 2.8.3'. See: https://github.com/urllib3/urllib3/issues/3020
warnings.warn(
[sdl_client] session = kql-proof-522c2f83-8d0b-490f-a46e-63bb41cc8b4d
Verifying 17 .pq files run cleanly on SDL ...
✓ 01_anomalous_signin_location_increase.pq matching=63.0 (3.3s)
✓ 02_rare_audit_activity_by_app.pq matching=3.0 (3.0s)
✓ 03_azure_rare_subscription_ops.pq matching=48.0 (2.5s)
✓ 04_daily_signin_location_trend.pq matching=63.0 (3.9s)
✓ 05_daily_network_traffic_per_source.pq matching=126.0 (2.7s)
✓ 06_daily_process_execution_trend.pq matching=10.0 (2.1s)
✓ 07_rare_user_agent_by_app.pq matching=20.0 (3.7s)
✓ 08_network_ioc_match.pq matching=118.0 (2.2s)
✓ 09_new_processes_24h.pq matching=2.0 (3.1s)
✓ 10_sharepoint_anomaly.pq matching=400.0 (3.1s)
✓ 11_palo_alto_beacon.pq matching=125.0 (3.6s)
✓ 12_suspicious_windows_logon_off_hours.pq matching=1.0 (3.1s)
✓ 13_insider_threat_sensitive_files.pq matching=18.0 (4.5s)
✓ 14_priv_escalation.pq matching=1.0 (3.4s)
✓ 15_slow_brute_force.pq matching=43.0 (2.6s)
✓ 16_suspicious_travel.pq matching=20.0 (3.8s)
✓ 17_daily_baseline_new_locations.pq matching=20.0 (3.9s)
PASS: 17 FAIL: 0
+18
View File
@@ -0,0 +1,18 @@
/Users/marc.chisinevski/.venvs/azcli/lib/python3.9/site-packages/urllib3/__init__.py:35: NotOpenSSLWarning: urllib3 v2 only supports OpenSSL 1.1.1+, currently the 'ssl' module is compiled with 'LibreSSL 2.8.3'. See: https://github.com/urllib3/urllib3/issues/3020
warnings.warn(
probe = 10eecdaa
body size = 217698 bytes (445 lines)
HTTP 200 -> {"status":"success"}
Waiting 15 s ...
[sdl_client] session = kql-proof-22f35fda-cce9-4b85-9a2b-6129180e0b04
Query result: matching=445.0
{'event_type': 'AuditLogs', 'n': 12}
{'event_type': 'AzureActivity', 'n': 6}
{'event_type': 'CommonSecurityLog', 'n': 84}
{'event_type': 'DeviceFileEvents', 'n': 9}
{'event_type': 'OfficeActivity', 'n': 203}
{'event_type': 'SecurityEvent', 'n': 61}
{'event_type': 'SigninLogs', 'n': 69}
{'event_type': 'ThreatIntelIndicators', 'n': 1}
+42
View File
@@ -0,0 +1,42 @@
/Users/marc.chisinevski/.venvs/azcli/lib/python3.9/site-packages/urllib3/__init__.py:35: NotOpenSSLWarning: urllib3 v2 only supports OpenSSL 1.1.1+, currently the 'ssl' module is compiled with 'LibreSSL 2.8.3'. See: https://github.com/urllib3/urllib3/issues/3020
warnings.warn(
[sdl_client] session = kql-proof-fbfe7c67-c796-46d2-901d-7b948657d89b
Verifying 17 .pq files run cleanly on SDL ...
(Each file tested in TWO forms: as-written and whitespace-collapsed.)
✓ 01_anomalous_signin_location_increase.pq [as-written] matching=63.0 (3.0s)
✓ 01_anomalous_signin_location_increase.pq [collapsed] matching=63.0 (1.8s)
✓ 02_rare_audit_activity_by_app.pq [as-written] matching=3.0 (2.0s)
✓ 02_rare_audit_activity_by_app.pq [collapsed] matching=3.0 (2.6s)
✓ 03_azure_rare_subscription_ops.pq [as-written] matching=48.0 (1.9s)
✓ 03_azure_rare_subscription_ops.pq [collapsed] matching=48.0 (2.2s)
✓ 04_daily_signin_location_trend.pq [as-written] matching=63.0 (3.2s)
✓ 04_daily_signin_location_trend.pq [collapsed] matching=63.0 (5.4s)
✓ 05_daily_network_traffic_per_source.pq [as-written] matching=126.0 (3.9s)
✓ 05_daily_network_traffic_per_source.pq [collapsed] matching=126.0 (2.9s)
✓ 06_daily_process_execution_trend.pq [as-written] matching=10.0 (2.2s)
✓ 06_daily_process_execution_trend.pq [collapsed] matching=10.0 (3.6s)
✓ 07_rare_user_agent_by_app.pq [as-written] matching=20.0 (2.8s)
✓ 07_rare_user_agent_by_app.pq [collapsed] matching=20.0 (3.2s)
✓ 08_network_ioc_match.pq [as-written] matching=118.0 (3.0s)
✓ 08_network_ioc_match.pq [collapsed] matching=118.0 (4.6s)
✓ 09_new_processes_24h.pq [as-written] matching=2.0 (2.9s)
✓ 09_new_processes_24h.pq [collapsed] matching=2.0 (2.5s)
✓ 10_sharepoint_anomaly.pq [as-written] matching=400.0 (3.0s)
✓ 10_sharepoint_anomaly.pq [collapsed] matching=400.0 (3.4s)
✓ 11_palo_alto_beacon.pq [as-written] matching=125.0 (3.2s)
✓ 11_palo_alto_beacon.pq [collapsed] matching=125.0 (2.2s)
✓ 12_suspicious_windows_logon_off_hours.pq [as-written] matching=1.0 (2.9s)
✓ 12_suspicious_windows_logon_off_hours.pq [collapsed] matching=1.0 (2.4s)
✓ 13_insider_threat_sensitive_files.pq [as-written] matching=18.0 (4.8s)
✓ 13_insider_threat_sensitive_files.pq [collapsed] matching=18.0 (4.2s)
✓ 14_priv_escalation.pq [as-written] matching=1.0 (2.4s)
✓ 14_priv_escalation.pq [collapsed] matching=1.0 (2.5s)
✓ 15_slow_brute_force.pq [as-written] matching=43.0 (2.1s)
✓ 15_slow_brute_force.pq [collapsed] matching=43.0 (3.3s)
✓ 16_suspicious_travel.pq [as-written] matching=20.0 (2.1s)
✓ 16_suspicious_travel.pq [collapsed] matching=20.0 (2.0s)
✓ 17_daily_baseline_new_locations.pq [as-written] matching=20.0 (4.2s)
✓ 17_daily_baseline_new_locations.pq [collapsed] matching=20.0 (3.5s)
PASS: 17 FAIL: 0