souleyez 2.43.26__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 +23434 -10286
  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.26.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.26.dist-info → souleyez-2.43.34.dist-info}/WHEEL +1 -1
  355. souleyez-2.43.26.dist-info/RECORD +0 -379
  356. {souleyez-2.43.26.dist-info → souleyez-2.43.34.dist-info}/entry_points.txt +0 -0
  357. {souleyez-2.43.26.dist-info → souleyez-2.43.34.dist-info}/licenses/LICENSE +0 -0
  358. {souleyez-2.43.26.dist-info → souleyez-2.43.34.dist-info}/top_level.txt +0 -0
@@ -28,6 +28,7 @@ from souleyez.storage.database import get_db
28
28
  @dataclass
29
29
  class RuleRecommendation:
30
30
  """Recommendation for improving detection coverage."""
31
+
31
32
  attack_type: str
32
33
  gap_description: str
33
34
  priority: str # critical, high, medium, low
@@ -40,6 +41,7 @@ class RuleRecommendation:
40
41
  @dataclass
41
42
  class HostDetectionStats:
42
43
  """Detection statistics for a single host."""
44
+
43
45
  host_ip: str
44
46
  total_attacks: int = 0
45
47
  detected: int = 0
@@ -53,6 +55,7 @@ class HostDetectionStats:
53
55
  @dataclass
54
56
  class SeverityBreakdown:
55
57
  """Breakdown of alerts by severity level."""
58
+
56
59
  critical: int = 0
57
60
  high: int = 0
58
61
  medium: int = 0
@@ -64,6 +67,7 @@ class SeverityBreakdown:
64
67
  @dataclass
65
68
  class TopRule:
66
69
  """A frequently triggered rule/sourcetype."""
70
+
67
71
  rule_id: str
68
72
  rule_name: str
69
73
  count: int
@@ -74,6 +78,7 @@ class TopRule:
74
78
  @dataclass
75
79
  class SampleAlert:
76
80
  """A sample alert for display in the report."""
81
+
77
82
  rule_id: str
78
83
  rule_name: str
79
84
  severity: str
@@ -86,6 +91,7 @@ class SampleAlert:
86
91
  @dataclass
87
92
  class HostVulnerability:
88
93
  """Vulnerability info for a host."""
94
+
89
95
  cve_id: str
90
96
  name: str
91
97
  severity: str
@@ -97,6 +103,7 @@ class HostVulnerability:
97
103
  @dataclass
98
104
  class HostVulnerabilitySummary:
99
105
  """Vulnerability summary for a single host."""
106
+
100
107
  host_ip: str
101
108
  agent_name: str = ""
102
109
  total_vulns: int = 0
@@ -111,6 +118,7 @@ class HostVulnerabilitySummary:
111
118
  @dataclass
112
119
  class VulnerabilitySection:
113
120
  """Vulnerability data for the report."""
121
+
114
122
  total_vulns: int = 0
115
123
  hosts_with_vulns: int = 0
116
124
  critical_count: int = 0
@@ -124,6 +132,7 @@ class VulnerabilitySection:
124
132
  @dataclass
125
133
  class DetectionReportData:
126
134
  """Complete data structure for a detection coverage report."""
135
+
127
136
  engagement: Dict[str, Any]
128
137
  summary: EngagementDetectionSummary
129
138
  detection_results: List[DetectionResult]
@@ -143,7 +152,9 @@ class DetectionReportData:
143
152
  executive_summary: str = ""
144
153
  risk_level: str = "UNKNOWN" # CRITICAL, HIGH, MEDIUM, LOW
145
154
  avg_detection_latency: str = ""
146
- vulnerability_section: VulnerabilitySection = field(default_factory=VulnerabilitySection)
155
+ vulnerability_section: VulnerabilitySection = field(
156
+ default_factory=VulnerabilitySection
157
+ )
147
158
 
148
159
 
149
160
  class DetectionReportGatherer:
@@ -226,8 +237,7 @@ class DetectionReportGatherer:
226
237
  )
227
238
 
228
239
  def _build_per_host_analysis(
229
- self,
230
- results: List[DetectionResult]
240
+ self, results: List[DetectionResult]
231
241
  ) -> Dict[str, HostDetectionStats]:
