souleyez 2.43.29__py3-none-any.whl → 2.43.32__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.
Files changed (356) hide show
  1. souleyez/__init__.py +1 -2
  2. souleyez/ai/__init__.py +21 -15
  3. souleyez/ai/action_mapper.py +249 -150
  4. souleyez/ai/chain_advisor.py +116 -100
  5. souleyez/ai/claude_provider.py +29 -28
  6. souleyez/ai/context_builder.py +80 -62
  7. souleyez/ai/executor.py +158 -117
  8. souleyez/ai/feedback_handler.py +136 -121
  9. souleyez/ai/llm_factory.py +27 -20
  10. souleyez/ai/llm_provider.py +4 -2
  11. souleyez/ai/ollama_provider.py +6 -9
  12. souleyez/ai/ollama_service.py +44 -37
  13. souleyez/ai/path_scorer.py +91 -76
  14. souleyez/ai/recommender.py +176 -144
  15. souleyez/ai/report_context.py +74 -73
  16. souleyez/ai/report_service.py +84 -66
  17. souleyez/ai/result_parser.py +222 -229
  18. souleyez/ai/safety.py +67 -44
  19. souleyez/auth/__init__.py +23 -22
  20. souleyez/auth/audit.py +36 -26
  21. souleyez/auth/engagement_access.py +65 -48
  22. souleyez/auth/permissions.py +14 -3
  23. souleyez/auth/session_manager.py +54 -37
  24. souleyez/auth/user_manager.py +109 -64
  25. souleyez/commands/audit.py +40 -43
  26. souleyez/commands/auth.py +35 -15
  27. souleyez/commands/deliverables.py +55 -50
  28. souleyez/commands/engagement.py +47 -28
  29. souleyez/commands/license.py +32 -23
  30. souleyez/commands/screenshots.py +36 -32
  31. souleyez/commands/user.py +82 -36
  32. souleyez/config.py +52 -44
  33. souleyez/core/credential_tester.py +87 -81
  34. souleyez/core/cve_mappings.py +179 -192
  35. souleyez/core/cve_matcher.py +162 -148
  36. souleyez/core/msf_auto_mapper.py +100 -83
  37. souleyez/core/msf_chain_engine.py +294 -256
  38. souleyez/core/msf_database.py +153 -70
  39. souleyez/core/msf_integration.py +679 -673
  40. souleyez/core/msf_rpc_client.py +40 -42
  41. souleyez/core/msf_rpc_manager.py +77 -79
  42. souleyez/core/msf_sync_manager.py +241 -181
  43. souleyez/core/network_utils.py +22 -15
  44. souleyez/core/parser_handler.py +34 -25
  45. souleyez/core/pending_chains.py +114 -63
  46. souleyez/core/templates.py +158 -107
  47. souleyez/core/tool_chaining.py +9592 -2879
  48. souleyez/core/version_utils.py +79 -94
  49. souleyez/core/vuln_correlation.py +136 -89
  50. souleyez/core/web_utils.py +33 -32
  51. souleyez/data/wordlists/ad_users.txt +378 -0
  52. souleyez/data/wordlists/api_endpoints_large.txt +769 -0
  53. souleyez/data/wordlists/home_dir_sensitive.txt +39 -0
  54. souleyez/data/wordlists/lfi_payloads.txt +82 -0
  55. souleyez/data/wordlists/passwords_brute.txt +1548 -0
  56. souleyez/data/wordlists/passwords_crack.txt +2479 -0
  57. souleyez/data/wordlists/passwords_spray.txt +386 -0
  58. souleyez/data/wordlists/subdomains_large.txt +5057 -0
  59. souleyez/data/wordlists/usernames_common.txt +694 -0
  60. souleyez/data/wordlists/web_dirs_large.txt +4769 -0
  61. souleyez/detection/__init__.py +1 -1
  62. souleyez/detection/attack_signatures.py +12 -17
  63. souleyez/detection/mitre_mappings.py +61 -55
  64. souleyez/detection/validator.py +97 -86
  65. souleyez/devtools.py +23 -10
  66. souleyez/docs/README.md +4 -4
  67. souleyez/docs/api-reference/cli-commands.md +2 -2
  68. souleyez/docs/developer-guide/adding-new-tools.md +562 -0
  69. souleyez/docs/user-guide/auto-chaining.md +30 -8
  70. souleyez/docs/user-guide/getting-started.md +1 -1
  71. souleyez/docs/user-guide/installation.md +26 -3
  72. souleyez/docs/user-guide/metasploit-integration.md +2 -2
  73. souleyez/docs/user-guide/rbac.md +1 -1
  74. souleyez/docs/user-guide/scope-management.md +1 -1
  75. souleyez/docs/user-guide/siem-integration.md +1 -1
  76. souleyez/docs/user-guide/tools-reference.md +1 -8
  77. souleyez/docs/user-guide/worker-management.md +1 -1
  78. souleyez/engine/background.py +1238 -535
  79. souleyez/engine/base.py +4 -1
  80. souleyez/engine/job_status.py +17 -49
  81. souleyez/engine/log_sanitizer.py +103 -77
  82. souleyez/engine/manager.py +38 -7
  83. souleyez/engine/result_handler.py +2198 -1550
  84. souleyez/engine/worker_manager.py +50 -41
  85. souleyez/export/evidence_bundle.py +72 -62
  86. souleyez/feature_flags/features.py +16 -20
  87. souleyez/feature_flags.py +5 -9
  88. souleyez/handlers/__init__.py +11 -0
  89. souleyez/handlers/base.py +188 -0
  90. souleyez/handlers/bash_handler.py +277 -0
  91. souleyez/handlers/bloodhound_handler.py +243 -0
  92. souleyez/handlers/certipy_handler.py +311 -0
  93. souleyez/handlers/crackmapexec_handler.py +486 -0
  94. souleyez/handlers/dnsrecon_handler.py +344 -0
  95. souleyez/handlers/enum4linux_handler.py +400 -0
  96. souleyez/handlers/evil_winrm_handler.py +493 -0
  97. souleyez/handlers/ffuf_handler.py +815 -0
  98. souleyez/handlers/gobuster_handler.py +1114 -0
  99. souleyez/handlers/gpp_extract_handler.py +334 -0
  100. souleyez/handlers/hashcat_handler.py +444 -0
  101. souleyez/handlers/hydra_handler.py +563 -0
  102. souleyez/handlers/impacket_getuserspns_handler.py +343 -0
  103. souleyez/handlers/impacket_psexec_handler.py +222 -0
  104. souleyez/handlers/impacket_secretsdump_handler.py +426 -0
  105. souleyez/handlers/john_handler.py +286 -0
  106. souleyez/handlers/katana_handler.py +425 -0
  107. souleyez/handlers/kerbrute_handler.py +298 -0
  108. souleyez/handlers/ldapsearch_handler.py +636 -0
  109. souleyez/handlers/lfi_extract_handler.py +464 -0
  110. souleyez/handlers/msf_auxiliary_handler.py +408 -0
  111. souleyez/handlers/msf_exploit_handler.py +380 -0
  112. souleyez/handlers/nikto_handler.py +413 -0
  113. souleyez/handlers/nmap_handler.py +821 -0
  114. souleyez/handlers/nuclei_handler.py +359 -0
  115. souleyez/handlers/nxc_handler.py +371 -0
  116. souleyez/handlers/rdp_sec_check_handler.py +353 -0
  117. souleyez/handlers/registry.py +288 -0
  118. souleyez/handlers/responder_handler.py +232 -0
  119. souleyez/handlers/service_explorer_handler.py +434 -0
  120. souleyez/handlers/smbclient_handler.py +344 -0
  121. souleyez/handlers/smbmap_handler.py +510 -0
  122. souleyez/handlers/smbpasswd_handler.py +296 -0
  123. souleyez/handlers/sqlmap_handler.py +1116 -0
  124. souleyez/handlers/theharvester_handler.py +601 -0
  125. souleyez/handlers/whois_handler.py +277 -0
  126. souleyez/handlers/wpscan_handler.py +554 -0
  127. souleyez/history.py +32 -16
  128. souleyez/importers/msf_importer.py +106 -75
  129. souleyez/importers/smart_importer.py +208 -147
  130. souleyez/integrations/siem/__init__.py +10 -10
  131. souleyez/integrations/siem/base.py +17 -18
  132. souleyez/integrations/siem/elastic.py +108 -122
  133. souleyez/integrations/siem/factory.py +207 -80
  134. souleyez/integrations/siem/googlesecops.py +146 -154
  135. souleyez/integrations/siem/rule_mappings/__init__.py +1 -1
  136. souleyez/integrations/siem/rule_mappings/wazuh_rules.py +8 -5
  137. souleyez/integrations/siem/sentinel.py +107 -109
  138. souleyez/integrations/siem/splunk.py +246 -212
  139. souleyez/integrations/siem/wazuh.py +65 -71
  140. souleyez/integrations/wazuh/__init__.py +5 -5
  141. souleyez/integrations/wazuh/client.py +70 -93
  142. souleyez/integrations/wazuh/config.py +85 -57
  143. souleyez/integrations/wazuh/host_mapper.py +28 -36
  144. souleyez/integrations/wazuh/sync.py +78 -68
  145. souleyez/intelligence/__init__.py +4 -5
  146. souleyez/intelligence/correlation_analyzer.py +309 -295
  147. souleyez/intelligence/exploit_knowledge.py +661 -623
  148. souleyez/intelligence/exploit_suggestions.py +159 -139
  149. souleyez/intelligence/gap_analyzer.py +132 -97
  150. souleyez/intelligence/gap_detector.py +251 -214
  151. souleyez/intelligence/sensitive_tables.py +266 -129
  152. souleyez/intelligence/service_parser.py +137 -123
  153. souleyez/intelligence/surface_analyzer.py +407 -268
  154. souleyez/intelligence/target_parser.py +159 -162
  155. souleyez/licensing/__init__.py +6 -6
  156. souleyez/licensing/validator.py +17 -19
  157. souleyez/log_config.py +79 -54
  158. souleyez/main.py +1505 -687
  159. souleyez/migrations/fix_job_counter.py +16 -14
  160. souleyez/parsers/bloodhound_parser.py +41 -39
  161. souleyez/parsers/crackmapexec_parser.py +178 -111
  162. souleyez/parsers/dalfox_parser.py +72 -77
  163. souleyez/parsers/dnsrecon_parser.py +103 -91
  164. souleyez/parsers/enum4linux_parser.py +183 -153
  165. souleyez/parsers/ffuf_parser.py +29 -25
  166. souleyez/parsers/gobuster_parser.py +301 -41
  167. souleyez/parsers/hashcat_parser.py +324 -79
  168. souleyez/parsers/http_fingerprint_parser.py +350 -103
  169. souleyez/parsers/hydra_parser.py +131 -111
  170. souleyez/parsers/impacket_parser.py +231 -178
  171. souleyez/parsers/john_parser.py +98 -86
  172. souleyez/parsers/katana_parser.py +316 -0
  173. souleyez/parsers/msf_parser.py +943 -498
  174. souleyez/parsers/nikto_parser.py +346 -65
  175. souleyez/parsers/nmap_parser.py +262 -174
  176. souleyez/parsers/nuclei_parser.py +40 -44
  177. souleyez/parsers/responder_parser.py +26 -26
  178. souleyez/parsers/searchsploit_parser.py +74 -74
  179. souleyez/parsers/service_explorer_parser.py +279 -0
  180. souleyez/parsers/smbmap_parser.py +180 -124
  181. souleyez/parsers/sqlmap_parser.py +434 -308
  182. souleyez/parsers/theharvester_parser.py +75 -57
  183. souleyez/parsers/whois_parser.py +135 -94
  184. souleyez/parsers/wpscan_parser.py +278 -190
  185. souleyez/plugins/afp.py +44 -36
  186. souleyez/plugins/afp_brute.py +114 -46
  187. souleyez/plugins/ard.py +48 -37
  188. souleyez/plugins/bloodhound.py +95 -61
  189. souleyez/plugins/certipy.py +303 -0
  190. souleyez/plugins/crackmapexec.py +186 -85
  191. souleyez/plugins/dalfox.py +120 -59
  192. souleyez/plugins/dns_hijack.py +146 -41
  193. souleyez/plugins/dnsrecon.py +97 -61
  194. souleyez/plugins/enum4linux.py +91 -66
  195. souleyez/plugins/evil_winrm.py +291 -0
  196. souleyez/plugins/ffuf.py +166 -90
  197. souleyez/plugins/firmware_extract.py +133 -29
  198. souleyez/plugins/gobuster.py +387 -190
  199. souleyez/plugins/gpp_extract.py +393 -0
  200. souleyez/plugins/hashcat.py +100 -73
  201. souleyez/plugins/http_fingerprint.py +854 -267
  202. souleyez/plugins/hydra.py +566 -200
  203. souleyez/plugins/impacket_getnpusers.py +117 -69
  204. souleyez/plugins/impacket_psexec.py +84 -64
  205. souleyez/plugins/impacket_secretsdump.py +103 -69
  206. souleyez/plugins/impacket_smbclient.py +89 -75
  207. souleyez/plugins/john.py +86 -69
  208. souleyez/plugins/katana.py +313 -0
  209. souleyez/plugins/kerbrute.py +237 -0
  210. souleyez/plugins/lfi_extract.py +541 -0
  211. souleyez/plugins/macos_ssh.py +117 -48
  212. souleyez/plugins/mdns.py +35 -30
  213. souleyez/plugins/msf_auxiliary.py +253 -130
  214. souleyez/plugins/msf_exploit.py +239 -161
  215. souleyez/plugins/nikto.py +134 -78
  216. souleyez/plugins/nmap.py +275 -91
  217. souleyez/plugins/nuclei.py +180 -89
  218. souleyez/plugins/nxc.py +285 -0
  219. souleyez/plugins/plugin_base.py +35 -36
  220. souleyez/plugins/plugin_template.py +13 -5
  221. souleyez/plugins/rdp_sec_check.py +130 -0
  222. souleyez/plugins/responder.py +112 -71
  223. souleyez/plugins/router_http_brute.py +76 -65
  224. souleyez/plugins/router_ssh_brute.py +118 -41
  225. souleyez/plugins/router_telnet_brute.py +124 -42
  226. souleyez/plugins/routersploit.py +91 -59
  227. souleyez/plugins/routersploit_exploit.py +77 -55
  228. souleyez/plugins/searchsploit.py +91 -77
  229. souleyez/plugins/service_explorer.py +1160 -0
  230. souleyez/plugins/smbmap.py +122 -72
  231. souleyez/plugins/smbpasswd.py +215 -0
  232. souleyez/plugins/sqlmap.py +301 -113
  233. souleyez/plugins/theharvester.py +127 -75
  234. souleyez/plugins/tr069.py +79 -57
  235. souleyez/plugins/upnp.py +65 -47
  236. souleyez/plugins/upnp_abuse.py +73 -55
  237. souleyez/plugins/vnc_access.py +129 -42
  238. souleyez/plugins/vnc_brute.py +109 -38
  239. souleyez/plugins/whois.py +77 -58
  240. souleyez/plugins/wpscan.py +173 -69
  241. souleyez/reporting/__init__.py +2 -1
  242. souleyez/reporting/attack_chain.py +411 -346
  243. souleyez/reporting/charts.py +436 -501
  244. souleyez/reporting/compliance_mappings.py +334 -201
  245. souleyez/reporting/detection_report.py +126 -125
  246. souleyez/reporting/formatters.py +828 -591
  247. souleyez/reporting/generator.py +386 -302
  248. souleyez/reporting/metrics.py +72 -75
  249. souleyez/scanner.py +35 -29
  250. souleyez/security/__init__.py +37 -11
  251. souleyez/security/scope_validator.py +175 -106
  252. souleyez/security/validation.py +223 -149
  253. souleyez/security.py +22 -6
  254. souleyez/storage/credentials.py +247 -186
  255. souleyez/storage/crypto.py +296 -129
  256. souleyez/storage/database.py +73 -50
  257. souleyez/storage/db.py +58 -36
  258. souleyez/storage/deliverable_evidence.py +177 -128
  259. souleyez/storage/deliverable_exporter.py +282 -246
  260. souleyez/storage/deliverable_templates.py +134 -116
  261. souleyez/storage/deliverables.py +135 -130
  262. souleyez/storage/engagements.py +109 -56
  263. souleyez/storage/evidence.py +181 -152
  264. souleyez/storage/execution_log.py +31 -17
  265. souleyez/storage/exploit_attempts.py +93 -57
  266. souleyez/storage/exploits.py +67 -36
  267. souleyez/storage/findings.py +48 -61
  268. souleyez/storage/hosts.py +176 -144
  269. souleyez/storage/migrate_to_engagements.py +43 -19
  270. souleyez/storage/migrations/_001_add_credential_enhancements.py +22 -12
  271. souleyez/storage/migrations/_002_add_status_tracking.py +10 -7
  272. souleyez/storage/migrations/_003_add_execution_log.py +14 -8
  273. souleyez/storage/migrations/_005_screenshots.py +13 -5
  274. souleyez/storage/migrations/_006_deliverables.py +13 -5
  275. souleyez/storage/migrations/_007_deliverable_templates.py +12 -7
  276. souleyez/storage/migrations/_008_add_nuclei_table.py +10 -4
  277. souleyez/storage/migrations/_010_evidence_linking.py +17 -10
  278. souleyez/storage/migrations/_011_timeline_tracking.py +20 -13
  279. souleyez/storage/migrations/_012_team_collaboration.py +34 -21
  280. souleyez/storage/migrations/_013_add_host_tags.py +12 -6
  281. souleyez/storage/migrations/_014_exploit_attempts.py +22 -10
  282. souleyez/storage/migrations/_015_add_mac_os_fields.py +15 -7
  283. souleyez/storage/migrations/_016_add_domain_field.py +10 -4
  284. souleyez/storage/migrations/_017_msf_sessions.py +16 -8
  285. souleyez/storage/migrations/_018_add_osint_target.py +10 -6
  286. souleyez/storage/migrations/_019_add_engagement_type.py +10 -6
  287. souleyez/storage/migrations/_020_add_rbac.py +36 -15
  288. souleyez/storage/migrations/_021_wazuh_integration.py +20 -8
  289. souleyez/storage/migrations/_022_wazuh_indexer_columns.py +6 -4
  290. souleyez/storage/migrations/_023_fix_detection_results_fk.py +16 -6
  291. souleyez/storage/migrations/_024_wazuh_vulnerabilities.py +26 -10
  292. souleyez/storage/migrations/_025_multi_siem_support.py +3 -5
  293. souleyez/storage/migrations/_026_add_engagement_scope.py +31 -12
  294. souleyez/storage/migrations/_027_multi_siem_persistence.py +32 -15
  295. souleyez/storage/migrations/__init__.py +26 -26
  296. souleyez/storage/migrations/migration_manager.py +19 -19
  297. souleyez/storage/msf_sessions.py +100 -65
  298. souleyez/storage/osint.py +17 -24
  299. souleyez/storage/recommendation_engine.py +269 -235
  300. souleyez/storage/screenshots.py +33 -32
  301. souleyez/storage/smb_shares.py +136 -92
  302. souleyez/storage/sqlmap_data.py +183 -128
  303. souleyez/storage/team_collaboration.py +135 -141
  304. souleyez/storage/timeline_tracker.py +122 -94
  305. souleyez/storage/wazuh_vulns.py +64 -66
  306. souleyez/storage/web_paths.py +33 -37
  307. souleyez/testing/credential_tester.py +221 -205
  308. souleyez/ui/__init__.py +1 -1
  309. souleyez/ui/ai_quotes.py +12 -12
  310. souleyez/ui/attack_surface.py +2439 -1516
  311. souleyez/ui/chain_rules_view.py +914 -382
  312. souleyez/ui/correlation_view.py +312 -230
  313. souleyez/ui/dashboard.py +2382 -1130
  314. souleyez/ui/deliverables_view.py +148 -62
  315. souleyez/ui/design_system.py +13 -13
  316. souleyez/ui/errors.py +49 -49
  317. souleyez/ui/evidence_linking_view.py +284 -179
  318. souleyez/ui/evidence_vault.py +393 -285
  319. souleyez/ui/exploit_suggestions_view.py +555 -349
  320. souleyez/ui/export_view.py +100 -66
  321. souleyez/ui/gap_analysis_view.py +315 -171
  322. souleyez/ui/help_system.py +105 -97
  323. souleyez/ui/intelligence_view.py +436 -293
  324. souleyez/ui/interactive.py +22783 -10678
  325. souleyez/ui/interactive_selector.py +75 -68
  326. souleyez/ui/log_formatter.py +47 -39
  327. souleyez/ui/menu_components.py +22 -13
  328. souleyez/ui/msf_auxiliary_menu.py +184 -133
  329. souleyez/ui/pending_chains_view.py +336 -172
  330. souleyez/ui/progress_indicators.py +5 -3
  331. souleyez/ui/recommendations_view.py +195 -137
  332. souleyez/ui/rule_builder.py +343 -225
  333. souleyez/ui/setup_wizard.py +678 -284
  334. souleyez/ui/shortcuts.py +217 -165
  335. souleyez/ui/splunk_gap_analysis_view.py +452 -270
  336. souleyez/ui/splunk_vulns_view.py +139 -86
  337. souleyez/ui/team_dashboard.py +498 -335
  338. souleyez/ui/template_selector.py +196 -105
  339. souleyez/ui/terminal.py +6 -6
  340. souleyez/ui/timeline_view.py +198 -127
  341. souleyez/ui/tool_setup.py +264 -164
  342. souleyez/ui/tutorial.py +202 -72
  343. souleyez/ui/tutorial_state.py +40 -40
  344. souleyez/ui/wazuh_vulns_view.py +235 -141
  345. souleyez/ui/wordlist_browser.py +260 -107
  346. souleyez/ui.py +464 -312
  347. souleyez/utils/tool_checker.py +427 -367
  348. souleyez/utils.py +33 -29
  349. souleyez/wordlists.py +134 -167
  350. {souleyez-2.43.29.dist-info → souleyez-2.43.32.dist-info}/METADATA +1 -1
  351. souleyez-2.43.32.dist-info/RECORD +441 -0
  352. {souleyez-2.43.29.dist-info → souleyez-2.43.32.dist-info}/WHEEL +1 -1
  353. souleyez-2.43.29.dist-info/RECORD +0 -379
  354. {souleyez-2.43.29.dist-info → souleyez-2.43.32.dist-info}/entry_points.txt +0 -0
  355. {souleyez-2.43.29.dist-info → souleyez-2.43.32.dist-info}/licenses/LICENSE +0 -0
  356. {souleyez-2.43.29.dist-info → souleyez-2.43.32.dist-info}/top_level.txt +0 -0
