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.

Potentially problematic release.


This version of souleyez might be problematic. Click here for more details.

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
@@ -3,6 +3,7 @@ souleyez.ai.report_context - Build context for AI report generation
3
3
 
4
4
  Prepares engagement data in formats suitable for LLM prompt templates.
5
5
  """
6
+
6
7
  import logging
7
8
  from typing import Dict, Any, List, Optional
8
9
 
@@ -48,44 +49,44 @@ class ReportContextBuilder:
48
49
  creds = self.cm.list_credentials(engagement_id)
49
50
 
50
51
  # Count findings by severity
51
- severity_counts = {'critical': 0, 'high': 0, 'medium': 0, 'low': 0, 'info': 0}
52
+ severity_counts = {"critical": 0, "high": 0, "medium": 0, "low": 0, "info": 0}
52
53
  for f in findings:
53
- sev = f.get('severity', 'info').lower()
54
+ sev = f.get("severity", "info").lower()
54
55
  if sev in severity_counts:
55
56
  severity_counts[sev] += 1
56
57
  else:
57
- severity_counts['info'] += 1
58
+ severity_counts["info"] += 1
58
59
 
59
60
  # Count hosts with critical/high findings
60
61
  hosts_with_issues = set()
61
62
  for f in findings:
62
- if f.get('severity', '').lower() in ['critical', 'high']:
63
- if f.get('host_id'):
64
- hosts_with_issues.add(f['host_id'])
63
+ if f.get("severity", "").lower() in ["critical", "high"]:
64
+ if f.get("host_id"):
65
+ hosts_with_issues.add(f["host_id"])
65
66
 
66
67
  # Build top findings summary
67
68
  top_findings = self._format_top_findings(findings, limit=5)
68
69
 
69
70
  # Engagement type and duration
70
- eng_type = engagement.get('type', 'Penetration Test')
71
+ eng_type = engagement.get("type", "Penetration Test")
71
72
  duration = self._calculate_duration(engagement)
72
- scope = engagement.get('description', 'Network and application assessment')
73
+ scope = engagement.get("description", "Network and application assessment")
73
74
 
74
75
  return {
75
- 'engagement_name': engagement.get('name', 'Unknown'),
76
- 'engagement_type': eng_type,
77
- 'duration': duration,
78
- 'scope_summary': scope[:200] if scope else 'Full scope assessment',
79
- 'total_findings': len(findings),
80
- 'critical_count': severity_counts['critical'],
81
- 'high_count': severity_counts['high'],
82
- 'medium_count': severity_counts['medium'],
83
- 'low_count': severity_counts['low'],
84
- 'info_count': severity_counts['info'],
85
- 'total_hosts': len(hosts),
86
- 'compromised_hosts': len(hosts_with_issues),
87
- 'credentials_count': len(creds),
88
- 'top_findings': top_findings,
76
+ "engagement_name": engagement.get("name", "Unknown"),
77
+ "engagement_type": eng_type,
78
+ "duration": duration,
79
+ "scope_summary": scope[:200] if scope else "Full scope assessment",
80
+ "total_findings": len(findings),
81
+ "critical_count": severity_counts["critical"],
82
+ "high_count": severity_counts["high"],
83
+ "medium_count": severity_counts["medium"],
84
+ "low_count": severity_counts["low"],
85
+ "info_count": severity_counts["info"],
86
+ "total_hosts": len(hosts),
87
+ "compromised_hosts": len(hosts_with_issues),
88
+ "credentials_count": len(creds),
89
+ "top_findings": top_findings,
89
90
  }
90
91
 
91
92
  def build_finding_context(self, finding: Dict[str, Any]) -> Dict[str, Any]:
@@ -99,35 +100,35 @@ class ReportContextBuilder:
99
100
  dict: Context variables for FINDING_ENHANCEMENT_PROMPT
100
101
  """
101
102
  # Get host info if available
102
- host_ip = 'N/A'
103
- hostname = ''
104
- if finding.get('host_id'):
105
- host = self.hm.get_host(finding['host_id'])
103
+ host_ip = "N/A"
104
+ hostname = ""
105
+ if finding.get("host_id"):
106
+ host = self.hm.get_host(finding["host_id"])
106
107
  if host:
