howler-sentinel-plugin 0.2.0.dev170__py3-none-any.whl

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,782 @@
1
+ from typing import Any
2
+ from uuid import uuid4
3
+
4
+
5
+ def default_unknown_evidence(evidence: dict[str, Any]) -> dict[str, Any]:
6
+ """Default function to handle unknown evidence types."""
7
+ return {}
8
+
9
+
10
+ class XDRAlertEvidence:
11
+ """Parse graph evidence into howler evidence format."""
12
+
13
+ @staticmethod
14
+ def amazon_resource_evidence(evidence: dict[str, Any]) -> dict[str, Any]:
15
+ """Convert Amazon resource evidence to howler evidence format."""
16
+ return {
17
+ "aws": {
18
+ "account": {
19
+ "id": evidence.get("amazonAccountId"),
20
+ },
21
+ "resource": {
22
+ "id": evidence.get("amazonResourceId"),
23
+ "type": evidence.get("resourceType"),
24
+ "name": evidence.get("resourceName"),
25
+ },
26
+ },
27
+ }
28
+
29
+ @staticmethod
30
+ def analyzed_message_evidence(evidence: dict[str, Any]) -> dict[str, Any]:
31
+ """Convert analyzed message evidence to howler evidence format."""
32
+ sender = [
33
+ evidence.get("p1Sender", {}).get("emailAddress", "Unknown"),
34
+ evidence.get("p2Sender", {}).get("emailAddress", "Unknown"),
35
+ ]
36
+ if not sender:
37
+ sender = []
38
+ return {
39
+ "email": {
40
+ "direction": evidence.get("antiSpamDirection"),
41
+ "local_id": evidence.get("networkMessageId"),
42
+ "to": evidence.get("recipientEmailAddress"),
43
+ "sender": ", ".join(sender),
44
+ "subject": evidence.get("subject"),
45
+ },
46
+ "host": {"ip": [evidence.get("senderIp")]},
47
+ "url": {
48
+ "full": ", ".join(evidence.get("urls", [])),
49
+ },
50
+ }
51
+
52
+ @staticmethod
53
+ def azure_resource_evidence(evidence: dict[str, Any]) -> dict[str, Any]:
54
+ """Convert Azure resource evidence to howler evidence format."""
55
+ return {
56
+ "azure": {
57
+ "resource": [
58
+ {
59
+ "resource_id": evidence.get("resourceId"),
60
+ "resource_type": evidence.get("resourceType"),
61
+ "resource_name": evidence.get("resourceName"),
62
+ }
63
+ ]
64
+ },
65
+ }
66
+
67
+ @staticmethod
68
+ def blob_container_evidence(evidence: dict[str, Any]) -> dict[str, Any]:
69
+ """Convert blob container evidence to howler evidence format."""
70
+ resource = evidence.get("storageResource", {})
71
+ _hashes = evidence.get("fileHashes", {})
72
+ if not resource:
73
+ resource = {}
74
+ if not _hashes:
75
+ _hashes = {}
76
+ return {
77
+ "Azure": {
78
+ "resource": [
79
+ {
80
+ "resource_id": resource.get("resourceId"),
81
+ "resource_type": resource.get("resourceType"),
82
+ "resource_name": resource.get("resourceName"),
83
+ }
84
+ ]
85
+ },
86
+ "file": {
87
+ "created": evidence.get("createdDateTime"),
88
+ "hash": {
89
+ "sha1": _hashes.get("sha1"),
90
+ "sha256": _hashes.get("sha256"),
91
+ "md5": _hashes.get("md5"),
92
+ },
93
+ "name": evidence.get("name"),
94
+ },
95
+ }
96
+
97
+ @staticmethod
98
+ def blob_evidence(evidence: dict[str, Any]) -> dict[str, Any]:
99
+ """Convert blob evidence to howler evidence format."""
100
+ container_evidence = evidence.get("blobContainer", {})
101
+ resource = container_evidence.get("storageResource", {})
102
+ _hashes = evidence.get("fileHashes", {})
103
+ if not container_evidence:
104
+ container_evidence = {}
105
+ if not resource:
106
+ resource = {}
107
+ if not _hashes:
108
+ _hashes = {}
109
+ return {
110
+ "Azure": {
111
+ "resource": [
112
+ {
113
+ "resource_id": resource.get("resourceId"),
114
+ "resource_type": resource.get("resourceType"),
115
+ "resource_name": resource.get("resourceName"),
116
+ }
117
+ ]
118
+ },
119
+ "file": {
120
+ "created": evidence.get("createdDateTime"),
121
+ "hash": {
122
+ "sha1": _hashes.get("sha1"),
123
+ "sha256": _hashes.get("sha256"),
124
+ "md5": _hashes.get("md5"),
125
+ },
126
+ "name": evidence.get("name"),
127
+ },
128
+ }
129
+
130
+ @staticmethod
131
+ def cloud_application_evidence(evidence: dict[str, Any]) -> dict[str, Any]:
132
+ """Convert cloud application evidence to howler evidence format."""
133
+ return {
134
+ "cloud": {
135
+ "project": {
136
+ "id": str(evidence.get("appId")),
137
+ "name": evidence.get("displayName"),
138
+ }
139
+ },
140
+ }
141
+
142
+ @staticmethod
143
+ def cloud_logon_session_evidence(evidence: dict[str, Any]) -> dict[str, Any]:
144
+ """Convert cloud logon session evidence to howler evidence format."""
145
+ account = evidence.get("account", {})
146
+ user = account.get("userAccount", {})
147
+ if not account:
148
+ account = {}
149
+ if not user:
150
+ user = {}
151
+ return {
152
+ "host": {
153
+ "name": evidence.get("deviceName"),
154
+ "os": {
155
+ "full": evidence.get("operatingSystem"),
156
+ },
157
+ },
158
+ "network": {
159
+ "protocol": evidence.get("protocol"),
160
+ },
161
+ "user": {
162
+ "name": user.get("accountName"),
163
+ "full_name": user.get("userPrincipalName"),
164
+ "id": user.get("azureAdUserId"),
165
+ "domain": user.get("domainName"),
166
+ },
167
+ "user_agent": {
168
+ "original": evidence.get("userAgent"),
169
+ },
170
+ }
171
+
172
+ @staticmethod
173
+ def container_evidence(evidence: dict[str, Any]) -> dict[str, Any]:
174
+ """Convert container evidence to howler evidence format."""
175
+ image = evidence.get("image", {})
176
+ pod = evidence.get("pod", {})
177
+ containers = pod.get("containers", [])
178
+ if not image:
179
+ image = {}
180
+ if not pod:
181
+ pod = {}
182
+ if not containers:
183
+ containers = []
184
+ return {
185
+ "container": {
186
+ "id": evidence.get("containerId"),
187
+ "image": evidence.get("name"),
188
+ },
189
+ "image": {"name": image.get("digestImage")},
190
+ "kubernetes": {
191
+ "pod": {
192
+ "containers": [
193
+ {
194
+ "name": container.get("name"),
195
+ "image": container.get("image"),
196
+ "id": container.get("containerId"),
197
+ "digest": container.get("digestImage"),
198
+ }
199
+ for container in containers
200
+ ],
201
+ "controller": pod.get("controller"),
202
+ "ephemeral_containers": pod.get("ephemeralContainers"),
203
+ "init_containers": pod.get("initContainers"),
204
+ "labels": pod.get("labels"),
205
+ "name": pod.get("name"),
206
+ "namespace": pod.get("namespace"),
207
+ "pod_ip": pod.get("podIp"),
208
+ "service_account": {
209
+ "name": pod.get("serviceAccountName"),
210
+ "namespace": pod.get("namespace"),
211
+ },
212
+ }
213
+ },
214
+ }
215
+
216
+ @staticmethod
217
+ def container_image_evidence(evidence: dict[str, Any]) -> dict[str, Any]:
218
+ """Convert container image evidence to howler evidence format."""
219
+ return {
220
+ "image": {"name": evidence.get("digestImage")},
221
+ }
222
+
223
+ @staticmethod
224
+ def container_registry_evidence(evidence: dict[str, Any]) -> dict[str, Any]:
225
+ """Convert container registry evidence to howler evidence format."""
226
+ return {
227
+ "container": {
228
+ "registry": evidence.get("registry"),
229
+ },
230
+ }
231
+
232
+ @staticmethod
233
+ def device_evidence(evidence: dict[str, Any]) -> dict[str, Any]:
234
+ """Convert device evidence to howler evidence format."""
235
+ hostname = evidence.get("hostName")
236
+ if not hostname:
237
+ hostname = evidence.get("deviceDnsName")
238
+ return {
239
+ "host": {
240
+ "id": evidence.get("mdeDeviceId"),
241
+ "name": hostname,
242
+ "domain": [evidence.get("ntDomain", "unknown")],
243
+ "os": {
244
+ "platform": evidence.get("osPlatform"),
245
+ "version": str(evidence.get("osBuild")),
246
+ },
247
+ "logged_on_users": [
248
+ {
249
+ "name": user.get("accountName"),
250
+ "domain": user.get("domainName"),
251
+ }
252
+ for user in evidence.get("loggedOnUsers", [])
253
+ ],
254
+ "risk_score": evidence.get("riskScore"),
255
+ "status_av": evidence.get("defenderAvStatus"),
256
+ "status_health": evidence.get("healthStatus"),
257
+ "status_onboarding": evidence.get("onboardingStatus"),
258
+ },
259
+ }
260
+
261
+ @staticmethod
262
+ def file_evidence(evidence: dict[str, Any]) -> dict[str, Any]:
263
+ """Convert file evidence to howler evidence format."""
264
+ file_details = evidence.get("fileDetails", {})
265
+ if not file_details:
266
+ file_details = {}
267
+ return {
268
+ "file": {
269
+ "created": evidence.get("createdDateTime"),
270
+ "hash": {
271
+ "sha1": file_details.get("sha1"),
272
+ "sha256": file_details.get("sha256"),
273
+ "md5": file_details.get("md5"),
274
+ },
275
+ "name": file_details.get("fileName"),
276
+ "path": file_details.get("filePath"),
277
+ "size": file_details.get("fileSize"),
278
+ "code_signature": {
279
+ "signing_id": file_details.get("signer"),
280
+ "team_id": file_details.get("issuer"),
281
+ },
282
+ },
283
+ }
284
+
285
+ @staticmethod
286
+ def google_cloud_resource_evidence(evidence: dict[str, Any]) -> dict[str, Any]:
287
+ """Convert Google Cloud resource evidence to howler evidence format."""
288
+ return {
289
+ "gcp": {
290
+ "zone": evidence.get("location"),
291
+ "zone_type": evidence.get("locationType"),
292
+ "project_id": evidence.get("projectId"),
293
+ "project_number": evidence.get("projectNumber"),
294
+ "resource_id": evidence.get("resourceName"),
295
+ "resource_type": evidence.get("resourceType"),
296
+ },
297
+ }
298
+
299
+ @staticmethod
300
+ def iot_device_evidence(evidence: dict[str, Any]) -> dict[str, Any]:
301
+ """Convert IoT device evidence to howler evidence format."""
302
+ return {
303
+ "iot": {
304
+ "hub": evidence.get("iotHub"),
305
+ "id": evidence.get("deviceId"),
306
+ "name": evidence.get("deviceName"),
307
+ "owners": evidence.get("owners"),
308
+ "ioTSecurityAgentId": evidence.get("securityAgentId"),
309
+ "deviceType": evidence.get("deviceType"),
310
+ "source": evidence.get("source"),
311
+ "source_ref": evidence.get("sourceRef"),
312
+ "manufacturer": evidence.get("manufacturer"),
313
+ "model": evidence.get("model"),
314
+ "serial": evidence.get("serialNumber"),
315
+ "site": evidence.get("site"),
316
+ "zone": evidence.get("zone"),
317
+ "sensor": evidence.get("sensor"),
318
+ "importance": evidence.get("importance"),
319
+ "purdue_layer": evidence.get("purdueLayer"),
320
+ "is_programming": evidence.get("isProgramming"),
321
+ "is_authorized": evidence.get("isAuthorized"),
322
+ "is_scanner": evidence.get("isScanner"),
323
+ "device_link": evidence.get("devicePageLink"),
324
+ "device_subtype": evidence.get("deviceSubtype"),
325
+ },
326
+ "host": {
327
+ "ip": evidence.get("ipAddress"),
328
+ "mac": evidence.get("macAddress"),
329
+ "os": {
330
+ "full": evidence.get("operatingSystem"),
331
+ },
332
+ },
333
+ }
334
+
335
+ @staticmethod
336
+ def ip_evidence(evidence: dict[str, Any]) -> dict[str, Any]:
337
+ """Convert IP evidence to howler evidence format."""
338
+ return {
339
+ "host": {
340
+ "ip": [evidence.get("ipAddress")],
341
+ "geo": {
342
+ "country_iso_code": evidence.get("countryCode"),
343
+ },
344
+ },
345
+ }
346
+
347
+ @staticmethod
348
+ def kubernetes_cluster_evidence(evidence: dict[str, Any]) -> dict[str, Any]:
349
+ """Convert Kubernetes cluster evidence to howler evidence format."""
350
+ return {
351
+ "kubernetes": {
352
+ "namespace": {
353
+ "cluster": {
354
+ "cloud_resource": evidence.get("cloudResource"),
355
+ "distribution": evidence.get("distribution"),
356
+ "name": evidence.get("name"),
357
+ "platform": evidence.get("platform"),
358
+ "version": evidence.get("version"),
359
+ }
360
+ }
361
+ },
362
+ }
363
+
364
+ @staticmethod
365
+ def kubernetes_controller_evidence(evidence: dict[str, Any]) -> dict[str, Any]:
366
+ """Convert Kubernetes controller evidence to howler evidence format."""
367
+ return {
368
+ "kubernetes": {
369
+ "pod": {
370
+ "controller": {
371
+ "labels": evidence.get("labels"),
372
+ "name": evidence.get("name"),
373
+ "namespace": evidence.get("namespace"),
374
+ "controller_type": evidence.get("type"),
375
+ }
376
+ }
377
+ },
378
+ }
379
+
380
+ @staticmethod
381
+ def kubernetes_namespace_evidence(evidence: dict[str, Any]) -> dict[str, Any]:
382
+ """Convert Kubernetes namespace evidence to howler evidence format."""
383
+ return {
384
+ "kubernetes": {
385
+ "namespace": {
386
+ "cluster": {
387
+ "cloud_resource": evidence.get("cloudResource"),
388
+ "distribution": evidence.get("distribution"),
389
+ "name": evidence.get("name"),
390
+ "platform": evidence.get("platform"),
391
+ "version": evidence.get("version"),
392
+ }
393
+ },
394
+ "labels": evidence.get("labels"),
395
+ "name": evidence.get("name"),
396
+ },
397
+ }
398
+
399
+ @staticmethod
400
+ def kubernetes_pod_evidence(evidence: dict[str, Any]) -> dict[str, Any]:
401
+ """Convert Kubernetes pod evidence to howler evidence format."""
402
+ return {
403
+ "kubernetes": {
404
+ "controller": {
405
+ "labels": evidence.get("labels"),
406
+ "name": evidence.get("name"),
407
+ "namespace": evidence.get("namespace"),
408
+ "controller_type": evidence.get("type"),
409
+ "ephemeral_containers": [
410
+ {
411
+ "id": container.get("containerId"),
412
+ "args": container.get("args"),
413
+ "command": container.get("command"),
414
+ "name": container.get("name"),
415
+ }
416
+ for container in evidence.get("ephemeralContainers", [])
417
+ ],
418
+ "init_containers": [
419
+ {
420
+ "id": container.get("containerId"),
421
+ "args": container.get("args"),
422
+ "command": container.get("command"),
423
+ "name": container.get("name"),
424
+ }
425
+ for container in evidence.get("ephemeralContainers", [])
426
+ ],
427
+ "pod_ip": evidence.get("podIp"),
428
+ "service_account": {
429
+ "name": evidence.get("serviceAccountName"),
430
+ "namespace": evidence.get("namespace"),
431
+ },
432
+ }
433
+ },
434
+ }
435
+
436
+ @staticmethod
437
+ def kubernetes_secret_evidence(evidence: dict[str, Any]) -> dict[str, Any]:
438
+ """Convert Kubernetes secret evidence to howler evidence format."""
439
+ return {
440
+ "kubernetes": {
441
+ "secret": {
442
+ "name": evidence.get("name"),
443
+ "namespace": evidence.get("namespace"),
444
+ "secret_type": evidence.get("secretType"),
445
+ }
446
+ },
447
+ }
448
+
449
+ @staticmethod
450
+ def kubernetes_service_evidence(evidence: dict[str, Any]) -> dict[str, Any]:
451
+ """Convert Kubernetes service evidence to howler evidence format."""
452
+ return {
453
+ "kubernetes": {
454
+ "service": {
455
+ "cluster_ip": evidence.get("clusterIp"),
456
+ "external_ips": [x.get("ipAddress") for x in evidence.get("externalIps", [])],
457
+ "labels": evidence.get("labels"),
458
+ "name": evidence.get("name"),
459
+ "namespace": evidence.get("namespace"),
460
+ "selector": evidence.get("selector"),
461
+ "service_ports": [
462
+ {
463
+ "app_protocol": port.get("appProtocol"),
464
+ "name": port.get("name"),
465
+ "node_port": port.get("nodePort"),
466
+ "port": port.get("port"),
467
+ "protocol": port.get("protocol"),
468
+ "target_port": port.get("targetPort"),
469
+ }
470
+ for port in evidence.get("servicePorts", [])
471
+ ],
472
+ "service_type": evidence.get("serviceType"),
473
+ }
474
+ },
475
+ }
476
+
477
+ @staticmethod
478
+ def kubernetes_service_account_evidence(evidence: dict[str, Any]) -> dict[str, Any]:
479
+ """Convert Kubernetes service account evidence to howler evidence format."""
480
+ return {
481
+ "kubernetes": {
482
+ "pod": {
483
+ "service_account": {
484
+ "name": evidence.get("name"),
485
+ "namespace": evidence.get("namespace"),
486
+ }
487
+ }
488
+ },
489
+ }
490
+
491
+ @staticmethod
492
+ def mail_cluster_evidence(evidence: dict[str, Any]) -> dict[str, Any]:
493
+ """Convert mail cluster evidence to howler evidence format."""
494
+ return {
495
+ "metadata": {
496
+ "id": str(uuid4()),
497
+ "kind": "evidence",
498
+ "provider": "Alert",
499
+ "rawdata": evidence,
500
+ },
501
+ "mailcluster": {
502
+ "cluster_by": evidence.get("clusterBy"),
503
+ "cluster_by_value": evidence.get("clusterByValue"),
504
+ "email_count": evidence.get("emailCount"),
505
+ "network_message_ids": evidence.get("networkMessageIds"),
506
+ "query": evidence.get("query"),
507
+ "urn": evidence.get("urn"),
508
+ },
509
+ }
510
+
511
+ @staticmethod
512
+ def mailbox_evidence(evidence: dict[str, Any]) -> dict[str, Any]:
513
+ """Convert mailbox evidence to howler evidence format."""
514
+ user = evidence.get("userAccount", {})
515
+ if not user:
516
+ user = {}
517
+ return {
518
+ "metadata": {
519
+ "kind": "evidence",
520
+ "provider": "Alert",
521
+ "rawdata": evidence,
522
+ },
523
+ "email": {
524
+ "id": str(uuid4()),
525
+ "to": evidence.get("primaryAddress"),
526
+ },
527
+ "user": {
528
+ "id": user.get("azureAdUserId"),
529
+ "name": user.get("accountName"),
530
+ "full_name": user.get("userPrincipalName"),
531
+ "domain": user.get("domain"),
532
+ },
533
+ }
534
+
535
+ @staticmethod
536
+ def nic_evidence(evidence: dict[str, Any]) -> dict[str, Any]:
537
+ """Convert NIC evidence to howler evidence format."""
538
+ return {
539
+ "host": {
540
+ "ip": evidence.get("ipAddress"),
541
+ "mac": evidence.get("macAddress"),
542
+ },
543
+ }
544
+
545
+ @staticmethod
546
+ def oauth_application_evidence(evidence: dict[str, Any]) -> dict[str, Any]:
547
+ """Convert OAuth application evidence to howler evidence format."""
548
+ return {
549
+ "metadata": {
550
+ "id": str(uuid4()),
551
+ "kind": "evidence",
552
+ "provider": "Alert",
553
+ "rawdata": evidence,
554
+ },
555
+ "oauth": {
556
+ "app_id": evidence.get("appId"),
557
+ "display_name": evidence.get("displayName"),
558
+ "object_id": evidence.get("objectId"),
559
+ "publisher": evidence.get("publisher"),
560
+ },
561
+ }
562
+
563
+ @staticmethod
564
+ def process_evidence(evidence: dict[str, Any]) -> dict[str, Any]:
565
+ """Convert process evidence to howler evidence format."""
566
+ image_file = evidence.get("imageFile", {})
567
+ parent_image_file = evidence.get("parentImageFile", {})
568
+ if not image_file:
569
+ image_file = {}
570
+ if not parent_image_file:
571
+ parent_image_file = {}
572
+ executable = f"{image_file.get('filePath')}\\{image_file.get('fileName')}"
573
+ return {
574
+ "metadata": {
575
+ "kind": "evidence",
576
+ "provider": "Alert",
577
+ "rawdata": evidence,
578
+ "status_remediation": evidence.get("detectionStatus"),
579
+ },
580
+ "process": {
581
+ "start": evidence.get("processCreationDateTime"),
582
+ "pid": evidence.get("processId"),
583
+ "name": image_file.get("fileName"),
584
+ "hash": {
585
+ "sha1": image_file.get("sha1"),
586
+ "sha256": image_file.get("sha256"),
587
+ "md5": image_file.get("md5"),
588
+ },
589
+ "executable": executable,
590
+ "code_signature": {
591
+ "team_id": image_file.get("issuer"),
592
+ "signing_id": image_file.get("signer"),
593
+ },
594
+ "parent": {
595
+ "start": evidence.get("parentProcessCreationDateTime"),
596
+ "pid": evidence.get("parentProcessId"),
597
+ "name": parent_image_file.get("fileName"),
598
+ "hash": {
599
+ "sha1": parent_image_file.get("sha1"),
600
+ "sha256": parent_image_file.get("sha256"),
601
+ "md5": parent_image_file.get("md5"),
602
+ },
603
+ "executable": parent_image_file.get("filePath"),
604
+ "code_signature": {
605
+ "team_id": parent_image_file.get("issuer"),
606
+ "signing_id": parent_image_file.get("signer"),
607
+ },
608
+ },
609
+ },
610
+ }
611
+
612
+ @staticmethod
613
+ def registry_key_evidence(evidence: dict[str, Any]) -> dict[str, Any]:
614
+ """Convert registry key evidence to howler evidence format."""
615
+ return {
616
+ "metadata": {
617
+ "kind": "evidence",
618
+ "provider": "Alert",
619
+ "rawdata": evidence,
620
+ },
621
+ "key": evidence.get("registryKey"),
622
+ "hive": evidence.get("registryHive"),
623
+ }
624
+
625
+ @staticmethod
626
+ def registry_value_evidence(evidence: dict[str, Any]) -> dict[str, Any]:
627
+ """Convert registry value evidence to howler evidence format."""
628
+ return {
629
+ "metadata": {
630
+ "kind": "evidence",
631
+ "provider": "Alert",
632
+ "rawdata": evidence,
633
+ },
634
+ "registry": {
635
+ "hive": evidence.get("registryHive"),
636
+ "key": evidence.get("registryKey"),
637
+ "value": evidence.get("registryValue"),
638
+ },
639
+ "host": {
640
+ "id": evidence.get("mdeDeviceId"),
641
+ },
642
+ }
643
+
644
+ @staticmethod
645
+ def security_group_evidence(evidence: dict[str, Any]) -> dict[str, Any]:
646
+ """Convert security group evidence to howler evidence format."""
647
+ return {
648
+ "metadata": {
649
+ "kind": "evidence",
650
+ "provider": "Alert",
651
+ "rawdata": evidence,
652
+ },
653
+ "group": {
654
+ "id": evidence.get("securityGroupId"),
655
+ "name": evidence.get("displayName"),
656
+ },
657
+ }
658
+
659
+ @staticmethod
660
+ def teams_message_evidence(evidence: dict[str, Any]) -> dict[str, Any]:
661
+ """Convert Teams message evidence to howler evidence format."""
662
+ return {
663
+ "metadata": {
664
+ "kind": "evidence",
665
+ "provider": "Alert",
666
+ "rawdata": evidence,
667
+ "roles": evidence.get("roles"),
668
+ "tags": evidence.get("tags"),
669
+ "verdict": evidence.get("verdict"),
670
+ "status_remediation": evidence.get("remediationStatus"),
671
+ "status_remediation_details": evidence.get("remediationStatusDetails"),
672
+ },
673
+ "teams": {
674
+ "campaign_id": evidence.get("campaignId"),
675
+ "channel_id": evidence.get("channelId"),
676
+ "delivery_action": evidence.get("deliveryAction"),
677
+ "delivery_location": evidence.get("deliveryLocation"),
678
+ "detailed_roles": evidence.get("detailedRoles"),
679
+ "files": [
680
+ {
681
+ "path": file.get("fileDetails", {}).get("filePath"),
682
+ "name": file.get("fileDetails", {}).get("fileName"),
683
+ "hash": {
684
+ "sha1": file.get("fileDetails", {}).get("sha1"),
685
+ "sha256": file.get("fileDetails", {}).get("sha256"),
686
+ "md5": file.get("fileDetails", {}).get("md5"),
687
+ },
688
+ "code_signature": {
689
+ "signing_id": file.get("fileDetails", {}).get("signer"),
690
+ "team_id": file.get("fileDetails", {}).get("issuer"),
691
+ },
692
+ "size": file.get("fileDetails", {}).get("fileSize"),
693
+ }
694
+ for file in evidence.get("files", [])
695
+ ],
696
+ "group_id": evidence.get("groupId"),
697
+ "is_external": evidence.get("isExternal"),
698
+ "is_owned": evidence.get("isOwned"),
699
+ "last_modified": evidence.get("lastModified"),
700
+ "message_direction": evidence.get("messageDirection"),
701
+ "message_id": evidence.get("messageId"),
702
+ "owning_tenant_id": evidence.get("owningTenantId"),
703
+ "parent_message_id": evidence.get("parentMessageId"),
704
+ "received": evidence.get("received"),
705
+ "recipients": evidence.get("recipients"),
706
+ "sender_from_address": evidence.get("senderFromAddress"),
707
+ "sender_ip": evidence.get("senderIp"),
708
+ "source_add_name": evidence.get("sourceAddName"),
709
+ "source_id": evidence.get("sourceId"),
710
+ "subject": evidence.get("subject"),
711
+ "suspicious_recipients": evidence.get("suspiciousRecipients"),
712
+ "thread_id": evidence.get("threadId"),
713
+ "thread_type": evidence.get("threadType"),
714
+ "urls": [
715
+ {
716
+ "full": url.get("url"),
717
+ }
718
+ for url in evidence.get("urls", [])
719
+ ],
720
+ },
721
+ }
722
+
723
+ @staticmethod
724
+ def url_evidence(evidence: dict[str, Any]) -> dict[str, Any]:
725
+ """Convert URL evidence to howler evidence format."""
726
+ return {
727
+ "url": {
728
+ "full": evidence.get("url"),
729
+ },
730
+ }
731
+
732
+ @staticmethod
733
+ def user_evidence(evidence: dict[str, Any]) -> dict[str, Any]:
734
+ """Convert user evidence to howler evidence format."""
735
+ user = evidence.get("userAccount", {})
736
+ if not user:
737
+ user = {}
738
+ return {
739
+ "user": {
740
+ "name": user.get("accountName"),
741
+ "full_name": user.get("userPrincipalName"),
742
+ "id": user.get("azureAdUserId"),
743
+ "domain": user.get("domainName"),
744
+ },
745
+ }
746
+
747
+
748
+ evidence_function_map = {
749
+ "amazonResourceEvidence": XDRAlertEvidence.amazon_resource_evidence,
750
+ "analyzedMessageEvidence": XDRAlertEvidence.analyzed_message_evidence,
751
+ "azureResourceEvidence": XDRAlertEvidence.azure_resource_evidence,
752
+ "blobContainerEvidence": XDRAlertEvidence.blob_container_evidence,
753
+ "blobEvidence": XDRAlertEvidence.blob_evidence,
754
+ "cloudApplicationEvidence": XDRAlertEvidence.cloud_application_evidence,
755
+ "cloudLogonSessionEvidence": XDRAlertEvidence.cloud_logon_session_evidence,
756
+ "containerEvidence": XDRAlertEvidence.container_evidence,
757
+ "containerImageEvidence": XDRAlertEvidence.container_image_evidence,
758
+ "containerRegistryEvidence": XDRAlertEvidence.container_registry_evidence,
759
+ "deviceEvidence": XDRAlertEvidence.device_evidence,
760
+ "fileEvidence": XDRAlertEvidence.file_evidence,
761
+ "googleCloudResourceEvidence": XDRAlertEvidence.google_cloud_resource_evidence,
762
+ "iotDeviceEvidence": XDRAlertEvidence.iot_device_evidence,
763
+ "ipEvidence": XDRAlertEvidence.ip_evidence,
764
+ "kubernetesClusterEvidence": XDRAlertEvidence.kubernetes_cluster_evidence,
765
+ "kubernetesControllerEvidence": XDRAlertEvidence.kubernetes_controller_evidence,
766
+ "kubernetesNamespaceEvidence": XDRAlertEvidence.kubernetes_namespace_evidence,
767
+ "kubernetesPodEvidence": XDRAlertEvidence.kubernetes_pod_evidence,
768
+ "kubernetesSecretEvidence": XDRAlertEvidence.kubernetes_secret_evidence,
769
+ "kubernetesServiceEvidence": XDRAlertEvidence.kubernetes_service_evidence,
770
+ "kubernetesServiceAccountEvidence": XDRAlertEvidence.kubernetes_service_account_evidence,
771
+ "mailClusterEvidence": XDRAlertEvidence.mail_cluster_evidence,
772
+ "mailboxEvidence": XDRAlertEvidence.mailbox_evidence,
773
+ "nicEvidence": XDRAlertEvidence.nic_evidence,
774
+ "oauthApplicationEvidence": XDRAlertEvidence.oauth_application_evidence,
775
+ "processEvidence": XDRAlertEvidence.process_evidence,
776
+ "registryKeyEvidence": XDRAlertEvidence.registry_key_evidence,
777
+ "registryValueEvidence": XDRAlertEvidence.registry_value_evidence,
778
+ "securityGroupEvidence": XDRAlertEvidence.security_group_evidence,
779
+ "teamsMessageEvidence": XDRAlertEvidence.teams_message_evidence,
780
+ "urlEvidence": XDRAlertEvidence.url_evidence,
781
+ "userEvidence": XDRAlertEvidence.user_evidence,
782
+ }