@@ -60,8 +60,12 @@ class SentinelSIEMClient(SIEMClient):
60
60
  self._token_expiry: Optional[datetime] = None
61
61
 
62
62
  # Azure endpoints
63
- self.login_url = f"https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/token"
64
- self.log_analytics_url = f"https://api.loganalytics.io/v1/workspaces/{workspace_id}"
63
+ self.login_url = (
64
+ f"https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/token"
65
+ )
66
+ self.log_analytics_url = (
67
+ f"https://api.loganalytics.io/v1/workspaces/{workspace_id}"
68
+ )
65
69
  self.management_base = "https://management.azure.com"
66
70
  self.sentinel_base = (
67
71
  f"{self.management_base}/subscriptions/{subscription_id}"
@@ -71,7 +75,7 @@ class SentinelSIEMClient(SIEMClient):
71
75
  )
72
76
 
73
77
  @classmethod
74
- def from_config(cls, config: Dict[str, Any]) -> 'SentinelSIEMClient':
78
+ def from_config(cls, config: Dict[str, Any]) -> "SentinelSIEMClient":
75
79
  """Create client from configuration dictionary.
76
80
 
77
81
  Args:
@@ -81,21 +85,23 @@ class SentinelSIEMClient(SIEMClient):
81
85
  SentinelSIEMClient instance
82
86
  """