232
242
  """
233
243
  Build detection statistics grouped by target host.
@@ -242,21 +252,21 @@ class DetectionReportGatherer:
242
252
 
243
253
  for result in results:
244
254
  # Get target IP - handle both object and dict
245
- target_ip = getattr(result, 'target_ip', None)
255
+ target_ip = getattr(result, "target_ip", None)
246
256
  if target_ip is None and isinstance(result, dict):
247
- target_ip = result.get('target_ip')
257
+ target_ip = result.get("target_ip")
248
258
  if not target_ip:
249
259
  target_ip = "unknown"
250
260
 
251
261
  # Get attack type
252
- attack_type = getattr(result, 'attack_type', None)
262
+ attack_type = getattr(result, "attack_type", None)
253
263
  if attack_type is None and isinstance(result, dict):
254
- attack_type = result.get('attack_type', 'unknown')
264
+ attack_type = result.get("attack_type", "unknown")
255
265
 
256
266
  # Get status
257
- status = getattr(result, 'status', None)
267
+ status = getattr(result, "status", None)
258
268
  if status is None and isinstance(result, dict):
259
- status = result.get('status', 'unknown')
269
+ status = result.get("status", "unknown")
260
270
 
261
271
  # Initialize host stats if needed
262
272
  if target_ip not in hosts:
@@ -283,16 +293,12 @@ class DetectionReportGatherer:
283
293
  for host in hosts.values():
284
294
  countable = host.detected + host.not_detected + host.partial
285
295
  if countable > 0:
286
- host.coverage_percent = round(
287
- (host.detected / countable) * 100, 1
288
- )
296
+ host.coverage_percent = round((host.detected / countable) * 100, 1)
289
297
 
290
298
  return hosts
291
299
 
292
300
  def _generate_rule_recommendations(
293
- self,
294
- gaps: List[DetectionResult],
295
- mitre_gaps: List[TechniqueResult]
301
+ self, gaps: List[DetectionResult], mitre_gaps: List[TechniqueResult]
296
302
  ) -> List[RuleRecommendation]:
297
303
  """
298
304
  Generate SIEM rule recommendations for detection gaps.
@@ -309,9 +315,9 @@ class DetectionReportGatherer:
309
315
 
310
316
  for gap in gaps:
311
317
  # Get attack type
312
- attack_type = getattr(gap, 'attack_type', None)
318
+ attack_type = getattr(gap, "attack_type", None)
313
319
  if attack_type is None and isinstance(gap, dict):
314
- attack_type = gap.get('attack_type')
320
+ attack_type = gap.get("attack_type")
315
321
  if not attack_type or attack_type in seen_attack_types:
316
322
  continue
317
323
 
@@ -361,9 +367,7 @@ class DetectionReportGatherer:
361
367
  return recommendations
362
368
 
363
369
  def _get_detection_guidance(
364
- self,
365
- attack_type: str,
366
- signature: Dict[str, Any]
370
+ self, attack_type: str, signature: Dict[str, Any]
367
371
  ) -> str:
368
372
  """
369
373
  Generate human-readable detection guidance for an attack type.
@@ -453,13 +457,11 @@ class DetectionReportGatherer:
453
457
  }
454
458
 
455
459
  return category_guidance.get(
456
- category,
457
- "Review SIEM rule configuration for this attack category."
460
+ category, "Review SIEM rule configuration for this attack category."
458
461
  )
459
462
 
460
463
  def _build_severity_breakdown(
461
- self,
462
- results: List[DetectionResult]
464
+ self, results: List[DetectionResult]
463
465
  ) -> SeverityBreakdown:
464
466
  """
465
467
  Build severity breakdown from detected alerts.
@@ -473,20 +475,20 @@ class DetectionReportGatherer:
473
475
  breakdown = SeverityBreakdown()
474
476
 
475
477
  for result in results:
476
- if result.status != 'detected' or not result.alerts:
478
+ if result.status != "detected" or not result.alerts:
477
479
  continue
478
480
 
479
481
  for alert in result.alerts:
480
- severity = str(alert.get('severity', 'info')).lower()
482
+ severity = str(alert.get("severity", "info")).lower()
481
483
  breakdown.total += 1
