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
@@ -5,6 +5,7 @@ This module provides the connection and management layer for Ollama,
5
5
  enabling SoulEyez to generate AI-powered attack path recommendations
6
6
  without sending sensitive engagement data to the cloud.
7
7
  """
8
+
8
9
  import logging
9
10
  from typing import Optional, Dict, Any
10
11
 
@@ -12,6 +13,7 @@ from typing import Optional, Dict, Any
12
13
  try:
13
14
  import ollama
14
15
  from ollama import Client
16
+
15
17
  OLLAMA_AVAILABLE = True
16
18
  except ImportError:
17
19
  ollama = None
@@ -35,8 +37,12 @@ class OllamaService:
35
37
  DEFAULT_MODEL = "llama3.1:8b" # 8B parameter model for good quality reasoning
36
38
  CONNECTION_TIMEOUT = 30 # seconds
37
39
 
38
- def __init__(self, endpoint: Optional[str] = None, model: Optional[str] = None,
39
- allow_failback: bool = True):
40
+ def __init__(
41
+ self,
42
+ endpoint: Optional[str] = None,
43
+ model: Optional[str] = None,
44
+ allow_failback: bool = True,
45
+ ):
40
46
  """
41
47
  Initialize Ollama service.
42
48
 
@@ -52,15 +58,16 @@ class OllamaService:
52
58
 
53
59
  # Read endpoint from config if not provided
54
60
  if endpoint is None:
55
- endpoint = get('ai.ollama_url', self.DEFAULT_ENDPOINT)
61
+ endpoint = get("ai.ollama_url", self.DEFAULT_ENDPOINT)
56
62
 
57
63
  self._original_endpoint = endpoint
58
64
  self._failback_used = False
59
65
  self._allow_failback = allow_failback
60
- self._ollama_mode = get('ai.ollama_mode', 'local')
66
+ self._ollama_mode = get("ai.ollama_mode", "local")
61
67
 
62
68
  # Security: Validate endpoint is localhost or VM host gateway
63
69
  from souleyez.core.network_utils import is_valid_ollama_host
70
+
64
71
  is_valid, reason = is_valid_ollama_host(endpoint)
65
72
  if not is_valid:
66
73
  logger.error(f"Ollama endpoint blocked: {reason}")
@@ -80,7 +87,7 @@ class OllamaService:
80
87
 
81
88
  # Read model from config if not provided
82
89
  if model is None:
83
- model = get('ai.ollama_model', self.DEFAULT_MODEL)
90
+ model = get("ai.ollama_model", self.DEFAULT_MODEL)
84
91
 
85
92
  self.model = model
86
93
 
@@ -100,14 +107,16 @@ class OllamaService:
100
107
  return False
101
108
 
102
109
  # Only failback if we're in remote mode and not already on localhost
103
- if self._ollama_mode != 'remote':
110
+ if self._ollama_mode != "remote":
104
111
  return False
105
112
 
106
113
  if self.endpoint == self.DEFAULT_ENDPOINT:
107
114
  return False
108
115
 
109
- logger.warning(f"Remote Ollama at {self._original_endpoint} unreachable, "
110
- f"falling back to localhost")
116
+ logger.warning(
117
+ f"Remote Ollama at {self._original_endpoint} unreachable, "
118
+ f"falling back to localhost"
119
+ )
111
120
 
112
121
  # Try localhost
113
122
  try:
@@ -183,7 +192,9 @@ class OllamaService:
183
192
  if self._model_available:
184
193
  logger.info(f"Model '{model_name}' is available")
185
194
  else:
186
- logger.warning(f"Model '{model_name}' not found. Available: {available_models}")
195
+ logger.warning(
196
+ f"Model '{model_name}' not found. Available: {available_models}"
197
+ )
187
198
 
188
199
  return self._model_available
189
200
  except Exception as e:
@@ -216,7 +227,9 @@ class OllamaService:
216
227
  logger.error(f"Failed to pull model: {e}")
