souleyez 2.43.29__py3-none-any.whl → 3.0.0__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 +9564 -2881
- 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 +564 -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 +409 -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 +417 -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 +913 -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 +219 -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 +237 -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 +23034 -10679
- 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.29.dist-info → souleyez-3.0.0.dist-info}/METADATA +2 -2
- souleyez-3.0.0.dist-info/RECORD +443 -0
- {souleyez-2.43.29.dist-info → souleyez-3.0.0.dist-info}/WHEEL +1 -1
- souleyez-2.43.29.dist-info/RECORD +0 -379
- {souleyez-2.43.29.dist-info → souleyez-3.0.0.dist-info}/entry_points.txt +0 -0
- {souleyez-2.43.29.dist-info → souleyez-3.0.0.dist-info}/licenses/LICENSE +0 -0
- {souleyez-2.43.29.dist-info → souleyez-3.0.0.dist-info}/top_level.txt +0 -0
souleyez/storage/hosts.py
CHANGED
|
@@ -24,7 +24,7 @@ class HostManager:
|
|
|
24
24
|
Returns:
|
|
25
25
|
host_id
|
|
26
26
|
"""
|
|
27
|
-
ip = host_data.get(
|
|
27
|
+
ip = host_data.get("ip")
|
|
28
28
|
if not ip:
|
|
29
29
|
raise ValueError("Host must have an IP address")
|
|
30
30
|
|
|
@@ -34,53 +34,58 @@ class HostManager:
|
|
|
34
34
|
# Check if host already exists
|
|
35
35
|
existing = self.db.execute_one(
|
|
36
36
|
"SELECT id, scope_status FROM hosts WHERE engagement_id = ? AND ip_address = ?",
|
|
37
|
-
(engagement_id, ip)
|
|
37
|
+
(engagement_id, ip),
|
|
38
38
|
)
|
|
39
39
|
|
|
40
40
|
if existing:
|
|
41
41
|
# Update existing host
|
|
42
|
-
host_id = existing[
|
|
42
|
+
host_id = existing["id"]
|
|
43
43
|
# Only update fields that have values (don't overwrite with NULL)
|
|
44
44
|
update_data = {}
|
|
45
45
|
|
|
46
46
|
# Always update status
|
|
47
|
-
update_data[
|
|
47
|
+
update_data["status"] = host_data.get("status", "up")
|
|
48
48
|
|
|
49
49
|
# Update scope_status if it was unknown and we now have a determination
|
|
50
|
-
if existing.get(
|
|
51
|
-
update_data[
|
|
50
|
+
if existing.get("scope_status") == "unknown" and scope_status != "unknown":
|
|
51
|
+
update_data["scope_status"] = scope_status
|
|
52
52
|
|
|
53
53
|
# Only update these fields if they have values
|
|
54
|
-
if host_data.get(
|
|
55
|
-
update_data[
|
|
56
|
-
if host_data.get(
|
|
57
|
-
update_data[
|
|
58
|
-
if host_data.get(
|
|
59
|
-
update_data[
|
|
60
|
-
if host_data.get(
|
|
61
|
-
update_data[
|
|
62
|
-
if host_data.get(
|
|
63
|
-
update_data[
|
|
54
|
+
if host_data.get("hostname"):
|
|
55
|
+
update_data["hostname"] = host_data["hostname"]
|
|
56
|
+
if host_data.get("domain"):
|
|
57
|
+
update_data["domain"] = host_data["domain"]
|
|
58
|
+
if host_data.get("os"):
|
|
59
|
+
update_data["os_name"] = host_data["os"]
|
|
60
|
+
if host_data.get("mac_address"):
|
|
61
|
+
update_data["mac_address"] = host_data["mac_address"]
|
|
62
|
+
if host_data.get("os_accuracy") is not None:
|
|
63
|
+
update_data["os_accuracy"] = host_data["os_accuracy"]
|
|
64
64
|
|
|
65
65
|
if update_data:
|
|
66
|
-
updates =
|
|
66
|
+
updates = ", ".join([f"{k} = ?" for k in update_data.keys()])
|
|
67
67
|
values = list(update_data.values()) + [host_id]
|
|
68
|
-
self.db.execute(
|
|
68
|
+
self.db.execute(
|
|
69
|
+
f"UPDATE hosts SET {updates} WHERE id = ?", tuple(values)
|
|
70
|
+
)
|
|
69
71
|
|
|
70
72
|
return host_id
|
|
71
73
|
else:
|
|
72
74
|
# Insert new host
|
|
73
|
-
host_id = self.db.insert(
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
75
|
+
host_id = self.db.insert(
|
|
76
|
+
"hosts",
|
|
77
|
+
{
|
|
78
|
+
"engagement_id": engagement_id,
|
|
79
|
+
"ip_address": ip,
|
|
80
|
+
"hostname": host_data.get("hostname"),
|
|
81
|
+
"domain": host_data.get("domain"),
|
|
82
|
+
"os_name": host_data.get("os"),
|
|
83
|
+
"mac_address": host_data.get("mac_address"),
|
|
84
|
+
"os_accuracy": host_data.get("os_accuracy"),
|
|
85
|
+
"status": host_data.get("status", "up"),
|
|
86
|
+
"scope_status": scope_status,
|
|
87
|
+
},
|
|
88
|
+
)
|
|
84
89
|
|
|
85
90
|
return host_id
|
|
86
91
|
|
|
@@ -97,14 +102,15 @@ class HostManager:
|
|
|
97
102
|
"""
|
|
98
103
|
try:
|
|
99
104
|
from souleyez.security.scope_validator import ScopeValidator
|
|
105
|
+
|
|
100
106
|
validator = ScopeValidator(engagement_id)
|
|
101
107
|
if validator.has_scope_defined():
|
|
102
108
|
result = validator.validate_ip(ip)
|
|
103
|
-
return
|
|
104
|
-
return
|
|
109
|
+
return "in_scope" if result.is_in_scope else "out_of_scope"
|
|
110
|
+
return "unknown" # No scope defined
|
|
105
111
|
except Exception as e:
|
|
106
112
|
logger.warning(f"Failed to determine scope status for {ip}: {e}")
|
|
107
|
-
return
|
|
113
|
+
return "unknown"
|
|
108
114
|
|
|
109
115
|
def update_scope_status(self, host_id: int, scope_status: str) -> bool:
|
|
110
116
|
"""
|
|
@@ -117,14 +123,16 @@ class HostManager:
|
|
|
117
123
|
Returns:
|
|
118
124
|
True if successful
|
|
119
125
|
"""
|
|
120
|
-
valid_statuses = [
|
|
126
|
+
valid_statuses = ["in_scope", "out_of_scope", "unknown"]
|
|
121
127
|
if scope_status not in valid_statuses:
|
|
122
|
-
raise ValueError(
|
|
128
|
+
raise ValueError(
|
|
129
|
+
f"Invalid scope_status: {scope_status}. Must be one of: {valid_statuses}"
|
|
130
|
+
)
|
|
123
131
|
|
|
124
132
|
try:
|
|
125
133
|
self.db.execute(
|
|
126
134
|
"UPDATE hosts SET scope_status = ? WHERE id = ?",
|
|
127
|
-
(scope_status, host_id)
|
|
135
|
+
(scope_status, host_id),
|
|
128
136
|
)
|
|
129
137
|
return True
|
|
130
138
|
except Exception:
|
|
@@ -148,23 +156,26 @@ class HostManager:
|
|
|
148
156
|
out_of_scope = 0
|
|
149
157
|
|
|
150
158
|
for host in hosts:
|
|
151
|
-
ip = host.get(
|
|
159
|
+
ip = host.get("ip") or host.get("ip_address")
|
|
152
160
|
new_status = self._determine_scope_status(engagement_id, ip)
|
|
153
|
-
if new_status != host.get(
|
|
154
|
-
self.update_scope_status(host[
|
|
161
|
+
if new_status != host.get("scope_status"):
|
|
162
|
+
self.update_scope_status(host["id"], new_status)
|
|
155
163
|
updated += 1
|
|
156
164
|
|
|
157
|
-
if new_status ==
|
|
165
|
+
if new_status == "in_scope":
|
|
158
166
|
in_scope += 1
|
|
159
|
-
elif new_status ==
|
|
167
|
+
elif new_status == "out_of_scope":
|
|
160
168
|
out_of_scope += 1
|
|
161
169
|
|
|
162
|
-
return {
|
|
170
|
+
return {"updated": updated, "in_scope": in_scope, "out_of_scope": out_of_scope}
|
|
163
171
|
|
|
164
172
|
def add_service(self, host_id: int, service_data: Dict[str, Any]) -> int:
|
|
165
173
|
"""
|
|
166
174
|
Add or update a service for a host.
|
|
167
175
|
|
|
176
|
+
Uses atomic upsert (INSERT ... ON CONFLICT DO UPDATE) to handle
|
|
177
|
+
duplicate services properly without race conditions.
|
|
178
|
+
|
|
168
179
|
Args:
|
|
169
180
|
host_id: Host ID
|
|
170
181
|
service_data: Service data (port, protocol, state, service, version)
|
|
@@ -172,49 +183,57 @@ class HostManager:
|
|
|
172
183
|
Returns:
|
|
173
184
|
service_id
|
|
174
185
|
"""
|
|
175
|
-
port = service_data.get(
|
|
176
|
-
protocol = service_data.get(
|
|
186
|
+
port = service_data.get("port")
|
|
187
|
+
protocol = service_data.get("protocol", "tcp")
|
|
177
188
|
|
|
178
189
|
if not port:
|
|
179
190
|
raise ValueError("Service must have a port")
|
|
180
191
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
)
|
|
186
|
-
|
|
187
|
-
if existing:
|
|
188
|
-
# Update existing service
|
|
189
|
-
service_id = existing['id']
|
|
190
|
-
update_data = {
|
|
191
|
-
'state': service_data.get('state', 'open'),
|
|
192
|
-
'service_name': service_data.get('service') or 'unknown',
|
|
193
|
-
'service_version': service_data.get('version'),
|
|
194
|
-
'service_product': service_data.get('product')
|
|
195
|
-
}
|
|
192
|
+
state = service_data.get("state", "open")
|
|
193
|
+
service_name = service_data.get("service") or "unknown"
|
|
194
|
+
service_version = service_data.get("version")
|
|
195
|
+
service_product = service_data.get("product")
|
|
196
196
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
197
|
+
# Use atomic upsert - INSERT with ON CONFLICT UPDATE
|
|
198
|
+
# This handles duplicates properly without race conditions
|
|
199
|
+
conn = self.db.get_connection()
|
|
200
|
+
cursor = conn.cursor()
|
|
201
|
+
try:
|
|
202
|
+
cursor.execute(
|
|
203
|
+
"""
|
|
204
|
+
INSERT INTO services (host_id, port, protocol, state, service_name, service_version, service_product)
|
|
205
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
206
|
+
ON CONFLICT(host_id, port, protocol) DO UPDATE SET
|
|
207
|
+
state = excluded.state,
|
|
208
|
+
service_name = excluded.service_name,
|
|
209
|
+
service_version = COALESCE(excluded.service_version, service_version),
|
|
210
|
+
service_product = COALESCE(excluded.service_product, service_product)
|
|
211
|
+
""",
|
|
212
|
+
(
|
|
213
|
+
host_id,
|
|
214
|
+
port,
|
|
215
|
+
protocol,
|
|
216
|
+
state,
|
|
217
|
+
service_name,
|
|
218
|
+
service_version,
|
|
219
|
+
service_product,
|
|
220
|
+
),
|
|
221
|
+
)
|
|
222
|
+
conn.commit()
|
|
223
|
+
|
|
224
|
+
# Get the service_id (either newly inserted or existing)
|
|
225
|
+
result = cursor.execute(
|
|
226
|
+
"SELECT id FROM services WHERE host_id = ? AND port = ? AND protocol = ?",
|
|
227
|
+
(host_id, port, protocol),
|
|
228
|
+
).fetchone()
|
|
229
|
+
return result[0] if result else 0
|
|
230
|
+
except Exception as e:
|
|
231
|
+
conn.rollback()
|
|
232
|
+
raise e
|
|
216
233
|
|
|
217
|
-
def import_nmap_results(
|
|
234
|
+
def import_nmap_results(
|
|
235
|
+
self, engagement_id: int, parsed_data: Dict[str, Any]
|
|
236
|
+
) -> Dict[str, int]:
|
|
218
237
|
"""
|
|
219
238
|
Import parsed nmap results into the database.
|
|
220
239
|
|
|
@@ -228,57 +247,49 @@ class HostManager:
|
|
|
228
247
|
hosts_added = 0
|
|
229
248
|
services_added = 0
|
|
230
249
|
|
|
231
|
-
for host_data in parsed_data.get(
|
|
250
|
+
for host_data in parsed_data.get("hosts", []):
|
|
232
251
|
# Add/update host
|
|
233
252
|
host_id = self.add_or_update_host(engagement_id, host_data)
|
|
234
253
|
|
|
235
254
|
# Only count live hosts
|
|
236
|
-
if host_data.get(
|
|
255
|
+
if host_data.get("status") == "up":
|
|
237
256
|
hosts_added += 1
|
|
238
257
|
|
|
239
258
|
# Add services
|
|
240
|
-
for service_data in host_data.get(
|
|
259
|
+
for service_data in host_data.get("services", []):
|
|
241
260
|
self.add_service(host_id, service_data)
|
|
242
261
|
services_added += 1
|
|
243
262
|
|
|
244
|
-
return {
|
|
245
|
-
'hosts_added': hosts_added,
|
|
246
|
-
'services_added': services_added
|
|
247
|
-
}
|
|
263
|
+
return {"hosts_added": hosts_added, "services_added": services_added}
|
|
248
264
|
|
|
249
265
|
def list_hosts(self, engagement_id: int, limit: int = None) -> List[Dict[str, Any]]:
|
|
250
266
|
"""List all hosts in engagement with optional limit."""
|
|
251
267
|
query = "SELECT * FROM hosts WHERE engagement_id = ? ORDER BY ip_address"
|
|
252
268
|
params = [engagement_id]
|
|
253
|
-
|
|
269
|
+
|
|
254
270
|
if limit:
|
|
255
271
|
query += " LIMIT ?"
|
|
256
272
|
params.append(limit)
|
|
257
|
-
|
|
273
|
+
|
|
258
274
|
hosts = self.db.execute(query, tuple(params))
|
|
259
|
-
|
|
275
|
+
|
|
260
276
|
# Normalize column names for compatibility (ip_address -> ip)
|
|
261
277
|
return [
|
|
262
|
-
{**host,
|
|
263
|
-
for host in hosts
|
|
278
|
+
{**host, "ip": host.get("ip_address") or host.get("ip")} for host in hosts
|
|
264
279
|
]
|
|
265
280
|
|
|
266
281
|
def get_host(self, host_id: int) -> Optional[Dict[str, Any]]:
|
|
267
282
|
"""Get a single host by ID."""
|
|
268
|
-
host = self.db.execute_one(
|
|
269
|
-
"SELECT * FROM hosts WHERE id = ?",
|
|
270
|
-
(host_id,)
|
|
271
|
-
)
|
|
283
|
+
host = self.db.execute_one("SELECT * FROM hosts WHERE id = ?", (host_id,))
|
|
272
284
|
if host:
|
|
273
285
|
# Normalize column names for compatibility (ip_address -> ip)
|
|
274
|
-
host[
|
|
286
|
+
host["ip"] = host.get("ip_address") or host.get("ip")
|
|
275
287
|
return host
|
|
276
288
|
|
|
277
289
|
def get_host_services(self, host_id: int) -> List[Dict[str, Any]]:
|
|
278
290
|
"""Get all services for a host."""
|
|
279
291
|
return self.db.execute(
|
|
280
|
-
"SELECT * FROM services WHERE host_id = ? ORDER BY port",
|
|
281
|
-
(host_id,)
|
|
292
|
+
"SELECT * FROM services WHERE host_id = ? ORDER BY port", (host_id,)
|
|
282
293
|
)
|
|
283
294
|
|
|
284
295
|
def get_all_services(
|
|
@@ -288,7 +299,7 @@ class HostManager:
|
|
|
288
299
|
port_min: int = None,
|
|
289
300
|
port_max: int = None,
|
|
290
301
|
protocol: str = None,
|
|
291
|
-
sort_by: str =
|
|
302
|
+
sort_by: str = "port",
|
|
292
303
|
) -> List[Dict[str, Any]]:
|
|
293
304
|
"""
|
|
294
305
|
Get all services across all hosts in engagement with optional filters.
|
|
@@ -332,9 +343,9 @@ class HostManager:
|
|
|
332
343
|
params.append(protocol)
|
|
333
344
|
|
|
334
345
|
# Add sorting
|
|
335
|
-
if sort_by ==
|
|
346
|
+
if sort_by == "service":
|
|
336
347
|
query += " ORDER BY s.service_name, s.port"
|
|
337
|
-
elif sort_by ==
|
|
348
|
+
elif sort_by == "protocol":
|
|
338
349
|
query += " ORDER BY s.protocol, s.port"
|
|
339
350
|
else: # default to port
|
|
340
351
|
query += " ORDER BY s.port"
|
|
@@ -345,7 +356,7 @@ class HostManager:
|
|
|
345
356
|
"""Get host by IP address."""
|
|
346
357
|
return self.db.execute_one(
|
|
347
358
|
"SELECT * FROM hosts WHERE engagement_id = ? AND ip_address = ?",
|
|
348
|
-
(engagement_id, ip)
|
|
359
|
+
(engagement_id, ip),
|
|
349
360
|
)
|
|
350
361
|
|
|
351
362
|
def search_hosts(
|
|
@@ -354,7 +365,7 @@ class HostManager:
|
|
|
354
365
|
search: str = None,
|
|
355
366
|
os_name: str = None,
|
|
356
367
|
status: str = None,
|
|
357
|
-
tags: str = None
|
|
368
|
+
tags: str = None,
|
|
358
369
|
) -> List[Dict[str, Any]]:
|
|
359
370
|
"""
|
|
360
371
|
Search and filter hosts.
|
|
@@ -409,17 +420,19 @@ class HostManager:
|
|
|
409
420
|
if not host:
|
|
410
421
|
return False
|
|
411
422
|
|
|
412
|
-
current_tags = host.get(
|
|
413
|
-
tag_list = [t.strip() for t in current_tags.split(
|
|
423
|
+
current_tags = host.get("tags", "") or ""
|
|
424
|
+
tag_list = [t.strip() for t in current_tags.split(",") if t.strip()]
|
|
414
425
|
|
|
415
426
|
# Add tag if not already present
|
|
416
427
|
if tag not in tag_list:
|
|
417
428
|
tag_list.append(tag)
|
|
418
429
|
|
|
419
|
-
new_tags =
|
|
430
|
+
new_tags = ", ".join(tag_list)
|
|
420
431
|
|
|
421
432
|
try:
|
|
422
|
-
self.db.execute(
|
|
433
|
+
self.db.execute(
|
|
434
|
+
"UPDATE hosts SET tags = ? WHERE id = ?", (new_tags, host_id)
|
|
435
|
+
)
|
|
423
436
|
return True
|
|
424
437
|
except Exception:
|
|
425
438
|
return False
|
|
@@ -439,17 +452,19 @@ class HostManager:
|
|
|
439
452
|
if not host:
|
|
440
453
|
return False
|
|
441
454
|
|
|
442
|
-
current_tags = host.get(
|
|
443
|
-
tag_list = [t.strip() for t in current_tags.split(
|
|
455
|
+
current_tags = host.get("tags", "") or ""
|
|
456
|
+
tag_list = [t.strip() for t in current_tags.split(",") if t.strip()]
|
|
444
457
|
|
|
445
458
|
# Remove tag if present
|
|
446
459
|
if tag in tag_list:
|
|
447
460
|
tag_list.remove(tag)
|
|
448
461
|
|
|
449
|
-
new_tags =
|
|
462
|
+
new_tags = ", ".join(tag_list)
|
|
450
463
|
|
|
451
464
|
try:
|
|
452
|
-
self.db.execute(
|
|
465
|
+
self.db.execute(
|
|
466
|
+
"UPDATE hosts SET tags = ? WHERE id = ?", (new_tags, host_id)
|
|
467
|
+
)
|
|
453
468
|
return True
|
|
454
469
|
except Exception:
|
|
455
470
|
return False
|
|
@@ -473,91 +488,103 @@ class HostManager:
|
|
|
473
488
|
|
|
474
489
|
def get_all_tags(self, engagement_id: int) -> List[str]:
|
|
475
490
|
"""Get list of all unique tags used in engagement."""
|
|
476
|
-
hosts = self.db.execute(
|
|
491
|
+
hosts = self.db.execute(
|
|
492
|
+
"SELECT tags FROM hosts WHERE engagement_id = ?", (engagement_id,)
|
|
493
|
+
)
|
|
477
494
|
|
|
478
495
|
all_tags = set()
|
|
479
496
|
for host in hosts:
|
|
480
|
-
tags_str = host.get(
|
|
497
|
+
tags_str = host.get("tags", "") or ""
|
|
481
498
|
if tags_str:
|
|
482
|
-
tags = [t.strip() for t in tags_str.split(
|
|
499
|
+
tags = [t.strip() for t in tags_str.split(",") if t.strip()]
|
|
483
500
|
all_tags.update(tags)
|
|
484
501
|
|
|
485
502
|
return sorted(list(all_tags))
|
|
486
503
|
|
|
487
|
-
def update_host_status(
|
|
504
|
+
def update_host_status(
|
|
505
|
+
self,
|
|
506
|
+
host_id: int,
|
|
507
|
+
status: str = None,
|
|
508
|
+
access_level: str = None,
|
|
509
|
+
notes: str = None,
|
|
510
|
+
) -> bool:
|
|
488
511
|
"""
|
|
489
512
|
Update host status and access level.
|
|
490
|
-
|
|
513
|
+
|
|
491
514
|
Args:
|
|
492
515
|
host_id: Host ID
|
|
493
516
|
status: Host status (active/compromised/offline)
|
|
494
517
|
access_level: Access level (none/user/admin/root)
|
|
495
518
|
notes: Optional notes
|
|
496
|
-
|
|
519
|
+
|
|
497
520
|
Returns:
|
|
498
521
|
bool: True if successful
|
|
499
522
|
"""
|
|
500
523
|
updates = []
|
|
501
524
|
params = []
|
|
502
|
-
|
|
525
|
+
|
|
503
526
|
if status:
|
|
504
527
|
updates.append("status = ?")
|
|
505
528
|
params.append(status)
|
|
506
|
-
|
|
529
|
+
|
|
507
530
|
if access_level:
|
|
508
531
|
updates.append("access_level = ?")
|
|
509
532
|
params.append(access_level)
|
|
510
|
-
|
|
533
|
+
|
|
511
534
|
if notes is not None:
|
|
512
535
|
updates.append("notes = ?")
|
|
513
536
|
params.append(notes)
|
|
514
|
-
|
|
537
|
+
|
|
515
538
|
if not updates:
|
|
516
539
|
return False
|
|
517
|
-
|
|
540
|
+
|
|
518
541
|
params.append(host_id)
|
|
519
542
|
query = f"UPDATE hosts SET {', '.join(updates)} WHERE id = ?"
|
|
520
|
-
|
|
543
|
+
|
|
521
544
|
try:
|
|
522
545
|
self.db.execute(query, tuple(params))
|
|
523
546
|
return True
|
|
524
547
|
except Exception:
|
|
525
548
|
return False
|
|
526
|
-
|
|
549
|
+
|
|
527
550
|
def update_hostname(self, host_id: int, hostname: str) -> bool:
|
|
528
551
|
"""
|
|
529
552
|
Update hostname for a host.
|
|
530
|
-
|
|
553
|
+
|
|
531
554
|
Args:
|
|
532
555
|
host_id: Host ID
|
|
533
556
|
hostname: New hostname
|
|
534
|
-
|
|
557
|
+
|
|
535
558
|
Returns:
|
|
536
559
|
bool: True if successful
|
|
537
560
|
"""
|
|
538
561
|
try:
|
|
539
|
-
self.db.execute(
|
|
562
|
+
self.db.execute(
|
|
563
|
+
"UPDATE hosts SET hostname = ? WHERE id = ?", (hostname, host_id)
|
|
564
|
+
)
|
|
540
565
|
return True
|
|
541
566
|
except Exception:
|
|
542
567
|
return False
|
|
543
|
-
|
|
568
|
+
|
|
544
569
|
def update_os(self, host_id: int, os_name: str) -> bool:
|
|
545
570
|
"""
|
|
546
571
|
Update OS name for a host.
|
|
547
|
-
|
|
572
|
+
|
|
548
573
|
Args:
|
|
549
574
|
host_id: Host ID
|
|
550
575
|
os_name: New OS name
|
|
551
|
-
|
|
576
|
+
|
|
552
577
|
Returns:
|
|
553
578
|
bool: True if successful
|
|
554
579
|
"""
|
|
555
580
|
try:
|
|
556
|
-
self.db.execute(
|
|
581
|
+
self.db.execute(
|
|
582
|
+
"UPDATE hosts SET os_name = ? WHERE id = ?", (os_name, host_id)
|
|
583
|
+
)
|
|
557
584
|
return True
|
|
558
585
|
except Exception:
|
|
559
586
|
return False
|
|
560
|
-
|
|
587
|
+
|
|
561
588
|
def delete_host(self, host_id: int) -> bool:
|
|
562
589
|
"""
|
|
563
590
|
Delete a host and all associated data (services, findings, etc.).
|
|
@@ -574,6 +601,7 @@ class HostManager:
|
|
|
574
601
|
# Check permission
|
|
575
602
|
from souleyez.auth import get_current_user
|
|
576
603
|
from souleyez.auth.permissions import Permission, PermissionChecker
|
|
604
|
+
|
|
577
605
|
user = get_current_user()
|
|
578
606
|
if user:
|
|
579
607
|
checker = PermissionChecker(user.role, user.tier)
|
|
@@ -583,25 +611,29 @@ class HostManager:
|
|
|
583
611
|
try:
|
|
584
612
|
# Delete all associated data in correct order (respecting foreign keys)
|
|
585
613
|
# Note: Foreign keys are not enabled by default in SQLite, so we handle explicitly
|
|
586
|
-
|
|
614
|
+
|
|
587
615
|
# 1. Delete services (has FK to hosts with CASCADE)
|
|
588
616
|
self.db.execute("DELETE FROM services WHERE host_id = ?", (host_id,))
|
|
589
|
-
|
|
617
|
+
|
|
590
618
|
# 2. Delete web paths (has FK to hosts with CASCADE)
|
|
591
619
|
self.db.execute("DELETE FROM web_paths WHERE host_id = ?", (host_id,))
|
|
592
|
-
|
|
620
|
+
|
|
593
621
|
# 3. Delete SMB shares (has FK to hosts with CASCADE)
|
|
594
622
|
self.db.execute("DELETE FROM smb_shares WHERE host_id = ?", (host_id,))
|
|
595
|
-
|
|
623
|
+
|
|
596
624
|
# 4. Update findings to set host_id to NULL (has FK with SET NULL)
|
|
597
|
-
self.db.execute(
|
|
598
|
-
|
|
625
|
+
self.db.execute(
|
|
626
|
+
"UPDATE findings SET host_id = NULL WHERE host_id = ?", (host_id,)
|
|
627
|
+
)
|
|
628
|
+
|
|
599
629
|
# 5. Update credentials to set host_id to NULL (has FK but no ON DELETE clause)
|
|
600
|
-
self.db.execute(
|
|
601
|
-
|
|
630
|
+
self.db.execute(
|
|
631
|
+
"UPDATE credentials SET host_id = NULL WHERE host_id = ?", (host_id,)
|
|
632
|
+
)
|
|
633
|
+
|
|
602
634
|
# 6. Finally delete the host
|
|
603
635
|
self.db.execute("DELETE FROM hosts WHERE id = ?", (host_id,))
|
|
604
|
-
|
|
636
|
+
|
|
605
637
|
logger.info(f"Deleted host {host_id} and all associated data")
|
|
606
638
|
return True
|
|
607
639
|
except Exception as e:
|
|
@@ -611,10 +643,10 @@ class HostManager:
|
|
|
611
643
|
def get_host_vulnerability_count(self, host_id: int) -> int:
|
|
612
644
|
"""
|
|
613
645
|
Get count of vulnerabilities (findings) for a specific host.
|
|
614
|
-
|
|
646
|
+
|
|
615
647
|
Args:
|
|
616
648
|
host_id: Host ID
|
|
617
|
-
|
|
649
|
+
|
|
618
650
|
Returns:
|
|
619
651
|
Count of vulnerability findings for this host
|
|
620
652
|
"""
|
|
@@ -623,6 +655,6 @@ class HostManager:
|
|
|
623
655
|
WHERE host_id = ?
|
|
624
656
|
AND finding_type IN ('vulnerability', 'sql_injection', 'xss', 'file_inclusion',
|
|
625
657
|
'web_vulnerability', 'sql_injection_exploitation')""",
|
|
626
|
-
(host_id,)
|
|
658
|
+
(host_id,),
|
|
627
659
|
)
|
|
628
|
-
return result[
|
|
660
|
+
return result["count"] if result else 0
|