482
484
 
483
- if severity in ('critical', 'crit'):
485
+ if severity in ("critical", "crit"):
484
486
  breakdown.critical += 1
485
- elif severity == 'high':
487
+ elif severity == "high":
486
488
  breakdown.high += 1
487
- elif severity in ('medium', 'med'):
489
+ elif severity in ("medium", "med"):
488
490
  breakdown.medium += 1
489
- elif severity == 'low':
491
+ elif severity == "low":
490
492
  breakdown.low += 1
491
493
  else:
492
494
  breakdown.info += 1
@@ -494,9 +496,7 @@ class DetectionReportGatherer:
494
496
  return breakdown
495
497
 
496
498
  def _get_top_rules(
497
- self,
498
- results: List[DetectionResult],
499
- limit: int = 10
499
+ self, results: List[DetectionResult], limit: int = 10
500
500
  ) -> List[TopRule]:
501
501
  """
502
502
  Get the most frequently triggered rules.
@@ -511,47 +511,43 @@ class DetectionReportGatherer:
511
511
  rule_counts: Dict[str, Dict[str, Any]] = {}
512
512
 
513
513
  for result in results:
514
- if result.status != 'detected' or not result.alerts:
514
+ if result.status != "detected" or not result.alerts:
515
515
  continue
516
516
 
517
517
  for alert in result.alerts:
518
- rule_id = str(alert.get('rule_id', 'unknown'))
519
- rule_name = alert.get('rule_name', alert.get('name', 'Unknown Rule'))
520
- severity = str(alert.get('severity', 'info')).lower()
521
- description = alert.get('description', '')
518
+ rule_id = str(alert.get("rule_id", "unknown"))
519
+ rule_name = alert.get("rule_name", alert.get("name", "Unknown Rule"))
520
+ severity = str(alert.get("severity", "info")).lower()
521
+ description = alert.get("description", "")
522
522
 
523
523
  if rule_id not in rule_counts:
524
524
  rule_counts[rule_id] = {
525
- 'rule_id': rule_id,
526
- 'rule_name': rule_name,
527
- 'severity': severity,
528
- 'description': description,
529
- 'count': 0
525
+ "rule_id": rule_id,
526
+ "rule_name": rule_name,
527
+ "severity": severity,
528
+ "description": description,
529
+ "count": 0,
530
530
  }
531
- rule_counts[rule_id]['count'] += 1
531
+ rule_counts[rule_id]["count"] += 1
532
532
 
533
533
  # Sort by count descending
534
534
  sorted_rules = sorted(
535
- rule_counts.values(),
536
- key=lambda r: r['count'],
537
- reverse=True
535
+ rule_counts.values(), key=lambda r: r["count"], reverse=True
538
536
  )[:limit]
539
537
 
540
538
  return [
541
539
  TopRule(
542
- rule_id=r['rule_id'],
543
- rule_name=r['rule_name'],
544
- count=r['count'],
545
- severity=r['severity'],
546
- description=r['description'][:200] if r['description'] else ""
540
+ rule_id=r["rule_id"],
541
+ rule_name=r["rule_name"],
542
+ count=r["count"],
543
+ severity=r["severity"],
544
+ description=r["description"][:200] if r["description"] else "",
547
545
  )
548
546
  for r in sorted_rules
549
547
  ]
550
548
 
551
549
  def _get_sample_alerts(
552
- self,
553
- results: List[DetectionResult],
554
- limit: int = 5
550
+ self, results: List[DetectionResult], limit: int = 5
555
551
  ) -> List[SampleAlert]:
556
552
  """
557
553
  Get sample alerts for display in the report.
@@ -566,11 +562,19 @@ class DetectionReportGatherer:
566
562
  samples: List[SampleAlert] = []
567
563
 
568
564
  # Prioritize alerts from detected attacks, favor higher severity
569
- severity_order = {'critical': 0, 'crit': 0, 'high': 1, 'medium': 2, 'med': 2, 'low': 3, 'info': 4}
565
+ severity_order = {
566
+ "critical": 0,
567
+ "crit": 0,
568
+ "high": 1,
569
+ "medium": 2,
570
+ "med": 2,
571
+ "low": 3,
572
+ "info": 4,
573
+ }
570
574
 
