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.
- souleyez/__init__.py +1 -2
- souleyez/ai/__init__.py +21 -15
- souleyez/ai/action_mapper.py +249 -150
- souleyez/ai/chain_advisor.py +116 -100
- souleyez/ai/claude_provider.py +29 -28
- souleyez/ai/context_builder.py +80 -62
- souleyez/ai/executor.py +158 -117
- souleyez/ai/feedback_handler.py +136 -121
- souleyez/ai/llm_factory.py +27 -20
- souleyez/ai/llm_provider.py +4 -2
- souleyez/ai/ollama_provider.py +6 -9
- souleyez/ai/ollama_service.py +44 -37
- souleyez/ai/path_scorer.py +91 -76
- souleyez/ai/recommender.py +176 -144
- souleyez/ai/report_context.py +74 -73
- souleyez/ai/report_service.py +84 -66
- souleyez/ai/result_parser.py +222 -229
- souleyez/ai/safety.py +67 -44
- souleyez/auth/__init__.py +23 -22
- souleyez/auth/audit.py +36 -26
- souleyez/auth/engagement_access.py +65 -48
- souleyez/auth/permissions.py +14 -3
- souleyez/auth/session_manager.py +54 -37
- souleyez/auth/user_manager.py +109 -64
- souleyez/commands/audit.py +40 -43
- souleyez/commands/auth.py +35 -15
- souleyez/commands/deliverables.py +55 -50
- souleyez/commands/engagement.py +47 -28
- souleyez/commands/license.py +32 -23
- souleyez/commands/screenshots.py +36 -32
- souleyez/commands/user.py +82 -36
- souleyez/config.py +52 -44
- souleyez/core/credential_tester.py +87 -81
- souleyez/core/cve_mappings.py +179 -192
- souleyez/core/cve_matcher.py +162 -148
- souleyez/core/msf_auto_mapper.py +100 -83
- souleyez/core/msf_chain_engine.py +294 -256
- souleyez/core/msf_database.py +153 -70
- souleyez/core/msf_integration.py +679 -673
- souleyez/core/msf_rpc_client.py +40 -42
- souleyez/core/msf_rpc_manager.py +77 -79
- souleyez/core/msf_sync_manager.py +241 -181
- souleyez/core/network_utils.py +22 -15
- souleyez/core/parser_handler.py +34 -25
- souleyez/core/pending_chains.py +114 -63
- souleyez/core/templates.py +158 -107
- souleyez/core/tool_chaining.py +9526 -2879
- souleyez/core/version_utils.py +79 -94
- souleyez/core/vuln_correlation.py +136 -89
- souleyez/core/web_utils.py +33 -32
- souleyez/data/wordlists/ad_users.txt +378 -0
- souleyez/data/wordlists/api_endpoints_large.txt +769 -0
- souleyez/data/wordlists/home_dir_sensitive.txt +39 -0
- souleyez/data/wordlists/lfi_payloads.txt +82 -0
- souleyez/data/wordlists/passwords_brute.txt +1548 -0
- souleyez/data/wordlists/passwords_crack.txt +2479 -0
- souleyez/data/wordlists/passwords_spray.txt +386 -0
- souleyez/data/wordlists/subdomains_large.txt +5057 -0
- souleyez/data/wordlists/usernames_common.txt +694 -0
- souleyez/data/wordlists/web_dirs_large.txt +4769 -0
- souleyez/detection/__init__.py +1 -1
- souleyez/detection/attack_signatures.py +12 -17
- souleyez/detection/mitre_mappings.py +61 -55
- souleyez/detection/validator.py +97 -86
- souleyez/devtools.py +23 -10
- souleyez/docs/README.md +4 -4
- souleyez/docs/api-reference/cli-commands.md +2 -2
- souleyez/docs/developer-guide/adding-new-tools.md +562 -0
- souleyez/docs/user-guide/auto-chaining.md +30 -8
- souleyez/docs/user-guide/getting-started.md +1 -1
- souleyez/docs/user-guide/installation.md +26 -3
- souleyez/docs/user-guide/metasploit-integration.md +2 -2
- souleyez/docs/user-guide/rbac.md +1 -1
- souleyez/docs/user-guide/scope-management.md +1 -1
- souleyez/docs/user-guide/siem-integration.md +1 -1
- souleyez/docs/user-guide/tools-reference.md +1 -8
- souleyez/docs/user-guide/worker-management.md +1 -1
- souleyez/engine/background.py +1239 -535
- souleyez/engine/base.py +4 -1
- souleyez/engine/job_status.py +17 -49
- souleyez/engine/log_sanitizer.py +103 -77
- souleyez/engine/manager.py +38 -7
- souleyez/engine/result_handler.py +2200 -1550
- souleyez/engine/worker_manager.py +50 -41
- souleyez/export/evidence_bundle.py +72 -62
- souleyez/feature_flags/features.py +16 -20
- souleyez/feature_flags.py +5 -9
- souleyez/handlers/__init__.py +11 -0
- souleyez/handlers/base.py +188 -0
- souleyez/handlers/bash_handler.py +277 -0
- souleyez/handlers/bloodhound_handler.py +243 -0
- souleyez/handlers/certipy_handler.py +311 -0
- souleyez/handlers/crackmapexec_handler.py +486 -0
- souleyez/handlers/dnsrecon_handler.py +344 -0
- souleyez/handlers/enum4linux_handler.py +400 -0
- souleyez/handlers/evil_winrm_handler.py +493 -0
- souleyez/handlers/ffuf_handler.py +815 -0
- souleyez/handlers/gobuster_handler.py +1114 -0
- souleyez/handlers/gpp_extract_handler.py +334 -0
- souleyez/handlers/hashcat_handler.py +444 -0
- souleyez/handlers/hydra_handler.py +563 -0
- souleyez/handlers/impacket_getuserspns_handler.py +343 -0
- souleyez/handlers/impacket_psexec_handler.py +222 -0
- souleyez/handlers/impacket_secretsdump_handler.py +426 -0
- souleyez/handlers/john_handler.py +286 -0
- souleyez/handlers/katana_handler.py +425 -0
- souleyez/handlers/kerbrute_handler.py +298 -0
- souleyez/handlers/ldapsearch_handler.py +636 -0
- souleyez/handlers/lfi_extract_handler.py +464 -0
- souleyez/handlers/msf_auxiliary_handler.py +408 -0
- souleyez/handlers/msf_exploit_handler.py +380 -0
- souleyez/handlers/nikto_handler.py +413 -0
- souleyez/handlers/nmap_handler.py +821 -0
- souleyez/handlers/nuclei_handler.py +359 -0
- souleyez/handlers/nxc_handler.py +371 -0
- souleyez/handlers/rdp_sec_check_handler.py +353 -0
- souleyez/handlers/registry.py +292 -0
- souleyez/handlers/responder_handler.py +232 -0
- souleyez/handlers/service_explorer_handler.py +434 -0
- souleyez/handlers/smbclient_handler.py +344 -0
- souleyez/handlers/smbmap_handler.py +510 -0
- souleyez/handlers/smbpasswd_handler.py +296 -0
- souleyez/handlers/sqlmap_handler.py +1116 -0
- souleyez/handlers/theharvester_handler.py +601 -0
- souleyez/handlers/web_login_test_handler.py +327 -0
- souleyez/handlers/whois_handler.py +277 -0
- souleyez/handlers/wpscan_handler.py +554 -0
- souleyez/history.py +32 -16
- souleyez/importers/msf_importer.py +106 -75
- souleyez/importers/smart_importer.py +208 -147
- souleyez/integrations/siem/__init__.py +10 -10
- souleyez/integrations/siem/base.py +17 -18
- souleyez/integrations/siem/elastic.py +108 -122
- souleyez/integrations/siem/factory.py +207 -80
- souleyez/integrations/siem/googlesecops.py +146 -154
- souleyez/integrations/siem/rule_mappings/__init__.py +1 -1
- souleyez/integrations/siem/rule_mappings/wazuh_rules.py +8 -5
- souleyez/integrations/siem/sentinel.py +107 -109
- souleyez/integrations/siem/splunk.py +246 -212
- souleyez/integrations/siem/wazuh.py +65 -71
- souleyez/integrations/wazuh/__init__.py +5 -5
- souleyez/integrations/wazuh/client.py +70 -93
- souleyez/integrations/wazuh/config.py +85 -57
- souleyez/integrations/wazuh/host_mapper.py +28 -36
- souleyez/integrations/wazuh/sync.py +78 -68
- souleyez/intelligence/__init__.py +4 -5
- souleyez/intelligence/correlation_analyzer.py +309 -295
- souleyez/intelligence/exploit_knowledge.py +661 -623
- souleyez/intelligence/exploit_suggestions.py +159 -139
- souleyez/intelligence/gap_analyzer.py +132 -97
- souleyez/intelligence/gap_detector.py +251 -214
- souleyez/intelligence/sensitive_tables.py +266 -129
- souleyez/intelligence/service_parser.py +137 -123
- souleyez/intelligence/surface_analyzer.py +407 -268
- souleyez/intelligence/target_parser.py +159 -162
- souleyez/licensing/__init__.py +6 -6
- souleyez/licensing/validator.py +17 -19
- souleyez/log_config.py +79 -54
- souleyez/main.py +1505 -687
- souleyez/migrations/fix_job_counter.py +16 -14
- souleyez/parsers/bloodhound_parser.py +41 -39
- souleyez/parsers/crackmapexec_parser.py +178 -111
- souleyez/parsers/dalfox_parser.py +72 -77
- souleyez/parsers/dnsrecon_parser.py +103 -91
- souleyez/parsers/enum4linux_parser.py +183 -153
- souleyez/parsers/ffuf_parser.py +29 -25
- souleyez/parsers/gobuster_parser.py +301 -41
- souleyez/parsers/hashcat_parser.py +324 -79
- souleyez/parsers/http_fingerprint_parser.py +350 -103
- souleyez/parsers/hydra_parser.py +131 -111
- souleyez/parsers/impacket_parser.py +231 -178
- souleyez/parsers/john_parser.py +98 -86
- souleyez/parsers/katana_parser.py +316 -0
- souleyez/parsers/msf_parser.py +943 -498
- souleyez/parsers/nikto_parser.py +346 -65
- souleyez/parsers/nmap_parser.py +262 -174
- souleyez/parsers/nuclei_parser.py +40 -44
- souleyez/parsers/responder_parser.py +26 -26
- souleyez/parsers/searchsploit_parser.py +74 -74
- souleyez/parsers/service_explorer_parser.py +279 -0
- souleyez/parsers/smbmap_parser.py +180 -124
- souleyez/parsers/sqlmap_parser.py +434 -308
- souleyez/parsers/theharvester_parser.py +75 -57
- souleyez/parsers/whois_parser.py +135 -94
- souleyez/parsers/wpscan_parser.py +278 -190
- souleyez/plugins/afp.py +44 -36
- souleyez/plugins/afp_brute.py +114 -46
- souleyez/plugins/ard.py +48 -37
- souleyez/plugins/bloodhound.py +95 -61
- souleyez/plugins/certipy.py +303 -0
- souleyez/plugins/crackmapexec.py +186 -85
- souleyez/plugins/dalfox.py +120 -59
- souleyez/plugins/dns_hijack.py +146 -41
- souleyez/plugins/dnsrecon.py +97 -61
- souleyez/plugins/enum4linux.py +91 -66
- souleyez/plugins/evil_winrm.py +291 -0
- souleyez/plugins/ffuf.py +166 -90
- souleyez/plugins/firmware_extract.py +133 -29
- souleyez/plugins/gobuster.py +387 -190
- souleyez/plugins/gpp_extract.py +393 -0
- souleyez/plugins/hashcat.py +100 -73
- souleyez/plugins/http_fingerprint.py +854 -267
- souleyez/plugins/hydra.py +566 -200
- souleyez/plugins/impacket_getnpusers.py +117 -69
- souleyez/plugins/impacket_psexec.py +84 -64
- souleyez/plugins/impacket_secretsdump.py +103 -69
- souleyez/plugins/impacket_smbclient.py +89 -75
- souleyez/plugins/john.py +86 -69
- souleyez/plugins/katana.py +313 -0
- souleyez/plugins/kerbrute.py +237 -0
- souleyez/plugins/lfi_extract.py +541 -0
- souleyez/plugins/macos_ssh.py +117 -48
- souleyez/plugins/mdns.py +35 -30
- souleyez/plugins/msf_auxiliary.py +253 -130
- souleyez/plugins/msf_exploit.py +239 -161
- souleyez/plugins/nikto.py +134 -78
- souleyez/plugins/nmap.py +275 -91
- souleyez/plugins/nuclei.py +180 -89
- souleyez/plugins/nxc.py +285 -0
- souleyez/plugins/plugin_base.py +35 -36
- souleyez/plugins/plugin_template.py +13 -5
- souleyez/plugins/rdp_sec_check.py +130 -0
- souleyez/plugins/responder.py +112 -71
- souleyez/plugins/router_http_brute.py +76 -65
- souleyez/plugins/router_ssh_brute.py +118 -41
- souleyez/plugins/router_telnet_brute.py +124 -42
- souleyez/plugins/routersploit.py +91 -59
- souleyez/plugins/routersploit_exploit.py +77 -55
- souleyez/plugins/searchsploit.py +91 -77
- souleyez/plugins/service_explorer.py +1160 -0
- souleyez/plugins/smbmap.py +122 -72
- souleyez/plugins/smbpasswd.py +215 -0
- souleyez/plugins/sqlmap.py +301 -113
- souleyez/plugins/theharvester.py +127 -75
- souleyez/plugins/tr069.py +79 -57
- souleyez/plugins/upnp.py +65 -47
- souleyez/plugins/upnp_abuse.py +73 -55
- souleyez/plugins/vnc_access.py +129 -42
- souleyez/plugins/vnc_brute.py +109 -38
- souleyez/plugins/web_login_test.py +417 -0
- souleyez/plugins/whois.py +77 -58
- souleyez/plugins/wpscan.py +173 -69
- souleyez/reporting/__init__.py +2 -1
- souleyez/reporting/attack_chain.py +411 -346
- souleyez/reporting/charts.py +436 -501
- souleyez/reporting/compliance_mappings.py +334 -201
- souleyez/reporting/detection_report.py +126 -125
- souleyez/reporting/formatters.py +828 -591
- souleyez/reporting/generator.py +386 -302
- souleyez/reporting/metrics.py +72 -75
- souleyez/scanner.py +35 -29
- souleyez/security/__init__.py +37 -11
- souleyez/security/scope_validator.py +175 -106
- souleyez/security/validation.py +223 -149
- souleyez/security.py +22 -6
- souleyez/storage/credentials.py +247 -186
- souleyez/storage/crypto.py +296 -129
- souleyez/storage/database.py +73 -50
- souleyez/storage/db.py +58 -36
- souleyez/storage/deliverable_evidence.py +177 -128
- souleyez/storage/deliverable_exporter.py +282 -246
- souleyez/storage/deliverable_templates.py +134 -116
- souleyez/storage/deliverables.py +135 -130
- souleyez/storage/engagements.py +109 -56
- souleyez/storage/evidence.py +181 -152
- souleyez/storage/execution_log.py +31 -17
- souleyez/storage/exploit_attempts.py +93 -57
- souleyez/storage/exploits.py +67 -36
- souleyez/storage/findings.py +48 -61
- souleyez/storage/hosts.py +176 -144
- souleyez/storage/migrate_to_engagements.py +43 -19
- souleyez/storage/migrations/_001_add_credential_enhancements.py +22 -12
- souleyez/storage/migrations/_002_add_status_tracking.py +10 -7
- souleyez/storage/migrations/_003_add_execution_log.py +14 -8
- souleyez/storage/migrations/_005_screenshots.py +13 -5
- souleyez/storage/migrations/_006_deliverables.py +13 -5
- souleyez/storage/migrations/_007_deliverable_templates.py +12 -7
- souleyez/storage/migrations/_008_add_nuclei_table.py +10 -4
- souleyez/storage/migrations/_010_evidence_linking.py +17 -10
- souleyez/storage/migrations/_011_timeline_tracking.py +20 -13
- souleyez/storage/migrations/_012_team_collaboration.py +34 -21
- souleyez/storage/migrations/_013_add_host_tags.py +12 -6
- souleyez/storage/migrations/_014_exploit_attempts.py +22 -10
- souleyez/storage/migrations/_015_add_mac_os_fields.py +15 -7
- souleyez/storage/migrations/_016_add_domain_field.py +10 -4
- souleyez/storage/migrations/_017_msf_sessions.py +16 -8
- souleyez/storage/migrations/_018_add_osint_target.py +10 -6
- souleyez/storage/migrations/_019_add_engagement_type.py +10 -6
- souleyez/storage/migrations/_020_add_rbac.py +36 -15
- souleyez/storage/migrations/_021_wazuh_integration.py +20 -8
- souleyez/storage/migrations/_022_wazuh_indexer_columns.py +6 -4
- souleyez/storage/migrations/_023_fix_detection_results_fk.py +16 -6
- souleyez/storage/migrations/_024_wazuh_vulnerabilities.py +26 -10
- souleyez/storage/migrations/_025_multi_siem_support.py +3 -5
- souleyez/storage/migrations/_026_add_engagement_scope.py +31 -12
- souleyez/storage/migrations/_027_multi_siem_persistence.py +32 -15
- souleyez/storage/migrations/__init__.py +26 -26
- souleyez/storage/migrations/migration_manager.py +19 -19
- souleyez/storage/msf_sessions.py +100 -65
- souleyez/storage/osint.py +17 -24
- souleyez/storage/recommendation_engine.py +269 -235
- souleyez/storage/screenshots.py +33 -32
- souleyez/storage/smb_shares.py +136 -92
- souleyez/storage/sqlmap_data.py +183 -128
- souleyez/storage/team_collaboration.py +135 -141
- souleyez/storage/timeline_tracker.py +122 -94
- souleyez/storage/wazuh_vulns.py +64 -66
- souleyez/storage/web_paths.py +33 -37
- souleyez/testing/credential_tester.py +221 -205
- souleyez/ui/__init__.py +1 -1
- souleyez/ui/ai_quotes.py +12 -12
- souleyez/ui/attack_surface.py +2439 -1516
- souleyez/ui/chain_rules_view.py +914 -382
- souleyez/ui/correlation_view.py +312 -230
- souleyez/ui/dashboard.py +2382 -1130
- souleyez/ui/deliverables_view.py +148 -62
- souleyez/ui/design_system.py +13 -13
- souleyez/ui/errors.py +49 -49
- souleyez/ui/evidence_linking_view.py +284 -179
- souleyez/ui/evidence_vault.py +393 -285
- souleyez/ui/exploit_suggestions_view.py +555 -349
- souleyez/ui/export_view.py +100 -66
- souleyez/ui/gap_analysis_view.py +315 -171
- souleyez/ui/help_system.py +105 -97
- souleyez/ui/intelligence_view.py +436 -293
- souleyez/ui/interactive.py +23434 -10286
- souleyez/ui/interactive_selector.py +75 -68
- souleyez/ui/log_formatter.py +47 -39
- souleyez/ui/menu_components.py +22 -13
- souleyez/ui/msf_auxiliary_menu.py +184 -133
- souleyez/ui/pending_chains_view.py +336 -172
- souleyez/ui/progress_indicators.py +5 -3
- souleyez/ui/recommendations_view.py +195 -137
- souleyez/ui/rule_builder.py +343 -225
- souleyez/ui/setup_wizard.py +678 -284
- souleyez/ui/shortcuts.py +217 -165
- souleyez/ui/splunk_gap_analysis_view.py +452 -270
- souleyez/ui/splunk_vulns_view.py +139 -86
- souleyez/ui/team_dashboard.py +498 -335
- souleyez/ui/template_selector.py +196 -105
- souleyez/ui/terminal.py +6 -6
- souleyez/ui/timeline_view.py +198 -127
- souleyez/ui/tool_setup.py +264 -164
- souleyez/ui/tutorial.py +202 -72
- souleyez/ui/tutorial_state.py +40 -40
- souleyez/ui/wazuh_vulns_view.py +235 -141
- souleyez/ui/wordlist_browser.py +260 -107
- souleyez/ui.py +464 -312
- souleyez/utils/tool_checker.py +427 -367
- souleyez/utils.py +33 -29
- souleyez/wordlists.py +134 -167
- {souleyez-2.43.26.dist-info → souleyez-2.43.34.dist-info}/METADATA +1 -1
- souleyez-2.43.34.dist-info/RECORD +443 -0
- {souleyez-2.43.26.dist-info → souleyez-2.43.34.dist-info}/WHEEL +1 -1
- souleyez-2.43.26.dist-info/RECORD +0 -379
- {souleyez-2.43.26.dist-info → souleyez-2.43.34.dist-info}/entry_points.txt +0 -0
- {souleyez-2.43.26.dist-info → souleyez-2.43.34.dist-info}/licenses/LICENSE +0 -0
- {souleyez-2.43.26.dist-info → souleyez-2.43.34.dist-info}/top_level.txt +0 -0
souleyez/ui/wazuh_vulns_view.py
CHANGED
|
@@ -18,10 +18,10 @@ console = Console()
|
|
|
18
18
|
|
|
19
19
|
# Severity colors
|
|
20
20
|
SEVERITY_COLORS = {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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(
|
|
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(
|
|
60
|
-
click.echo(
|
|
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() ==
|
|
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(
|
|
78
|
-
last_sync = sync_status.get(
|
|
79
|
-
stale = sync_status.get(
|
|
80
|
-
status_color =
|
|
81
|
-
status_text =
|
|
82
|
-
click.echo(
|
|
83
|
-
|
|
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(
|
|
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(
|
|
94
|
-
verified = summary.get(
|
|
95
|
-
by_sev = summary.get(
|
|
96
|
-
mapped = mapping_stats.get(
|
|
97
|
-
unmapped = mapping_stats.get(
|
|
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[
|
|
117
|
+
filter_kwargs["severity"] = severity_filter
|
|
103
118
|
if host_filter:
|
|
104
|
-
filter_kwargs[
|
|
119
|
+
filter_kwargs["agent_ip"] = host_filter
|
|
105
120
|
|
|
106
|
-
all_vulns = vulns_manager.list_vulnerabilities(
|
|
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,
|
|
111
|
-
|
|
112
|
-
|
|
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 ==
|
|
159
|
+
if choice == "q":
|
|
132
160
|
return
|
|
133
|
-
elif choice ==
|
|
161
|
+
elif choice == "s":
|
|
134
162
|
_do_sync(engagement_id)
|
|
135
|
-
elif choice ==
|
|
163
|
+
elif choice == "m":
|
|
136
164
|
_mapping_menu(engagement_id)
|
|
137
|
-
elif choice ==
|
|
165
|
+
elif choice == "i":
|
|
138
166
|
# Interactive mode
|
|
139
167
|
_interactive_mode(engagement_id, all_vulns, selected_ids)
|
|
140
|
-
elif choice ==
|
|
168
|
+
elif choice == "t":
|
|
141
169
|
view_all = not view_all
|
|
142
170
|
if not view_all:
|
|
143
171
|
page = 0
|
|
144
|
-
elif choice ==
|
|
172
|
+
elif choice == "n" and not view_all and page < total_pages - 1:
|
|
145
173
|
page += 1
|
|
146
|
-
elif choice ==
|
|
174
|
+
elif choice == "p" and not view_all and page > 0:
|
|
147
175
|
page -= 1
|
|
148
|
-
elif choice ==
|
|
176
|
+
elif choice == "f":
|
|
149
177
|
severity_filter = _select_severity_filter()
|
|
150
178
|
page = 0
|
|
151
|
-
elif choice ==
|
|
179
|
+
elif choice == "h":
|
|
152
180
|
host_filter = _select_host_filter(engagement_id)
|
|
153
181
|
page = 0
|
|
154
|
-
elif choice ==
|
|
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=
|
|
192
|
+
click.echo(click.style(" Invalid number", fg="red"))
|
|
165
193
|
click.pause()
|
|
166
194
|
else:
|
|
167
|
-
click.echo(click.style("Invalid option", fg=
|
|
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,
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
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(
|
|
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(
|
|
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=
|
|
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=
|
|
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(
|
|
308
|
+
vuln_id = vuln.get("id")
|
|
262
309
|
|
|
263
310
|
# Checkbox
|
|
264
|
-
checkbox =
|
|
311
|
+
checkbox = "●" if vuln_id in selected_ids else "○"
|
|
265
312
|
|
|
266
313
|
# CVE
|
|
267
|
-
cve = vuln.get(
|
|
314
|
+
cve = vuln.get("cve_id", "-")
|
|
268
315
|
|
|
269
316
|
# Severity with color
|
|
270
|
-
severity = vuln.get(
|
|
271
|
-
sev_color = SEVERITY_COLORS.get(severity,
|
|
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(
|
|
322
|
+
host_ip = vuln.get("host_ip") or vuln.get("agent_ip", "-")
|
|
276
323
|
|
|
277
324
|
# Package (truncated)
|
|
278
|
-
package = vuln.get(
|
|
325
|
+
package = vuln.get("package_name", "-")[:24]
|
|
279
326
|
|
|
280
327
|
# CVSS
|
|
281
|
-
cvss = vuln.get(
|
|
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=
|
|
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
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
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
|
-
{
|
|
334
|
-
{
|
|
335
|
-
{
|
|
336
|
-
{
|
|
337
|
-
{
|
|
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 ==
|
|
342
|
-
sev = item.get(
|
|
343
|
-
color = SEVERITY_COLORS.get(sev,
|
|
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[
|
|
352
|
-
title=
|
|
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(
|
|
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[
|
|
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(
|
|
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 =
|
|
429
|
+
choice = (
|
|
430
|
+
click.prompt(" Select option", default="q", show_default=False)
|
|
431
|
+
.strip()
|
|
432
|
+
.lower()
|
|
433
|
+
)
|
|
379
434
|
|
|
380
|
-
if choice ==
|
|
435
|
+
if choice == "v":
|
|
381
436
|
# View details of first selected
|
|
382
|
-
vuln = selected[0][
|
|
437
|
+
vuln = selected[0]["raw"]
|
|
383
438
|
_show_vuln_detail(vuln)
|
|
384
|
-
elif choice ==
|
|
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(
|
|
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=
|
|
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 ==
|
|
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(
|
|
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(
|
|
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 ==
|
|
471
|
+
elif choice == "e":
|
|
411
472
|
# Export selected
|
|
412
473
|
_export_selected(selected)
|
|
413
|
-
elif choice ==
|
|
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(
|
|
430
|
-
export_data.append(
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
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(
|
|
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 = {
|
|
521
|
+
severity_map = {"1": "Critical", "2": "High", "3": "Medium", "4": "Low"}
|
|
455
522
|
|
|
456
|
-
if key ==
|
|
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() ==
|
|
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(
|
|
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=
|
|
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(
|
|
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=
|
|
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=
|
|
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=
|
|
586
|
+
click.echo(click.style(f" {err}", fg="red"))
|
|
512
587
|
|
|
513
588
|
click.echo()
|
|
514
|
-
click.echo(
|
|
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(
|
|
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(
|
|
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=
|
|
622
|
+
click.echo(click.style(" Unmapped Agents:", fg="yellow"))
|
|
540
623
|
for agent in unmapped[:10]:
|
|
541
|
-
click.echo(
|
|
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=
|
|
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=
|
|
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 ==
|
|
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(
|
|
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(
|
|
583
|
-
severity = vuln.get(
|
|
584
|
-
sev_color = SEVERITY_COLORS.get(severity,
|
|
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(
|
|
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(
|
|
592
|
-
|
|
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(
|
|
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(
|
|
709
|
+
refs = vuln.get("reference_urls", [])
|
|
616
710
|
if refs:
|
|
617
711
|
click.echo()
|
|
618
712
|
click.echo(click.style(" References:", bold=True))
|