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
@@ -18,10 +18,10 @@ console = Console()
18
18
 
19
19
  # Severity colors
20
20
  SEVERITY_COLORS = {
21
- 'Critical': 'red',
22
- 'High': 'yellow',
23
- 'Medium': 'white',
24
- 'Low': 'bright_black'
21
+ "Critical": "red",
22
+ "High": "yellow",
23
+ "Medium": "white",
24
+ "Low": "bright_black",
25
25
  }
26
26
 
27
27
 
@@ -50,14 +50,24 @@ def show_wazuh_vulns_view(engagement_id: int, engagement_name: str = "") -> None
50
50
 
51
51
  # Header
52
52
  click.echo("\n┌" + "─" * (width - 2) + "┐")
53
- click.echo("│" + click.style(" WAZUH VULNERABILITIES ".center(width - 2), bold=True, fg='cyan') + "│")
53
+ click.echo(
54
+ "│"
55
+ + click.style(
56
+ " WAZUH VULNERABILITIES ".center(width - 2), bold=True, fg="cyan"
57
+ )
58
+ + "│"
59
+ )
54
60
  click.echo("└" + "─" * (width - 2) + "┘")
55
61
  click.echo()
56
62
 
57
63
  # Check if Wazuh is configured
58
64
  config = WazuhConfig.get_config(engagement_id)
59
- if not config or not config.get('enabled'):
60
- click.echo(click.style(" ⚠️ Wazuh is not configured for this engagement.", fg='yellow'))
65
+ if not config or not config.get("enabled"):
66
+ click.echo(
67
+ click.style(
68
+ " ⚠️ Wazuh is not configured for this engagement.", fg="yellow"
69
+ )
70
+ )
61
71
  click.echo()
62
72
  click.echo(" Configure Wazuh in Settings → Integrations → Wazuh SIEM")
63
73
  click.echo()
@@ -65,7 +75,7 @@ def show_wazuh_vulns_view(engagement_id: int, engagement_name: str = "") -> None
65
75
  click.echo()
66
76
  click.echo(" [q] Back")
67
77
  click.echo()
68
- if click.getchar().lower() == 'q':
78
+ if click.getchar().lower() == "q":
69
79
  return
70
80
  continue
71
81
 
@@ -74,42 +84,60 @@ def show_wazuh_vulns_view(engagement_id: int, engagement_name: str = "") -> None
74
84
  sync_status = sync.get_sync_status()
75
85
 
76
86
  # Sync status line at top
77
- if sync_status.get('synced'):
78
- last_sync = sync_status.get('last_sync_at', 'Unknown')
79
- stale = sync_status.get('is_stale')
80
- status_color = 'yellow' if stale else 'green'
81
- status_text = '(stale)' if stale else '(fresh)'
82
- click.echo(f" Last sync: {last_sync} " + click.style(status_text, fg=status_color) +
83
- f" | Count: {sync_status.get('last_sync_count', 0)}")
87
+ if sync_status.get("synced"):
88
+ last_sync = sync_status.get("last_sync_at", "Unknown")
89
+ stale = sync_status.get("is_stale")
90
+ status_color = "yellow" if stale else "green"
91
+ status_text = "(stale)" if stale else "(fresh)"
92
+ click.echo(
93
+ f" Last sync: {last_sync} "
94
+ + click.style(status_text, fg=status_color)
95
+ + f" | Count: {sync_status.get('last_sync_count', 0)}"
96
+ )
84
97
  click.echo()
85
98
  else:
86
- click.echo(click.style(" Never synced - press [s] to sync now", fg='yellow'))
99
+ click.echo(
100
+ click.style(" Never synced - press [s] to sync now", fg="yellow")
101
+ )
87
102
  click.echo()
88
103
 
89
104
  # Get summary
90
105
  summary = vulns_manager.get_summary(engagement_id)
91
106
  mapping_stats = host_mapper.get_mapping_stats(engagement_id)
92
107
 
