souleyez 2.43.29__py3-none-any.whl → 2.43.34__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 (358) 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 +9526 -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 +1239 -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 +2200 -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 +292 -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/web_login_test_handler.py +327 -0
  126. souleyez/handlers/whois_handler.py +277 -0
  127. souleyez/handlers/wpscan_handler.py +554 -0
  128. souleyez/history.py +32 -16
  129. souleyez/importers/msf_importer.py +106 -75
  130. souleyez/importers/smart_importer.py +208 -147
  131. souleyez/integrations/siem/__init__.py +10 -10
  132. souleyez/integrations/siem/base.py +17 -18
  133. souleyez/integrations/siem/elastic.py +108 -122
  134. souleyez/integrations/siem/factory.py +207 -80
  135. souleyez/integrations/siem/googlesecops.py +146 -154
  136. souleyez/integrations/siem/rule_mappings/__init__.py +1 -1
  137. souleyez/integrations/siem/rule_mappings/wazuh_rules.py +8 -5
  138. souleyez/integrations/siem/sentinel.py +107 -109
  139. souleyez/integrations/siem/splunk.py +246 -212
  140. souleyez/integrations/siem/wazuh.py +65 -71
  141. souleyez/integrations/wazuh/__init__.py +5 -5
  142. souleyez/integrations/wazuh/client.py +70 -93
  143. souleyez/integrations/wazuh/config.py +85 -57
  144. souleyez/integrations/wazuh/host_mapper.py +28 -36
  145. souleyez/integrations/wazuh/sync.py +78 -68
  146. souleyez/intelligence/__init__.py +4 -5
  147. souleyez/intelligence/correlation_analyzer.py +309 -295
  148. souleyez/intelligence/exploit_knowledge.py +661 -623
  149. souleyez/intelligence/exploit_suggestions.py +159 -139
  150. souleyez/intelligence/gap_analyzer.py +132 -97
  151. souleyez/intelligence/gap_detector.py +251 -214
  152. souleyez/intelligence/sensitive_tables.py +266 -129
  153. souleyez/intelligence/service_parser.py +137 -123
  154. souleyez/intelligence/surface_analyzer.py +407 -268
  155. souleyez/intelligence/target_parser.py +159 -162
  156. souleyez/licensing/__init__.py +6 -6
  157. souleyez/licensing/validator.py +17 -19
  158. souleyez/log_config.py +79 -54
  159. souleyez/main.py +1505 -687
  160. souleyez/migrations/fix_job_counter.py +16 -14
  161. souleyez/parsers/bloodhound_parser.py +41 -39
  162. souleyez/parsers/crackmapexec_parser.py +178 -111
  163. souleyez/parsers/dalfox_parser.py +72 -77
  164. souleyez/parsers/dnsrecon_parser.py +103 -91
  165. souleyez/parsers/enum4linux_parser.py +183 -153
  166. souleyez/parsers/ffuf_parser.py +29 -25
  167. souleyez/parsers/gobuster_parser.py +301 -41
  168. souleyez/parsers/hashcat_parser.py +324 -79
  169. souleyez/parsers/http_fingerprint_parser.py +350 -103
  170. souleyez/parsers/hydra_parser.py +131 -111
  171. souleyez/parsers/impacket_parser.py +231 -178
  172. souleyez/parsers/john_parser.py +98 -86
  173. souleyez/parsers/katana_parser.py +316 -0
  174. souleyez/parsers/msf_parser.py +943 -498
  175. souleyez/parsers/nikto_parser.py +346 -65
  176. souleyez/parsers/nmap_parser.py +262 -174
  177. souleyez/parsers/nuclei_parser.py +40 -44
  178. souleyez/parsers/responder_parser.py +26 -26
  179. souleyez/parsers/searchsploit_parser.py +74 -74
  180. souleyez/parsers/service_explorer_parser.py +279 -0
  181. souleyez/parsers/smbmap_parser.py +180 -124
  182. souleyez/parsers/sqlmap_parser.py +434 -308
  183. souleyez/parsers/theharvester_parser.py +75 -57
  184. souleyez/parsers/whois_parser.py +135 -94
  185. souleyez/parsers/wpscan_parser.py +278 -190
  186. souleyez/plugins/afp.py +44 -36
  187. souleyez/plugins/afp_brute.py +114 -46
  188. souleyez/plugins/ard.py +48 -37
  189. souleyez/plugins/bloodhound.py +95 -61
  190. souleyez/plugins/certipy.py +303 -0
  191. souleyez/plugins/crackmapexec.py +186 -85
  192. souleyez/plugins/dalfox.py +120 -59
  193. souleyez/plugins/dns_hijack.py +146 -41
  194. souleyez/plugins/dnsrecon.py +97 -61
  195. souleyez/plugins/enum4linux.py +91 -66
  196. souleyez/plugins/evil_winrm.py +291 -0
  197. souleyez/plugins/ffuf.py +166 -90
  198. souleyez/plugins/firmware_extract.py +133 -29
  199. souleyez/plugins/gobuster.py +387 -190
  200. souleyez/plugins/gpp_extract.py +393 -0
  201. souleyez/plugins/hashcat.py +100 -73
  202. souleyez/plugins/http_fingerprint.py +854 -267
  203. souleyez/plugins/hydra.py +566 -200
  204. souleyez/plugins/impacket_getnpusers.py +117 -69
  205. souleyez/plugins/impacket_psexec.py +84 -64
  206. souleyez/plugins/impacket_secretsdump.py +103 -69
  207. souleyez/plugins/impacket_smbclient.py +89 -75
  208. souleyez/plugins/john.py +86 -69
  209. souleyez/plugins/katana.py +313 -0
  210. souleyez/plugins/kerbrute.py +237 -0
  211. souleyez/plugins/lfi_extract.py +541 -0
  212. souleyez/plugins/macos_ssh.py +117 -48
  213. souleyez/plugins/mdns.py +35 -30
  214. souleyez/plugins/msf_auxiliary.py +253 -130
  215. souleyez/plugins/msf_exploit.py +239 -161
  216. souleyez/plugins/nikto.py +134 -78
  217. souleyez/plugins/nmap.py +275 -91
  218. souleyez/plugins/nuclei.py +180 -89
  219. souleyez/plugins/nxc.py +285 -0
  220. souleyez/plugins/plugin_base.py +35 -36
  221. souleyez/plugins/plugin_template.py +13 -5
  222. souleyez/plugins/rdp_sec_check.py +130 -0
  223. souleyez/plugins/responder.py +112 -71
  224. souleyez/plugins/router_http_brute.py +76 -65
  225. souleyez/plugins/router_ssh_brute.py +118 -41
  226. souleyez/plugins/router_telnet_brute.py +124 -42
  227. souleyez/plugins/routersploit.py +91 -59
  228. souleyez/plugins/routersploit_exploit.py +77 -55
  229. souleyez/plugins/searchsploit.py +91 -77
  230. souleyez/plugins/service_explorer.py +1160 -0
  231. souleyez/plugins/smbmap.py +122 -72
  232. souleyez/plugins/smbpasswd.py +215 -0
  233. souleyez/plugins/sqlmap.py +301 -113
  234. souleyez/plugins/theharvester.py +127 -75
  235. souleyez/plugins/tr069.py +79 -57
  236. souleyez/plugins/upnp.py +65 -47
  237. souleyez/plugins/upnp_abuse.py +73 -55
  238. souleyez/plugins/vnc_access.py +129 -42
  239. souleyez/plugins/vnc_brute.py +109 -38
  240. souleyez/plugins/web_login_test.py +417 -0
  241. souleyez/plugins/whois.py +77 -58
  242. souleyez/plugins/wpscan.py +173 -69
  243. souleyez/reporting/__init__.py +2 -1
  244. souleyez/reporting/attack_chain.py +411 -346
  245. souleyez/reporting/charts.py +436 -501
  246. souleyez/reporting/compliance_mappings.py +334 -201
  247. souleyez/reporting/detection_report.py +126 -125
  248. souleyez/reporting/formatters.py +828 -591
  249. souleyez/reporting/generator.py +386 -302
  250. souleyez/reporting/metrics.py +72 -75
  251. souleyez/scanner.py +35 -29
  252. souleyez/security/__init__.py +37 -11
  253. souleyez/security/scope_validator.py +175 -106
  254. souleyez/security/validation.py +223 -149
  255. souleyez/security.py +22 -6
  256. souleyez/storage/credentials.py +247 -186
  257. souleyez/storage/crypto.py +296 -129
  258. souleyez/storage/database.py +73 -50
  259. souleyez/storage/db.py +58 -36
  260. souleyez/storage/deliverable_evidence.py +177 -128
  261. souleyez/storage/deliverable_exporter.py +282 -246
  262. souleyez/storage/deliverable_templates.py +134 -116
  263. souleyez/storage/deliverables.py +135 -130
  264. souleyez/storage/engagements.py +109 -56
  265. souleyez/storage/evidence.py +181 -152
  266. souleyez/storage/execution_log.py +31 -17
  267. souleyez/storage/exploit_attempts.py +93 -57
  268. souleyez/storage/exploits.py +67 -36
  269. souleyez/storage/findings.py +48 -61
  270. souleyez/storage/hosts.py +176 -144
  271. souleyez/storage/migrate_to_engagements.py +43 -19
  272. souleyez/storage/migrations/_001_add_credential_enhancements.py +22 -12
  273. souleyez/storage/migrations/_002_add_status_tracking.py +10 -7
  274. souleyez/storage/migrations/_003_add_execution_log.py +14 -8
  275. souleyez/storage/migrations/_005_screenshots.py +13 -5
  276. souleyez/storage/migrations/_006_deliverables.py +13 -5
  277. souleyez/storage/migrations/_007_deliverable_templates.py +12 -7
  278. souleyez/storage/migrations/_008_add_nuclei_table.py +10 -4
  279. souleyez/storage/migrations/_010_evidence_linking.py +17 -10
  280. souleyez/storage/migrations/_011_timeline_tracking.py +20 -13
  281. souleyez/storage/migrations/_012_team_collaboration.py +34 -21
  282. souleyez/storage/migrations/_013_add_host_tags.py +12 -6
  283. souleyez/storage/migrations/_014_exploit_attempts.py +22 -10
  284. souleyez/storage/migrations/_015_add_mac_os_fields.py +15 -7
  285. souleyez/storage/migrations/_016_add_domain_field.py +10 -4
  286. souleyez/storage/migrations/_017_msf_sessions.py +16 -8
  287. souleyez/storage/migrations/_018_add_osint_target.py +10 -6
  288. souleyez/storage/migrations/_019_add_engagement_type.py +10 -6
  289. souleyez/storage/migrations/_020_add_rbac.py +36 -15
  290. souleyez/storage/migrations/_021_wazuh_integration.py +20 -8
  291. souleyez/storage/migrations/_022_wazuh_indexer_columns.py +6 -4
  292. souleyez/storage/migrations/_023_fix_detection_results_fk.py +16 -6
  293. souleyez/storage/migrations/_024_wazuh_vulnerabilities.py +26 -10
  294. souleyez/storage/migrations/_025_multi_siem_support.py +3 -5
  295. souleyez/storage/migrations/_026_add_engagement_scope.py +31 -12
  296. souleyez/storage/migrations/_027_multi_siem_persistence.py +32 -15
  297. souleyez/storage/migrations/__init__.py +26 -26
  298. souleyez/storage/migrations/migration_manager.py +19 -19
  299. souleyez/storage/msf_sessions.py +100 -65
  300. souleyez/storage/osint.py +17 -24
  301. souleyez/storage/recommendation_engine.py +269 -235
  302. souleyez/storage/screenshots.py +33 -32
  303. souleyez/storage/smb_shares.py +136 -92
  304. souleyez/storage/sqlmap_data.py +183 -128
  305. souleyez/storage/team_collaboration.py +135 -141
  306. souleyez/storage/timeline_tracker.py +122 -94
  307. souleyez/storage/wazuh_vulns.py +64 -66
  308. souleyez/storage/web_paths.py +33 -37
  309. souleyez/testing/credential_tester.py +221 -205
  310. souleyez/ui/__init__.py +1 -1
  311. souleyez/ui/ai_quotes.py +12 -12
  312. souleyez/ui/attack_surface.py +2439 -1516
  313. souleyez/ui/chain_rules_view.py +914 -382
  314. souleyez/ui/correlation_view.py +312 -230
  315. souleyez/ui/dashboard.py +2382 -1130
  316. souleyez/ui/deliverables_view.py +148 -62
  317. souleyez/ui/design_system.py +13 -13
  318. souleyez/ui/errors.py +49 -49
  319. souleyez/ui/evidence_linking_view.py +284 -179
  320. souleyez/ui/evidence_vault.py +393 -285
  321. souleyez/ui/exploit_suggestions_view.py +555 -349
  322. souleyez/ui/export_view.py +100 -66
  323. souleyez/ui/gap_analysis_view.py +315 -171
  324. souleyez/ui/help_system.py +105 -97
  325. souleyez/ui/intelligence_view.py +436 -293
  326. souleyez/ui/interactive.py +22827 -10678
  327. souleyez/ui/interactive_selector.py +75 -68
  328. souleyez/ui/log_formatter.py +47 -39
  329. souleyez/ui/menu_components.py +22 -13
  330. souleyez/ui/msf_auxiliary_menu.py +184 -133
  331. souleyez/ui/pending_chains_view.py +336 -172
  332. souleyez/ui/progress_indicators.py +5 -3
  333. souleyez/ui/recommendations_view.py +195 -137
  334. souleyez/ui/rule_builder.py +343 -225
  335. souleyez/ui/setup_wizard.py +678 -284
  336. souleyez/ui/shortcuts.py +217 -165
  337. souleyez/ui/splunk_gap_analysis_view.py +452 -270
  338. souleyez/ui/splunk_vulns_view.py +139 -86
  339. souleyez/ui/team_dashboard.py +498 -335
  340. souleyez/ui/template_selector.py +196 -105
  341. souleyez/ui/terminal.py +6 -6
  342. souleyez/ui/timeline_view.py +198 -127
  343. souleyez/ui/tool_setup.py +264 -164
  344. souleyez/ui/tutorial.py +202 -72
  345. souleyez/ui/tutorial_state.py +40 -40
  346. souleyez/ui/wazuh_vulns_view.py +235 -141
  347. souleyez/ui/wordlist_browser.py +260 -107
  348. souleyez/ui.py +464 -312
  349. souleyez/utils/tool_checker.py +427 -367
  350. souleyez/utils.py +33 -29
  351. souleyez/wordlists.py +134 -167
  352. {souleyez-2.43.29.dist-info → souleyez-2.43.34.dist-info}/METADATA +1 -1
  353. souleyez-2.43.34.dist-info/RECORD +443 -0
  354. {souleyez-2.43.29.dist-info → souleyez-2.43.34.dist-info}/WHEEL +1 -1
  355. souleyez-2.43.29.dist-info/RECORD +0 -379
  356. {souleyez-2.43.29.dist-info → souleyez-2.43.34.dist-info}/entry_points.txt +0 -0
  357. {souleyez-2.43.29.dist-info → souleyez-2.43.34.dist-info}/licenses/LICENSE +0 -0
  358. {souleyez-2.43.29.dist-info → souleyez-2.43.34.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
  ]