217
228
  return False
218
229
 
219
- def generate(self, prompt: str, model_name: Optional[str] = None, timeout: int = 120) -> Optional[str]:
230
+ def generate(
231
+ self, prompt: str, model_name: Optional[str] = None, timeout: int = 120
232
+ ) -> Optional[str]:
220
233
  """
221
234
  Generate a response from the LLM.
222
235
 
@@ -255,13 +268,10 @@ class OllamaService:
255
268
  signal.alarm(timeout)
256
269
 
257
270
  try:
258
- response = self.client.generate(
259
- model=model_name,
260
- prompt=prompt
261
- )
271
+ response = self.client.generate(model=model_name, prompt=prompt)
262
272
  signal.alarm(0)
263
273
  signal.signal(signal.SIGALRM, old_handler)
264
- return response.get('response', '')
274
+ return response.get("response", "")
265
275
  except TimeoutError:
266
276
  signal.alarm(0)
267
277
  signal.signal(signal.SIGALRM, old_handler)
@@ -274,11 +284,8 @@ class OllamaService:
274
284
  return None
275
285
  else:
276
286
  # In a thread - can't use signals, just call directly
277
- response = self.client.generate(
278
- model=model_name,
279
- prompt=prompt
280
- )
281
- return response.get('response', '')
287
+ response = self.client.generate(model=model_name, prompt=prompt)
288
+ return response.get("response", "")
282
289
  except Exception as e:
283
290
  logger.error(f"Generation failed: {e}")
284
291
  return None
@@ -291,36 +298,36 @@ class OllamaService:
291
298
  dict: Status information including connection, models, etc.
292
299
  """
293
300
  status = {
294
- 'endpoint': self.endpoint,
295
- 'original_endpoint': self._original_endpoint,
296
- 'mode': self._ollama_mode,
297
- 'failback_used': self._failback_used,
298
- 'connected': False,
299
- 'models': [],
300
- 'default_model': self.DEFAULT_MODEL,
301
- 'configured_model': self.model,
302
- 'model_available': False,
303
- 'error': None
301
+ "endpoint": self.endpoint,
302
+ "original_endpoint": self._original_endpoint,
303
+ "mode": self._ollama_mode,
304
+ "failback_used": self._failback_used,
305
+ "connected": False,
306
+ "models": [],
307
+ "default_model": self.DEFAULT_MODEL,
308
+ "configured_model": self.model,
309
+ "model_available": False,
310
+ "error": None,
304
311
  }
305
312
 
306
313
  # Check connection
307
314
  if not self.check_connection():
308
- status['error'] = 'Cannot connect to Ollama. Is it running?'
315
+ status["error"] = "Cannot connect to Ollama. Is it running?"
309
316
  return status
310
317
 
311
- status['connected'] = True
312
- status['endpoint'] = self.endpoint # Update in case failback changed it
313
- status['failback_used'] = self._failback_used
318
+ status["connected"] = True
319
+ status["endpoint"] = self.endpoint # Update in case failback changed it
320
+ status["failback_used"] = self._failback_used
314
321
 
315
322
  # Get available models
316
323
  try:
317
324
  models_response = self.client.list()
318
- status['models'] = [m.model for m in models_response.models]
325
+ status["models"] = [m.model for m in models_response.models]
319
326
  except Exception as e:
320
- status['error'] = f'Error listing models: {e}'
327
+ status["error"] = f"Error listing models: {e}"
321
328
  return status
322
329
 
323
330
  # Check if default model is available
324
- status['model_available'] = self.check_model()
331
+ status["model_available"] = self.check_model()
325
332
 
326
333
  return status
@@ -18,52 +18,47 @@ class PathScorer:
18
18
 
19
19
  # Scoring weights (configurable)