93
- total = summary.get('total', 0)
94
- verified = summary.get('verified', 0)
95
- by_sev = summary.get('by_severity', {})
96
- mapped = mapping_stats.get('mapped', 0)
97
- unmapped = mapping_stats.get('unmapped', 0)
108
+ total = summary.get("total", 0)
109
+ verified = summary.get("verified", 0)
110
+ by_sev = summary.get("by_severity", {})
111
+ mapped = mapping_stats.get("mapped", 0)
112
+ unmapped = mapping_stats.get("unmapped", 0)
98
113
 
99
114
  # Get all vulnerabilities with filters
100
115
  filter_kwargs = {}
101
116
  if severity_filter:
102
- filter_kwargs['severity'] = severity_filter
117
+ filter_kwargs["severity"] = severity_filter
103
118
  if host_filter:
104
- filter_kwargs['agent_ip'] = host_filter
119
+ filter_kwargs["agent_ip"] = host_filter
105
120
 
106
- all_vulns = vulns_manager.list_vulnerabilities(engagement_id, limit=1000, **filter_kwargs)
121
+ all_vulns = vulns_manager.list_vulnerabilities(
122
+ engagement_id, limit=1000, **filter_kwargs
123
+ )
107
124
 
108
125
  # Display table with summary header
109
126
  page, total_pages = _display_vulns_table(
110
- console, all_vulns, selected_ids, page, page_size,
111
- view_all, severity_filter, host_filter, width, by_sev, total,
112
- mapped, unmapped, verified
127
+ console,
128
+ all_vulns,
129
+ selected_ids,
130
+ page,
131
+ page_size,
132
+ view_all,
133
+ severity_filter,
134
+ host_filter,
135
+ width,
136
+ by_sev,
137
+ total,
138
+ mapped,
139
+ unmapped,
140
+ verified,
113
141
  )
114
142
 
115
143
  # Menu
@@ -128,30 +156,30 @@ def show_wazuh_vulns_view(engagement_id: int, engagement_name: str = "") -> None
128
156
  try:
129
157
  choice = input(" Select option: ").strip().lower()
130
158
 
131
- if choice == 'q':
159
+ if choice == "q":
132
160
  return
133
- elif choice == 's':
161
+ elif choice == "s":
134
162
  _do_sync(engagement_id)
135
- elif choice == 'm':
163
+ elif choice == "m":
136
164
  _mapping_menu(engagement_id)
137
- elif choice == 'i':
165
+ elif choice == "i":
138
166
  # Interactive mode
139
167
  _interactive_mode(engagement_id, all_vulns, selected_ids)
140
- elif choice == 't':
168
+ elif choice == "t":
141
169
  view_all = not view_all
142
170
  if not view_all:
143
171
  page = 0
144
- elif choice == 'n' and not view_all and page < total_pages - 1:
172
+ elif choice == "n" and not view_all and page < total_pages - 1:
145
173
  page += 1
146
- elif choice == 'p' and not view_all and page > 0:
174
+ elif choice == "p" and not view_all and page > 0:
147
175
  page -= 1
148
- elif choice == 'f':
176
+ elif choice == "f":
149
177
  severity_filter = _select_severity_filter()
150
178
  page = 0
151
- elif choice == 'h':
179
+ elif choice == "h":
152
180
  host_filter = _select_host_filter(engagement_id)
153
181
  page = 0
154
- elif choice == 'c':
182
+ elif choice == "c":
155
183
  severity_filter = None
156
184
  host_filter = None
157
185
  page = 0
@@ -161,10 +189,10 @@ def show_wazuh_vulns_view(engagement_id: int, engagement_name: str = "") -> None
161
189
  if 0 <= vuln_idx < len(all_vulns):
162
190
  _show_vuln_detail(all_vulns[vuln_idx])
163
191
  else:
164
- click.echo(click.style(" Invalid number", fg='red'))
192
+ click.echo(click.style(" Invalid number", fg="red"))
165
193
  click.pause()
166
194
  else:
167
- click.echo(click.style("Invalid option", fg='red'))
195
+ click.echo(click.style("Invalid option", fg="red"))
168
196
  click.pause()
169
197
 
170
198
  except (KeyboardInterrupt, EOFError):
@@ -172,10 +200,20 @@ def show_wazuh_vulns_view(engagement_id: int, engagement_name: str = "") -> None
172
200
 
173
201
 