571
575
  all_alerts = []
572
576
  for result in results:
573
- if result.status != 'detected' or not result.alerts:
577
+ if result.status != "detected" or not result.alerts:
574
578
  continue
575
579
  for alert in result.alerts[:3]: # Max 3 per result
576
580
  all_alerts.append((result.attack_type, alert))
@@ -578,38 +582,40 @@ class DetectionReportGatherer:
578
582
  # Sort by severity
579
583
  all_alerts.sort(
580
584
  key=lambda x: severity_order.get(
581
- str(x[1].get('severity', 'info')).lower(), 5
585
+ str(x[1].get("severity", "info")).lower(), 5
582
586
  )
583
587
  )
584
588
 
585
589
  for attack_type, alert in all_alerts[:limit]:
586
590
  # Extract raw snippet from alert data
587
- raw_data = alert.get('raw_data', {})
591
+ raw_data = alert.get("raw_data", {})
588
592
  raw_snippet = ""
589
593
  if isinstance(raw_data, dict):
590
594
  # Try to get meaningful snippet
591
- full_log = raw_data.get('full_log', '')
595
+ full_log = raw_data.get("full_log", "")
592
596
  if full_log:
593
597
  raw_snippet = full_log[:300]
594
598
  else:
595
599
  # Try message or description
596
- raw_snippet = raw_data.get('message', '')[:300]
600
+ raw_snippet = raw_data.get("message", "")[:300]
597
601
  elif isinstance(raw_data, str):
598
602
  raw_snippet = raw_data[:300]
599
603
 
600
- timestamp = alert.get('timestamp', '')
601
- if hasattr(timestamp, 'isoformat'):
604
+ timestamp = alert.get("timestamp", "")
605
+ if hasattr(timestamp, "isoformat"):
602
606
  timestamp = timestamp.isoformat()
603
607
 
604
- samples.append(SampleAlert(
605
- rule_id=str(alert.get('rule_id', 'N/A')),
606
- rule_name=alert.get('rule_name', alert.get('name', 'Unknown')),
607
- severity=str(alert.get('severity', 'info')),
608
- timestamp=str(timestamp),
609
- source=attack_type,
610
- description=alert.get('description', '')[:200],
611
- raw_snippet=raw_snippet
612
- ))
608
+ samples.append(
609
+ SampleAlert(
610
+ rule_id=str(alert.get("rule_id", "N/A")),
611
+ rule_name=alert.get("rule_name", alert.get("name", "Unknown")),
612
+ severity=str(alert.get("severity", "info")),
613
+ timestamp=str(timestamp),
614
+ source=attack_type,
615
+ description=alert.get("description", "")[:200],
616
+ raw_snippet=raw_snippet,
617
+ )
618
+ )
613
619
 
614
620
  return samples
615
621
 
@@ -618,7 +624,7 @@ class DetectionReportGatherer:
618
624
  summary: EngagementDetectionSummary,
619
625
  severity: SeverityBreakdown,
620
626
  gaps: List[DetectionResult],
621
- mitre_gaps: List[TechniqueResult]
627
+ mitre_gaps: List[TechniqueResult],
622
628
  ) -> str:
623
629
  """
624
630
  Generate an executive summary paragraph for the report.
@@ -699,9 +705,7 @@ class DetectionReportGatherer:
699
705
  return " ".join(parts)
700
706
 
701
707
  def _calculate_risk_level(
702
- self,
703
- coverage_percent: float,
704
- gaps: List[DetectionResult]
708
+ self, coverage_percent: float, gaps: List[DetectionResult]
705
709
  ) -> str:
706
710
  """
707
711
  Calculate overall risk level based on coverage and gap severity.
@@ -716,9 +720,9 @@ class DetectionReportGatherer:
716
720
  # Check for critical gaps
717
721
  critical_gaps = 0
718
722
  for gap in gaps:
719
- attack_type = gap.attack_type if hasattr(gap, 'attack_type') else ''
723
+ attack_type = gap.attack_type if hasattr(gap, "attack_type") else ""
720
724
  signature = get_signature(attack_type)
