howler-sentinel-plugin 0.2.0.dev104__py3-none-any.whl → 0.2.0.dev113__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: howler-sentinel-plugin
3
- Version: 0.2.0.dev104
3
+ Version: 0.2.0.dev113
4
4
  Summary: A howler plugin for integration with Microsoft's Sentinel API
5
5
  License: MIT
6
6
  Author: CCCS
@@ -0,0 +1,18 @@
1
+ sentinel/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ sentinel/actions/azure_emit_hash.py,sha256=ES9u3iIkm18qcwVT7-f3r8K5d-haCnjSTC7cyOKxH7A,4000
3
+ sentinel/actions/send_to_sentinel.py,sha256=RRmUSiPDKr7oQjA4f-iSGjEtziUQH77O2s-578pb1Uc,4022
4
+ sentinel/actions/update_defender_xdr_alert.py,sha256=bHBHEZwAAT1pHTF2L4JK5gttzguq12NdV58fx0tj4dQ,6799
5
+ sentinel/actions/update_defender_xdr_incident.py,sha256=HjvK8yTDafYePY5LoMkYat_cFUHnFWK3tzpCfAO3JNE,6854
6
+ sentinel/mapping/sentinel_incident.py,sha256=U7fIh8N4Jdr1A4z1E0jPRP28Ll0Cq7u9Q6292AnyRDI,9548
7
+ sentinel/mapping/xdr_alert.py,sha256=UPoqdZsjUXmJz0dCf_qMlh9Jr0D2HcSNOFvbg8lE4wY,18250
8
+ sentinel/mapping/xdr_alert_evidence.py,sha256=iMn9Wd5NB7Wi9l0Fl0vmJhugX8L6hAO9jYA9AtLLX2o,31429
9
+ sentinel/odm/hit.py,sha256=hAuO2ONMK3Ml8Xu6E7tHrmZ7M6HG5tT38RD9ZxwY254,666
10
+ sentinel/odm/models/sentinel.py,sha256=XT3XdT92uoCV5vmY9dT1jmcxRyuu9vp1gE8AwZdKBIc,337
11
+ sentinel/routes/__init__.py,sha256=JYmKRwIfEsiPos1XuMQ2mlGDbxk6TN_cVEM0K_RNze4,130
12
+ sentinel/routes/ingest.py,sha256=c7GtZakMizDaubXDm_qtw4SXLJyvsHoMheADTIjIiTY,8821
13
+ sentinel/utils/tenant_utils.py,sha256=nGOCbLzUx9OyATLAZ5UbW0WNao_1ioW4wL-htn2ltKU,1324
14
+ howler_sentinel_plugin-0.2.0.dev113.dist-info/LICENSE,sha256=Wg2luVnxEkP2NSn11nh1US6W_nFFbICBAVTG9iG3t5M,1091
15
+ howler_sentinel_plugin-0.2.0.dev113.dist-info/METADATA,sha256=21QcysnK4MtoG5BZ9VBzUYcQA5hAOrDYpmArynPt9HA,749
16
+ howler_sentinel_plugin-0.2.0.dev113.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
17
+ howler_sentinel_plugin-0.2.0.dev113.dist-info/entry_points.txt,sha256=4IJyMY0V49s3Wp659ngN_7U8g66-czeKxI-_dNAFP5g,60
18
+ howler_sentinel_plugin-0.2.0.dev113.dist-info/RECORD,,
@@ -71,6 +71,14 @@ def execute(
71
71
  },
72
72
  timeout=5.0,
73
73
  )
74
+ report.append(
75
+ {
76
+ "query": f"howler.id:{hit.howler.id}",
77
+ "outcome": "success",
78
+ "title": "Webhook Triggered",
79
+ "message": f"Field {field} from alert {hit.howler.id} was successfully sent to url {url}.",
80
+ }
81
+ )
74
82
  except Exception:
75
83
  logger.exception("Exception on network call for alert %s", hit.howler.id)
76
84
  report.append(
@@ -109,6 +117,7 @@ def specification():
109
117
  {
110
118
  "args": {"url": [], "field": []},
111
119
  "options": {"field": [field for field in Hit.flat_fields().keys() if field.endswith("sha256")]},
120
+ "validation": {"warn": {"query": "-_exists_:$field"}},
112
121
  }
113
122
  ],
114
123
  "triggers": VALID_TRIGGERS,