83
87
  return cls(
84
- tenant_id=config.get('tenant_id', ''),
85
- client_id=config.get('client_id', ''),
86
- client_secret=config.get('client_secret', ''),
87
- subscription_id=config.get('subscription_id', ''),
88
- resource_group=config.get('resource_group', ''),
89
- workspace_name=config.get('workspace_name', ''),
90
- workspace_id=config.get('workspace_id', ''),
88
+ tenant_id=config.get("tenant_id", ""),
89
+ client_id=config.get("client_id", ""),
90
+ client_secret=config.get("client_secret", ""),
91
+ subscription_id=config.get("subscription_id", ""),
92
+ resource_group=config.get("resource_group", ""),
93
+ workspace_name=config.get("workspace_name", ""),
94
+ workspace_id=config.get("workspace_id", ""),
91
95
  )
92
96
 
93
97
  @property
94
98
  def siem_type(self) -> str:
95
99
  """Return the SIEM type identifier."""
96
- return 'sentinel'
100
+ return "sentinel"
97
101
 
98
- def _get_access_token(self, scope: str = "https://api.loganalytics.io/.default") -> str:
102
+ def _get_access_token(
103
+ self, scope: str = "https://api.loganalytics.io/.default"
104
+ ) -> str:
99
105
  """Get Azure AD access token.
100
106
 
101
107
  Args:
@@ -111,22 +117,18 @@ class SentinelSIEMClient(SIEMClient):
111
117
 
112
118
  # Request new token
113
119
  data = {
114
- 'grant_type': 'client_credentials',
115
- 'client_id': self.client_id,
116
- 'client_secret': self.client_secret,
117
- 'scope': scope,
120
+ "grant_type": "client_credentials",
121
+ "client_id": self.client_id,
122
+ "client_secret": self.client_secret,
123
+ "scope": scope,
118
124
  }
119
125
 
120
- response = requests.post(
121
- self.login_url,
122
- data=data,
123
- timeout=30
124
- )
126
+ response = requests.post(self.login_url, data=data, timeout=30)
125
127
  response.raise_for_status()
126
128
 
127
129
  token_data = response.json()
128
- self._access_token = token_data['access_token']
129
- expires_in = token_data.get('expires_in', 3600)
130
+ self._access_token = token_data["access_token"]
131
+ expires_in = token_data.get("expires_in", 3600)
130
132
  self._token_expiry = datetime.now() + timedelta(seconds=expires_in - 60)
131
133
 
132
134
  return self._access_token
@@ -143,28 +145,28 @@ class SentinelSIEMClient(SIEMClient):
143
145
  token = self._get_access_token("https://api.loganalytics.io/.default")
144
146
 
145
147
  headers = {
146
- 'Authorization': f'Bearer {token}',
147
- 'Content-Type': 'application/json',
148
+ "Authorization": f"Bearer {token}",
149
+ "Content-Type": "application/json",
148
150
  }
149
151
 
150
152
  response = requests.post(
151
153
  f"{self.log_analytics_url}/query",
152
154
  headers=headers,
153
- json={'query': kql},
154
- timeout=60
155
+ json={"query": kql},
156
+ timeout=60,
155
157
  )
156
158
  response.raise_for_status()
157
159
 
158
160
  data = response.json()
159
- tables = data.get('tables', [])
161
+ tables = data.get("tables", [])
160
162
 
161
163
  if not tables:
162
164
  return []
163
165
 
164
166
  # Convert first table to list of dicts
165
167
  table = tables[0]
166
- columns = [col['name'] for col in table.get('columns', [])]
167
- rows = table.get('rows', [])
168
+ columns = [col["name"] for col in table.get("columns", [])]
169
+ rows = table.get("rows", [])
168
170
 
169
171
  return [dict(zip(columns, row)) for row in rows]
170
172
 
@@ -187,18 +189,14 @@ class SentinelSIEMClient(SIEMClient):
187
189
  token = self._get_access_token("https://management.azure.com/.default")
188
190
 
189
191
  headers = {
190
- 'Authorization': f'Bearer {token}',
191
- 'Content-Type': 'application/json',
192
+ "Authorization": f"Bearer {token}",
193
+ "Content-Type": "application/json",
192
194
  }
193
195
 
194
196
  url = f"{self.sentinel_base}{endpoint}?api-version=2023-02-01"
195
197
 
196
198
  return requests.request(
197
- method=method,
198
- url=url,
199
- headers=headers,
200
- json=json_data,
201
- timeout=60
199
+ method=method, url=url, headers=headers, json=json_data, timeout=60
202
200
  )
203
201
 
204
202
  def test_connection(self) -> SIEMConnectionStatus:
@@ -214,32 +212,30 @@ class SentinelSIEMClient(SIEMClient):
214
212
  if result:
215
213
  return SIEMConnectionStatus(
216
214
  connected=True,
217
- version='Azure Sentinel',
218
- siem_type='sentinel',
215
+ version="Azure Sentinel",
216
+ siem_type="sentinel",
219
217
  details={
220
- 'workspace_id': self.workspace_id,
221
- 'workspace_name': self.workspace_name,
222
- 'subscription': self.subscription_id,
223
- }
218
+ "workspace_id": self.workspace_id,
219
+ "workspace_name": self.workspace_name,
220
+ "subscription": self.subscription_id,
221
+ },
224
222
  )
225
223
  else:
226
224
  return SIEMConnectionStatus(
227
225
  connected=False,
228
- error='Query returned no results',
229
- siem_type='sentinel'
226
+ error="Query returned no results",
227
+ siem_type="sentinel",
230
228
  )
231
229
 
232
230
  except requests.exceptions.ConnectionError as e:
233
231
  return SIEMConnectionStatus(
234
232
  connected=False,
235
233
  error=f"Connection failed: {str(e)}",
236
- siem_type='sentinel'
234
+ siem_type="sentinel",
237
235
  )
238
236
  except Exception as e:
239
237
  return SIEMConnectionStatus(
240
- connected=False,
241
- error=str(e),
242
- siem_type='sentinel'
238
+ connected=False, error=str(e), siem_type="sentinel"
243
239
  )
244
240
 
245
241
  def get_alerts(
@@ -250,7 +246,7 @@ class SentinelSIEMClient(SIEMClient):
250
246
  dest_ip: Optional[str] = None,
251
247
  rule_ids: Optional[List[str]] = None,
252
248
  search_text: Optional[str] = None,
253
- limit: int = 100
249
+ limit: int = 100,
254
250
  ) -> List[SIEMAlert]:
255
251
  """Query alerts from Microsoft Sentinel.