721
- if signature.get('severity') in ('critical', 'high'):
725
+ if signature.get("severity") in ("critical", "high"):
722
726
  critical_gaps += 1
723
727
 
724
728
  # Determine risk level
@@ -732,8 +736,7 @@ class DetectionReportGatherer:
732
736
  return "LOW"
733
737
 
734
738
  def _gather_vulnerability_data(
735
- self,
736
- per_host_analysis: Dict[str, HostDetectionStats]
739
+ self, per_host_analysis: Dict[str, HostDetectionStats]
737
740
  ) -> VulnerabilitySection:
738
741
  """
739
742
  Gather vulnerability data for hosts that were attacked.
@@ -756,8 +759,7 @@ class DetectionReportGatherer:
756
759
 
757
760
  # Get all vulnerabilities for this engagement
758
761
  all_vulns = vulns_manager.list_vulnerabilities(
759
- engagement_id=self.engagement_id,
760
- limit=1000
762
+ engagement_id=self.engagement_id, limit=1000
761
763
  )
762
764
 
763
765
  if not all_vulns:
@@ -769,12 +771,12 @@ class DetectionReportGatherer:
769
771
  # Build summary by severity
770
772
  for vuln in all_vulns:
771
773
  section.total_vulns += 1
772
- severity = (vuln.get('severity') or 'Low').lower()
773
- if severity == 'critical':
774
+ severity = (vuln.get("severity") or "Low").lower()
775
+ if severity == "critical":
774
776
  section.critical_count += 1
775
- elif severity == 'high':
777
+ elif severity == "high":
776
778
  section.high_count += 1
777
- elif severity == 'medium':
779
+ elif severity == "medium":
778
780
  section.medium_count += 1
779
781
  else:
780
782
  section.low_count += 1
@@ -782,7 +784,7 @@ class DetectionReportGatherer:
782
784
  # Group vulnerabilities by host/agent IP
783
785
  host_vulns: Dict[str, List[Dict]] = {}
784
786
  for vuln in all_vulns:
785
- agent_ip = vuln.get('agent_ip') or vuln.get('host_ip') or 'unknown'
787
+ agent_ip = vuln.get("agent_ip") or vuln.get("host_ip") or "unknown"
786
788
  if agent_ip not in host_vulns:
787
789
  host_vulns[agent_ip] = []
788
790
  host_vulns[agent_ip].append(vuln)
@@ -795,39 +797,39 @@ class DetectionReportGatherer:
795
797
 
796
798
  host_summary = HostVulnerabilitySummary(
797
799
  host_ip=host_ip,
798
- agent_name=vulns[0].get('agent_name', '') if vulns else '',
800
+ agent_name=vulns[0].get("agent_name", "") if vulns else "",
799
801
  total_vulns=len(vulns),
800
- was_attacked=was_attacked
802
+ was_attacked=was_attacked,
801
803
  )
802
804
 
803
805
  # Count by severity for this host
804
806
  for v in vulns:
805
- sev = (v.get('severity') or 'Low').lower()
806
- if sev == 'critical':
807
+ sev = (v.get("severity") or "Low").lower()
808
+ if sev == "critical":
807
809
  host_summary.critical += 1
808
- elif sev == 'high':
810
+ elif sev == "high":
809
811
  host_summary.high += 1
810
- elif sev == 'medium':
812
+ elif sev == "medium":
811
813
  host_summary.medium += 1
812
814
  else:
813
815
  host_summary.low += 1
814
816
 
815
817
  # Get top 5 vulns for this host (by CVSS score)
816
818
  sorted_vulns = sorted(
817
- vulns,
818
- key=lambda x: x.get('cvss_score') or 0,
819
- reverse=True
819
+ vulns, key=lambda x: x.get("cvss_score") or 0, reverse=True
820
820
  )[:5]
821
821
 
822
822
  for v in sorted_vulns:
823
- host_summary.top_vulns.append(HostVulnerability(
824
- cve_id=v.get('cve_id', 'N/A'),
825
- name=v.get('name', '')[:100],
826
- severity=v.get('severity', 'Low'),
827
- cvss_score=v.get('cvss_score') or 0.0,
828
- package_name=v.get('package_name', ''),
829
- package_version=v.get('package_version', '')
830
- ))
823
+ host_summary.top_vulns.append(
824
+ HostVulnerability(
825
+ cve_id=v.get("cve_id", "N/A"),
826
+ name=v.get("name", "")[:100],
827
+ severity=v.get("severity", "Low"),
828
+ cvss_score=v.get("cvss_score") or 0.0,
829
+ package_name=v.get("package_name", ""),
830
+ package_version=v.get("package_version", ""),
831
+ )
832
+ )
831
833
 
832
834
  section.host_summaries.append(host_summary)
833
835
 
@@ -839,33 +841,36 @@ class DetectionReportGatherer:
839
841
  # Get top 10 CVEs across all hosts
840
842
  all_cves: Dict[str, Dict] = {}
841
843
  for vuln in all_vulns:
842
- cve_id = vuln.get('cve_id')
844
+ cve_id = vuln.get("cve_id")
843
845
  if not cve_id:
844
846
  continue
845
847
  if cve_id not in all_cves:
846
848
  all_cves[cve_id] = vuln
847
- elif (vuln.get('cvss_score') or 0) > (all_cves[cve_id].get('cvss_score') or 0):
849
+ elif (vuln.get("cvss_score") or 0) > (
850
+ all_cves[cve_id].get("cvss_score") or 0
851
+ ):
848
852
  all_cves[cve_id] = vuln
849
853
 
850
854
  sorted_cves = sorted(
851
- all_cves.values(),
852
- key=lambda x: x.get('cvss_score') or 0,
853
- reverse=True
855
+ all_cves.values(), key=lambda x: x.get("cvss_score") or 0, reverse=True
854
856
  )[:10]
855
857
 
856
858
  for v in sorted_cves:
857
- section.top_cves.append(HostVulnerability(
858
- cve_id=v.get('cve_id', 'N/A'),
859
- name=v.get('name', '')[:100],
860
- severity=v.get('severity', 'Low'),
861
- cvss_score=v.get('cvss_score') or 0.0,
862
- package_name=v.get('package_name', ''),
863
- package_version=v.get('package_version', '')
864
- ))
859
+ section.top_cves.append(
860
+ HostVulnerability(
861
+ cve_id=v.get("cve_id", "N/A"),
862
+ name=v.get("name", "")[:100],
863
+ severity=v.get("severity", "Low"),
864
+ cvss_score=v.get("cvss_score") or 0.0,
865
+ package_name=v.get("package_name", ""),
866
+ package_version=v.get("package_version", ""),
867
+ )
868
+ )
865
869
 
866
870
  except Exception as e:
867
871
  # Log but don't fail the report
868
872
  import logging
873
+
869
874
  logging.getLogger(__name__).warning(
870
875
  f"Failed to gather vulnerability data: {e}"
871
876
  )
@@ -894,12 +899,10 @@ class DetectionReportGatherer:
894
899
 
895
900
  # Count tactics with coverage
896
901
  tactics_tested = sum(
897
- 1 for t in data.tactic_summary.values()
898
- if t.techniques_tested > 0
902
+ 1 for t in data.tactic_summary.values() if t.techniques_tested > 0
899
903
  )
900
904
  tactics_with_gaps = sum(
901
- 1 for t in data.tactic_summary.values()
902
- if t.techniques_not_detected > 0
905
+ 1 for t in data.tactic_summary.values() if t.techniques_not_detected > 0
903
906
  )
904
907
 
905
908
  return {
@@ -912,13 +915,11 @@ class DetectionReportGatherer:
912
915
  "tactics_with_gaps": tactics_with_gaps,
913
916
  "techniques_tested": len(data.mitre_coverage),
914
917
  "techniques_detected": sum(
915
- 1 for t in data.mitre_coverage.values()
916
- if t.detected > 0
918
+ 1 for t in data.mitre_coverage.values() if t.detected > 0
917
919
  ),
918
920
  "hosts_tested": len(data.per_host_analysis),
919
921
  "critical_recommendations": sum(
920
- 1 for r in data.rule_recommendations
921
- if r.priority == "critical"
922
+ 1 for r in data.rule_recommendations if r.priority == "critical"
922
923
  ),
923
924
  }
924
925