@@ -101,7 +101,7 @@ def execute(query: str, **kwargs) -> list[dict[str, Any]]:
101
101
  "query": f"howler.id:{hit.howler.id}",
102
102
  "outcome": "success",
103
103
  "title": "Alert updated in Sentinel",
104
- "message": "Howler has successfuly propagated changes to this alert to Sentinel.",
104
+ "message": "Howler has successfully propagated changes to this alert to Sentinel.",
105
105
  }
106
106
  )
107
107
 
@@ -165,15 +165,15 @@ def execute(query: str, **kwargs):
165
165
  "message": f"PATCH request to Microsoft Graph failed with status code {response.status_code}.",
166
166
  }
167
167
  )
168
-
169
- report.append(
170
- {
171
- "query": f"howler.id:{hit.howler.id}",
172
- "outcome": "success",
173
- "title": "Alert updated in XDR Defender",
174
- "message": "Howler has successfuly propagated changes to this alert to XDR Defender.",
175
- }
176
- )
168
+ else:
169
+ report.append(
170
+ {
171
+ "query": f"howler.id:{hit.howler.id}",
172
+ "outcome": "success",
173
+ "title": "Alert updated in XDR Defender",
174
+ "message": "Howler has successfully propagated changes to this alert to XDR Defender.",
175
+ }
176
+ )
177
177
 
178
178
  return report
179
179
 