256
252
 
@@ -280,21 +276,23 @@ class SentinelSIEMClient(SIEMClient):
280
276
 
281
277
  # Rule ID filter
282
278
  if rule_ids:
283
- rule_filter = ' or '.join(f'AlertName == "{r}"' for r in rule_ids)
284
- kql_parts.append(f'| where {rule_filter}')
279
+ rule_filter = " or ".join(f'AlertName == "{r}"' for r in rule_ids)
280
+ kql_parts.append(f"| where {rule_filter}")
285
281
 
286
282
  # Search text
287
283
  if search_text:
288
284
  kql_parts.append(f'| where * contains "{search_text}"')
289
285
 
290
286
  # Limit and project
291
- kql_parts.extend([
292
- f"| take {limit}",
293
- "| project TimeGenerated, AlertName, AlertSeverity, Description, "
294
- "Tactics, Entities, ExtendedProperties, ProviderName, SystemAlertId"
295
- ])
287
+ kql_parts.extend(
288
+ [
289
+ f"| take {limit}",
290
+ "| project TimeGenerated, AlertName, AlertSeverity, Description, "
291
+ "Tactics, Entities, ExtendedProperties, ProviderName, SystemAlertId",
292
+ ]
293
+ )
296
294
 
297
- kql = '\n'.join(kql_parts)
295
+ kql = "\n".join(kql_parts)
298
296
 
