souleyez 2.43.29__py3-none-any.whl → 2.43.34__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (358) hide show
  1. souleyez/__init__.py +1 -2
  2. souleyez/ai/__init__.py +21 -15
  3. souleyez/ai/action_mapper.py +249 -150
  4. souleyez/ai/chain_advisor.py +116 -100
  5. souleyez/ai/claude_provider.py +29 -28
  6. souleyez/ai/context_builder.py +80 -62
  7. souleyez/ai/executor.py +158 -117
  8. souleyez/ai/feedback_handler.py +136 -121
  9. souleyez/ai/llm_factory.py +27 -20
  10. souleyez/ai/llm_provider.py +4 -2
  11. souleyez/ai/ollama_provider.py +6 -9
  12. souleyez/ai/ollama_service.py +44 -37
  13. souleyez/ai/path_scorer.py +91 -76
  14. souleyez/ai/recommender.py +176 -144
  15. souleyez/ai/report_context.py +74 -73
  16. souleyez/ai/report_service.py +84 -66
  17. souleyez/ai/result_parser.py +222 -229
  18. souleyez/ai/safety.py +67 -44
  19. souleyez/auth/__init__.py +23 -22
  20. souleyez/auth/audit.py +36 -26
  21. souleyez/auth/engagement_access.py +65 -48
  22. souleyez/auth/permissions.py +14 -3
  23. souleyez/auth/session_manager.py +54 -37
  24. souleyez/auth/user_manager.py +109 -64
  25. souleyez/commands/audit.py +40 -43
  26. souleyez/commands/auth.py +35 -15
  27. souleyez/commands/deliverables.py +55 -50
  28. souleyez/commands/engagement.py +47 -28
  29. souleyez/commands/license.py +32 -23
  30. souleyez/commands/screenshots.py +36 -32
  31. souleyez/commands/user.py +82 -36
  32. souleyez/config.py +52 -44
  33. souleyez/core/credential_tester.py +87 -81
  34. souleyez/core/cve_mappings.py +179 -192
  35. souleyez/core/cve_matcher.py +162 -148
  36. souleyez/core/msf_auto_mapper.py +100 -83
  37. souleyez/core/msf_chain_engine.py +294 -256
  38. souleyez/core/msf_database.py +153 -70
  39. souleyez/core/msf_integration.py +679 -673
  40. souleyez/core/msf_rpc_client.py +40 -42
  41. souleyez/core/msf_rpc_manager.py +77 -79
  42. souleyez/core/msf_sync_manager.py +241 -181
  43. souleyez/core/network_utils.py +22 -15
  44. souleyez/core/parser_handler.py +34 -25
  45. souleyez/core/pending_chains.py +114 -63
  46. souleyez/core/templates.py +158 -107
  47. souleyez/core/tool_chaining.py +9526 -2879
  48. souleyez/core/version_utils.py +79 -94
  49. souleyez/core/vuln_correlation.py +136 -89
  50. souleyez/core/web_utils.py +33 -32
  51. souleyez/data/wordlists/ad_users.txt +378 -0
  52. souleyez/data/wordlists/api_endpoints_large.txt +769 -0
  53. souleyez/data/wordlists/home_dir_sensitive.txt +39 -0
  54. souleyez/data/wordlists/lfi_payloads.txt +82 -0
  55. souleyez/data/wordlists/passwords_brute.txt +1548 -0
  56. souleyez/data/wordlists/passwords_crack.txt +2479 -0
  57. souleyez/data/wordlists/passwords_spray.txt +386 -0
  58. souleyez/data/wordlists/subdomains_large.txt +5057 -0
  59. souleyez/data/wordlists/usernames_common.txt +694 -0
  60. souleyez/data/wordlists/web_dirs_large.txt +4769 -0
  61. souleyez/detection/__init__.py +1 -1
  62. souleyez/detection/attack_signatures.py +12 -17
  63. souleyez/detection/mitre_mappings.py +61 -55
  64. souleyez/detection/validator.py +97 -86
  65. souleyez/devtools.py +23 -10
  66. souleyez/docs/README.md +4 -4
  67. souleyez/docs/api-reference/cli-commands.md +2 -2
  68. souleyez/docs/developer-guide/adding-new-tools.md +562 -0
  69. souleyez/docs/user-guide/auto-chaining.md +30 -8
  70. souleyez/docs/user-guide/getting-started.md +1 -1
  71. souleyez/docs/user-guide/installation.md +26 -3
  72. souleyez/docs/user-guide/metasploit-integration.md +2 -2
  73. souleyez/docs/user-guide/rbac.md +1 -1
  74. souleyez/docs/user-guide/scope-management.md +1 -1
  75. souleyez/docs/user-guide/siem-integration.md +1 -1
  76. souleyez/docs/user-guide/tools-reference.md +1 -8
  77. souleyez/docs/user-guide/worker-management.md +1 -1
  78. souleyez/engine/background.py +1239 -535
  79. souleyez/engine/base.py +4 -1
  80. souleyez/engine/job_status.py +17 -49
  81. souleyez/engine/log_sanitizer.py +103 -77
  82. souleyez/engine/manager.py +38 -7
  83. souleyez/engine/result_handler.py +2200 -1550
  84. souleyez/engine/worker_manager.py +50 -41
  85. souleyez/export/evidence_bundle.py +72 -62
  86. souleyez/feature_flags/features.py +16 -20
  87. souleyez/feature_flags.py +5 -9
  88. souleyez/handlers/__init__.py +11 -0
  89. souleyez/handlers/base.py +188 -0
  90. souleyez/handlers/bash_handler.py +277 -0
  91. souleyez/handlers/bloodhound_handler.py +243 -0
  92. souleyez/handlers/certipy_handler.py +311 -0
  93. souleyez/handlers/crackmapexec_handler.py +486 -0
  94. souleyez/handlers/dnsrecon_handler.py +344 -0
  95. souleyez/handlers/enum4linux_handler.py +400 -0
  96. souleyez/handlers/evil_winrm_handler.py +493 -0
  97. souleyez/handlers/ffuf_handler.py +815 -0
  98. souleyez/handlers/gobuster_handler.py +1114 -0
  99. souleyez/handlers/gpp_extract_handler.py +334 -0
  100. souleyez/handlers/hashcat_handler.py +444 -0
  101. souleyez/handlers/hydra_handler.py +563 -0
  102. souleyez/handlers/impacket_getuserspns_handler.py +343 -0
  103. souleyez/handlers/impacket_psexec_handler.py +222 -0
  104. souleyez/handlers/impacket_secretsdump_handler.py +426 -0
  105. souleyez/handlers/john_handler.py +286 -0
  106. souleyez/handlers/katana_handler.py +425 -0
  107. souleyez/handlers/kerbrute_handler.py +298 -0
  108. souleyez/handlers/ldapsearch_handler.py +636 -0
  109. souleyez/handlers/lfi_extract_handler.py +464 -0
  110. souleyez/handlers/msf_auxiliary_handler.py +408 -0
  111. souleyez/handlers/msf_exploit_handler.py +380 -0
  112. souleyez/handlers/nikto_handler.py +413 -0
  113. souleyez/handlers/nmap_handler.py +821 -0
  114. souleyez/handlers/nuclei_handler.py +359 -0
  115. souleyez/handlers/nxc_handler.py +371 -0
  116. souleyez/handlers/rdp_sec_check_handler.py +353 -0
  117. souleyez/handlers/registry.py +292 -0
  118. souleyez/handlers/responder_handler.py +232 -0
  119. souleyez/handlers/service_explorer_handler.py +434 -0
  120. souleyez/handlers/smbclient_handler.py +344 -0
  121. souleyez/handlers/smbmap_handler.py +510 -0
  122. souleyez/handlers/smbpasswd_handler.py +296 -0
  123. souleyez/handlers/sqlmap_handler.py +1116 -0
  124. souleyez/handlers/theharvester_handler.py +601 -0
  125. souleyez/handlers/web_login_test_handler.py +327 -0
  126. souleyez/handlers/whois_handler.py +277 -0
  127. souleyez/handlers/wpscan_handler.py +554 -0
  128. souleyez/history.py +32 -16
  129. souleyez/importers/msf_importer.py +106 -75
  130. souleyez/importers/smart_importer.py +208 -147
  131. souleyez/integrations/siem/__init__.py +10 -10
  132. souleyez/integrations/siem/base.py +17 -18
  133. souleyez/integrations/siem/elastic.py +108 -122
  134. souleyez/integrations/siem/factory.py +207 -80
  135. souleyez/integrations/siem/googlesecops.py +146 -154
  136. souleyez/integrations/siem/rule_mappings/__init__.py +1 -1
  137. souleyez/integrations/siem/rule_mappings/wazuh_rules.py +8 -5
  138. souleyez/integrations/siem/sentinel.py +107 -109
  139. souleyez/integrations/siem/splunk.py +246 -212
  140. souleyez/integrations/siem/wazuh.py +65 -71
  141. souleyez/integrations/wazuh/__init__.py +5 -5
  142. souleyez/integrations/wazuh/client.py +70 -93
  143. souleyez/integrations/wazuh/config.py +85 -57
  144. souleyez/integrations/wazuh/host_mapper.py +28 -36
  145. souleyez/integrations/wazuh/sync.py +78 -68
  146. souleyez/intelligence/__init__.py +4 -5
  147. souleyez/intelligence/correlation_analyzer.py +309 -295
  148. souleyez/intelligence/exploit_knowledge.py +661 -623
  149. souleyez/intelligence/exploit_suggestions.py +159 -139
  150. souleyez/intelligence/gap_analyzer.py +132 -97
  151. souleyez/intelligence/gap_detector.py +251 -214
  152. souleyez/intelligence/sensitive_tables.py +266 -129
  153. souleyez/intelligence/service_parser.py +137 -123
  154. souleyez/intelligence/surface_analyzer.py +407 -268
  155. souleyez/intelligence/target_parser.py +159 -162
  156. souleyez/licensing/__init__.py +6 -6
  157. souleyez/licensing/validator.py +17 -19
  158. souleyez/log_config.py +79 -54
  159. souleyez/main.py +1505 -687
  160. souleyez/migrations/fix_job_counter.py +16 -14
  161. souleyez/parsers/bloodhound_parser.py +41 -39
  162. souleyez/parsers/crackmapexec_parser.py +178 -111
  163. souleyez/parsers/dalfox_parser.py +72 -77
  164. souleyez/parsers/dnsrecon_parser.py +103 -91
  165. souleyez/parsers/enum4linux_parser.py +183 -153
  166. souleyez/parsers/ffuf_parser.py +29 -25
  167. souleyez/parsers/gobuster_parser.py +301 -41
  168. souleyez/parsers/hashcat_parser.py +324 -79
  169. souleyez/parsers/http_fingerprint_parser.py +350 -103
  170. souleyez/parsers/hydra_parser.py +131 -111
  171. souleyez/parsers/impacket_parser.py +231 -178
  172. souleyez/parsers/john_parser.py +98 -86
  173. souleyez/parsers/katana_parser.py +316 -0
  174. souleyez/parsers/msf_parser.py +943 -498
  175. souleyez/parsers/nikto_parser.py +346 -65
  176. souleyez/parsers/nmap_parser.py +262 -174
  177. souleyez/parsers/nuclei_parser.py +40 -44
  178. souleyez/parsers/responder_parser.py +26 -26
  179. souleyez/parsers/searchsploit_parser.py +74 -74
  180. souleyez/parsers/service_explorer_parser.py +279 -0
  181. souleyez/parsers/smbmap_parser.py +180 -124
  182. souleyez/parsers/sqlmap_parser.py +434 -308
  183. souleyez/parsers/theharvester_parser.py +75 -57
  184. souleyez/parsers/whois_parser.py +135 -94
  185. souleyez/parsers/wpscan_parser.py +278 -190
  186. souleyez/plugins/afp.py +44 -36
  187. souleyez/plugins/afp_brute.py +114 -46
  188. souleyez/plugins/ard.py +48 -37
  189. souleyez/plugins/bloodhound.py +95 -61
  190. souleyez/plugins/certipy.py +303 -0
  191. souleyez/plugins/crackmapexec.py +186 -85
  192. souleyez/plugins/dalfox.py +120 -59
  193. souleyez/plugins/dns_hijack.py +146 -41
  194. souleyez/plugins/dnsrecon.py +97 -61
  195. souleyez/plugins/enum4linux.py +91 -66
  196. souleyez/plugins/evil_winrm.py +291 -0
  197. souleyez/plugins/ffuf.py +166 -90
  198. souleyez/plugins/firmware_extract.py +133 -29
  199. souleyez/plugins/gobuster.py +387 -190
  200. souleyez/plugins/gpp_extract.py +393 -0
  201. souleyez/plugins/hashcat.py +100 -73
  202. souleyez/plugins/http_fingerprint.py +854 -267
  203. souleyez/plugins/hydra.py +566 -200
  204. souleyez/plugins/impacket_getnpusers.py +117 -69
  205. souleyez/plugins/impacket_psexec.py +84 -64
  206. souleyez/plugins/impacket_secretsdump.py +103 -69
  207. souleyez/plugins/impacket_smbclient.py +89 -75
  208. souleyez/plugins/john.py +86 -69
  209. souleyez/plugins/katana.py +313 -0
  210. souleyez/plugins/kerbrute.py +237 -0
  211. souleyez/plugins/lfi_extract.py +541 -0
  212. souleyez/plugins/macos_ssh.py +117 -48
  213. souleyez/plugins/mdns.py +35 -30
  214. souleyez/plugins/msf_auxiliary.py +253 -130
  215. souleyez/plugins/msf_exploit.py +239 -161
  216. souleyez/plugins/nikto.py +134 -78
  217. souleyez/plugins/nmap.py +275 -91
  218. souleyez/plugins/nuclei.py +180 -89
  219. souleyez/plugins/nxc.py +285 -0
  220. souleyez/plugins/plugin_base.py +35 -36
  221. souleyez/plugins/plugin_template.py +13 -5
  222. souleyez/plugins/rdp_sec_check.py +130 -0
  223. souleyez/plugins/responder.py +112 -71
  224. souleyez/plugins/router_http_brute.py +76 -65
  225. souleyez/plugins/router_ssh_brute.py +118 -41
  226. souleyez/plugins/router_telnet_brute.py +124 -42
  227. souleyez/plugins/routersploit.py +91 -59
  228. souleyez/plugins/routersploit_exploit.py +77 -55
  229. souleyez/plugins/searchsploit.py +91 -77
  230. souleyez/plugins/service_explorer.py +1160 -0
  231. souleyez/plugins/smbmap.py +122 -72
  232. souleyez/plugins/smbpasswd.py +215 -0
  233. souleyez/plugins/sqlmap.py +301 -113
  234. souleyez/plugins/theharvester.py +127 -75
  235. souleyez/plugins/tr069.py +79 -57
  236. souleyez/plugins/upnp.py +65 -47
  237. souleyez/plugins/upnp_abuse.py +73 -55
  238. souleyez/plugins/vnc_access.py +129 -42
  239. souleyez/plugins/vnc_brute.py +109 -38
  240. souleyez/plugins/web_login_test.py +417 -0
  241. souleyez/plugins/whois.py +77 -58
  242. souleyez/plugins/wpscan.py +173 -69
  243. souleyez/reporting/__init__.py +2 -1
  244. souleyez/reporting/attack_chain.py +411 -346
  245. souleyez/reporting/charts.py +436 -501
  246. souleyez/reporting/compliance_mappings.py +334 -201
  247. souleyez/reporting/detection_report.py +126 -125
  248. souleyez/reporting/formatters.py +828 -591
  249. souleyez/reporting/generator.py +386 -302
  250. souleyez/reporting/metrics.py +72 -75
  251. souleyez/scanner.py +35 -29
  252. souleyez/security/__init__.py +37 -11
  253. souleyez/security/scope_validator.py +175 -106
  254. souleyez/security/validation.py +223 -149
  255. souleyez/security.py +22 -6
  256. souleyez/storage/credentials.py +247 -186
  257. souleyez/storage/crypto.py +296 -129
  258. souleyez/storage/database.py +73 -50
  259. souleyez/storage/db.py +58 -36
  260. souleyez/storage/deliverable_evidence.py +177 -128
  261. souleyez/storage/deliverable_exporter.py +282 -246
  262. souleyez/storage/deliverable_templates.py +134 -116
  263. souleyez/storage/deliverables.py +135 -130
  264. souleyez/storage/engagements.py +109 -56
  265. souleyez/storage/evidence.py +181 -152
  266. souleyez/storage/execution_log.py +31 -17
  267. souleyez/storage/exploit_attempts.py +93 -57
  268. souleyez/storage/exploits.py +67 -36
  269. souleyez/storage/findings.py +48 -61
  270. souleyez/storage/hosts.py +176 -144
  271. souleyez/storage/migrate_to_engagements.py +43 -19
  272. souleyez/storage/migrations/_001_add_credential_enhancements.py +22 -12
  273. souleyez/storage/migrations/_002_add_status_tracking.py +10 -7
  274. souleyez/storage/migrations/_003_add_execution_log.py +14 -8
  275. souleyez/storage/migrations/_005_screenshots.py +13 -5
  276. souleyez/storage/migrations/_006_deliverables.py +13 -5
  277. souleyez/storage/migrations/_007_deliverable_templates.py +12 -7
  278. souleyez/storage/migrations/_008_add_nuclei_table.py +10 -4
  279. souleyez/storage/migrations/_010_evidence_linking.py +17 -10
  280. souleyez/storage/migrations/_011_timeline_tracking.py +20 -13
  281. souleyez/storage/migrations/_012_team_collaboration.py +34 -21
  282. souleyez/storage/migrations/_013_add_host_tags.py +12 -6
  283. souleyez/storage/migrations/_014_exploit_attempts.py +22 -10
  284. souleyez/storage/migrations/_015_add_mac_os_fields.py +15 -7
  285. souleyez/storage/migrations/_016_add_domain_field.py +10 -4
  286. souleyez/storage/migrations/_017_msf_sessions.py +16 -8
  287. souleyez/storage/migrations/_018_add_osint_target.py +10 -6
  288. souleyez/storage/migrations/_019_add_engagement_type.py +10 -6
  289. souleyez/storage/migrations/_020_add_rbac.py +36 -15
  290. souleyez/storage/migrations/_021_wazuh_integration.py +20 -8
  291. souleyez/storage/migrations/_022_wazuh_indexer_columns.py +6 -4
  292. souleyez/storage/migrations/_023_fix_detection_results_fk.py +16 -6
  293. souleyez/storage/migrations/_024_wazuh_vulnerabilities.py +26 -10
  294. souleyez/storage/migrations/_025_multi_siem_support.py +3 -5
  295. souleyez/storage/migrations/_026_add_engagement_scope.py +31 -12
  296. souleyez/storage/migrations/_027_multi_siem_persistence.py +32 -15
  297. souleyez/storage/migrations/__init__.py +26 -26
  298. souleyez/storage/migrations/migration_manager.py +19 -19
  299. souleyez/storage/msf_sessions.py +100 -65
  300. souleyez/storage/osint.py +17 -24
  301. souleyez/storage/recommendation_engine.py +269 -235
  302. souleyez/storage/screenshots.py +33 -32
  303. souleyez/storage/smb_shares.py +136 -92
  304. souleyez/storage/sqlmap_data.py +183 -128
  305. souleyez/storage/team_collaboration.py +135 -141
  306. souleyez/storage/timeline_tracker.py +122 -94
  307. souleyez/storage/wazuh_vulns.py +64 -66
  308. souleyez/storage/web_paths.py +33 -37
  309. souleyez/testing/credential_tester.py +221 -205
  310. souleyez/ui/__init__.py +1 -1
  311. souleyez/ui/ai_quotes.py +12 -12
  312. souleyez/ui/attack_surface.py +2439 -1516
  313. souleyez/ui/chain_rules_view.py +914 -382
  314. souleyez/ui/correlation_view.py +312 -230
  315. souleyez/ui/dashboard.py +2382 -1130
  316. souleyez/ui/deliverables_view.py +148 -62
  317. souleyez/ui/design_system.py +13 -13
  318. souleyez/ui/errors.py +49 -49
  319. souleyez/ui/evidence_linking_view.py +284 -179
  320. souleyez/ui/evidence_vault.py +393 -285
  321. souleyez/ui/exploit_suggestions_view.py +555 -349
  322. souleyez/ui/export_view.py +100 -66
  323. souleyez/ui/gap_analysis_view.py +315 -171
  324. souleyez/ui/help_system.py +105 -97
  325. souleyez/ui/intelligence_view.py +436 -293
  326. souleyez/ui/interactive.py +22827 -10678
  327. souleyez/ui/interactive_selector.py +75 -68
  328. souleyez/ui/log_formatter.py +47 -39
  329. souleyez/ui/menu_components.py +22 -13
  330. souleyez/ui/msf_auxiliary_menu.py +184 -133
  331. souleyez/ui/pending_chains_view.py +336 -172
  332. souleyez/ui/progress_indicators.py +5 -3
  333. souleyez/ui/recommendations_view.py +195 -137
  334. souleyez/ui/rule_builder.py +343 -225
  335. souleyez/ui/setup_wizard.py +678 -284
  336. souleyez/ui/shortcuts.py +217 -165
  337. souleyez/ui/splunk_gap_analysis_view.py +452 -270
  338. souleyez/ui/splunk_vulns_view.py +139 -86
  339. souleyez/ui/team_dashboard.py +498 -335
  340. souleyez/ui/template_selector.py +196 -105
  341. souleyez/ui/terminal.py +6 -6
  342. souleyez/ui/timeline_view.py +198 -127
  343. souleyez/ui/tool_setup.py +264 -164
  344. souleyez/ui/tutorial.py +202 -72
  345. souleyez/ui/tutorial_state.py +40 -40
  346. souleyez/ui/wazuh_vulns_view.py +235 -141
  347. souleyez/ui/wordlist_browser.py +260 -107
  348. souleyez/ui.py +464 -312
  349. souleyez/utils/tool_checker.py +427 -367
  350. souleyez/utils.py +33 -29
  351. souleyez/wordlists.py +134 -167
  352. {souleyez-2.43.29.dist-info → souleyez-2.43.34.dist-info}/METADATA +1 -1
  353. souleyez-2.43.34.dist-info/RECORD +443 -0
  354. {souleyez-2.43.29.dist-info → souleyez-2.43.34.dist-info}/WHEEL +1 -1
  355. souleyez-2.43.29.dist-info/RECORD +0 -379
  356. {souleyez-2.43.29.dist-info → souleyez-2.43.34.dist-info}/entry_points.txt +0 -0
  357. {souleyez-2.43.29.dist-info → souleyez-2.43.34.dist-info}/licenses/LICENSE +0 -0
  358. {souleyez-2.43.29.dist-info → souleyez-2.43.34.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
- deliverable = self.db.execute_one("SELECT engagement_id FROM deliverables WHERE id = ?", (deliverable_id,))
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['engagement_id'], 'started')
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('engagement_id') if deliverable else None
57
-
58
- if deliverable and deliverable.get('started_at'):
59
- started = datetime.fromisoformat(deliverable['started_at'].replace('Z', '+00:00'))
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
- deliverable = self.db.execute_one("SELECT engagement_id FROM deliverables WHERE id = ?", (deliverable_id,))
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(deliverable_id, deliverable['engagement_id'], 'completed')
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 = ['reconnaissance', 'enumeration', 'exploitation', 'post_exploitation', 'techniques']
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
- 'total': stats['total'] or 0,
128
- 'completed': stats['completed'] or 0,
129
- 'estimated_hours': stats['estimated_hours'] or 0,
130
- 'actual_hours': stats['actual_hours'] or 0,
131
- 'completion_rate': (stats['completed'] / stats['total'] * 100) if stats['total'] > 0 else 0
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['total_hours']:
173
+
174
+ if not stats or not stats["total_hours"]:
155
175
  return {
156
- 'velocity': 0,
157
- 'avg_hours_per_deliverable': 0,
158
- 'total_hours': 0,
159
- 'completed_deliverables': 0
176
+ "velocity": 0,
177
+ "avg_hours_per_deliverable": 0,
178
+ "total_hours": 0,
179
+ "completed_deliverables": 0,
160
180
  }