@@ -0,0 +1,194 @@
1
+ import requests
2
+ from howler.common.exceptions import HowlerRuntimeError
3
+ from howler.common.loader import datastore
4
+ from howler.common.logging import get_logger
5
+ from howler.odm.models.action import VALID_TRIGGERS
6
+ from howler.odm.models.hit import Hit
7
+ from howler.odm.models.howler_data import Assessment, HitStatus
8
+
9
+ from sentinel.utils.tenant_utils import get_token
10
+
11
+ logger = get_logger(__file__)
12
+
13
+ OPERATION_ID = "update_defender_xdr_incident"
14
+
15
+ properties_map = {
16
+ "graph": {
17
+ "status": {
18
+ HitStatus.OPEN: "active",
19
+ HitStatus.IN_PROGRESS: "inProgress",
20
+ HitStatus.ON_HOLD: "inProgress",
21
+ HitStatus.RESOLVED: "resolved",
22
+ },
23
+ "classification": {
24
+ Assessment.AMBIGUOUS: "unknown",
25
+ Assessment.SECURITY: "informationalExpectedActivity",
26
+ Assessment.DEVELOPMENT: "informationalExpectedActivity",
27
+ Assessment.FALSE_POSITIVE: "falsePositive",
28
+ Assessment.LEGITIMATE: "informationalExpectedActivity",
29
+ Assessment.TRIVIAL: "falsePositive",
30
+ Assessment.RECON: "truePositive",
31
+ Assessment.ATTEMPT: "truePositive",
32
+ Assessment.COMPROMISE: "truePositive",
33
+ Assessment.MITIGATED: "truePositive",
34
+ None: "unknown",
35
+ },
36
+ "determination": {
37
+ Assessment.AMBIGUOUS: "unknown",
38
+ Assessment.SECURITY: "securityTesting",
39
+ Assessment.DEVELOPMENT: "confirmedUserActivity",
40
+ Assessment.FALSE_POSITIVE: "other",
41
+ Assessment.LEGITIMATE: "lineOfBusinessApplication",
42
+ Assessment.TRIVIAL: "other",
43
+ Assessment.RECON: "multiStagedAttack",
44
+ Assessment.ATTEMPT: "other",
45
+ Assessment.COMPROMISE: "maliciousUserActivity",
46
+ Assessment.MITIGATED: "other",
47
+ None: "unknown",
48
+ },
49
+ },
50
+ }
51
+
52
+
53
+ def execute(query: str, **kwargs):
54
+ """Update Microsoft Defender XDR incident.
55
+
56
+ Args:
57
+ query (str): The query on which to apply this automation.
58
+ """
59
+ report = []
60
+ ds = datastore()
61
+
62
+ hits: list[Hit] = ds.hit.search(query, as_obj=True)["items"]
63
+
64
+ if not hits:
65
+ report.append(
66
+ {
67
+ "query": query,
68
+ "outcome": "error",
69
+ "title": "No hits returned by query",
70
+ "message": f"No hits returned by '{query}'",
71
+ }
72
+ )
73
+ return report
74
+
75
+ for hit in hits:
76
+ if hit.azure and hit.azure.tenant_id:
77
+ tenant_id = hit.azure.tenant_id
78
+ elif hit.organization.id:
79
+ tenant_id = hit.organization.id
80
+ else:
81
+ report.append(
82
+ {
83
+ "query": f"howler.id:{hit.howler.id}",
84
+ "outcome": "skipped",
85
+ "title": "Azure Tenant ID is missing",
86
+ "message": "This incident does not have a set tenant ID.",
87
+ }
88
+ )
89
+ continue
90
+
91
+ try:
92
+ token = get_token(tenant_id, "https://graph.microsoft.com/.default")[0]
93
+ except HowlerRuntimeError as err:
94
+ logger.exception("Error on token fetching")
95
+ report.append(
96
+ {
97
+ "query": f"howler.id:{hit.howler.id}",
98
+ "outcome": "error",
99
+ "title": "Invalid Credentials",
100
+ "message": err.message,
101
+ }
102
+ )
103
+ continue
104
+
105
+ # Fetch incident details
106
+ incident_url = f"https://graph.microsoft.com/v1.0/security/incidents/{hit.sentinel.id}"
107
+ response = requests.get(incident_url, headers={"Authorization": f"Bearer {token}"}, timeout=5.0)
108
+ if not response.ok:
109
+ logger.warning(
110
+ "GET request to Microsoft Graph failed with status code %s. Content:\n%s",
111
+ response.status_code,
112
+ response.text,
113
+ )
114
+ report.append(
115
+ {
116
+ "query": query,
117
+ "outcome": "error",
118
+ "title": "Microsoft Graph API request failed",
119
+ "message": f"GET request to Microsoft Graph failed with status code {response.status_code}.",
120
+ }
121
+ )
122
+ continue
123
+
124
+ incident_data = response.json()
125
+
126
+ # Update incident
127
+ if (
128
+ "assessment" in hit.howler
129
+ and hit.howler.assessment in properties_map["graph"]["classification"]
130
+ and hit.howler.assessment in properties_map["graph"]["determination"]
131
+ ):
132
+ classification = properties_map["graph"]["classification"][hit.howler.assessment]
133
+ determination = properties_map["graph"]["determination"][hit.howler.assessment]
134
+ else:
135
+ classification = incident_data["classification"]
136
+ determination = incident_data["determination"]
137
+
138
+ status = properties_map["graph"]["status"][hit.howler.status]
139
+ assigned_to = incident_data["assignedTo"]
140
+
141
+ data = {
142
+ "assignedTo": assigned_to,
143
+ "classification": classification,
144
+ "determination": determination,
145
+ "status": status,
146
+ }
147
+
148
+ response = requests.patch(
149
+ incident_url,
150
+ json=data,
151
+ headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"},
152
+ timeout=5.0,
153
+ )
154
+ if not response.ok:
155
+ logger.warning(
156
+ "PATCH request to Microsoft Graph failed with status code %s. Content:\n%s",
157
+ response.status_code,
158
+ response.text,
159
+ )
160
+ report.append(
161
+ {
162
+ "query": query,
163
+ "outcome": "error",
164
+ "title": "Microsoft Graph API request failed",
165
+ "message": f"PATCH request to Microsoft Graph failed with status code {response.status_code}.",
166
+ }
167
+ )
168
+ else:
169
+ report.append(
170
+ {
171
+ "query": f"howler.id:{hit.howler.id}",
172
+ "outcome": "success",
173
+ "title": "Incident updated in XDR Defender",
174
+ "message": "Howler has successfully propagated changes to this incident to XDR Defender.",
175
+ }
176
+ )
177
+
178
+ return report
179
+
180
+
181
+ def specification():
182
+ "Update Defender action specification"
183
+ return {
184
+ "id": OPERATION_ID,
185
+ "title": "Update Microsoft Defender XDR incident",
186
+ "priority": 8,
187
+ "description": {
188
+ "short": "Update Microsoft Defender XDR incident",
189
+ "long": execute.__doc__,
190
+ },
191
+ "roles": ["automation_basic"],
192
+ "steps": [{"args": {}, "options": {}, "validation": {}}],
193
+ "triggers": VALID_TRIGGERS,
194
+ }
@@ -232,10 +232,13 @@ class XDRAlertEvidence:
232
232
  @staticmethod
233
233
  def device_evidence(evidence: dict[str, Any]) -> dict[str, Any]:
234
234
  """Convert device evidence to howler evidence format."""