174
202
  def _display_vulns_table(
175
- console: Console, vulns: List[Dict], selected_ids: set,
176
- page: int, page_size: int, view_all: bool,
177
- severity_filter: Optional[str], host_filter: Optional[str],
178
- width: int, by_sev: Dict, total: int, mapped: int, unmapped: int, verified: int
203
+ console: Console,
204
+ vulns: List[Dict],
205
+ selected_ids: set,
206
+ page: int,
207
+ page_size: int,
208
+ view_all: bool,
209
+ severity_filter: Optional[str],
210
+ host_filter: Optional[str],
211
+ width: int,
212
+ by_sev: Dict,
213
+ total: int,
214
+ mapped: int,
215
+ unmapped: int,
216
+ verified: int,
179
217
  ) -> tuple:
180
218
  """Display vulnerabilities table with summary header.
181
219
 
@@ -183,15 +221,19 @@ def _display_vulns_table(
183
221
  """
184
222
  # Count by severity from current vulns
185
223
  severity_counts = {
186
- 'Critical': sum(1 for v in vulns if v.get('severity') == 'Critical'),
187
- 'High': sum(1 for v in vulns if v.get('severity') == 'High'),
188
- 'Medium': sum(1 for v in vulns if v.get('severity') == 'Medium'),
189
- 'Low': sum(1 for v in vulns if v.get('severity') == 'Low')
224
+ "Critical": sum(1 for v in vulns if v.get("severity") == "Critical"),
225
+ "High": sum(1 for v in vulns if v.get("severity") == "High"),
226
+ "Medium": sum(1 for v in vulns if v.get("severity") == "Medium"),
227
+ "Low": sum(1 for v in vulns if v.get("severity") == "Low"),
190
228
  }
191
229
 
192
230
  # Summary header
193
231
  click.echo("═" * width)
194
- click.echo(click.style(f"⚠️ WAZUH VULNERABILITIES ({len(vulns)} total)", bold=True, fg='yellow'))
232
+ click.echo(
233
+ click.style(
234
+ f"⚠️ WAZUH VULNERABILITIES ({len(vulns)} total)", bold=True, fg="yellow"
235
+ )
236
+ )
195
237
 
196
238
  # Severity breakdown line with emojis
