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.
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 +9564 -2881
  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 +564 -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 +409 -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 +417 -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 +913 -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 +219 -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 +237 -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 +23034 -10679
  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-3.0.0.dist-info}/METADATA +2 -2
  353. souleyez-3.0.0.dist-info/RECORD +443 -0
  354. {souleyez-2.43.29.dist-info → souleyez-3.0.0.dist-info}/WHEEL +1 -1
  355. souleyez-2.43.29.dist-info/RECORD +0 -379
  356. {souleyez-2.43.29.dist-info → souleyez-3.0.0.dist-info}/entry_points.txt +0 -0
  357. {souleyez-2.43.29.dist-info → souleyez-3.0.0.dist-info}/licenses/LICENSE +0 -0
  358. {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
  AI-powered recommendation engine 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
@@ -11,29 +12,29 @@ from .timeline_tracker import TimelineTracker
11
12
 
12
13
  class RecommendationEngine:
13
14
  """Generate smart recommendations for next actions and coverage gaps."""
14
-
15
+
15
16
  def __init__(self):
16
17
  self.db = get_db()
17
18
  self.dm = DeliverableManager()
18
19
  self.em = EvidenceManager()
19
20
  self.tt = TimelineTracker()
20
-
21
+
21
22
  def get_recommendations(self, engagement_id: int) -> Dict:
22
23
  """
23
24
  Generate comprehensive recommendations for an engagement.
24
-
25
+
25
26
  Returns:
26
27
  Dict with recommendation categories
27
28
  """
28
29
  return {
29
- 'next_actions': self._get_next_actions(engagement_id),
30
- 'blockers': self._get_blocker_recommendations(engagement_id),
31
- 'quick_wins': self._get_quick_wins(engagement_id),
32
- 'coverage_gaps': self._get_coverage_gaps(engagement_id),
33
- 'at_risk': self._get_at_risk_deliverables(engagement_id),
34
- 'priority_boost': self._get_priority_boost_suggestions(engagement_id)
30
+ "next_actions": self._get_next_actions(engagement_id),
31
+ "blockers": self._get_blocker_recommendations(engagement_id),
32
+ "quick_wins": self._get_quick_wins(engagement_id),
33
+ "coverage_gaps": self._get_coverage_gaps(engagement_id),
34
+ "at_risk": self._get_at_risk_deliverables(engagement_id),
35
+ "priority_boost": self._get_priority_boost_suggestions(engagement_id),
35
36
  }
36
-
37
+
37
38
  def _get_next_actions(self, engagement_id: int) -> List[Dict]:
38
39
  """
39
40
  Recommend next deliverables to work on based on:
@@ -43,141 +44,152 @@ class RecommendationEngine:
43
44
  - Time estimates
44
45
  """
45
46
  deliverables = self.dm.list_deliverables(engagement_id)
46
-
47
+
47
48
  # Filter to pending/in_progress only
48
- actionable = [d for d in deliverables if d['status'] in ['pending', 'in_progress']]
49
-
49
+ actionable = [
50
+ d for d in deliverables if d["status"] in ["pending", "in_progress"]
51
+ ]
52
+
50
53
  recommendations = []
51
-
54
+
52
55
  for d in actionable:
53
56
  score = 0
54
57
  reasons = []
55
-
58
+
56
59
  # Priority scoring (critical = 100, high = 75, medium = 50, low = 25)
57
- priority_scores = {
58
- 'critical': 100,
59
- 'high': 75,
60
- 'medium': 50,
61
- 'low': 25
62
- }
63
- priority_score = priority_scores.get(d.get('priority', 'medium'), 50)
60
+ priority_scores = {"critical": 100, "high": 75, "medium": 50, "low": 25}
61
+ priority_score = priority_scores.get(d.get("priority", "medium"), 50)
64
62
  score += priority_score
65
-
66
- if d.get('priority') == 'critical':
67
- reasons.append('Critical priority')
68
-
63
+
64
+ if d.get("priority") == "critical":
65
+ reasons.append("Critical priority")
66
+
69
67
  # Phase scoring (earlier phases = higher score)
70
68
  phase_scores = {
71
- 'reconnaissance': 90,
72
- 'enumeration': 80,
73
- 'exploitation': 70,
74
- 'post_exploitation': 60,
75
- 'techniques': 50
69
+ "reconnaissance": 90,
70
+ "enumeration": 80,
71
+ "exploitation": 70,
72
+ "post_exploitation": 60,
73
+ "techniques": 50,
76
74
  }
77
- phase_score = phase_scores.get(d['category'], 50)
75
+ phase_score = phase_scores.get(d["category"], 50)
78
76
  score += phase_score
79
-
77
+
80
78
  # Evidence availability (if we already have related evidence, easier to complete)
81
- evidence_count = self.em.get_evidence_count(d['id'])
79
+ evidence_count = self.em.get_evidence_count(d["id"])
82
80
  if evidence_count > 0:
83
81
  score += 20
84
- reasons.append(f'{evidence_count} evidence items available')
85
-
82
+ reasons.append(f"{evidence_count} evidence items available")
83
+
86
84
  # In-progress items get boost (finish what we started)
87
- if d['status'] == 'in_progress':
85
+ if d["status"] == "in_progress":
88
86
  score += 30
89
- reasons.append('Already in progress')
90
-
87
+ reasons.append("Already in progress")
88
+
91
89
  # Add time pressure if started long ago
92
- if d.get('started_at'):
90
+ if d.get("started_at"):
93
91
  try:
94
- started = datetime.fromisoformat(d['started_at'].replace('Z', '+00:00'))
92
+ started = datetime.fromisoformat(
93
+ d["started_at"].replace("Z", "+00:00")
94
+ )
95
95
  hours_since = (datetime.now() - started).total_seconds() / 3600
96
96
  if hours_since > 24:
97
97
  score += 15
98
- reasons.append(f'Started {int(hours_since)}h ago')
98
+ reasons.append(f"Started {int(hours_since)}h ago")
99
99
  except:
100
100
  pass
101
-
101
+
102
102
  # Blocker penalty
103
- if d.get('blocker'):
103
+ if d.get("blocker"):
104
104
  score -= 50
105
- reasons.append('⚠️ BLOCKED')
106
-
105
+ reasons.append("⚠️ BLOCKED")
106
+
107
107
  # Auto-validate items (quick wins)
108
- if d.get('auto_validate'):
108
+ if d.get("auto_validate"):
109
109
  score += 10
110
- reasons.append('Auto-validates')
111
-
112
- recommendations.append({
113
- 'deliverable': d,
114
- 'score': score,
115
- 'reasons': reasons,
116
- 'confidence': min(100, int(score / 3)) # Scale to 0-100
117
- })
118
-
110
+ reasons.append("Auto-validates")
111
+
112
+ recommendations.append(
113
+ {
114
+ "deliverable": d,
115
+ "score": score,
116
+ "reasons": reasons,
117
+ "confidence": min(100, int(score / 3)), # Scale to 0-100
118
+ }
119
+ )
120
+
119
121
  # Sort by score descending
120
- recommendations.sort(key=lambda x: x['score'], reverse=True)
121
-
122
+ recommendations.sort(key=lambda x: x["score"], reverse=True)
123
+
122
124
  return recommendations[:10] # Top 10
123
-
125
+
124
126
  def _get_blocker_recommendations(self, engagement_id: int) -> List[Dict]:
125
127
  """
126
128
  Recommend actions to unblock deliverables.
127
129
  """
128
130
  blockers = self.tt.get_blockers(engagement_id)
129
-
131
+
130
132
  recommendations = []
131
-
133
+
132
134
  for blocker in blockers:
133
135
  suggestions = []
134
- blocker_text = blocker.get('blocker', '').lower()
135
-
136
+ blocker_text = blocker.get("blocker", "").lower()
137
+
136
138
  # Pattern matching for common blockers
137
- if 'credential' in blocker_text or 'password' in blocker_text or 'login' in blocker_text:
138
- suggestions.append('Run credential enumeration tools (hydra, medusa)')
139
- suggestions.append('Check for default credentials')
140
- suggestions.append('Review already discovered credentials')
141
-
142
- if 'access' in blocker_text or 'permission' in blocker_text:
143
- suggestions.append('Verify network connectivity')
144
- suggestions.append('Check firewall rules')
145
- suggestions.append('Request VPN access from client')
146
-
147
- if 'client' in blocker_text or 'waiting' in blocker_text:
148
- suggestions.append('Send follow-up email to client')
149
- suggestions.append('Schedule status call')
150
- suggestions.append('Document waiting time for billing')
151
-
152
- if 'tool' in blocker_text or 'error' in blocker_text or 'fail' in blocker_text:
153
- suggestions.append('Check tool installation')
154
- suggestions.append('Review error logs')
155
- suggestions.append('Try alternative tool')
156
-
157
- if 'scope' in blocker_text or 'clarification' in blocker_text:
158
- suggestions.append('Review engagement SOW')
159
- suggestions.append('Email client for clarification')
160
- suggestions.append('Document scope questions')
161
-
139
+ if (
140
+ "credential" in blocker_text
141
+ or "password" in blocker_text
142
+ or "login" in blocker_text
143
+ ):
144
+ suggestions.append("Run credential enumeration tools (hydra, medusa)")
145
+ suggestions.append("Check for default credentials")
146
+ suggestions.append("Review already discovered credentials")
147
+
148
+ if "access" in blocker_text or "permission" in blocker_text:
149
+ suggestions.append("Verify network connectivity")
150
+ suggestions.append("Check firewall rules")
151
+ suggestions.append("Request VPN access from client")
152
+
153
+ if "client" in blocker_text or "waiting" in blocker_text:
154
+ suggestions.append("Send follow-up email to client")
155
+ suggestions.append("Schedule status call")
156
+ suggestions.append("Document waiting time for billing")
157
+
158
+ if (
159
+ "tool" in blocker_text
160
+ or "error" in blocker_text
161
+ or "fail" in blocker_text
162
+ ):
163
+ suggestions.append("Check tool installation")
164
+ suggestions.append("Review error logs")
165
+ suggestions.append("Try alternative tool")
166
+
167
+ if "scope" in blocker_text or "clarification" in blocker_text:
168
+ suggestions.append("Review engagement SOW")
169
+ suggestions.append("Email client for clarification")
170
+ suggestions.append("Document scope questions")
171
+
162
172
  # Generic suggestions if no patterns matched
163
173
  if not suggestions:
164
- suggestions.append('Review blocker details')
165
- suggestions.append('Consult with team lead')
166
- suggestions.append('Document resolution steps')
167
-
168
- recommendations.append({
169
- 'deliverable': blocker,
170
- 'blocker': blocker.get('blocker'),
171
- 'suggestions': suggestions,
172
- 'priority': blocker.get('priority', 'medium')
173
- })
174
-
174
+ suggestions.append("Review blocker details")
175
+ suggestions.append("Consult with team lead")
176
+ suggestions.append("Document resolution steps")
177
+
178
+ recommendations.append(
179
+ {
180
+ "deliverable": blocker,
181
+ "blocker": blocker.get("blocker"),
182
+ "suggestions": suggestions,
183
+ "priority": blocker.get("priority", "medium"),
184
+ }
185
+ )
186
+
175
187
  return recommendations
176
-
188
+
177
189
  def _get_quick_wins(self, engagement_id: int) -> List[Dict]:
178
190
  """
179
191
  Identify deliverables that can be completed quickly.
180
-
192
+
181
193
  Criteria:
182
194
  - Auto-validate enabled
183
195
  - Low estimated hours
@@ -185,108 +197,114 @@ class RecommendationEngine:
185
197
  - Low priority (save critical for focused time)
186
198
  """
187
199
  deliverables = self.dm.list_deliverables(engagement_id)
188
-
200
+
189
201
  quick_wins = []
190
-
202
+
191
203
  for d in deliverables:
192
- if d['status'] != 'pending':
204
+ if d["status"] != "pending":
193
205
  continue
194
-
206
+
195
207
  score = 0
196
208
  reasons = []
197
-
209
+
198
210
  # Auto-validate = quick
199
- if d.get('auto_validate'):
211
+ if d.get("auto_validate"):
200
212
  score += 40
201
- reasons.append('Auto-validates')
202
-
213
+ reasons.append("Auto-validates")
214
+
203
215
  # Low estimated hours
204
- est_hours = d.get('estimated_hours', 0)
216
+ est_hours = d.get("estimated_hours", 0)
205
217
  if est_hours > 0 and est_hours <= 2:
206
218
  score += 30
207
- reasons.append(f'Quick ({est_hours}h estimated)')
219
+ reasons.append(f"Quick ({est_hours}h estimated)")
208
220
  elif est_hours == 0:
209
221
  score += 20 # No estimate, assume might be quick
210
-
222
+
211
223
  # Evidence available
212
- evidence_count = self.em.get_evidence_count(d['id'])
224
+ evidence_count = self.em.get_evidence_count(d["id"])
213
225
  if evidence_count > 0:
214
226
  score += 20
215
- reasons.append(f'{evidence_count} evidence ready')
216
-
227
+ reasons.append(f"{evidence_count} evidence ready")
228
+
217
229
  # Boolean target type (simple yes/no)
218
- if d.get('target_type') == 'boolean':
230
+ if d.get("target_type") == "boolean":
219
231
  score += 15
220
- reasons.append('Simple yes/no target')
221
-
232
+ reasons.append("Simple yes/no target")
233
+
222
234
  # Not critical (save those for focused time)
223
- if d.get('priority') in ['low', 'medium']:
235
+ if d.get("priority") in ["low", "medium"]:
224
236
  score += 10
225
-
237
+
226
238
  if score >= 40: # Threshold for "quick win"
227
- quick_wins.append({
228
- 'deliverable': d,
229
- 'score': score,
230
- 'reasons': reasons,
231
- 'estimated_minutes': int(est_hours * 60) if est_hours > 0 else 30
232
- })
233
-
239
+ quick_wins.append(
240
+ {
241
+ "deliverable": d,
242
+ "score": score,
243
+ "reasons": reasons,
244
+ "estimated_minutes": (
245
+ int(est_hours * 60) if est_hours > 0 else 30
246
+ ),
247
+ }
248
+ )
249
+
234
250
  # Sort by score
235
- quick_wins.sort(key=lambda x: x['score'], reverse=True)
236
-
251
+ quick_wins.sort(key=lambda x: x["score"], reverse=True)
252
+
237
253
  return quick_wins[:5] # Top 5
238
-
254
+
239
255
  def _get_coverage_gaps(self, engagement_id: int) -> List[Dict]:
240
256
  """
241
257
  Identify phases or categories with low completion rates.
242
258
  """
243
259
  phase_breakdown = self.tt.get_phase_breakdown(engagement_id)
244
-
260
+
245
261
  gaps = []
246
-
262
+
247
263
  for phase, stats in phase_breakdown.items():
248
- if stats['total'] == 0:
264
+ if stats["total"] == 0:
249
265
  continue
250
-
251
- completion_rate = stats['completion_rate']
252
-
266
+
267
+ completion_rate = stats["completion_rate"]
268
+
253
269
  # Flag phases below 50% completion
254
270
  if completion_rate < 50:
255
- severity = 'critical' if completion_rate < 25 else 'high'
256
-
257
- gaps.append({
258
- 'phase': phase,
259
- 'completion_rate': completion_rate,
260
- 'completed': stats['completed'],
261
- 'total': stats['total'],
262
- 'remaining': stats['total'] - stats['completed'],
263
- 'severity': severity,
264
- 'recommendation': self._get_phase_recommendation(phase, stats)
265
- })
266
-
271
+ severity = "critical" if completion_rate < 25 else "high"
272
+
273
+ gaps.append(
274
+ {
275
+ "phase": phase,
276
+ "completion_rate": completion_rate,
277
+ "completed": stats["completed"],
278
+ "total": stats["total"],
279
+ "remaining": stats["total"] - stats["completed"],
280
+ "severity": severity,
281
+ "recommendation": self._get_phase_recommendation(phase, stats),
282
+ }
283
+ )
284
+
267
285
  # Sort by completion rate (lowest first)
268
- gaps.sort(key=lambda x: x['completion_rate'])
269
-
286
+ gaps.sort(key=lambda x: x["completion_rate"])
287
+
270
288
  return gaps
271
-
289
+
272
290
  def _get_phase_recommendation(self, phase: str, stats: Dict) -> str:
273
291
  """Generate phase-specific recommendation."""
274
- remaining = stats['total'] - stats['completed']
275
-
292
+ remaining = stats["total"] - stats["completed"]
293
+
276
294
  recommendations = {
277
- 'reconnaissance': f'Run OSINT tools to complete {remaining} recon deliverables',
278
- 'enumeration': f'Enumerate services and users ({remaining} items remaining)',
279
- 'exploitation': f'Test for vulnerabilities - {remaining} exploit deliverables pending',
280
- 'post_exploitation': f'Complete post-exploitation activities ({remaining} remaining)',
281
- 'techniques': f'Document techniques and methodologies ({remaining} items left)'
295
+ "reconnaissance": f"Run OSINT tools to complete {remaining} recon deliverables",
296
+ "enumeration": f"Enumerate services and users ({remaining} items remaining)",
297
+ "exploitation": f"Test for vulnerabilities - {remaining} exploit deliverables pending",
298
+ "post_exploitation": f"Complete post-exploitation activities ({remaining} remaining)",
299
+ "techniques": f"Document techniques and methodologies ({remaining} items left)",
282
300
  }
283
-
284
- return recommendations.get(phase, f'Complete {remaining} {phase} deliverables')
285
-
301
+
302
+ return recommendations.get(phase, f"Complete {remaining} {phase} deliverables")
303
+
286
304
  def _get_at_risk_deliverables(self, engagement_id: int) -> List[Dict]:
287
305
  """
288
306
  Identify deliverables at risk of delay.
289
-
307
+
290
308
  At risk if:
291
309
  - In progress for > 24 hours
292
310
  - Critical priority but not started
@@ -294,126 +312,142 @@ class RecommendationEngine:
294
312
  - High estimated hours but no progress
295
313
  """
296
314
  deliverables = self.dm.list_deliverables(engagement_id)
297
-
315
+
298
316
  at_risk = []
299
-
317
+
300
318
  for d in deliverables:
301
- if d['status'] == 'completed':
319
+ if d["status"] == "completed":
302
320
  continue
303
-
321
+
304
322
  risk_factors = []
305
323
  risk_score = 0
306
-
324
+
307
325
  # In progress too long
308
- if d['status'] == 'in_progress' and d.get('started_at'):
326
+ if d["status"] == "in_progress" and d.get("started_at"):
309
327
  try:
310
- started = datetime.fromisoformat(d['started_at'].replace('Z', '+00:00'))
328
+ started = datetime.fromisoformat(
329
+ d["started_at"].replace("Z", "+00:00")
330
+ )
311
331
  hours_since = (datetime.now() - started).total_seconds() / 3600
312
-
332
+
313
333
  if hours_since > 48:
314
334
  risk_score += 40
315
- risk_factors.append(f'In progress {int(hours_since)}h')
335
+ risk_factors.append(f"In progress {int(hours_since)}h")
316
336
  elif hours_since > 24:
317
337
  risk_score += 20
318
- risk_factors.append(f'In progress {int(hours_since)}h')
338
+ risk_factors.append(f"In progress {int(hours_since)}h")
319
339
  except:
320
340
  pass
321
-
341
+
322
342
  # Critical but not started
323
- if d.get('priority') == 'critical' and d['status'] == 'pending':
343
+ if d.get("priority") == "critical" and d["status"] == "pending":
324
344
  risk_score += 30
325
- risk_factors.append('Critical priority not started')
326
-
345
+ risk_factors.append("Critical priority not started")
346
+
327
347
  # Has blocker
328
- if d.get('blocker'):
348
+ if d.get("blocker"):
329
349
  risk_score += 50
330
- risk_factors.append('Blocked')
331
-
350
+ risk_factors.append("Blocked")
351
+
332
352
  # High estimated hours but pending
333
- est_hours = d.get('estimated_hours', 0)
334
- if est_hours > 8 and d['status'] == 'pending':
353
+ est_hours = d.get("estimated_hours", 0)
354
+ if est_hours > 8 and d["status"] == "pending":
335
355
  risk_score += 15
336
- risk_factors.append(f'{est_hours}h estimated, not started')
337
-
356
+ risk_factors.append(f"{est_hours}h estimated, not started")
357
+
338
358
  if risk_score >= 20: # Risk threshold
339
- severity = 'critical' if risk_score >= 50 else ('high' if risk_score >= 35 else 'medium')
340
-
341
- at_risk.append({
342
- 'deliverable': d,
343
- 'risk_score': risk_score,
344
- 'risk_factors': risk_factors,
345
- 'severity': severity
346
- })
347
-
359
+ severity = (
360
+ "critical"
361
+ if risk_score >= 50
362
+ else ("high" if risk_score >= 35 else "medium")
363
+ )
364
+
365
+ at_risk.append(
366
+ {
367
+ "deliverable": d,
368
+ "risk_score": risk_score,
369
+ "risk_factors": risk_factors,
370
+ "severity": severity,
371
+ }
372
+ )
373
+
348
374
  # Sort by risk score
349
- at_risk.sort(key=lambda x: x['risk_score'], reverse=True)
350
-
375
+ at_risk.sort(key=lambda x: x["risk_score"], reverse=True)
376
+
351
377
  return at_risk[:5] # Top 5
352
-
378
+
353
379
  def _get_priority_boost_suggestions(self, engagement_id: int) -> List[Dict]:
354
380
  """
355
381
  Suggest deliverables that should be higher priority.
356
-
382
+
357
383
  Based on:
358
384
  - Lots of evidence collected (we found something interesting)
359
385
  - Related to critical findings
360
386
  - Dependency for other deliverables
361
387
  """
362
388
  deliverables = self.dm.list_deliverables(engagement_id)
363
-
389
+
364
390
  suggestions = []
365
-
391
+
366
392
  for d in deliverables:
367
- if d['status'] == 'completed':
393
+ if d["status"] == "completed":
368
394
  continue
369
-
370
- current_priority = d.get('priority', 'medium')
371
-
395
+
396
+ current_priority = d.get("priority", "medium")
397
+
372
398
  # Skip if already critical
373
- if current_priority == 'critical':
399
+ if current_priority == "critical":
374
400
  continue
375
-
401
+
376
402
  boost_score = 0
377
403
  reasons = []
378
-
404
+
379
405
  # High evidence count (we found something interesting)
380
- evidence = self.em.get_evidence(d['id'])
381
-
382
- critical_findings = len([f for f in evidence['findings'] if f.get('severity') == 'critical'])
383
- high_findings = len([f for f in evidence['findings'] if f.get('severity') == 'high'])
384
-
406
+ evidence = self.em.get_evidence(d["id"])
407
+
408
+ critical_findings = len(
409
+ [f for f in evidence["findings"] if f.get("severity") == "critical"]
410
+ )
411
+ high_findings = len(
412
+ [f for f in evidence["findings"] if f.get("severity") == "high"]
413
+ )
414
+
385
415
  if critical_findings > 0:
386
416
  boost_score += 50
387
- reasons.append(f'{critical_findings} critical findings linked')
388
-
417
+ reasons.append(f"{critical_findings} critical findings linked")
418
+
389
419
  if high_findings > 0:
390
420
  boost_score += 25
391
- reasons.append(f'{high_findings} high findings linked')
392
-
421
+ reasons.append(f"{high_findings} high findings linked")
422
+
393
423
  # Lots of evidence in general
394
- total_evidence = sum([
395
- len(evidence['findings']),
396
- len(evidence['credentials']),
397
- len(evidence['screenshots']),
398
- len(evidence['jobs'])
399
- ])
400
-
424
+ total_evidence = sum(
425
+ [
426
+ len(evidence["findings"]),
427
+ len(evidence["credentials"]),
428
+ len(evidence["screenshots"]),
429
+ len(evidence["jobs"]),
430
+ ]
431
+ )
432
+
401
433
  if total_evidence > 10:
402
434
  boost_score += 20
403
- reasons.append(f'{total_evidence} evidence items')
404
-
435
+ reasons.append(f"{total_evidence} evidence items")
436
+
405
437
  if boost_score >= 25: # Threshold
406
- suggested_priority = 'critical' if boost_score >= 50 else 'high'
407
-
408
- suggestions.append({
409
- 'deliverable': d,
410
- 'current_priority': current_priority,
411
- 'suggested_priority': suggested_priority,
412
- 'boost_score': boost_score,
413
- 'reasons': reasons
414
- })
415
-
438
+ suggested_priority = "critical" if boost_score >= 50 else "high"
439
+
440
+ suggestions.append(
441
+ {
442
+ "deliverable": d,
443
+ "current_priority": current_priority,
444
+ "suggested_priority": suggested_priority,
445
+ "boost_score": boost_score,
446
+ "reasons": reasons,
447
+ }
448
+ )
449
+
416
450
  # Sort by boost score
417
- suggestions.sort(key=lambda x: x['boost_score'], reverse=True)
418
-
451
+ suggestions.sort(key=lambda x: x["boost_score"], reverse=True)
452
+
419
453
  return suggestions[:5] # Top 5