107
- host_ip = host.get('ip_address', 'N/A')
108
- hostname = host.get('hostname', '')
108
+ host_ip = host.get("ip_address", "N/A")
109
+ hostname = host.get("hostname", "")
109
110
 
110
111
  # Extract CVE/CWE
111
- cve = finding.get('refs', '') or 'N/A'
112
+ cve = finding.get("refs", "") or "N/A"
112
113
  if isinstance(cve, list):
113
- cve = ', '.join(cve[:3]) # Limit to first 3
114
+ cve = ", ".join(cve[:3]) # Limit to first 3
114
115
 
115
116
  # Clean evidence (truncate if too long)
116
- evidence = finding.get('evidence', '')
117
+ evidence = finding.get("evidence", "")
117
118
  if len(evidence) > 1000:
118
- evidence = evidence[:1000] + '\n... [truncated]'
119
+ evidence = evidence[:1000] + "\n... [truncated]"
119
120
 
120
121
  return {
121
- 'title': finding.get('title', 'Unknown Finding'),
122
- 'severity': finding.get('severity', 'Unknown').upper(),
123
- 'host': host_ip,
124
- 'hostname': hostname,
125
- 'port': finding.get('port', 'N/A'),
126
- 'service': finding.get('service', 'Unknown'),
127
- 'tool': finding.get('tool', 'Manual'),
128
- 'description': finding.get('description', 'No description provided'),
129
- 'cve': cve,
130
- 'evidence': evidence or 'No evidence recorded',
122
+ "title": finding.get("title", "Unknown Finding"),
123
+ "severity": finding.get("severity", "Unknown").upper(),
124
+ "host": host_ip,
125
+ "hostname": hostname,
126
+ "port": finding.get("port", "N/A"),
127
+ "service": finding.get("service", "Unknown"),
128
+ "tool": finding.get("tool", "Manual"),
129
+ "description": finding.get("description", "No description provided"),
130
+ "cve": cve,
131
+ "evidence": evidence or "No evidence recorded",
131
132
  }
132
133
 
133
134
  def build_remediation_context(self, engagement_id: int) -> Dict[str, Any]:
@@ -145,9 +146,9 @@ class ReportContextBuilder:
145
146
  creds = self.cm.list_credentials(engagement_id)
146
147
 
147
148
  # Count by severity
148
- severity_counts = {'critical': 0, 'high': 0, 'medium': 0, 'low': 0, 'info': 0}
149
+ severity_counts = {"critical": 0, "high": 0, "medium": 0, "low": 0, "info": 0}
149
150
  for f in findings:
150
- sev = f.get('severity', 'info').lower()
151
+ sev = f.get("severity", "info").lower()
151
152
  if sev in severity_counts:
152
153
  severity_counts[sev] += 1
153
154
 
@@ -158,61 +159,59 @@ class ReportContextBuilder:
158
159
  top_vulns = self._format_top_findings(findings, limit=10, include_medium=True)
159
160
 
160
161
  return {
161
- 'findings_summary': findings_summary,
162
- 'total_hosts': len(hosts),
163
- 'critical_count': severity_counts['critical'],
164
- 'high_count': severity_counts['high'],
165
- 'medium_count': severity_counts['medium'],
166
- 'creds_count': len(creds),
167
- 'top_vulnerabilities': top_vulns,
162
+ "findings_summary": findings_summary,
163
+ "total_hosts": len(hosts),
164
+ "critical_count": severity_counts["critical"],
165
+ "high_count": severity_counts["high"],
166
+ "medium_count": severity_counts["medium"],
167
+ "creds_count": len(creds),
168
+ "top_vulnerabilities": top_vulns,
168
169
  }
169
170
 
170
171
  def _format_top_findings(
171
- self,
172
- findings: List[Dict],
173
- limit: int = 5,
174
- include_medium: bool = False
172
+ self, findings: List[Dict], limit: int = 5, include_medium: bool = False
175
173
  ) -> str:
176
174
  """Format top critical/high findings as text."""
177
- severity_order = {'critical': 0, 'high': 1, 'medium': 2, 'low': 3, 'info': 4}
175
+ severity_order = {"critical": 0, "high": 1, "medium": 2, "low": 3, "info": 4}
178
176
 
179
177
  # Filter to critical/high (and optionally medium)