299
297
  try:
300
298
  results = self._log_analytics_query(kql)
@@ -314,26 +312,24 @@ class SentinelSIEMClient(SIEMClient):
314
312
  import json
315
313
 
316
314
  # Parse timestamp
317
- timestamp_str = raw_alert.get('TimeGenerated', '')
315
+ timestamp_str = raw_alert.get("TimeGenerated", "")
318
316
  try:
319
- timestamp = datetime.fromisoformat(
320
- timestamp_str.replace('Z', '+00:00')
321
- )
317
+ timestamp = datetime.fromisoformat(timestamp_str.replace("Z", "+00:00"))
322
318
  except (ValueError, AttributeError):
323
319
  timestamp = datetime.now()
324
320
 
325
321
  # Map severity
326
- severity = self._map_severity(raw_alert.get('AlertSeverity', ''))
322
+ severity = self._map_severity(raw_alert.get("AlertSeverity", ""))
327
323
 
328
324
  # Parse entities for IPs
329
325
  source_ip = None
330
326
  dest_ip = None
331
- entities_str = raw_alert.get('Entities', '[]')
327
+ entities_str = raw_alert.get("Entities", "[]")
332
328
  try:
333
329
  entities = json.loads(entities_str) if entities_str else []
334
330
  for entity in entities:
335
- if entity.get('Type') == 'ip':
336
- addr = entity.get('Address', '')
331
+ if entity.get("Type") == "ip":
332
+ addr = entity.get("Address", "")
337
333
  if not source_ip:
338
334
  source_ip = addr
339
335
  elif not dest_ip:
@@ -342,23 +338,27 @@ class SentinelSIEMClient(SIEMClient):
342
338
  pass
343
339
 
344
340
  # Parse tactics
345
- tactics_str = raw_alert.get('Tactics', '')
341
+ tactics_str = raw_alert.get("Tactics", "")
346
342
  mitre_tactics = []
347
343
  if tactics_str:
348
344
  try:
349
- mitre_tactics = json.loads(tactics_str) if tactics_str.startswith('[') else [tactics_str]
345
+ mitre_tactics = (
346
+ json.loads(tactics_str)
347
+ if tactics_str.startswith("[")
348
+ else [tactics_str]
349
+ )
350
350
  except json.JSONDecodeError:
351
351
  mitre_tactics = [tactics_str] if tactics_str else []
352
352
 
353
353
  return SIEMAlert(
354
- id=raw_alert.get('SystemAlertId', ''),
354
+ id=raw_alert.get("SystemAlertId", ""),
355
355
  timestamp=timestamp,
356
- rule_id=raw_alert.get('AlertName', ''),
357
- rule_name=raw_alert.get('AlertName', ''),
356
+ rule_id=raw_alert.get("AlertName", ""),
357
+ rule_name=raw_alert.get("AlertName", ""),
358
358
  severity=severity,
359
359
  source_ip=source_ip,
360
360
  dest_ip=dest_ip,
361
- description=raw_alert.get('Description', ''),
361
+ description=raw_alert.get("Description", ""),
362
362
  raw_data=raw_alert,
363
363
  mitre_tactics=mitre_tactics,
364
364
  mitre_techniques=[],
@@ -367,18 +367,16 @@ class SentinelSIEMClient(SIEMClient):
367
367
  def _map_severity(self, severity: str) -> str:
368
368
  """Map Sentinel severity to normalized severity."""
369
369
  severity_lower = str(severity).lower()
370
- if severity_lower == 'high':
371
- return 'critical'
372
- elif severity_lower == 'medium':
373
- return 'high'
374
- elif severity_lower == 'low':
375
- return 'medium'
376
- return 'low'
370
+ if severity_lower == "high":
371
+ return "critical"
372
+ elif severity_lower == "medium":
373
+ return "high"
374
+ elif severity_lower == "low":
375
+ return "medium"
376
+ return "low"
377
377
 
378
378
  def get_rules(
379
- self,
380
- rule_ids: Optional[List[str]] = None,
381
- enabled_only: bool = True
379
+ self, rule_ids: Optional[List[str]] = None, enabled_only: bool = True
382
380
  ) -> List[SIEMRule]:
383
381
  """Get analytics rules from Microsoft Sentinel.
384
382
 
@@ -396,30 +394,30 @@ class SentinelSIEMClient(SIEMClient):
396
394
  return []
397
395
 
398
396
  data = response.json()
399
- raw_rules = data.get('value', [])
397
+ raw_rules = data.get("value", [])
400
398
 
401
399
  rules = []
402
400
  for raw_rule in raw_rules:
403
- properties = raw_rule.get('properties', {})
401
+ properties = raw_rule.get("properties", {})
404
402
 
405
403
  # Filter by rule_ids if provided
406
- rule_id = raw_rule.get('name', '')
404
+ rule_id = raw_rule.get("name", "")
407
405
  if rule_ids and rule_id not in rule_ids:
408
406
  continue
409
407
 
410
408
  # Filter disabled if requested
411
- enabled = properties.get('enabled', True)
409
+ enabled = properties.get("enabled", True)
412
410
  if enabled_only and not enabled:
413
411
  continue
414
412
 
415
413
  # Extract MITRE tactics
416
- mitre_tactics = properties.get('tactics', [])
414
+ mitre_tactics = properties.get("tactics", [])
417
415
 
418
416
  rule = SIEMRule(
419
417
  id=rule_id,
420
- name=properties.get('displayName', ''),
421
- description=properties.get('description', ''),
422
- severity=self._map_severity(properties.get('severity', '')),
418
+ name=properties.get("displayName", ""),
419
+ description=properties.get("description", ""),
420
+ severity=self._map_severity(properties.get("severity", "")),
423
421
  enabled=enabled,
424
422
  mitre_tactics=mitre_tactics,
425
423
  mitre_techniques=[],
@@ -443,25 +441,25 @@ class SentinelSIEMClient(SIEMClient):
443
441
  """
444
442
  # Sentinel content hub rule templates
445
443
  recommendations_map = {
446
- 'nmap': [
444
+ "nmap": [
447
445
  {
448
- 'rule_id': 'port-scan-detection',
449
- 'rule_name': 'Potential Port Scan Detection',
450
- 'kql': 'AzureFirewall | where Action == "Deny" | summarize count() by SourceIP | where count_ > 100',
446
+ "rule_id": "port-scan-detection",
447
+ "rule_name": "Potential Port Scan Detection",
448
+ "kql": 'AzureFirewall | where Action == "Deny" | summarize count() by SourceIP | where count_ > 100',
451
449
  },
452
450
  ],
453
- 'hydra': [
451
+ "hydra": [
454
452
  {
455
- 'rule_id': 'brute-force-detection',
456
- 'rule_name': 'Brute Force Attack Detection',
457
- 'kql': 'SigninLogs | where ResultType != 0 | summarize count() by IPAddress | where count_ > 10',
453
+ "rule_id": "brute-force-detection",
454
+ "rule_name": "Brute Force Attack Detection",
455
+ "kql": "SigninLogs | where ResultType != 0 | summarize count() by IPAddress | where count_ > 10",
458
456
  },
459
457
  ],
460
- 'sqlmap': [
458
+ "sqlmap": [
461
459
  {
462
- 'rule_id': 'sql-injection-detection',
463
- 'rule_name': 'SQL Injection Attack Detection',
464
- 'kql': 'AzureDiagnostics | where Category == "SQLSecurityAuditEvents" | where action_name_s == "BATCH COMPLETED"',
460
+ "rule_id": "sql-injection-detection",
461
+ "rule_name": "SQL Injection Attack Detection",
462
+ "kql": 'AzureDiagnostics | where Category == "SQLSecurityAuditEvents" | where action_name_s == "BATCH COMPLETED"',
465
463
  },
466
464
  ],
467
465
  }
@@ -471,13 +469,13 @@ class SentinelSIEMClient(SIEMClient):
471
469
 
472
470
  return [
473
471
  {
474
- 'rule_id': r['rule_id'],
475
- 'rule_name': r['rule_name'],
476
- 'description': f"Sentinel analytics rule for {attack_type}",
477
- 'severity': 'high',
478
- 'enabled': True,
479
- 'siem_type': 'sentinel',
480
- 'kql_query': r.get('kql', ''),
472
+ "rule_id": r["rule_id"],
473
+ "rule_name": r["rule_name"],
474
+ "description": f"Sentinel analytics rule for {attack_type}",
475
+ "severity": "high",
476
+ "enabled": True,
477
+ "siem_type": "sentinel",
478
+ "kql_query": r.get("kql", ""),
481
479
  }
482
480
  for r in recommendations
483
481
  ]