197
239
  sev_line = (
@@ -203,7 +245,12 @@ def _display_vulns_table(
203
245
  click.echo(sev_line)
204
246
 
205
247
  # Stats line
206
- click.echo(click.style(f" Verified: {verified} │ Mapped: {mapped} │ Unmapped: {unmapped}", fg='bright_black'))
248
+ click.echo(
249
+ click.style(
250
+ f" Verified: {verified} │ Mapped: {mapped} │ Unmapped: {unmapped}",
251
+ fg="bright_black",
252
+ )
253
+ )
207
254
 
208
255
  # Show active filters
209
256
  if severity_filter or host_filter:
@@ -212,13 +259,13 @@ def _display_vulns_table(
212
259
  filter_parts.append(f"Severity: {severity_filter}")
213
260
  if host_filter:
214
261
  filter_parts.append(f"Host: {host_filter}")
215
- click.echo(click.style(f" 🔍 Filters: {', '.join(filter_parts)}", fg='cyan'))
262
+ click.echo(click.style(f" 🔍 Filters: {', '.join(filter_parts)}", fg="cyan"))
216
263
 
217
264
  click.echo("─" * width)
218
265
  click.echo()
219
266
 
220
267
  if not vulns:
221
- click.echo(" " + click.style("No vulnerabilities found!", fg='green'))
268
+ click.echo(" " + click.style("No vulnerabilities found!", fg="green"))
222
269
  click.echo(" Press [s] to sync from Wazuh.")
223
270
  click.echo()
224
271
  return 0, 1
@@ -240,7 +287,7 @@ def _display_vulns_table(
240
287
  header_style="bold cyan",
241
288
  box=DesignSystem.TABLE_BOX,
242
289
  padding=(0, 1),
243
- expand=True
290
+ expand=True,
244
291
  )
245
292
 
246
293
  table.add_column("○", width=3, justify="center") # Checkbox
@@ -258,37 +305,31 @@ def _display_vulns_table(
258
305
  else:
259
306
  display_idx = (page * page_size) + idx + 1
260
307
 
261
- vuln_id = vuln.get('id')
308
+ vuln_id = vuln.get("id")
262
309
 
263
310
  # Checkbox
264
- checkbox = '' if vuln_id in selected_ids else ''
311
+ checkbox = "" if vuln_id in selected_ids else ""
265
312
 
266
313
  # CVE
267
- cve = vuln.get('cve_id', '-')
314
+ cve = vuln.get("cve_id", "-")
268
315
 
269
316
  # Severity with color
270
- severity = vuln.get('severity', 'Medium')
271
- sev_color = SEVERITY_COLORS.get(severity, 'white')
317
+ severity = vuln.get("severity", "Medium")
318
+ sev_color = SEVERITY_COLORS.get(severity, "white")
272
319
  sev_display = f"[{sev_color}]{severity}[/{sev_color}]"
273
320
 
274
321
  # Host
275
- host_ip = vuln.get('host_ip') or vuln.get('agent_ip', '-')
322
+ host_ip = vuln.get("host_ip") or vuln.get("agent_ip", "-")
276
323
 
277
324
  # Package (truncated)
278
- package = vuln.get('package_name', '-')[:24]
325
+ package = vuln.get("package_name", "-")[:24]
279
326
 
280
327
  # CVSS
281
- cvss = vuln.get('cvss_score')
328
+ cvss = vuln.get("cvss_score")
282
329
  cvss_display = f"{cvss:.1f}" if cvss else "-"
283
330
 
284
331
  table.add_row(
285
- checkbox,
286
- str(display_idx),
287
- cve,
288
- sev_display,
289
- host_ip,
290
- package,
291
- cvss_display
332
+ checkbox, str(display_idx), cve, sev_display, host_ip, package, cvss_display
292
333
  )
293
334
 
294
335
  console.print(" ", table)
@@ -312,60 +353,70 @@ def _display_vulns_table(
312
353
  def _interactive_mode(engagement_id: int, vulns: List[Dict], selected_ids: set) -> None:
313
354
  """Run interactive selection mode."""
314
355
  if not vulns:
315
- click.echo(click.style(" No vulnerabilities to select.", fg='yellow'))
356
+ click.echo(click.style(" No vulnerabilities to select.", fg="yellow"))
316
357
  click.pause()
317
358
  return
318
359
 
319
360
  # Prepare items for interactive selector
320
361
  vuln_items = []
321
362
  for vuln in vulns:
322
- vuln_items.append({
323
- 'id': vuln.get('id'),
324
- 'cve_id': vuln.get('cve_id', '-'),
325
- 'severity': vuln.get('severity', 'Medium'),
326
- 'host': vuln.get('host_ip') or vuln.get('agent_ip', '-'),
327
- 'package': vuln.get('package_name', '-')[:24],
328
- 'cvss': f"{vuln.get('cvss_score', 0):.1f}" if vuln.get('cvss_score') else '-',
329
- 'raw': vuln
330
- })
363
+ vuln_items.append(
364
+ {
365
+ "id": vuln.get("id"),
366
+ "cve_id": vuln.get("cve_id", "-"),
367
+ "severity": vuln.get("severity", "Medium"),
368
+ "host": vuln.get("host_ip") or vuln.get("agent_ip", "-"),
369
+ "package": vuln.get("package_name", "-")[:24],
370
+ "cvss": (
371
+ f"{vuln.get('cvss_score', 0):.1f}"
372
+ if vuln.get("cvss_score")
373
+ else "-"
374
+ ),
375
+ "raw": vuln,
376
+ }
377
+ )
331
378
 
332
379
  columns = [
333
- {'name': 'CVE', 'key': 'cve_id', 'width': 18},
334
- {'name': 'Severity', 'key': 'severity', 'width': 10},
335
- {'name': 'Host', 'key': 'host', 'width': 16},
336
- {'name': 'Package', 'key': 'package', 'width': 24},
337
- {'name': 'CVSS', 'key': 'cvss', 'width': 6, 'justify': 'center'}
380
+ {"name": "CVE", "key": "cve_id", "width": 18},
381
+ {"name": "Severity", "key": "severity", "width": 10},
382
+ {"name": "Host", "key": "host", "width": 16},
383
+ {"name": "Package", "key": "package", "width": 24},
384
+ {"name": "CVSS", "key": "cvss", "width": 6, "justify": "center"},
338
385
  ]
339
386
 
340
387
  def format_cell(item: Dict, key: str) -> str:
341
- if key == 'severity':
342
- sev = item.get('severity', 'Medium')
343
- color = SEVERITY_COLORS.get(sev, 'white')
388
+ if key == "severity":
389
+ sev = item.get("severity", "Medium")
390
+ color = SEVERITY_COLORS.get(sev, "white")
344
391
  return f"[{color}]{sev}[/{color}]"
345
- return str(item.get(key, '-'))
392
+ return str(item.get(key, "-"))
346
393
 
347
394
  interactive_select(
348
395
  items=vuln_items,
349
396
  columns=columns,
350
397
  selected_ids=selected_ids,
351
- get_id=lambda v: v['id'],
352
- title='SELECT WAZUH VULNERABILITIES',
353
- format_cell=format_cell
398
+ get_id=lambda v: v["id"],
399
+ title="SELECT WAZUH VULNERABILITIES",
400
+ format_cell=format_cell,
354
401
  )
355
402
 
356
403
  if selected_ids:
357
404
  _bulk_action_menu(engagement_id, vuln_items, selected_ids)
358
405
 
359
406
 
360
- def _bulk_action_menu(engagement_id: int, vuln_items: List[Dict], selected_ids: set) -> None:
407
+ def _bulk_action_menu(
408
+ engagement_id: int, vuln_items: List[Dict], selected_ids: set
409
+ ) -> None:
361
410
  """Show bulk action menu for selected vulnerabilities."""
362
- selected = [v for v in vuln_items if v['id'] in selected_ids]
411
+ selected = [v for v in vuln_items if v["id"] in selected_ids]
363
412
 
364
413
  if not selected:
365
414
  return
366
415
 
367
416
  click.echo()
368
- click.echo(click.style(f" Selected: {len(selected)} vulnerability(ies)", bold=True))
417
+ click.echo(
418
+ click.style(f" Selected: {len(selected)} vulnerability(ies)", bold=True)
419
+ )
369
420
  click.echo(" [v] View details")
370
421
  click.echo(" [r] Mark as verified")
371
422
  click.echo(" [e] Export to file")
@@ -375,42 +426,52 @@ def _bulk_action_menu(engagement_id: int, vuln_items: List[Dict], selected_ids:
375
426
  click.echo()
376
427
 
377
428
  try:
378
- choice = click.prompt(" Select option", default='q', show_default=False).strip().lower()
429
+ choice = (
430
+ click.prompt(" Select option", default="q", show_default=False)
431
+ .strip()
432
+ .lower()
433
+ )
379
434
 
380
- if choice == 'v':
435
+ if choice == "v":
381
436
  # View details of first selected
382
- vuln = selected[0]['raw']
437
+ vuln = selected[0]["raw"]
383
438
  _show_vuln_detail(vuln)
384
- elif choice == 'r':
439
+ elif choice == "r":
385
440
  # Mark as verified
386
441
  vulns_manager = WazuhVulnsManager()
387
442
  count = 0
388
443
  for item in selected:
389
444
  try:
390
- vulns_manager.update_vulnerability(item['id'], status='confirmed', verified_by_scan=True)
445
+ vulns_manager.update_vulnerability(
446
+ item["id"], status="confirmed", verified_by_scan=True
447
+ )
391
448
  count += 1
392
449
  except Exception:
393
450
  pass
394
- click.echo(click.style(f" ✓ Verified {count} vulnerabilities", fg='green'))
451
+ click.echo(click.style(f" ✓ Verified {count} vulnerabilities", fg="green"))
395
452
  click.pause(" Press any key to continue...")
396
453
  selected_ids.clear()
397
- elif choice == 'x':
454
+ elif choice == "x":
398
455
  # Mark as false positive
399
456
  vulns_manager = WazuhVulnsManager()
400
457
  count = 0
401
458
  for item in selected:
402
459
  try:
403
- vulns_manager.update_vulnerability(item['id'], status='false_positive')
460
+ vulns_manager.update_vulnerability(
461
+ item["id"], status="false_positive"
462
+ )
404
463
  count += 1
405
464
  except Exception:
406
465
  pass
407
- click.echo(click.style(f" ✓ Marked {count} as false positive", fg='yellow'))
466
+ click.echo(
467
+ click.style(f" ✓ Marked {count} as false positive", fg="yellow")
468
+ )
408
469
  click.pause(" Press any key to continue...")
409
470
  selected_ids.clear()
410
- elif choice == 'e':
471
+ elif choice == "e":
411
472
  # Export selected
412
473
  _export_selected(selected)
413
- elif choice == 'c':
474
+ elif choice == "c":
414
475
  selected_ids.clear()
415
476
 
416
477
  except (KeyboardInterrupt, EOFError):
@@ -426,20 +487,26 @@ def _export_selected(selected: List[Dict]) -> None:
426
487
 
427
488
  export_data = []
428
489
  for item in selected:
429
- vuln = item.get('raw', item)
430
- export_data.append({
431
- 'cve_id': vuln.get('cve_id'),
432
- 'severity': vuln.get('severity'),
433
- 'cvss_score': vuln.get('cvss_score'),
434
- 'host_ip': vuln.get('host_ip') or vuln.get('agent_ip'),
435
- 'package_name': vuln.get('package_name'),
436
- 'package_version': vuln.get('package_version')
437
- })
438
-
439
- with open(filename, 'w') as f:
490
+ vuln = item.get("raw", item)
491
+ export_data.append(
492
+ {
493
+ "cve_id": vuln.get("cve_id"),
494
+ "severity": vuln.get("severity"),
495
+ "cvss_score": vuln.get("cvss_score"),
496
+ "host_ip": vuln.get("host_ip") or vuln.get("agent_ip"),
497
+ "package_name": vuln.get("package_name"),
498
+ "package_version": vuln.get("package_version"),
499
+ }
500
+ )
501
+
502
+ with open(filename, "w") as f:
440
503
  json.dump(export_data, f, indent=2)
441
504
 
442
- click.echo(click.style(f" ✓ Exported {len(selected)} vulnerabilities to {filename}", fg='green'))
505
+ click.echo(
506
+ click.style(
507
+ f" ✓ Exported {len(selected)} vulnerabilities to {filename}", fg="green"
508
+ )
509
+ )
443
510
  click.pause(" Press any key to continue...")
444
511
 
445
512
 
@@ -451,9 +518,9 @@ def _select_severity_filter() -> Optional[str]:
451
518
  click.echo()
452
519
 
453
520
  key = click.getchar().lower()
454
- severity_map = {'1': 'Critical', '2': 'High', '3': 'Medium', '4': 'Low'}
521
+ severity_map = {"1": "Critical", "2": "High", "3": "Medium", "4": "Low"}
455
522
 
456
- if key == 'c':
523
+ if key == "c":
457
524
  return None
458
525
  return severity_map.get(key)
459
526
 
@@ -463,7 +530,7 @@ def _select_host_filter(engagement_id: int) -> Optional[str]:
463
530
  click.echo()
464
531
  ip = click.prompt(" Enter host IP (partial match, or 'c' to clear)", default="")
465
532
 
466
- if ip.lower() == 'c' or not ip:
533
+ if ip.lower() == "c" or not ip:
467
534
  return None
468
535
  return ip
469
536
 
@@ -474,7 +541,11 @@ def _do_sync(engagement_id: int) -> None:
474
541
  width = DesignSystem.get_terminal_width()
475
542
 
476
543
  click.echo("\n┌" + "─" * (width - 2) + "┐")
477
- click.echo("│" + click.style(" SYNCING FROM WAZUH ".center(width - 2), bold=True, fg='blue') + "│")
544
+ click.echo(
545
+ "│"
546
+ + click.style(" SYNCING FROM WAZUH ".center(width - 2), bold=True, fg="blue")
547
+ + "│"
548
+ )
478
549
  click.echo("└" + "─" * (width - 2) + "┘")
479
550
  click.echo()
480
551
 
@@ -486,7 +557,7 @@ def _do_sync(engagement_id: int) -> None:
486
557
  click.echo()
487
558
 
488
559
  if result.success:
489
- click.echo(click.style(" ✓ Sync complete!", fg='green', bold=True))
560
+ click.echo(click.style(" ✓ Sync complete!", fg="green", bold=True))
490
561
  click.echo(f" Fetched: {result.total_fetched}")
491
562
  click.echo(f" New: {result.new_vulns}")
492
563
  click.echo(f" Updated: {result.updated_vulns}")
@@ -494,7 +565,11 @@ def _do_sync(engagement_id: int) -> None:
494
565
 
495
566
  if result.unmapped_agents:
496
567
  click.echo()
497
- click.echo(click.style(f" Unmapped agents ({len(result.unmapped_agents)}):", fg='yellow'))
568
+ click.echo(
569
+ click.style(
570
+ f" Unmapped agents ({len(result.unmapped_agents)}):", fg="yellow"
571
+ )
572
+ )
498
573
  for agent_ip in result.unmapped_agents[:5]:
499
574
  click.echo(f" - {agent_ip}")
500
575
  if len(result.unmapped_agents) > 5:
@@ -502,16 +577,18 @@ def _do_sync(engagement_id: int) -> None:
502
577
 
503
578
  if result.errors:
504
579
  click.echo()
505
- click.echo(click.style(f" Errors ({len(result.errors)}):", fg='yellow'))
580
+ click.echo(click.style(f" Errors ({len(result.errors)}):", fg="yellow"))
506
581
  for err in result.errors[:3]:
507
582
  click.echo(f" - {err}")
508
583
  else:
509
- click.echo(click.style(" ✗ Sync failed", fg='red', bold=True))
584
+ click.echo(click.style(" ✗ Sync failed", fg="red", bold=True))
510
585
  for err in result.errors:
511
- click.echo(click.style(f" {err}", fg='red'))
586
+ click.echo(click.style(f" {err}", fg="red"))
512
587
 
513
588
  click.echo()
514
- click.echo(click.style(f" Duration: {result.duration_seconds:.1f}s", fg='bright_black'))
589
+ click.echo(
590
+ click.style(f" Duration: {result.duration_seconds:.1f}s", fg="bright_black")
591
+ )
515
592
  click.echo()
516
593
  click.pause(" Press any key to continue...")
517
594
 
@@ -524,34 +601,42 @@ def _mapping_menu(engagement_id: int) -> None:
524
601
  width = DesignSystem.get_terminal_width()
525
602
 
526
603
  click.echo("\n┌" + "─" * (width - 2) + "┐")
527
- click.echo("│" + click.style(" HOST MAPPING ".center(width - 2), bold=True, fg='cyan') + "│")
604
+ click.echo(
605
+ "│"
606
+ + click.style(" HOST MAPPING ".center(width - 2), bold=True, fg="cyan")
607
+ + "│"
608
+ )
528
609
  click.echo("└" + "─" * (width - 2) + "┘")
529
610
  click.echo()
530
611
 
531
612
  # Get mapping stats
532
613
  stats = host_mapper.get_mapping_stats(engagement_id)
533
- click.echo(f" Mapped: {stats['mapped']} | Unmapped: {stats['unmapped']} | Total: {stats['total']}")
614
+ click.echo(
615
+ f" Mapped: {stats['mapped']} | Unmapped: {stats['unmapped']} | Total: {stats['total']}"
616
+ )
534
617
  click.echo()
535
618
 
536
619
  # Show unmapped agents
537
620
  unmapped = host_mapper.get_unmapped_agents(engagement_id)
538
621
  if unmapped:
539
- click.echo(click.style(" Unmapped Agents:", fg='yellow'))
622
+ click.echo(click.style(" Unmapped Agents:", fg="yellow"))
540
623
  for agent in unmapped[:10]:
541
- click.echo(f" - {agent.get('agent_ip')} ({agent.get('agent_name', 'unknown')}) - {agent.get('vuln_count')} vulns")
624
+ click.echo(
625
+ f" - {agent.get('agent_ip')} ({agent.get('agent_name', 'unknown')}) - {agent.get('vuln_count')} vulns"
626
+ )
542
627
 
543
628
  # Show suggestions
544
629
  suggestions = host_mapper.suggest_mappings(engagement_id)
545
630
  if suggestions:
546
631
  click.echo()
547
- click.echo(click.style(" Suggested Mappings:", fg='cyan'))
632
+ click.echo(click.style(" Suggested Mappings:", fg="cyan"))
548
633
  for sug in suggestions[:5]:
549
634
  click.echo(
550
635
  f" {sug['agent_ip']} → {sug['suggested_host_ip']} "
551
636
  f"({sug.get('suggested_host_name', '-')}) - {sug['reason']}"
552
637
  )
553
638
  else:
554
- click.echo(click.style(" ✓ All agents are mapped to hosts.", fg='green'))
639
+ click.echo(click.style(" ✓ All agents are mapped to hosts.", fg="green"))
555
640
 
556
641
  click.echo()
557
642
  click.echo("─" * width)
@@ -563,13 +648,15 @@ def _mapping_menu(engagement_id: int) -> None:
563
648
 
564
649
  key = click.getchar().lower()
565
650
 
566
- if key == '1':
651
+ if key == "1":
567
652
  click.echo()
568
653
  click.echo(" Auto-mapping agents to hosts...")
569
654
  mapping = host_mapper.auto_map_all(engagement_id)
570
655
 
571
656
  mapped_count = sum(1 for h in mapping.values() if h)
572
- click.echo(click.style(f" ✓ Mapped {mapped_count} agents to hosts", fg='green'))
657
+ click.echo(
658
+ click.style(f" ✓ Mapped {mapped_count} agents to hosts", fg="green")
659
+ )
573
660
  click.echo()
574
661
  click.pause(" Press any key to continue...")
575
662
 
@@ -579,17 +666,22 @@ def _show_vuln_detail(vuln: Dict) -> None:
579
666
  DesignSystem.clear_screen()
580
667
  width = DesignSystem.get_terminal_width()
581
668
 
582
- cve = vuln.get('cve_id', 'Unknown')
583
- severity = vuln.get('severity', 'Medium')
584
- sev_color = SEVERITY_COLORS.get(severity, 'white')
669
+ cve = vuln.get("cve_id", "Unknown")
670
+ severity = vuln.get("severity", "Medium")
671
+ sev_color = SEVERITY_COLORS.get(severity, "white")
585
672
 
586
673
  click.echo("\n┌" + "─" * (width - 2) + "┐")
587
- click.echo("│" + click.style(f" {cve} ".center(width - 2), bold=True, fg='cyan') + "│")
674
+ click.echo(
675
+ "│" + click.style(f" {cve} ".center(width - 2), bold=True, fg="cyan") + "│"
676
+ )
588
677
  click.echo("└" + "─" * (width - 2) + "┘")
589
678
  click.echo()
590
679
 
591
- click.echo(f" Severity: " + click.style(severity, fg=sev_color, bold=True) +
592
- f" | CVSS: {vuln.get('cvss_score', '-')}")
680
+ click.echo(
681
+ f" Severity: "
682
+ + click.style(severity, fg=sev_color, bold=True)
683
+ + f" | CVSS: {vuln.get('cvss_score', '-')}"
684
+ )
593
685
  click.echo()
594
686
 
595
687
  click.echo(click.style(" Agent/Host:", bold=True))
@@ -609,10 +701,12 @@ def _show_vuln_detail(vuln: Dict) -> None:
609
701
  click.echo(f" Detected: {vuln.get('detection_time', '-')}")
610
702
  click.echo(f" Published: {vuln.get('published_date', '-')}")
611
703
  click.echo(f" Status: {vuln.get('status', 'open')}")
612
- click.echo(f" Verified by scan: {'Yes' if vuln.get('verified_by_scan') else 'No'}")
704
+ click.echo(
705
+ f" Verified by scan: {'Yes' if vuln.get('verified_by_scan') else 'No'}"
706
+ )
613
707
 
614
708
  # References
615
- refs = vuln.get('reference_urls', [])
709
+ refs = vuln.get("reference_urls", [])
616
710
  if refs:
617
711
  click.echo()
618
712
  click.echo(click.style(" References:", bold=True))