180
178
  max_severity = 2 if include_medium else 1
181
179
  priority_findings = [
182
- f for f in findings
183
- if severity_order.get(f.get('severity', 'info').lower(), 4) <= max_severity
180
+ f
181
+ for f in findings
182
+ if severity_order.get(f.get("severity", "info").lower(), 4) <= max_severity
184
183
  ]
185
184
 
186
185
  # Sort by severity
187
186
  priority_findings.sort(
188
- key=lambda f: severity_order.get(f.get('severity', 'info').lower(), 4)
187
+ key=lambda f: severity_order.get(f.get("severity", "info").lower(), 4)
189
188
  )
190
189
 
191
190
  # Format
192
191
  lines = []
193
192
  for f in priority_findings[:limit]:
194
- sev = f.get('severity', 'unknown').upper()
195
- title = f.get('title', 'Unknown')
196
- desc = f.get('description', '')[:100]
193
+ sev = f.get("severity", "unknown").upper()
194
+ title = f.get("title", "Unknown")
195
+ desc = f.get("description", "")[:100]
197
196
  lines.append(f"- [{sev}] {title}")
198
197
  if desc:
199
198
  lines.append(f" {desc}...")
200
199
 
201
- return '\n'.join(lines) if lines else 'No critical or high severity findings.'
200
+ return "\n".join(lines) if lines else "No critical or high severity findings."
202
201
 
203
202
  def _format_findings_by_severity(self, findings: List[Dict]) -> str:
204
203
  """Format all findings grouped by severity."""
205
- by_severity = {'critical': [], 'high': [], 'medium': [], 'low': [], 'info': []}
204
+ by_severity = {"critical": [], "high": [], "medium": [], "low": [], "info": []}
206
205
 
207
206
  for f in findings:
208
- sev = f.get('severity', 'info').lower()
207
+ sev = f.get("severity", "info").lower()
209
208
  if sev in by_severity:
210
209
  by_severity[sev].append(f)
211
210
  else:
212
- by_severity['info'].append(f)
211
+ by_severity["info"].append(f)
213
212
 
214
213
  lines = []
215
- for sev in ['critical', 'high', 'medium', 'low']:
214
+ for sev in ["critical", "high", "medium", "low"]:
216
215
  items = by_severity[sev]
217
216
  if items:
218
217
  lines.append(f"\n{sev.upper()} ({len(items)}):")
@@ -221,20 +220,22 @@ class ReportContextBuilder:
221
220
  if len(items) > 5:
222
221
  lines.append(f" ... and {len(items) - 5} more")
223
222
 
224
- return '\n'.join(lines) if lines else 'No findings recorded.'
223
+ return "\n".join(lines) if lines else "No findings recorded."
225
224
 
226
225
  def _calculate_duration(self, engagement: Dict) -> str:
227
226
  """Calculate engagement duration from dates."""
228
227
  from datetime import datetime
229
228
 
230
- start = engagement.get('start_date')
231
- end = engagement.get('end_date')
229
+ start = engagement.get("start_date")
230
+ end = engagement.get("end_date")
232
231
 
233
232
  if not start:
234
- return 'Duration not specified'
233
+ return "Duration not specified"
235
234
 
236
235
  try:
237
- start_dt = datetime.fromisoformat(start) if isinstance(start, str) else start
236
+ start_dt = (
237
+ datetime.fromisoformat(start) if isinstance(start, str) else start
238
+ )
238
239
  if end:
239
240
  end_dt = datetime.fromisoformat(end) if isinstance(end, str) else end
240
241
  days = (end_dt - start_dt).days
@@ -242,4 +243,4 @@ class ReportContextBuilder:
242
243
  else:
243
244
  return "Ongoing"
244
245
  except Exception:
245
- return 'Duration not specified'
246
+ return "Duration not specified"
@@ -4,6 +4,7 @@ souleyez.ai.report_service - AI-powered report generation service
4
4
  Provides methods for generating AI-enhanced report sections using
5
5
  configured LLM providers (Claude or Ollama).