161
-
162
- velocity = stats['completed_deliverables'] / stats['total_hours'] if stats['total_hours'] > 0 else 0
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
- 'velocity': velocity, # deliverables per hour
166
- 'avg_hours_per_deliverable': stats['avg_hours_per_deliverable'] or 0,
167
- 'total_hours': stats['total_hours'] or 0,
168
- 'completed_deliverables': stats['completed_deliverables'] or 0
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['remaining'] == 0:
212
+
213
+ if not totals or totals["remaining"] == 0:
190
214
  return {
191
- 'status': 'complete',
192
- 'remaining_deliverables': 0,
193
- 'projected_hours': 0,
194
- 'projected_date': None
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['velocity'] == 0:
222
+
223
+ if velocity["velocity"] == 0:
200
224
  # No historical data, use average estimate
201
- avg_estimate = velocity['avg_hours_per_deliverable']
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['remaining'] * avg_estimate
228
+
229
+ projected_hours = totals["remaining"] * avg_estimate
206
230
  else:
207
231
  # Use velocity to project
208
- projected_hours = totals['remaining'] / velocity['velocity']
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
- 'status': 'in_progress',
216
- 'remaining_deliverables': totals['remaining'],
217
- 'projected_hours': round(projected_hours, 1),
218
- 'projected_days': round(work_days, 1),
219
- 'projected_date': projected_date.strftime('%Y-%m-%d'),
220
- 'velocity': round(velocity['velocity'], 2)
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
- 'phase_breakdown': phase_breakdown,
260
- 'velocity': velocity,
261
- 'projection': projection,
262
- 'blockers': blockers,
263
- 'in_progress': in_progress
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
- (stats['total_estimated'] or 0, stats['total_actual'] or 0, engagement_id)
306
+ (
307
+ stats["total_estimated"] or 0,
308
+ stats["total_actual"] or 0,
309
+ engagement_id,
310
+ ),
283
311
  )