235
+ hostname = evidence.get("hostName")
236
+ if not hostname:
237
+ hostname = evidence.get("deviceDnsName")
235
238
  return {
236
239
  "host": {
237
240
  "id": evidence.get("mdeDeviceId"),
238
- "name": evidence.get("hostName"),
241
+ "name": hostname,
239
242
  "domain": [evidence.get("ntDomain", "unknown")],
240
243
  "os": {
241
244
  "platform": evidence.get("osPlatform"),
@@ -668,51 +671,51 @@ class XDRAlertEvidence:
668
671
  "status_remediation_details": evidence.get("remediationStatusDetails"),
669
672
  },
670
673
  "teams": {
671
- "campaign_id": evidence.get("CampaignId"),
672
- "channel_id": evidence.get("ChannelId"),
673
- "delivery_action": evidence.get("DeliveryAction"),
674
- "delivery_location": evidence.get("DeliveryLocation"),
675
- "detailed_roles": evidence.get("DetailedRoles"),
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"),
676
679
  "files": [
677
680
  {
678
- "path": file.get("FileDetails", {}).get("FilePath"),
679
- "name": file.get("FileDetails", {}).get("FileName"),
681
+ "path": file.get("fileDetails", {}).get("filePath"),
682
+ "name": file.get("fileDetails", {}).get("fileName"),
680
683
  "hash": {
681
- "sha1": file.get("FileDetails", {}).get("Sha1"),
682
- "sha256": file.get("FileDetails", {}).get("Sha256"),
683
- "md5": file.get("FileDetails", {}).get("Md5"),
684
+ "sha1": file.get("fileDetails", {}).get("sha1"),
685
+ "sha256": file.get("fileDetails", {}).get("sha256"),
686
+ "md5": file.get("fileDetails", {}).get("md5"),
684
687
  },
685
688
  "code_signature": {
686
- "signing_id": file.get("FileDetails", {}).get("Signer"),
687
- "team_id": file.get("FileDetails", {}).get("Issuer"),
689
+ "signing_id": file.get("fileDetails", {}).get("signer"),
690
+ "team_id": file.get("fileDetails", {}).get("issuer"),
688
691
  },
689
- "size": file.get("FileDetails", {}).get("FileSize"),
692
+ "size": file.get("fileDetails", {}).get("fileSize"),
690
693
  }
691
- for file in evidence.get("Files", [])
694
+ for file in evidence.get("files", [])
692
695
  ],
693
- "group_id": evidence.get("GroupId"),
694
- "is_external": evidence.get("IsExternal"),
695
- "is_owned": evidence.get("IsOwned"),
696
- "last_modified": evidence.get("LastModified"),
697
- "message_direction": evidence.get("MessageDirection"),
698
- "message_id": evidence.get("MessageId"),
699
- "owning_tenant_id": evidence.get("OwningTenantId"),
700
- "parent_message_id": evidence.get("ParentMessageId"),
701
- "received": evidence.get("Received"),
702
- "recipients": evidence.get("Recipients"),
703
- "sender_from_address": evidence.get("SenderFromAddress"),
704
- "sender_ip": evidence.get("SenderIp"),
705
- "source_add_name": evidence.get("SourceAddName"),
706
- "source_id": evidence.get("SourceId"),
707
- "subject": evidence.get("Subject"),
708
- "suspicious_recipients": evidence.get("SuspiciousRecipients"),
709
- "thread_id": evidence.get("ThreadId"),
710
- "thread_type": evidence.get("ThreadType"),
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"),
711
714
  "urls": [
712
715
  {
713
- "full": url.get("Url"),
716
+ "full": url.get("url"),
714
717
  }
715
- for url in evidence.get("Urls", [])
718
+ for url in evidence.get("urls", [])
716
719
  ],
717
720
  },
718
721
  }
@@ -722,22 +725,22 @@ class XDRAlertEvidence:
722
725
  """Convert URL evidence to howler evidence format."""
723
726
  return {
724
727
  "url": {
725
- "full": evidence.get("Url"),
728
+ "full": evidence.get("url"),
726
729
  },
727
730
  }
728
731
 
729
732
  @staticmethod
730
733
  def user_evidence(evidence: dict[str, Any]) -> dict[str, Any]:
731
734
  """Convert user evidence to howler evidence format."""
732
- user = evidence.get("UserAccount", {})
735
+ user = evidence.get("userAccount", {})
733
736
  if not user:
734
737
  user = {}
735
738
  return {
736
739
  "user": {
737
- "name": user.get("AccountName"),
738
- "full_name": user.get("UserPrincipalName"),
739
- "id": user.get("AzureAdUserId"),
740
- "domain": user.get("DomainName"),
740
+ "name": user.get("accountName"),
741
+ "full_name": user.get("userPrincipalName"),
742
+ "id": user.get("azureAdUserId"),
743
+ "domain": user.get("domainName"),
741
744
  },
742
745
  }
743
746
 
sentinel/routes/ingest.py CHANGED
@@ -3,7 +3,7 @@ import re
3
3
  from typing import Any
4
4
 
5
5
  from flask import request
6
- from howler.api import bad_request, created, make_subapi_blueprint, unauthorized
6
+ from howler.api import bad_request, created, make_subapi_blueprint, ok, unauthorized
7
7
  from howler.common.exceptions import HowlerException
8
8
  from howler.common.loader import datastore
9
9
  from howler.common.logging import get_logger
@@ -49,7 +49,7 @@ def ingest_xdr_incident(**kwargs) -> tuple[dict[str, Any], int]: # noqa C901
49
49
  "hit_count": 1
50
50
  }