6
6
  """
7
+
7
8
  import logging
8
9
  import re
9
10
  import concurrent.futures
@@ -83,13 +84,11 @@ class AIReportService:
83
84
  def get_provider_info(self) -> Dict[str, Any]:
84
85
  """Get information about the current provider."""
85
86
  if not self.provider:
86
- return {'available': False, 'error': 'No provider configured'}
87
+ return {"available": False, "error": "No provider configured"}
87
88
  return self.provider.get_status()
88
89
 
89
90
  def generate_executive_summary(
90
- self,
91
- engagement_id: int,
92
- max_tokens: int = 1500
91
+ self, engagement_id: int, max_tokens: int = 1500
93
92
  ) -> Optional[str]:
94
93
  """
95
94
  Generate AI-powered executive summary.
@@ -118,13 +117,15 @@ class AIReportService:
118
117
  prompt=prompt,
119
118
  system_prompt=REPORT_SYSTEM_PROMPT,
120
119
  max_tokens=max_tokens,
121
- temperature=0.3
120
+ temperature=0.3,
122
121
  )
123
122
 
124
123
  result = _run_with_timeout(_generate)
125
124
 
126
125
  if result:
127
- logger.info(f"Generated executive summary for engagement {engagement_id}")
126
+ logger.info(
127
+ f"Generated executive summary for engagement {engagement_id}"
128
+ )
128
129
  return result
129
130
 
130
131
  except Exception as e:
@@ -132,9 +133,7 @@ class AIReportService:
132
133
  return None
133
134
 
134
135
  def enhance_finding(
135
- self,
136
- finding: Dict[str, Any],
137
- max_tokens: int = 800
136
+ self, finding: Dict[str, Any], max_tokens: int = 800
138
137
  ) -> Optional[Dict[str, str]]:
139
138
  """
140
139
  Enhance a single finding with business context.
@@ -160,7 +159,7 @@ class AIReportService:
160
159
  prompt=prompt,
161
160
  system_prompt=REPORT_SYSTEM_PROMPT,
162
161
  max_tokens=max_tokens,
163
- temperature=0.3
162
+ temperature=0.3,
164
163
  )
165
164
 
166
165
  result = _run_with_timeout(_generate)
@@ -174,9 +173,7 @@ class AIReportService:
174
173
  return None
175
174
 
176
175
  def generate_remediation_plan(
177
- self,
178
- engagement_id: int,
179
- max_tokens: int = 2500
176
+ self, engagement_id: int, max_tokens: int = 2500
180
177
  ) -> Optional[str]:
181
178
  """
182
179
  Generate prioritized remediation plan.
@@ -205,23 +202,22 @@ class AIReportService:
205
202
  prompt=prompt,
206
203
  system_prompt=REPORT_SYSTEM_PROMPT,
207
204
  max_tokens=max_tokens,
208
- temperature=0.3
205
+ temperature=0.3,
209
206
  )
210
207
 
211
208
  result = _run_with_timeout(_generate)
212
209
 
213
210
  if result:
214
- logger.info(f"Generated remediation plan for engagement {engagement_id}")
211
+ logger.info(
212
+ f"Generated remediation plan for engagement {engagement_id}"
213
+ )
215
214
  return result
216
215
 
217
216
  except Exception as e:
218
217
  logger.error(f"Failed to generate remediation plan: {e}")
219
218
  return None
220
219
 
221
- def generate_risk_rating(
222
- self,
223
- engagement_id: int
224
- ) -> Optional[Dict[str, str]]:
220
+ def generate_risk_rating(self, engagement_id: int) -> Optional[Dict[str, str]]:
225
221
  """
226
222
  Generate overall risk rating.
227
223
 
@@ -251,7 +247,7 @@ Credentials Compromised: {context.get('credentials_count', 0)}
251
247
  prompt=prompt,
252
248
  system_prompt=REPORT_SYSTEM_PROMPT,
253
249
  max_tokens=200,
254
- temperature=0.2
250
+ temperature=0.2,
255
251
  )
256
252
 
257
253
  result = _run_with_timeout(_generate)
@@ -265,10 +261,7 @@ Credentials Compromised: {context.get('credentials_count', 0)}
265
261
  return None
266
262
 
267
263
  def generate_all_content(
268
- self,
269
- engagement_id: int,
270
- enhance_findings: bool = True,
271
- max_findings: int = 10
264
+ self, engagement_id: int, enhance_findings: bool = True, max_findings: int = 10
272
265
  ) -> Dict[str, Any]:
273
266
  """