20
20
  WEIGHTS = {
21
- 'success': 0.4,
22
- 'impact': 0.3,
23
- 'stealth': 0.2,
24
- 'complexity': 0.1 # Negative weight
21
+ "success": 0.4,
22
+ "impact": 0.3,
23
+ "stealth": 0.2,
24
+ "complexity": 0.1, # Negative weight
25
25
  }
26
26
 
27
27
  # Success probability scoring
28
28
  SUCCESS_SCORES = {
29
- 'has_valid_creds': 30,
30
- 'known_vulnerability': 25,
31
- 'common_service': 15, # SSH, RDP, HTTP
32
- 'uncommon_service': 5
29
+ "has_valid_creds": 30,
30
+ "known_vulnerability": 25,
31
+ "common_service": 15, # SSH, RDP, HTTP
32
+ "uncommon_service": 5,
33
33
  }
34
34
 
35
35
  # Impact scoring
36
36
  IMPACT_SCORES = {
37
- 'domain_controller': 40,
38
- 'database': 30,
39
- 'file_server': 20,
40
- 'workstation': 10,
41
- 'unknown': 5
37
+ "domain_controller": 40,
38
+ "database": 30,
39
+ "file_server": 20,
40
+ "workstation": 10,
41
+ "unknown": 5,
42
42
  }
43
43
 
44
44
  # Stealth scoring
45
45
  STEALTH_SCORES = {
46
- 'credential_reuse': 30, # No exploits
47
- 'quiet_exploit': 20,
48
- 'noisy_exploit': 10,
49
- 'unknown': 15 # Default to middle ground
46
+ "credential_reuse": 30, # No exploits
47
+ "quiet_exploit": 20,
48
+ "noisy_exploit": 10,
49
+ "unknown": 15, # Default to middle ground
50
50
  }
51
51
 
52
52
  # Complexity penalty
53
- COMPLEXITY_PENALTIES = {
54
- 1: 0,
55
- 2: 5,
56
- 3: 5,
57
- 4: 10,
58
- 5: 10,
59
- 6: 20
60
- }
53
+ COMPLEXITY_PENALTIES = {1: 0, 2: 5, 3: 5, 4: 10, 5: 10, 6: 20}
61
54
 
62
55
  def __init__(self):
63
56
  """Initialize path scorer."""
64
57
  pass
65
58
 