@@ -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
- 'engagement_id': engagement_id,
63
- 'agent_id': agent_id,
64
- 'cve_id': cve_id,
65
- 'package_name': package_name,
66
- 'agent_name': agent_name,
67
- 'agent_ip': agent_ip,
68
- 'name': name,
69
- 'severity': severity,
70
- 'cvss_score': cvss_score,
71
- 'cvss_version': cvss_version,
72
- 'package_version': package_version,
73
- 'package_architecture': package_architecture,
74
- 'detection_time': detection_time,
75
- 'published_date': published_date,
76
- 'reference_urls': json.dumps(reference_urls) if reference_urls else None,
77
- 'raw_data': json.dumps(raw_data) if raw_data else None,
78
- 'synced_at': datetime.now().isoformat()
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['id']
84
- set_clause = ', '.join([f"{k} = ?" for k in data.keys()])
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('wazuh_vulnerabilities', data)
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
- 'total': 0,
197
- 'verified': 0,
198
- 'by_severity': {
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('severity', 'Low')
208
- count = row.get('count', 0)
209
- verified = row.get('verified_count', 0)
201
+ severity = row.get("severity", "Low")
202
+ count = row.get("count", 0)
203
+ verified = row.get("verified_count", 0)
210
204
 
211
- summary['total'] += count
212
- summary['verified'] += verified
205
+ summary["total"] += count
206
+ summary["verified"] += verified
213
207
 
214
- if severity in summary['by_severity']:
215
- summary['by_severity'][severity] = count
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['cve_id'] for row in results if row.get('cve_id')]
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 = {'status': status}
258
+ updates = {"status": status}
265
259
 
