souleyez 2.43.28__py3-none-any.whl → 2.43.32__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 (356) 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 +9592 -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 +1238 -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 +2198 -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 +288 -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/whois_handler.py +277 -0
  126. souleyez/handlers/wpscan_handler.py +554 -0
  127. souleyez/history.py +32 -16
  128. souleyez/importers/msf_importer.py +106 -75
  129. souleyez/importers/smart_importer.py +208 -147
  130. souleyez/integrations/siem/__init__.py +10 -10
  131. souleyez/integrations/siem/base.py +17 -18
  132. souleyez/integrations/siem/elastic.py +108 -122
  133. souleyez/integrations/siem/factory.py +207 -80
  134. souleyez/integrations/siem/googlesecops.py +146 -154
  135. souleyez/integrations/siem/rule_mappings/__init__.py +1 -1
  136. souleyez/integrations/siem/rule_mappings/wazuh_rules.py +8 -5
  137. souleyez/integrations/siem/sentinel.py +107 -109
  138. souleyez/integrations/siem/splunk.py +246 -212
  139. souleyez/integrations/siem/wazuh.py +65 -71
  140. souleyez/integrations/wazuh/__init__.py +5 -5
  141. souleyez/integrations/wazuh/client.py +70 -93
  142. souleyez/integrations/wazuh/config.py +85 -57
  143. souleyez/integrations/wazuh/host_mapper.py +28 -36
  144. souleyez/integrations/wazuh/sync.py +78 -68
  145. souleyez/intelligence/__init__.py +4 -5
  146. souleyez/intelligence/correlation_analyzer.py +309 -295
  147. souleyez/intelligence/exploit_knowledge.py +661 -623
  148. souleyez/intelligence/exploit_suggestions.py +159 -139
  149. souleyez/intelligence/gap_analyzer.py +132 -97
  150. souleyez/intelligence/gap_detector.py +251 -214
  151. souleyez/intelligence/sensitive_tables.py +266 -129
  152. souleyez/intelligence/service_parser.py +137 -123
  153. souleyez/intelligence/surface_analyzer.py +407 -268
  154. souleyez/intelligence/target_parser.py +159 -162
  155. souleyez/licensing/__init__.py +6 -6
  156. souleyez/licensing/validator.py +17 -19
  157. souleyez/log_config.py +79 -54
  158. souleyez/main.py +1505 -687
  159. souleyez/migrations/fix_job_counter.py +16 -14
  160. souleyez/parsers/bloodhound_parser.py +41 -39
  161. souleyez/parsers/crackmapexec_parser.py +178 -111
  162. souleyez/parsers/dalfox_parser.py +72 -77
  163. souleyez/parsers/dnsrecon_parser.py +103 -91
  164. souleyez/parsers/enum4linux_parser.py +183 -153
  165. souleyez/parsers/ffuf_parser.py +29 -25
  166. souleyez/parsers/gobuster_parser.py +301 -41
  167. souleyez/parsers/hashcat_parser.py +324 -79
  168. souleyez/parsers/http_fingerprint_parser.py +350 -103
  169. souleyez/parsers/hydra_parser.py +131 -111
  170. souleyez/parsers/impacket_parser.py +231 -178
  171. souleyez/parsers/john_parser.py +98 -86
  172. souleyez/parsers/katana_parser.py +316 -0
  173. souleyez/parsers/msf_parser.py +943 -498
  174. souleyez/parsers/nikto_parser.py +346 -65
  175. souleyez/parsers/nmap_parser.py +262 -174
  176. souleyez/parsers/nuclei_parser.py +40 -44
  177. souleyez/parsers/responder_parser.py +26 -26
  178. souleyez/parsers/searchsploit_parser.py +74 -74
  179. souleyez/parsers/service_explorer_parser.py +279 -0
  180. souleyez/parsers/smbmap_parser.py +180 -124
  181. souleyez/parsers/sqlmap_parser.py +434 -308
  182. souleyez/parsers/theharvester_parser.py +75 -57
  183. souleyez/parsers/whois_parser.py +135 -94
  184. souleyez/parsers/wpscan_parser.py +278 -190
  185. souleyez/plugins/afp.py +44 -36
  186. souleyez/plugins/afp_brute.py +114 -46
  187. souleyez/plugins/ard.py +48 -37
  188. souleyez/plugins/bloodhound.py +95 -61
  189. souleyez/plugins/certipy.py +303 -0
  190. souleyez/plugins/crackmapexec.py +186 -85
  191. souleyez/plugins/dalfox.py +120 -59
  192. souleyez/plugins/dns_hijack.py +146 -41
  193. souleyez/plugins/dnsrecon.py +97 -61
  194. souleyez/plugins/enum4linux.py +91 -66
  195. souleyez/plugins/evil_winrm.py +291 -0
  196. souleyez/plugins/ffuf.py +166 -90
  197. souleyez/plugins/firmware_extract.py +133 -29
  198. souleyez/plugins/gobuster.py +387 -190
  199. souleyez/plugins/gpp_extract.py +393 -0
  200. souleyez/plugins/hashcat.py +100 -73
  201. souleyez/plugins/http_fingerprint.py +854 -267
  202. souleyez/plugins/hydra.py +566 -200
  203. souleyez/plugins/impacket_getnpusers.py +117 -69
  204. souleyez/plugins/impacket_psexec.py +84 -64
  205. souleyez/plugins/impacket_secretsdump.py +103 -69
  206. souleyez/plugins/impacket_smbclient.py +89 -75
  207. souleyez/plugins/john.py +86 -69
  208. souleyez/plugins/katana.py +313 -0
  209. souleyez/plugins/kerbrute.py +237 -0
  210. souleyez/plugins/lfi_extract.py +541 -0
  211. souleyez/plugins/macos_ssh.py +117 -48
  212. souleyez/plugins/mdns.py +35 -30
  213. souleyez/plugins/msf_auxiliary.py +253 -130
  214. souleyez/plugins/msf_exploit.py +239 -161
  215. souleyez/plugins/nikto.py +134 -78
  216. souleyez/plugins/nmap.py +275 -91
  217. souleyez/plugins/nuclei.py +180 -89
  218. souleyez/plugins/nxc.py +285 -0
  219. souleyez/plugins/plugin_base.py +35 -36
  220. souleyez/plugins/plugin_template.py +13 -5
  221. souleyez/plugins/rdp_sec_check.py +130 -0
  222. souleyez/plugins/responder.py +112 -71
  223. souleyez/plugins/router_http_brute.py +76 -65
  224. souleyez/plugins/router_ssh_brute.py +118 -41
  225. souleyez/plugins/router_telnet_brute.py +124 -42
  226. souleyez/plugins/routersploit.py +91 -59
  227. souleyez/plugins/routersploit_exploit.py +77 -55
  228. souleyez/plugins/searchsploit.py +91 -77
  229. souleyez/plugins/service_explorer.py +1160 -0
  230. souleyez/plugins/smbmap.py +122 -72
  231. souleyez/plugins/smbpasswd.py +215 -0
  232. souleyez/plugins/sqlmap.py +301 -113
  233. souleyez/plugins/theharvester.py +127 -75
  234. souleyez/plugins/tr069.py +79 -57
  235. souleyez/plugins/upnp.py +65 -47
  236. souleyez/plugins/upnp_abuse.py +73 -55
  237. souleyez/plugins/vnc_access.py +129 -42
  238. souleyez/plugins/vnc_brute.py +109 -38
  239. souleyez/plugins/whois.py +77 -58
  240. souleyez/plugins/wpscan.py +173 -69
  241. souleyez/reporting/__init__.py +2 -1
  242. souleyez/reporting/attack_chain.py +411 -346
  243. souleyez/reporting/charts.py +436 -501
  244. souleyez/reporting/compliance_mappings.py +334 -201
  245. souleyez/reporting/detection_report.py +126 -125
  246. souleyez/reporting/formatters.py +828 -591
  247. souleyez/reporting/generator.py +386 -302
  248. souleyez/reporting/metrics.py +72 -75
  249. souleyez/scanner.py +35 -29
  250. souleyez/security/__init__.py +37 -11
  251. souleyez/security/scope_validator.py +175 -106
  252. souleyez/security/validation.py +223 -149
  253. souleyez/security.py +22 -6
  254. souleyez/storage/credentials.py +247 -186
  255. souleyez/storage/crypto.py +296 -129
  256. souleyez/storage/database.py +73 -50
  257. souleyez/storage/db.py +58 -36
  258. souleyez/storage/deliverable_evidence.py +177 -128
  259. souleyez/storage/deliverable_exporter.py +282 -246
  260. souleyez/storage/deliverable_templates.py +134 -116
  261. souleyez/storage/deliverables.py +135 -130
  262. souleyez/storage/engagements.py +109 -56
  263. souleyez/storage/evidence.py +181 -152
  264. souleyez/storage/execution_log.py +31 -17
  265. souleyez/storage/exploit_attempts.py +93 -57
  266. souleyez/storage/exploits.py +67 -36
  267. souleyez/storage/findings.py +48 -61
  268. souleyez/storage/hosts.py +176 -144
  269. souleyez/storage/migrate_to_engagements.py +43 -19
  270. souleyez/storage/migrations/_001_add_credential_enhancements.py +22 -12
  271. souleyez/storage/migrations/_002_add_status_tracking.py +10 -7
  272. souleyez/storage/migrations/_003_add_execution_log.py +14 -8
  273. souleyez/storage/migrations/_005_screenshots.py +13 -5
  274. souleyez/storage/migrations/_006_deliverables.py +13 -5
  275. souleyez/storage/migrations/_007_deliverable_templates.py +12 -7
  276. souleyez/storage/migrations/_008_add_nuclei_table.py +10 -4
  277. souleyez/storage/migrations/_010_evidence_linking.py +17 -10
  278. souleyez/storage/migrations/_011_timeline_tracking.py +20 -13
  279. souleyez/storage/migrations/_012_team_collaboration.py +34 -21
  280. souleyez/storage/migrations/_013_add_host_tags.py +12 -6
  281. souleyez/storage/migrations/_014_exploit_attempts.py +22 -10
  282. souleyez/storage/migrations/_015_add_mac_os_fields.py +15 -7
  283. souleyez/storage/migrations/_016_add_domain_field.py +10 -4
  284. souleyez/storage/migrations/_017_msf_sessions.py +16 -8
  285. souleyez/storage/migrations/_018_add_osint_target.py +10 -6
  286. souleyez/storage/migrations/_019_add_engagement_type.py +10 -6
  287. souleyez/storage/migrations/_020_add_rbac.py +36 -15
  288. souleyez/storage/migrations/_021_wazuh_integration.py +20 -8
  289. souleyez/storage/migrations/_022_wazuh_indexer_columns.py +6 -4
  290. souleyez/storage/migrations/_023_fix_detection_results_fk.py +16 -6
  291. souleyez/storage/migrations/_024_wazuh_vulnerabilities.py +26 -10
  292. souleyez/storage/migrations/_025_multi_siem_support.py +3 -5
  293. souleyez/storage/migrations/_026_add_engagement_scope.py +31 -12
  294. souleyez/storage/migrations/_027_multi_siem_persistence.py +32 -15
  295. souleyez/storage/migrations/__init__.py +26 -26
  296. souleyez/storage/migrations/migration_manager.py +19 -19
  297. souleyez/storage/msf_sessions.py +100 -65
  298. souleyez/storage/osint.py +17 -24
  299. souleyez/storage/recommendation_engine.py +269 -235
  300. souleyez/storage/screenshots.py +33 -32
  301. souleyez/storage/smb_shares.py +136 -92
  302. souleyez/storage/sqlmap_data.py +183 -128
  303. souleyez/storage/team_collaboration.py +135 -141
  304. souleyez/storage/timeline_tracker.py +122 -94
  305. souleyez/storage/wazuh_vulns.py +64 -66
  306. souleyez/storage/web_paths.py +33 -37
  307. souleyez/testing/credential_tester.py +221 -205
  308. souleyez/ui/__init__.py +1 -1
  309. souleyez/ui/ai_quotes.py +12 -12
  310. souleyez/ui/attack_surface.py +2439 -1516
  311. souleyez/ui/chain_rules_view.py +914 -382
  312. souleyez/ui/correlation_view.py +312 -230
  313. souleyez/ui/dashboard.py +2382 -1130
  314. souleyez/ui/deliverables_view.py +148 -62
  315. souleyez/ui/design_system.py +13 -13
  316. souleyez/ui/errors.py +49 -49
  317. souleyez/ui/evidence_linking_view.py +284 -179
  318. souleyez/ui/evidence_vault.py +393 -285
  319. souleyez/ui/exploit_suggestions_view.py +555 -349
  320. souleyez/ui/export_view.py +100 -66
  321. souleyez/ui/gap_analysis_view.py +315 -171
  322. souleyez/ui/help_system.py +105 -97
  323. souleyez/ui/intelligence_view.py +436 -293
  324. souleyez/ui/interactive.py +23142 -10430
  325. souleyez/ui/interactive_selector.py +75 -68
  326. souleyez/ui/log_formatter.py +47 -39
  327. souleyez/ui/menu_components.py +22 -13
  328. souleyez/ui/msf_auxiliary_menu.py +184 -133
  329. souleyez/ui/pending_chains_view.py +336 -172
  330. souleyez/ui/progress_indicators.py +5 -3
  331. souleyez/ui/recommendations_view.py +195 -137
  332. souleyez/ui/rule_builder.py +343 -225
  333. souleyez/ui/setup_wizard.py +678 -284
  334. souleyez/ui/shortcuts.py +217 -165
  335. souleyez/ui/splunk_gap_analysis_view.py +452 -270
  336. souleyez/ui/splunk_vulns_view.py +139 -86
  337. souleyez/ui/team_dashboard.py +498 -335
  338. souleyez/ui/template_selector.py +196 -105
  339. souleyez/ui/terminal.py +6 -6
  340. souleyez/ui/timeline_view.py +198 -127
  341. souleyez/ui/tool_setup.py +264 -164
  342. souleyez/ui/tutorial.py +202 -72
  343. souleyez/ui/tutorial_state.py +40 -40
  344. souleyez/ui/wazuh_vulns_view.py +235 -141
  345. souleyez/ui/wordlist_browser.py +260 -107
  346. souleyez/ui.py +464 -312
  347. souleyez/utils/tool_checker.py +427 -367
  348. souleyez/utils.py +33 -29
  349. souleyez/wordlists.py +134 -167
  350. {souleyez-2.43.28.dist-info → souleyez-2.43.32.dist-info}/METADATA +1 -1
  351. souleyez-2.43.32.dist-info/RECORD +441 -0
  352. {souleyez-2.43.28.dist-info → souleyez-2.43.32.dist-info}/WHEEL +1 -1
  353. souleyez-2.43.28.dist-info/RECORD +0 -379
  354. {souleyez-2.43.28.dist-info → souleyez-2.43.32.dist-info}/entry_points.txt +0 -0
  355. {souleyez-2.43.28.dist-info → souleyez-2.43.32.dist-info}/licenses/LICENSE +0 -0
  356. {souleyez-2.43.28.dist-info → souleyez-2.43.32.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