51
51
  """
52
- # API Key authentication
52
+ # TODO this endpoint need to be refactored to make it more readable and maintainable
53
53
  apikey = request.headers.get("Authorization", "Basic ", type=str).split(" ")[1]
54
54
 
55
55
  if not apikey or apikey != SECRET:
@@ -57,7 +57,6 @@ def ingest_xdr_incident(**kwargs) -> tuple[dict[str, Any], int]: # noqa C901
57
57
 
58
58
  logger.info("Received authorization header with value %s", re.sub(r"^(.{3}).+(.{3})$", r"\1...\2", apikey))
59
59
 
60
- # Validate JSON payload
61
60
  xdr_incident = request.json
62
61
  if not xdr_incident:
63
62
  return bad_request(err="No JSON data provided in request body")
@@ -65,7 +64,6 @@ def ingest_xdr_incident(**kwargs) -> tuple[dict[str, Any], int]: # noqa C901
65
64
  logger.info("XDR Incident received")
66
65
 
67
66
  try:
68
- # Configure tenant mapping for both mappers
69
67
  tenant_mapping = {"020cd98f-1002-45b7-90ff-69fc68bdd027": "Acme Corporation"}
70
68
 
71
69
  incident_mapper = SentinelIncident(tid_mapping=tenant_mapping)
@@ -74,6 +72,35 @@ def ingest_xdr_incident(**kwargs) -> tuple[dict[str, Any], int]: # noqa C901
74
72
  if bundle_hit is None:
75
73
  return bad_request(err="Failed to map XDR incident to Howler bundle format")
76
74
 
75
+ sentinel_id = xdr_incident.get("id")
76
+ if sentinel_id:
77
+ existing_bundles = datastore().hit.search(f"sentinel.id:{sentinel_id}", as_obj=True)["items"]
78
+ if existing_bundles:
79
+ existing_bundle = existing_bundles[0]
80
+ new_status = xdr_incident.get("status")
81
+ if new_status:
82
+ existing_bundle.howler.status = incident_mapper.map_sentinel_status_to_howler(new_status)
83
+ datastore().hit.save(existing_bundle.howler.id, existing_bundle)
84
+ for child_id in getattr(existing_bundle.howler, "hits", []):
85
+ child_hit = datastore().hit.get(child_id, as_obj=True)
86
+ if child_hit:
87
+ child_hit.howler.status = incident_mapper.map_sentinel_status_to_howler(new_status)
88
+ datastore().hit.save(child_id, child_hit)
89
+ datastore().hit.commit()
90
+ logger.info("Updated status for existing bundle %s and its child hits", existing_bundle.howler.id)
91
+ return ok(
92
+ {
93
+ "success": True,
94
+ "bundle_hit_id": existing_bundle.howler.id,
95
+ "bundle_id": existing_bundle.sentinel.id if hasattr(existing_bundle, "sentinel") else None,
96
+ "individual_hit_ids": getattr(existing_bundle.howler, "hits", []),
97
+ "total_hits_updated": 1 + len(getattr(existing_bundle.howler, "hits", [])),
98
+ "bundle_size": len(getattr(existing_bundle.howler, "hits", [])),
99
+ "organization": getattr(existing_bundle, "organization", {}).get("name", ""),
100
+ "updated": True,
101
+ }
102
+ )
103
+
77
104
  logger.info("Successfully mapped XDR incident to bundle")
78
105
 
79
106
  alerts = xdr_incident.get("alerts", [])
@@ -116,8 +143,9 @@ def ingest_xdr_incident(**kwargs) -> tuple[dict[str, Any], int]: # noqa C901
116
143
  bundle_odm.howler.hits.append(hit_id)
117
144
 
118
145
  if len(bundle_odm.howler.hits) < 1:
119
- logger.error("No valid child hits were created from the XDR incident alerts")
120
- return bad_request(err="No valid child hits were created from the XDR incident alerts.")
146
+ # TODO figure out how to handle incidens without alerts
147
+ logger.info("No valid child hits were created from the XDR incident alerts")
148
+ return ok("Incident contains no valid alerts to create hits from")
121
149
 
122
150
  bundle_odm.howler.bundle_size = len(bundle_odm.howler.hits)
123
151
 
@@ -151,7 +179,7 @@ def ingest_xdr_incident(**kwargs) -> tuple[dict[str, Any], int]: # noqa C901
151
179
  "bundle_hit_id": bundle_odm.howler.id,
152
180
  "bundle_id": bundle_hit["howler"].get("xdr.incident.id"),
153
181
  "individual_hit_ids": child_hit_ids,
154
- "total_hits_created": len(child_hit_ids) + 1, # +1 for the bundle itself
182
+ "total_hits_created": len(child_hit_ids) + 1,
155
183
  "bundle_size": len(child_hit_ids),
156
184
  "organization": bundle_hit["organization"]["name"],
157
185
  }
@@ -1,17 +0,0 @@
1
- sentinel/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- sentinel/actions/azure_emit_hash.py,sha256=MeprMe58pac2ntnDanadsPNjXKmIEBSPchT55XMGFWs,3557
3
- sentinel/actions/send_to_sentinel.py,sha256=s07DTPSVTSC4JwmUQe2q6W6MabfHtE43dmRj3nuE7EM,4021
4
- sentinel/actions/update_defender_xdr_alert.py,sha256=gXuVZfr7Ou0Lc6BuECmo18E8hfJPhUlM1VMKVCkofk4,6753
5
- sentinel/mapping/sentinel_incident.py,sha256=U7fIh8N4Jdr1A4z1E0jPRP28Ll0Cq7u9Q6292AnyRDI,9548
6
- sentinel/mapping/xdr_alert.py,sha256=UPoqdZsjUXmJz0dCf_qMlh9Jr0D2HcSNOFvbg8lE4wY,18250
7
- sentinel/mapping/xdr_alert_evidence.py,sha256=q622G4eZwFR3TCj418ZCpE83DGVicrWIQZo8Gkj_3FM,31323
8
- sentinel/odm/hit.py,sha256=hAuO2ONMK3Ml8Xu6E7tHrmZ7M6HG5tT38RD9ZxwY254,666
9
- sentinel/odm/models/sentinel.py,sha256=XT3XdT92uoCV5vmY9dT1jmcxRyuu9vp1gE8AwZdKBIc,337
10
- sentinel/routes/__init__.py,sha256=JYmKRwIfEsiPos1XuMQ2mlGDbxk6TN_cVEM0K_RNze4,130
11
- sentinel/routes/ingest.py,sha256=_9OdOw_9nBJseKIBnmHDLjnqZ_bDdM4wfLpLrek4-ak,7018
12
- sentinel/utils/tenant_utils.py,sha256=nGOCbLzUx9OyATLAZ5UbW0WNao_1ioW4wL-htn2ltKU,1324
13
- howler_sentinel_plugin-0.2.0.dev104.dist-info/LICENSE,sha256=Wg2luVnxEkP2NSn11nh1US6W_nFFbICBAVTG9iG3t5M,1091
14
- howler_sentinel_plugin-0.2.0.dev104.dist-info/METADATA,sha256=ZLe75ogUfw2jpQnn5HFe-im3p0YJpzrg7_p7QjhinAE,749
15
- howler_sentinel_plugin-0.2.0.dev104.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
16
- howler_sentinel_plugin-0.2.0.dev104.dist-info/entry_points.txt,sha256=4IJyMY0V49s3Wp659ngN_7U8g66-czeKxI-_dNAFP5g,60
17
- howler_sentinel_plugin-0.2.0.dev104.dist-info/RECORD,,