266
260
  if verified_by_scan is not None:
267
- updates['verified_by_scan'] = 1 if verified_by_scan else 0
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['matched_finding_id'] = matched_finding_id
264
+ updates["matched_finding_id"] = matched_finding_id
271
265
 
272
- set_clause = ', '.join([f"{k} = ?" for k in updates.keys()])
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('count', 0) if result else 0
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 = 'success',
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
- 'engagement_id': engagement_id,
361
- 'last_sync_at': datetime.now().isoformat(),
362
- 'last_sync_count': count,
363
- 'last_sync_status': status,
364
- 'last_sync_errors': json.dumps(errors) if errors else None
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 = ', '.join([f"{k} = ?" for k in data.keys() if k != 'engagement_id'])
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 != 'engagement_id'] + [engagement_id]
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('wazuh_vuln_sync', data)
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('last_sync_at'):
386
+ if not sync_status or not sync_status.get("last_sync_at"):
389
387
  return True
390
388
 
391
- last_sync = sync_status['last_sync_at']
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('reference_urls'):
403
+ if result.get("reference_urls"):
406
404
  try:
407
- result['reference_urls'] = json.loads(result['reference_urls'])
405
+ result["reference_urls"] = json.loads(result["reference_urls"])
408
406
  except (json.JSONDecodeError, TypeError):
409
- result['reference_urls'] = []
407
+ result["reference_urls"] = []
410
408
 
411
- if result.get('raw_data'):
409
+ if result.get("raw_data"):
412
410
  try:
413
- result['raw_data'] = json.loads(result['raw_data'])
411
+ result["raw_data"] = json.loads(result["raw_data"])
414
412
  except (json.JSONDecodeError, TypeError):
415
- result['raw_data'] = {}
413
+ result["raw_data"] = {}
416
414
 
417
415
  return result