66
- def score_path(self, path: Dict[str, Any], engagement_data: Dict[str, Any]) -> Dict[str, Any]:
59
+ def score_path(
60
+ self, path: Dict[str, Any], engagement_data: Dict[str, Any]
61
+ ) -> Dict[str, Any]:
67
62
  """
68
63
  Score a single attack path.
69
64
 
@@ -85,93 +80,111 @@ class PathScorer:
85
80
 
86
81
  # Calculate weighted total
87
82
  total_score = (
88
- (success_score * self.WEIGHTS['success']) +
89
- (impact_score * self.WEIGHTS['impact']) +
90
- (stealth_score * self.WEIGHTS['stealth']) -
91
- (complexity_penalty * self.WEIGHTS['complexity'])
83
+ (success_score * self.WEIGHTS["success"])
84
+ + (impact_score * self.WEIGHTS["impact"])
85
+ + (stealth_score * self.WEIGHTS["stealth"])
86
+ - (complexity_penalty * self.WEIGHTS["complexity"])
92
87
  )
93
88
 
94
89
  return {
95
- 'path': path,
96
- 'scores': {
97
- 'success': success_score,
98
- 'impact': impact_score,
99
- 'stealth': stealth_score,
100
- 'complexity': complexity_penalty
90
+ "path": path,
91
+ "scores": {
92
+ "success": success_score,
93
+ "impact": impact_score,
94
+ "stealth": stealth_score,
95
+ "complexity": complexity_penalty,
101
96
  },
102
- 'total_score': round(total_score, 2),
103
- 'rank': None # Set by rank_paths()
97
+ "total_score": round(total_score, 2),
98
+ "rank": None, # Set by rank_paths()
104
99
  }
105
100
 
106
- def _score_success(self, path: Dict[str, Any], engagement_data: Dict[str, Any]) -> float:
101
+ def _score_success(
102
+ self, path: Dict[str, Any], engagement_data: Dict[str, Any]
103
+ ) -> float:
107
104
  """Score success probability based on available resources."""
108
105
  score = 0
109
- action = path.get('action', '').lower()
110
- target = path.get('target', '').lower()
106
+ action = path.get("action", "").lower()
107
+ target = path.get("target", "").lower()
111
108
 
112
109
  # Check for valid credentials
113
- creds = engagement_data.get('credentials', [])
114
- valid_creds = [c for c in creds if c.get('status') == 'valid']
115
- if valid_creds and ('credential' in action or 'login' in action or 'auth' in action):
116
- score += self.SUCCESS_SCORES['has_valid_creds']
110
+ creds = engagement_data.get("credentials", [])
111
+ valid_creds = [c for c in creds if c.get("status") == "valid"]
112
+ if valid_creds and (
113
+ "credential" in action or "login" in action or "auth" in action
114
+ ):
115
+ score += self.SUCCESS_SCORES["has_valid_creds"]
117
116
 
118
117
  # Check for known vulnerabilities
119
- findings = engagement_data.get('findings', [])
120
- critical_vulns = [f for f in findings if f.get('severity') in ['critical', 'high']]
121
- if critical_vulns and ('exploit' in action or 'vulnerability' in action):
122
- score += self.SUCCESS_SCORES['known_vulnerability']
118
+ findings = engagement_data.get("findings", [])
119
+ critical_vulns = [
120
+ f for f in findings if f.get("severity") in ["critical", "high"]
121
+ ]
122
+ if critical_vulns and ("exploit" in action or "vulnerability" in action):
123
+ score += self.SUCCESS_SCORES["known_vulnerability"]
123
124
 
124
125
  # Check service type
125
- common_services = ['ssh', 'rdp', 'http', 'https', 'mysql', 'smb']
126
+ common_services = ["ssh", "rdp", "http", "https", "mysql", "smb"]
126
127
  if any(svc in target or svc in action for svc in common_services):
127
- score += self.SUCCESS_SCORES['common_service']
128
+ score += self.SUCCESS_SCORES["common_service"]
128
129
  else:
129
- score += self.SUCCESS_SCORES['uncommon_service']
130
+ score += self.SUCCESS_SCORES["uncommon_service"]
130
131
 
131
132
  return min(score, 100) # Cap at 100
132
133
 
133
- def _score_impact(self, path: Dict[str, Any], engagement_data: Dict[str, Any]) -> float:
134
+ def _score_impact(
135
+ self, path: Dict[str, Any], engagement_data: Dict[str, Any]
136
+ ) -> float:
134
137
  """Score potential impact of successful path."""
135
- target = path.get('target', '').lower()
136
- action = path.get('action', '').lower()
138
+ target = path.get("target", "").lower()
139
+ action = path.get("action", "").lower()
137
140
 
138
141
  # Check target type
139
- if 'domain controller' in target or 'dc' in target or 'ad' in target:
140
- return self.IMPACT_SCORES['domain_controller']
142
+ if "domain controller" in target or "dc" in target or "ad" in target:
143
+ return self.IMPACT_SCORES["domain_controller"]
141
144
 
142
- if 'database' in target or 'mysql' in target or 'postgres' in target or 'sql' in target:
143
- return self.IMPACT_SCORES['database']
145
+ if (
146
+ "database" in target
147
+ or "mysql" in target
148
+ or "postgres" in target
149
+ or "sql" in target
150
+ ):
151
+ return self.IMPACT_SCORES["database"]
144
152
 
145
- if 'file server' in target or 'smb' in target or 'share' in target:
146
- return self.IMPACT_SCORES['file_server']
153
+ if "file server" in target or "smb" in target or "share" in target:
154
+ return self.IMPACT_SCORES["file_server"]
147
155
 
148
156
  # Check for privilege escalation (high impact)
149
- if 'privilege' in action or 'escalat' in action or 'root' in action or 'admin' in action:
150
- return self.IMPACT_SCORES['database'] # High impact
157
+ if (
158
+ "privilege" in action
159
+ or "escalat" in action
160
+ or "root" in action
161
+ or "admin" in action
162
+ ):
163
+ return self.IMPACT_SCORES["database"] # High impact
151
164
 
152
165
  # Default to workstation
153
- return self.IMPACT_SCORES['workstation']
166
+ return self.IMPACT_SCORES["workstation"]
154
167
 
155
168
  def _score_stealth(self, path: Dict[str, Any]) -> float:
156
169
  """Score stealth/detection likelihood."""
157
- action = path.get('action', '').lower()
170
+ action = path.get("action", "").lower()
158
171
 
159
172
  # Credential reuse is stealthiest
160
- if 'credential' in action and 'exploit' not in action and 'brute' not in action:
161
- return self.STEALTH_SCORES['credential_reuse']
173
+ if "credential" in action and "exploit" not in action and "brute" not in action:
174
+ return self.STEALTH_SCORES["credential_reuse"]
162
175
 
163
176
  # Known quiet exploits
164
- quiet_keywords = ['pass-the-hash', 'token', 'kerberos', 'golden ticket']
177
+ quiet_keywords = ["pass-the-hash", "token", "kerberos", "golden ticket"]
165
178
  if any(keyword in action for keyword in quiet_keywords):
166
- return self.STEALTH_SCORES['quiet_exploit']
179
+ return self.STEALTH_SCORES["quiet_exploit"]
167
180
 
168
181
  # Noisy exploits
169
- noisy_keywords = ['brute', 'scan', 'spray', 'exploit', 'buffer overflow']
182
+ noisy_keywords = ["brute", "scan", "spray", "exploit", "buffer overflow"]
170
183
  if any(keyword in action for keyword in noisy_keywords):
171
- return self.STEALTH_SCORES['noisy_exploit']
184
+ return self.STEALTH_SCORES["noisy_exploit"]
172
185
 
173
186
  # Default
174
- return self.STEALTH_SCORES['unknown']
187
+ return self.STEALTH_SCORES["unknown"]
175
188
 
176
189
  def _score_complexity(self, path: Dict[str, Any]) -> float:
177
190
  """Score complexity (simpler = better, so this is a penalty)."""
@@ -179,7 +192,7 @@ class PathScorer:
179
192
  # Multi-step paths get penalties based on length
180
193
 
181
194
  # Check if this is a multi-step path
182
- steps = path.get('steps', [])
195
+ steps = path.get("steps", [])
183
196
  if steps:
184
197
  num_steps = len(steps)
185
198
  else:
@@ -191,7 +204,9 @@ class PathScorer:
191
204
  else:
192
205
  return self.COMPLEXITY_PENALTIES[6]
193
206
 
194
- def rank_paths(self, paths: List[Dict[str, Any]], engagement_data: Dict[str, Any]) -> List[Dict[str, Any]]:
207
+ def rank_paths(
208
+ self, paths: List[Dict[str, Any]], engagement_data: Dict[str, Any]
209
+ ) -> List[Dict[str, Any]]:
195
210
  """
196
211
  Score and rank multiple paths.
197
212
 
@@ -209,10 +224,10 @@ class PathScorer:
209
224
  scored_paths.append(scored_path)
210
225
 
211
226
  # Sort by total score (descending)
212
- scored_paths.sort(key=lambda x: x['total_score'], reverse=True)
227
+ scored_paths.sort(key=lambda x: x["total_score"], reverse=True)
213
228
 
214
229
  # Assign ranks
215
230
  for i, scored_path in enumerate(scored_paths, 1):
216
- scored_path['rank'] = i
231
+ scored_path["rank"] = i
217
232
 
218
233
  return scored_paths