274
267
  Generate all AI content for a report.
@@ -284,37 +277,39 @@ Credentials Compromised: {context.get('credentials_count', 0)}
284
277
  from souleyez.storage.findings import FindingsManager
285
278
 
286
279
  content = {
287
- 'executive_summary': None,
288
- 'remediation_plan': None,
289
- 'risk_rating': None,
290
- 'enhanced_findings': {},
291
- 'provider': None,
292
- 'errors': []
280
+ "executive_summary": None,
281
+ "remediation_plan": None,
282
+ "risk_rating": None,
283
+ "enhanced_findings": {},
284
+ "provider": None,
285
+ "errors": [],
293
286
  }
294
287
 
295
288
  if not self.is_available():
296
- content['errors'].append('AI provider not available')
289
+ content["errors"].append("AI provider not available")
297
290
  return content
298
291
 
299
- content['provider'] = self.provider.provider_type.value
292
+ content["provider"] = self.provider.provider_type.value
300
293
 
301
294
  # Generate executive summary
302
295
  try:
303
- content['executive_summary'] = self.generate_executive_summary(engagement_id)
296
+ content["executive_summary"] = self.generate_executive_summary(
297
+ engagement_id
298
+ )
304
299
  except Exception as e:
305
- content['errors'].append(f'Executive summary: {e}')
300
+ content["errors"].append(f"Executive summary: {e}")
306
301
 
307
302
  # Generate remediation plan
308
303
  try:
309
- content['remediation_plan'] = self.generate_remediation_plan(engagement_id)
304
+ content["remediation_plan"] = self.generate_remediation_plan(engagement_id)
310
305
  except Exception as e:
311
- content['errors'].append(f'Remediation plan: {e}')
306
+ content["errors"].append(f"Remediation plan: {e}")
312
307
 
313
308
  # Generate risk rating
314
309
  try:
315
- content['risk_rating'] = self.generate_risk_rating(engagement_id)
310
+ content["risk_rating"] = self.generate_risk_rating(engagement_id)
316
311
  except Exception as e:
317
- content['errors'].append(f'Risk rating: {e}')
312
+ content["errors"].append(f"Risk rating: {e}")
318
313
 
319
314
  # Enhance individual findings (top critical/high only)
320
315
  if enhance_findings:
@@ -323,38 +318,48 @@ Credentials Compromised: {context.get('credentials_count', 0)}
323
318
  findings = fm.list_findings(engagement_id)
324
319
 
325
320
  # Sort by severity and take top N
326
- severity_order = {'critical': 0, 'high': 1, 'medium': 2, 'low': 3, 'info': 4}
321
+ severity_order = {
322
+ "critical": 0,
323
+ "high": 1,
324
+ "medium": 2,
325
+ "low": 3,
326
+ "info": 4,
327
+ }
327
328
  priority_findings = sorted(
328
- [f for f in findings if f.get('severity', '').lower() in ['critical', 'high']],
329
- key=lambda f: severity_order.get(f.get('severity', 'info').lower(), 4)
329
+ [
330
+ f
331
+ for f in findings
332
+ if f.get("severity", "").lower() in ["critical", "high"]
333
+ ],
334
+ key=lambda f: severity_order.get(
335
+ f.get("severity", "info").lower(), 4
336
+ ),
330
337
  )[:max_findings]
331
338
 
332
339
  for finding in priority_findings:
333
340
  try:
334
341
  enhanced = self.enhance_finding(finding)
335
342
  if enhanced:
336
- content['enhanced_findings'][finding['id']] = enhanced
343
+ content["enhanced_findings"][finding["id"]] = enhanced
337
344
  except Exception as e:
338
- logger.warning(f"Failed to enhance finding {finding.get('id')}: {e}")
345
+ logger.warning(
346
+ f"Failed to enhance finding {finding.get('id')}: {e}"
347
+ )
339
348
 
340
349
  except Exception as e:
341
- content['errors'].append(f'Finding enhancement: {e}')
350
+ content["errors"].append(f"Finding enhancement: {e}")
342
351
 
343
352
  return content
344
353
 
345
354
  def _parse_finding_enhancement(self, response: str) -> Dict[str, str]:
346
355
  """Parse LLM response into structured finding enhancement."""
