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
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Timeline tracking and velocity calculations for deliverables.
|
|
3
3
|
"""
|
|
4
|
+
|
|
4
5
|
from typing import Dict, List, Optional
|
|
5
6
|
from datetime import datetime, timedelta
|
|
6
7
|
from .database import get_db
|
|
@@ -8,31 +9,34 @@ from .database import get_db
|
|
|
8
9
|
|
|
9
10
|
class TimelineTracker:
|
|
10
11
|
"""Track time spent on deliverables and predict completion."""
|
|
11
|
-
|
|
12
|
+
|
|
12
13
|
def __init__(self):
|
|
13
14
|
self.db = get_db()
|
|
14
|
-
|
|
15
|
+
|
|
15
16
|
def start_deliverable(self, deliverable_id: int):
|
|
16
17
|
"""Mark deliverable as started (sets started_at timestamp)."""
|
|
17
18
|
self.db.execute(
|
|
18
19
|
"UPDATE deliverables SET started_at = CURRENT_TIMESTAMP, status = 'in_progress' WHERE id = ?",
|
|
19
|
-
(deliverable_id,)
|
|
20
|
+
(deliverable_id,),
|
|
20
21
|
)
|
|
21
|
-
|
|
22
|
+
|
|
22
23
|
# Log activity
|
|
23
24
|
try:
|
|
24
25
|
from .team_collaboration import TeamCollaboration
|
|
25
|
-
|
|
26
|
+
|
|
27
|
+
deliverable = self.db.execute_one(
|
|
28
|
+
"SELECT engagement_id FROM deliverables WHERE id = ?", (deliverable_id,)
|
|
29
|
+
)
|
|
26
30
|
if deliverable:
|
|
27
31
|
tc = TeamCollaboration()
|
|
28
|
-
tc.log_activity(deliverable_id, deliverable[
|
|
32
|
+
tc.log_activity(deliverable_id, deliverable["engagement_id"], "started")
|
|
29
33
|
except:
|
|
30
34
|
pass # Fail silently if team collaboration not available
|
|
31
|
-
|
|
35
|
+
|
|
32
36
|
def complete_deliverable(self, deliverable_id: int, actual_hours: float = None):
|
|
33
37
|
"""
|
|
34
38
|
Mark deliverable as completed.
|
|
35
|
-
|
|
39
|
+
|
|
36
40
|
Args:
|
|
37
41
|
deliverable_id: Target deliverable
|
|
38
42
|
actual_hours: Optional manual time entry (hours)
|
|
@@ -44,72 +48,84 @@ class TimelineTracker:
|
|
|
44
48
|
status = 'completed',
|
|
45
49
|
actual_hours = ?
|
|
46
50
|
WHERE id = ?""",
|
|
47
|
-
(actual_hours, deliverable_id)
|
|
51
|
+
(actual_hours, deliverable_id),
|
|
48
52
|
)
|
|
49
53
|
else:
|
|
50
54
|
# Auto-calculate from started_at
|
|
51
55
|
deliverable = self.db.execute_one(
|
|
52
56
|
"SELECT started_at, engagement_id FROM deliverables WHERE id = ?",
|
|
53
|
-
(deliverable_id,)
|
|
57
|
+
(deliverable_id,),
|
|
54
58
|
)
|
|
55
|
-
|
|
56
|
-
engagement_id = deliverable.get(
|
|
57
|
-
|
|
58
|
-
if deliverable and deliverable.get(
|
|
59
|
-
started = datetime.fromisoformat(
|
|
59
|
+
|
|
60
|
+
engagement_id = deliverable.get("engagement_id") if deliverable else None
|
|
61
|
+
|
|
62
|
+
if deliverable and deliverable.get("started_at"):
|
|
63
|
+
started = datetime.fromisoformat(
|
|
64
|
+
deliverable["started_at"].replace("Z", "+00:00")
|
|
65
|
+
)
|
|
60
66
|
completed = datetime.now()
|
|
61
67
|
hours = (completed - started).total_seconds() / 3600
|
|
62
|
-
|
|
68
|
+
|
|
63
69
|
self.db.execute(
|
|
64
70
|
"""UPDATE deliverables
|
|
65
71
|
SET completed_at = CURRENT_TIMESTAMP,
|
|
66
72
|
status = 'completed',
|
|
67
73
|
actual_hours = ?
|
|
68
74
|
WHERE id = ?""",
|
|
69
|
-
(hours, deliverable_id)
|
|
75
|
+
(hours, deliverable_id),
|
|
70
76
|
)
|
|
71
77
|
else:
|
|
72
78
|
# No started_at, just mark complete
|
|
73
79
|
self.db.execute(
|
|
74
80
|
"UPDATE deliverables SET completed_at = CURRENT_TIMESTAMP, status = 'completed' WHERE id = ?",
|
|
75
|
-
(deliverable_id,)
|
|
81
|
+
(deliverable_id,),
|
|
76
82
|
)
|
|
77
|
-
|
|
83
|
+
|
|
78
84
|
# Log activity
|
|
79
85
|
try:
|
|
80
86
|
from .team_collaboration import TeamCollaboration
|
|
81
|
-
|
|
87
|
+
|
|
88
|
+
deliverable = self.db.execute_one(
|
|
89
|
+
"SELECT engagement_id FROM deliverables WHERE id = ?", (deliverable_id,)
|
|
90
|
+
)
|
|
82
91
|
if deliverable:
|
|
83
92
|
tc = TeamCollaboration()
|
|
84
|
-
tc.log_activity(
|
|
93
|
+
tc.log_activity(
|
|
94
|
+
deliverable_id, deliverable["engagement_id"], "completed"
|
|
95
|
+
)
|
|
85
96
|
except:
|
|
86
97
|
pass # Fail silently
|
|
87
|
-
|
|
98
|
+
|
|
88
99
|
def set_blocker(self, deliverable_id: int, blocker: str):
|
|
89
100
|
"""Set blocker text for a deliverable."""
|
|
90
101
|
self.db.execute(
|
|
91
102
|
"UPDATE deliverables SET blocker = ?, status = 'pending' WHERE id = ?",
|
|
92
|
-
(blocker, deliverable_id)
|
|
103
|
+
(blocker, deliverable_id),
|
|
93
104
|
)
|
|
94
|
-
|
|
105
|
+
|
|
95
106
|
def clear_blocker(self, deliverable_id: int):
|
|
96
107
|
"""Clear blocker for a deliverable."""
|
|
97
108
|
self.db.execute(
|
|
98
|
-
"UPDATE deliverables SET blocker = NULL WHERE id = ?",
|
|
99
|
-
(deliverable_id,)
|
|
109
|
+
"UPDATE deliverables SET blocker = NULL WHERE id = ?", (deliverable_id,)
|
|
100
110
|
)
|
|
101
|
-
|
|
111
|
+
|
|
102
112
|
def get_phase_breakdown(self, engagement_id: int) -> Dict:
|
|
103
113
|
"""
|
|
104
114
|
Get time breakdown by PTES phase.
|
|
105
|
-
|
|
115
|
+
|
|
106
116
|
Returns:
|
|
107
117
|
Dict with phase stats (count, completed, hours, etc.)
|
|
108
118
|
"""
|
|
109
|
-
phases = [
|
|
110
|
-
|
|
119
|
+
phases = [
|
|
120
|
+
"reconnaissance",
|
|
121
|
+
"enumeration",
|
|
122
|
+
"exploitation",
|
|
123
|
+
"post_exploitation",
|
|
124
|
+
"techniques",
|
|
125
|
+
]
|
|
126
|
+
|
|
111
127
|
breakdown = {}
|
|
112
|
-
|
|
128
|
+
|
|
113
129
|
for phase in phases:
|
|
114
130
|
deliverables = self.db.execute(
|
|
115
131
|
"""SELECT COUNT(*) as total,
|
|
@@ -118,25 +134,29 @@ class TimelineTracker:
|
|
|
118
134
|
SUM(actual_hours) as actual_hours
|
|
119
135
|
FROM deliverables
|
|
120
136
|
WHERE engagement_id = ? AND category = ?""",
|
|
121
|
-
(engagement_id, phase)
|
|
137
|
+
(engagement_id, phase),
|
|
122
138
|
)
|
|
123
|
-
|
|
139
|
+
|
|
124
140
|
if deliverables:
|
|
125
141
|
stats = deliverables[0]
|
|
126
142
|
breakdown[phase] = {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
143
|
+
"total": stats["total"] or 0,
|
|
144
|
+
"completed": stats["completed"] or 0,
|
|
145
|
+
"estimated_hours": stats["estimated_hours"] or 0,
|
|
146
|
+
"actual_hours": stats["actual_hours"] or 0,
|
|
147
|
+
"completion_rate": (
|
|
148
|
+
(stats["completed"] / stats["total"] * 100)
|
|
149
|
+
if stats["total"] > 0
|
|
150
|
+
else 0
|
|
151
|
+
),
|
|
132
152
|
}
|
|
133
|
-
|
|
153
|
+
|
|
134
154
|
return breakdown
|
|
135
|
-
|
|
155
|
+
|
|
136
156
|
def calculate_velocity(self, engagement_id: int) -> Dict:
|
|
137
157
|
"""
|
|
138
158
|
Calculate delivery velocity (deliverables per hour).
|
|
139
|
-
|
|
159
|
+
|
|
140
160
|
Returns:
|
|
141
161
|
Dict with velocity metrics
|
|
142
162
|
"""
|
|
@@ -148,30 +168,34 @@ class TimelineTracker:
|
|
|
148
168
|
AVG(actual_hours) as avg_hours_per_deliverable
|
|
149
169
|
FROM deliverables
|
|
150
170
|
WHERE engagement_id = ? AND actual_hours > 0""",
|
|
151
|
-
(engagement_id,)
|
|
171
|
+
(engagement_id,),
|
|
152
172
|
)
|
|
153
|
-
|
|
154
|
-
if not stats or not stats[
|
|
173
|
+
|
|
174
|
+
if not stats or not stats["total_hours"]:
|
|
155
175
|
return {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
176
|
+
"velocity": 0,
|
|
177
|
+
"avg_hours_per_deliverable": 0,
|
|
178
|
+
"total_hours": 0,
|
|
179
|
+
"completed_deliverables": 0,
|
|
160
180
|
}
|
|
161
|
-
|
|
162
|
-
velocity =
|
|
163
|
-
|
|
181
|
+
|
|
182
|
+
velocity = (
|
|
183
|
+
stats["completed_deliverables"] / stats["total_hours"]
|
|
184
|
+
if stats["total_hours"] > 0
|
|
185
|
+
else 0
|
|
186
|
+
)
|
|
187
|
+
|
|
164
188
|
return {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
189
|
+
"velocity": velocity, # deliverables per hour
|
|
190
|
+
"avg_hours_per_deliverable": stats["avg_hours_per_deliverable"] or 0,
|
|
191
|
+
"total_hours": stats["total_hours"] or 0,
|
|
192
|
+
"completed_deliverables": stats["completed_deliverables"] or 0,
|
|
169
193
|
}
|
|
170
|
-
|
|
194
|
+
|
|
171
195
|
def project_completion(self, engagement_id: int) -> Dict:
|
|
172
196
|
"""
|
|
173
197
|
Project completion date based on current velocity.
|
|
174
|
-
|
|
198
|
+
|
|
175
199
|
Returns:
|
|
176
200
|
Dict with projection data
|
|
177
201
|
"""
|
|
@@ -183,43 +207,43 @@ class TimelineTracker:
|
|
|
183
207
|
SUM(CASE WHEN status != 'completed' THEN 1 ELSE 0 END) as remaining
|
|
184
208
|
FROM deliverables
|
|
185
209
|
WHERE engagement_id = ?""",
|
|
186
|
-
(engagement_id,)
|
|
210
|
+
(engagement_id,),
|
|
187
211
|
)
|
|
188
|
-
|
|
189
|
-
if not totals or totals[
|
|
212
|
+
|
|
213
|
+
if not totals or totals["remaining"] == 0:
|
|
190
214
|
return {
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
215
|
+
"status": "complete",
|
|
216
|
+
"remaining_deliverables": 0,
|
|
217
|
+
"projected_hours": 0,
|
|
218
|
+
"projected_date": None,
|
|
195
219
|
}
|
|
196
|
-
|
|
220
|
+
|
|
197
221
|
velocity = self.calculate_velocity(engagement_id)
|
|
198
|
-
|
|
199
|
-
if velocity[
|
|
222
|
+
|
|
223
|
+
if velocity["velocity"] == 0:
|
|
200
224
|
# No historical data, use average estimate
|
|
201
|
-
avg_estimate = velocity[
|
|
225
|
+
avg_estimate = velocity["avg_hours_per_deliverable"]
|
|
202
226
|
if avg_estimate == 0:
|
|
203
227
|
avg_estimate = 2.0 # Default assumption: 2 hours per deliverable
|
|
204
|
-
|
|
205
|
-
projected_hours = totals[
|
|
228
|
+
|
|
229
|
+
projected_hours = totals["remaining"] * avg_estimate
|
|
206
230
|
else:
|
|
207
231
|
# Use velocity to project
|
|
208
|
-
projected_hours = totals[
|
|
209
|
-
|
|
232
|
+
projected_hours = totals["remaining"] / velocity["velocity"]
|
|
233
|
+
|
|
210
234
|
# Calculate projected completion date (assuming 8-hour work days)
|
|
211
235
|
work_days = projected_hours / 8
|
|
212
236
|
projected_date = datetime.now() + timedelta(days=work_days)
|
|
213
|
-
|
|
237
|
+
|
|
214
238
|
return {
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
239
|
+
"status": "in_progress",
|
|
240
|
+
"remaining_deliverables": totals["remaining"],
|
|
241
|
+
"projected_hours": round(projected_hours, 1),
|
|
242
|
+
"projected_days": round(work_days, 1),
|
|
243
|
+
"projected_date": projected_date.strftime("%Y-%m-%d"),
|
|
244
|
+
"velocity": round(velocity["velocity"], 2),
|
|
221
245
|
}
|
|
222
|
-
|
|
246
|
+
|
|
223
247
|
def get_blockers(self, engagement_id: int) -> List[Dict]:
|
|
224
248
|
"""Get all deliverables with blockers."""
|
|
225
249
|
return self.db.execute(
|
|
@@ -234,9 +258,9 @@ class TimelineTracker:
|
|
|
234
258
|
WHEN 'low' THEN 4
|
|
235
259
|
ELSE 5
|
|
236
260
|
END""",
|
|
237
|
-
(engagement_id,)
|
|
261
|
+
(engagement_id,),
|
|
238
262
|
)
|
|
239
|
-
|
|
263
|
+
|
|
240
264
|
def get_in_progress(self, engagement_id: int) -> List[Dict]:
|
|
241
265
|
"""Get all in-progress deliverables with time stats."""
|
|
242
266
|
return self.db.execute(
|
|
@@ -244,9 +268,9 @@ class TimelineTracker:
|
|
|
244
268
|
FROM deliverables
|
|
245
269
|
WHERE engagement_id = ? AND status = 'in_progress'
|
|
246
270
|
ORDER BY started_at DESC""",
|
|
247
|
-
(engagement_id,)
|
|
271
|
+
(engagement_id,),
|
|
248
272
|
)
|
|
249
|
-
|
|
273
|
+
|
|
250
274
|
def get_timeline_summary(self, engagement_id: int) -> Dict:
|
|
251
275
|
"""Get comprehensive timeline summary."""
|
|
252
276
|
phase_breakdown = self.get_phase_breakdown(engagement_id)
|
|
@@ -254,15 +278,15 @@ class TimelineTracker:
|
|
|
254
278
|
projection = self.project_completion(engagement_id)
|
|
255
279
|
blockers = self.get_blockers(engagement_id)
|
|
256
280
|
in_progress = self.get_in_progress(engagement_id)
|
|
257
|
-
|
|
281
|
+
|
|
258
282
|
return {
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
283
|
+
"phase_breakdown": phase_breakdown,
|
|
284
|
+
"velocity": velocity,
|
|
285
|
+
"projection": projection,
|
|
286
|
+
"blockers": blockers,
|
|
287
|
+
"in_progress": in_progress,
|
|
264
288
|
}
|
|
265
|
-
|
|
289
|
+
|
|
266
290
|
def update_engagement_hours(self, engagement_id: int):
|
|
267
291
|
"""Update total hours on engagement based on deliverables."""
|
|
268
292
|
stats = self.db.execute_one(
|
|
@@ -271,13 +295,17 @@ class TimelineTracker:
|
|
|
271
295
|
SUM(actual_hours) as total_actual
|
|
272
296
|
FROM deliverables
|
|
273
297
|
WHERE engagement_id = ?""",
|
|
274
|
-
(engagement_id,)
|
|
298
|
+
(engagement_id,),
|
|
275
299
|
)
|
|
276
|
-
|
|
300
|
+
|
|
277
301
|
if stats:
|
|
278
302
|
self.db.execute(
|
|
279
303
|
"""UPDATE engagements
|
|
280
304
|
SET estimated_hours = ?, actual_hours = ?
|
|
281
305
|
WHERE id = ?""",
|
|
282
|
-
(
|
|
306
|
+
(
|
|
307
|
+
stats["total_estimated"] or 0,
|
|
308
|
+
stats["total_actual"] or 0,
|
|
309
|
+
engagement_id,
|
|
310
|
+
),
|
|
283
311
|
)
|
souleyez/storage/wazuh_vulns.py
CHANGED
|
@@ -34,7 +34,7 @@ class WazuhVulnsManager:
|
|
|
34
34
|
detection_time: datetime = None,
|
|
35
35
|
published_date: str = None,
|
|
36
36
|
reference_urls: List[str] = None,
|
|
37
|
-
raw_data: dict = None
|
|
37
|
+
raw_data: dict = None,
|
|
38
38
|
) -> int:
|
|
39
39
|
"""
|
|
40
40
|
Insert or update a Wazuh vulnerability.
|
|
@@ -54,41 +54,40 @@ class WazuhVulnsManager:
|
|
|
54
54
|
AND COALESCE(package_name, '') = ?
|
|
55
55
|
"""
|
|
56
56
|
existing = self.db.execute_one(
|
|
57
|
-
query,
|
|
58
|
-
(engagement_id, agent_id, cve_id, package_name or '')
|
|
57
|
+
query, (engagement_id, agent_id, cve_id, package_name or "")
|
|
59
58
|
)
|
|
60
59
|
|
|
61
60
|
data = {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
61
|
+
"engagement_id": engagement_id,
|
|
62
|
+
"agent_id": agent_id,
|
|
63
|
+
"cve_id": cve_id,
|
|
64
|
+
"package_name": package_name,
|
|
65
|
+
"agent_name": agent_name,
|
|
66
|
+
"agent_ip": agent_ip,
|
|
67
|
+
"name": name,
|
|
68
|
+
"severity": severity,
|
|
69
|
+
"cvss_score": cvss_score,
|
|
70
|
+
"cvss_version": cvss_version,
|
|
71
|
+
"package_version": package_version,
|
|
72
|
+
"package_architecture": package_architecture,
|
|
73
|
+
"detection_time": detection_time,
|
|
74
|
+
"published_date": published_date,
|
|
75
|
+
"reference_urls": json.dumps(reference_urls) if reference_urls else None,
|
|
76
|
+
"raw_data": json.dumps(raw_data) if raw_data else None,
|
|
77
|
+
"synced_at": datetime.now().isoformat(),
|
|
79
78
|
}
|
|
80
79
|
|
|
81
80
|
if existing:
|
|
82
81
|
# Update existing
|
|
83
|
-
vuln_id = existing[
|
|
84
|
-
set_clause =
|
|
82
|
+
vuln_id = existing["id"]
|
|
83
|
+
set_clause = ", ".join([f"{k} = ?" for k in data.keys()])
|
|
85
84
|
update_query = f"UPDATE wazuh_vulnerabilities SET {set_clause} WHERE id = ?"
|
|
86
85
|
params = list(data.values()) + [vuln_id]
|
|
87
86
|
self.db.execute(update_query, tuple(params))
|
|
88
87
|
return vuln_id
|
|
89
88
|
else:
|
|
90
89
|
# Insert new
|
|
91
|
-
return self.db.insert(
|
|
90
|
+
return self.db.insert("wazuh_vulnerabilities", data)
|
|
92
91
|
|
|
93
92
|
def get_vulnerability(self, vuln_id: int) -> Optional[Dict[str, Any]]:
|
|
94
93
|
"""Get a vulnerability by ID."""
|
|
@@ -108,7 +107,7 @@ class WazuhVulnsManager:
|
|
|
108
107
|
cve_id: str = None,
|
|
109
108
|
status: str = None,
|
|
110
109
|
verified_only: bool = False,
|
|
111
|
-
limit: int = None
|
|
110
|
+
limit: int = None,
|
|
112
111
|
) -> List[Dict[str, Any]]:
|
|
113
112
|
"""
|
|
114
113
|
List vulnerabilities with optional filters.
|
|
@@ -193,26 +192,21 @@ class WazuhVulnsManager:
|
|
|
193
192
|
results = self.db.execute(query, (engagement_id,))
|
|
194
193
|
|
|
195
194
|
summary = {
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
'Critical': 0,
|
|
200
|
-
'High': 0,
|
|
201
|
-
'Medium': 0,
|
|
202
|
-
'Low': 0
|
|
203
|
-
}
|
|
195
|
+
"total": 0,
|
|
196
|
+
"verified": 0,
|
|
197
|
+
"by_severity": {"Critical": 0, "High": 0, "Medium": 0, "Low": 0},
|
|
204
198
|
}
|
|
205
199
|
|
|
206
200
|
for row in results:
|
|
207
|
-
severity = row.get(
|
|
208
|
-
count = row.get(
|
|
209
|
-
verified = row.get(
|
|
201
|
+
severity = row.get("severity", "Low")
|
|
202
|
+
count = row.get("count", 0)
|
|
203
|
+
verified = row.get("verified_count", 0)
|
|
210
204
|
|
|
211
|
-
summary[
|
|
212
|
-
summary[
|
|
205
|
+
summary["total"] += count
|
|
206
|
+
summary["verified"] += verified
|
|
213
207
|
|
|
214
|
-
if severity in summary[
|
|
215
|
-
summary[
|
|
208
|
+
if severity in summary["by_severity"]:
|
|
209
|
+
summary["by_severity"][severity] = count
|
|
216
210
|
|
|
217
211
|
return summary
|
|
218
212
|
|
|
@@ -225,7 +219,7 @@ class WazuhVulnsManager:
|
|
|
225
219
|
ORDER BY cve_id
|
|
226
220
|
"""
|
|
227
221
|
results = self.db.execute(query, (engagement_id,))
|
|
228
|
-
return [row[
|
|
222
|
+
return [row["cve_id"] for row in results if row.get("cve_id")]
|
|
229
223
|
|
|
230
224
|
def get_unique_agents(self, engagement_id: int) -> List[Dict[str, str]]:
|
|
231
225
|
"""Get list of unique agents with vuln counts."""
|
|
@@ -247,7 +241,7 @@ class WazuhVulnsManager:
|
|
|
247
241
|
vuln_id: int,
|
|
248
242
|
status: str,
|
|
249
243
|
verified_by_scan: bool = None,
|
|
250
|
-
matched_finding_id: int = None
|
|
244
|
+
matched_finding_id: int = None,
|
|
251
245
|
) -> bool:
|
|
252
246
|
"""
|
|
253
247
|
Update vulnerability status.
|
|
@@ -261,15 +255,15 @@ class WazuhVulnsManager:
|
|
|
261
255
|
Returns:
|
|
262
256
|
True if update succeeded
|
|
263
257
|
"""
|
|
264
|
-
updates = {
|
|
258
|
+
updates = {"status": status}
|
|
265
259
|
|
|
266
260
|
if verified_by_scan is not None:
|
|
267
|
-
updates[
|
|
261
|
+
updates["verified_by_scan"] = 1 if verified_by_scan else 0
|
|
268
262
|
|
|
269
263
|
if matched_finding_id is not None:
|
|
270
|
-
updates[
|
|
264
|
+
updates["matched_finding_id"] = matched_finding_id
|
|
271
265
|
|
|
272
|
-
set_clause =
|
|
266
|
+
set_clause = ", ".join([f"{k} = ?" for k in updates.keys()])
|
|
273
267
|
query = f"UPDATE wazuh_vulnerabilities SET {set_clause} WHERE id = ?"
|
|
274
268
|
params = list(updates.values()) + [vuln_id]
|
|
275
269
|
|
|
@@ -305,7 +299,7 @@ class WazuhVulnsManager:
|
|
|
305
299
|
WHERE engagement_id = ? AND agent_ip = ? AND host_id = ?
|
|
306
300
|
"""
|
|
307
301
|
result = self.db.execute_one(count_query, (engagement_id, agent_ip, host_id))
|
|
308
|
-
return result.get(
|
|
302
|
+
return result.get("count", 0) if result else 0
|
|
309
303
|
|
|
310
304
|
def get_unmapped(self, engagement_id: int) -> List[Dict[str, Any]]:
|
|
311
305
|
"""Get vulnerabilities not mapped to any host."""
|
|
@@ -322,7 +316,7 @@ class WazuhVulnsManager:
|
|
|
322
316
|
try:
|
|
323
317
|
self.db.execute(
|
|
324
318
|
"DELETE FROM wazuh_vulnerabilities WHERE engagement_id = ? AND agent_id = ?",
|
|
325
|
-
(engagement_id, agent_id)
|
|
319
|
+
(engagement_id, agent_id),
|
|
326
320
|
)
|
|
327
321
|
return True
|
|
328
322
|
except Exception:
|
|
@@ -333,7 +327,7 @@ class WazuhVulnsManager:
|
|
|
333
327
|
try:
|
|
334
328
|
self.db.execute(
|
|
335
329
|
"DELETE FROM wazuh_vulnerabilities WHERE engagement_id = ?",
|
|
336
|
-
(engagement_id,)
|
|
330
|
+
(engagement_id,),
|
|
337
331
|
)
|
|
338
332
|
return True
|
|
339
333
|
except Exception:
|
|
@@ -350,27 +344,31 @@ class WazuhVulnsManager:
|
|
|
350
344
|
self,
|
|
351
345
|
engagement_id: int,
|
|
352
346
|
count: int,
|
|
353
|
-
status: str =
|
|
354
|
-
errors: List[str] = None
|
|
347
|
+
status: str = "success",
|
|
348
|
+
errors: List[str] = None,
|
|
355
349
|
) -> None:
|
|
356
350
|
"""Update sync status after a sync operation."""
|
|
357
351
|
existing = self.get_sync_status(engagement_id)
|
|
358
352
|
|
|
359
353
|
data = {
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
354
|
+
"engagement_id": engagement_id,
|
|
355
|
+
"last_sync_at": datetime.now().isoformat(),
|
|
356
|
+
"last_sync_count": count,
|
|
357
|
+
"last_sync_status": status,
|
|
358
|
+
"last_sync_errors": json.dumps(errors) if errors else None,
|
|
365
359
|
}
|
|
366
360
|
|
|
367
361
|
if existing:
|
|
368
|
-
set_clause =
|
|
362
|
+
set_clause = ", ".join(
|
|
363
|
+
[f"{k} = ?" for k in data.keys() if k != "engagement_id"]
|
|
364
|
+
)
|
|
369
365
|
query = f"UPDATE wazuh_vuln_sync SET {set_clause} WHERE engagement_id = ?"
|
|
370
|
-
params = [v for k, v in data.items() if k !=
|
|
366
|
+
params = [v for k, v in data.items() if k != "engagement_id"] + [
|
|
367
|
+
engagement_id
|
|
368
|
+
]
|
|
371
369
|
self.db.execute(query, tuple(params))
|
|
372
370
|
else:
|
|
373
|
-
self.db.insert(
|
|
371
|
+
self.db.insert("wazuh_vuln_sync", data)
|
|
374
372
|
|
|
375
373
|
def is_stale(self, engagement_id: int, max_age_hours: int = 1) -> bool:
|
|
376
374
|
"""
|
|
@@ -385,10 +383,10 @@ class WazuhVulnsManager:
|
|
|
385
383
|
"""
|
|
386
384
|
sync_status = self.get_sync_status(engagement_id)
|
|
387
385
|
|
|
388
|
-
if not sync_status or not sync_status.get(
|
|
386
|
+
if not sync_status or not sync_status.get("last_sync_at"):
|
|
389
387
|
return True
|
|
390
388
|
|
|
391
|
-
last_sync = sync_status[
|
|
389
|
+
last_sync = sync_status["last_sync_at"]
|
|
392
390
|
if isinstance(last_sync, str):
|
|
393
391
|
try:
|
|
394
392
|
last_sync = datetime.fromisoformat(last_sync)
|
|
@@ -402,16 +400,16 @@ class WazuhVulnsManager:
|
|
|
402
400
|
"""Deserialize JSON fields."""
|
|
403
401
|
result = dict(row)
|
|
404
402
|
|
|
405
|
-
if result.get(
|
|
403
|
+
if result.get("reference_urls"):
|
|
406
404
|
try:
|
|
407
|
-
result[
|
|
405
|
+
result["reference_urls"] = json.loads(result["reference_urls"])
|
|
408
406
|
except (json.JSONDecodeError, TypeError):
|
|
409
|
-
result[
|
|
407
|
+
result["reference_urls"] = []
|
|
410
408
|
|
|
411
|
-
if result.get(
|
|
409
|
+
if result.get("raw_data"):
|
|
412
410
|
try:
|
|
413
|
-
result[
|
|
411
|
+
result["raw_data"] = json.loads(result["raw_data"])
|
|
414
412
|
except (json.JSONDecodeError, TypeError):
|
|
415
|
-
result[
|
|
413
|
+
result["raw_data"] = {}
|
|
416
414
|
|
|
417
415
|
return result
|