347
- result = {
348
- 'business_impact': '',
349
- 'attack_scenario': '',
350
- 'risk_context': ''
351
- }
356
+ result = {"business_impact": "", "attack_scenario": "", "risk_context": ""}
352
357
 
353
358
  # Try to extract sections
354
359
  sections = {
355
- 'business_impact': r'BUSINESS IMPACT[:\s]*(.+?)(?=ATTACK SCENARIO|RISK CONTEXT|$)',
356
- 'attack_scenario': r'ATTACK SCENARIO[:\s]*(.+?)(?=RISK CONTEXT|BUSINESS IMPACT|$)',
357
- 'risk_context': r'RISK CONTEXT[:\s]*(.+?)(?=BUSINESS IMPACT|ATTACK SCENARIO|$)'
360
+ "business_impact": r"BUSINESS IMPACT[:\s]*(.+?)(?=ATTACK SCENARIO|RISK CONTEXT|$)",
361
+ "attack_scenario": r"ATTACK SCENARIO[:\s]*(.+?)(?=RISK CONTEXT|BUSINESS IMPACT|$)",
362
+ "risk_context": r"RISK CONTEXT[:\s]*(.+?)(?=BUSINESS IMPACT|ATTACK SCENARIO|$)",
358
363
  }
359
364
 
360
365
  for key, pattern in sections.items():
@@ -364,19 +369,23 @@ Credentials Compromised: {context.get('credentials_count', 0)}
364
369
 
365
370
  # If parsing failed, use the whole response as business impact
366
371
  if not any(result.values()):
367
- result['business_impact'] = response.strip()
372
+ result["business_impact"] = response.strip()
368
373
 
369
374
  return result
370
375
 
371
376
  def _parse_risk_rating(self, response: str) -> Dict[str, str]:
372
377
  """Parse risk rating response."""
373
- result = {'rating': 'UNKNOWN', 'justification': response}
378
+ result = {"rating": "UNKNOWN", "justification": response}
374
379
 
375
380
  # Look for rating pattern
376
- match = re.search(r'RATING:\s*(CRITICAL|HIGH|MODERATE|LOW)\s*[-–]\s*(.+)', response, re.IGNORECASE)
381
+ match = re.search(
382
+ r"RATING:\s*(CRITICAL|HIGH|MODERATE|LOW)\s*[-–]\s*(.+)",
383
+ response,
384
+ re.IGNORECASE,
385
+ )
377
386
  if match:
378
- result['rating'] = match.group(1).upper()
379
- result['justification'] = match.group(2).strip()
387
+ result["rating"] = match.group(1).upper()
388
+ result["justification"] = match.group(2).strip()
380
389
 
381
390
  return result
382
391
 
@@ -404,18 +413,27 @@ Credentials Compromised: {context.get('credentials_count', 0)}
404
413
  remediation_input = len(REMEDIATION_PLAN_PROMPT.format(**remediation_ctx)) // 4
405
414
  remediation_output = 800
406
415
 
407
- critical_high = len([f for f in findings if f.get('severity', '').lower() in ['critical', 'high']])
416
+ critical_high = len(
417
+ [
418
+ f
419
+ for f in findings
420
+ if f.get("severity", "").lower() in ["critical", "high"]
421
+ ]
422
+ )
408
423
  findings_input = critical_high * 300
409
424
  findings_output = critical_high * 200
410
425
 
411
426
  return {
412
- 'executive_summary': executive_input + executive_output,
413
- 'remediation_plan': remediation_input + remediation_output,
414
- 'findings_enhancement': findings_input + findings_output,
415
- 'total_estimated': (
416
- executive_input + executive_output +
417
- remediation_input + remediation_output +
418
- findings_input + findings_output
427
+ "executive_summary": executive_input + executive_output,
428
+ "remediation_plan": remediation_input + remediation_output,
429
+ "findings_enhancement": findings_input + findings_output,
430
+ "total_estimated": (
431
+ executive_input
432
+ + executive_output
433
+ + remediation_input
434
+ + remediation_output
435
+ + findings_input
436
+ + findings_output
419
437
  ),
420
- 'findings_to_enhance': min(critical_high, 10)
438
+ "findings_to_enhance": min(critical_high, 10),
421
439
  }