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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (358) hide show
  1. souleyez/__init__.py +1 -2
  2. souleyez/ai/__init__.py +21 -15
  3. souleyez/ai/action_mapper.py +249 -150
  4. souleyez/ai/chain_advisor.py +116 -100
  5. souleyez/ai/claude_provider.py +29 -28
  6. souleyez/ai/context_builder.py +80 -62
  7. souleyez/ai/executor.py +158 -117
  8. souleyez/ai/feedback_handler.py +136 -121
  9. souleyez/ai/llm_factory.py +27 -20
  10. souleyez/ai/llm_provider.py +4 -2
  11. souleyez/ai/ollama_provider.py +6 -9
  12. souleyez/ai/ollama_service.py +44 -37
  13. souleyez/ai/path_scorer.py +91 -76
  14. souleyez/ai/recommender.py +176 -144
  15. souleyez/ai/report_context.py +74 -73
  16. souleyez/ai/report_service.py +84 -66
  17. souleyez/ai/result_parser.py +222 -229
  18. souleyez/ai/safety.py +67 -44
  19. souleyez/auth/__init__.py +23 -22
  20. souleyez/auth/audit.py +36 -26
  21. souleyez/auth/engagement_access.py +65 -48
  22. souleyez/auth/permissions.py +14 -3
  23. souleyez/auth/session_manager.py +54 -37
  24. souleyez/auth/user_manager.py +109 -64
  25. souleyez/commands/audit.py +40 -43
  26. souleyez/commands/auth.py +35 -15
  27. souleyez/commands/deliverables.py +55 -50
  28. souleyez/commands/engagement.py +47 -28
  29. souleyez/commands/license.py +32 -23
  30. souleyez/commands/screenshots.py +36 -32
  31. souleyez/commands/user.py +82 -36
  32. souleyez/config.py +52 -44
  33. souleyez/core/credential_tester.py +87 -81
  34. souleyez/core/cve_mappings.py +179 -192
  35. souleyez/core/cve_matcher.py +162 -148
  36. souleyez/core/msf_auto_mapper.py +100 -83
  37. souleyez/core/msf_chain_engine.py +294 -256
  38. souleyez/core/msf_database.py +153 -70
  39. souleyez/core/msf_integration.py +679 -673
  40. souleyez/core/msf_rpc_client.py +40 -42
  41. souleyez/core/msf_rpc_manager.py +77 -79
  42. souleyez/core/msf_sync_manager.py +241 -181
  43. souleyez/core/network_utils.py +22 -15
  44. souleyez/core/parser_handler.py +34 -25
  45. souleyez/core/pending_chains.py +114 -63
  46. souleyez/core/templates.py +158 -107
  47. souleyez/core/tool_chaining.py +9564 -2881
  48. souleyez/core/version_utils.py +79 -94
  49. souleyez/core/vuln_correlation.py +136 -89
  50. souleyez/core/web_utils.py +33 -32
  51. souleyez/data/wordlists/ad_users.txt +378 -0
  52. souleyez/data/wordlists/api_endpoints_large.txt +769 -0
  53. souleyez/data/wordlists/home_dir_sensitive.txt +39 -0
  54. souleyez/data/wordlists/lfi_payloads.txt +82 -0
  55. souleyez/data/wordlists/passwords_brute.txt +1548 -0
  56. souleyez/data/wordlists/passwords_crack.txt +2479 -0
  57. souleyez/data/wordlists/passwords_spray.txt +386 -0
  58. souleyez/data/wordlists/subdomains_large.txt +5057 -0
  59. souleyez/data/wordlists/usernames_common.txt +694 -0
  60. souleyez/data/wordlists/web_dirs_large.txt +4769 -0
  61. souleyez/detection/__init__.py +1 -1
  62. souleyez/detection/attack_signatures.py +12 -17
  63. souleyez/detection/mitre_mappings.py +61 -55
  64. souleyez/detection/validator.py +97 -86
  65. souleyez/devtools.py +23 -10
  66. souleyez/docs/README.md +4 -4
  67. souleyez/docs/api-reference/cli-commands.md +2 -2
  68. souleyez/docs/developer-guide/adding-new-tools.md +562 -0
  69. souleyez/docs/user-guide/auto-chaining.md +30 -8
  70. souleyez/docs/user-guide/getting-started.md +1 -1
  71. souleyez/docs/user-guide/installation.md +26 -3
  72. souleyez/docs/user-guide/metasploit-integration.md +2 -2
  73. souleyez/docs/user-guide/rbac.md +1 -1
  74. souleyez/docs/user-guide/scope-management.md +1 -1
  75. souleyez/docs/user-guide/siem-integration.md +1 -1
  76. souleyez/docs/user-guide/tools-reference.md +1 -8
  77. souleyez/docs/user-guide/worker-management.md +1 -1
  78. souleyez/engine/background.py +1239 -535
  79. souleyez/engine/base.py +4 -1
  80. souleyez/engine/job_status.py +17 -49
  81. souleyez/engine/log_sanitizer.py +103 -77
  82. souleyez/engine/manager.py +38 -7
  83. souleyez/engine/result_handler.py +2200 -1550
  84. souleyez/engine/worker_manager.py +50 -41
  85. souleyez/export/evidence_bundle.py +72 -62
  86. souleyez/feature_flags/features.py +16 -20
  87. souleyez/feature_flags.py +5 -9
  88. souleyez/handlers/__init__.py +11 -0
  89. souleyez/handlers/base.py +188 -0
  90. souleyez/handlers/bash_handler.py +277 -0
  91. souleyez/handlers/bloodhound_handler.py +243 -0
  92. souleyez/handlers/certipy_handler.py +311 -0
  93. souleyez/handlers/crackmapexec_handler.py +486 -0
  94. souleyez/handlers/dnsrecon_handler.py +344 -0
  95. souleyez/handlers/enum4linux_handler.py +400 -0
  96. souleyez/handlers/evil_winrm_handler.py +493 -0
  97. souleyez/handlers/ffuf_handler.py +815 -0
  98. souleyez/handlers/gobuster_handler.py +1114 -0
  99. souleyez/handlers/gpp_extract_handler.py +334 -0
  100. souleyez/handlers/hashcat_handler.py +444 -0
  101. souleyez/handlers/hydra_handler.py +564 -0
  102. souleyez/handlers/impacket_getuserspns_handler.py +343 -0
  103. souleyez/handlers/impacket_psexec_handler.py +222 -0
  104. souleyez/handlers/impacket_secretsdump_handler.py +426 -0
  105. souleyez/handlers/john_handler.py +286 -0
  106. souleyez/handlers/katana_handler.py +425 -0
  107. souleyez/handlers/kerbrute_handler.py +298 -0
  108. souleyez/handlers/ldapsearch_handler.py +636 -0
  109. souleyez/handlers/lfi_extract_handler.py +464 -0
  110. souleyez/handlers/msf_auxiliary_handler.py +409 -0
  111. souleyez/handlers/msf_exploit_handler.py +380 -0
  112. souleyez/handlers/nikto_handler.py +413 -0
  113. souleyez/handlers/nmap_handler.py +821 -0
  114. souleyez/handlers/nuclei_handler.py +359 -0
  115. souleyez/handlers/nxc_handler.py +417 -0
  116. souleyez/handlers/rdp_sec_check_handler.py +353 -0
  117. souleyez/handlers/registry.py +292 -0
  118. souleyez/handlers/responder_handler.py +232 -0
  119. souleyez/handlers/service_explorer_handler.py +434 -0
  120. souleyez/handlers/smbclient_handler.py +344 -0
  121. souleyez/handlers/smbmap_handler.py +510 -0
  122. souleyez/handlers/smbpasswd_handler.py +296 -0
  123. souleyez/handlers/sqlmap_handler.py +1116 -0
  124. souleyez/handlers/theharvester_handler.py +601 -0
  125. souleyez/handlers/web_login_test_handler.py +327 -0
  126. souleyez/handlers/whois_handler.py +277 -0
  127. souleyez/handlers/wpscan_handler.py +554 -0
  128. souleyez/history.py +32 -16
  129. souleyez/importers/msf_importer.py +106 -75
  130. souleyez/importers/smart_importer.py +208 -147
  131. souleyez/integrations/siem/__init__.py +10 -10
  132. souleyez/integrations/siem/base.py +17 -18
  133. souleyez/integrations/siem/elastic.py +108 -122
  134. souleyez/integrations/siem/factory.py +207 -80
  135. souleyez/integrations/siem/googlesecops.py +146 -154
  136. souleyez/integrations/siem/rule_mappings/__init__.py +1 -1
  137. souleyez/integrations/siem/rule_mappings/wazuh_rules.py +8 -5
  138. souleyez/integrations/siem/sentinel.py +107 -109
  139. souleyez/integrations/siem/splunk.py +246 -212
  140. souleyez/integrations/siem/wazuh.py +65 -71
  141. souleyez/integrations/wazuh/__init__.py +5 -5
  142. souleyez/integrations/wazuh/client.py +70 -93
  143. souleyez/integrations/wazuh/config.py +85 -57
  144. souleyez/integrations/wazuh/host_mapper.py +28 -36
  145. souleyez/integrations/wazuh/sync.py +78 -68
  146. souleyez/intelligence/__init__.py +4 -5
  147. souleyez/intelligence/correlation_analyzer.py +309 -295
  148. souleyez/intelligence/exploit_knowledge.py +661 -623
  149. souleyez/intelligence/exploit_suggestions.py +159 -139
  150. souleyez/intelligence/gap_analyzer.py +132 -97
  151. souleyez/intelligence/gap_detector.py +251 -214
  152. souleyez/intelligence/sensitive_tables.py +266 -129
  153. souleyez/intelligence/service_parser.py +137 -123
  154. souleyez/intelligence/surface_analyzer.py +407 -268
  155. souleyez/intelligence/target_parser.py +159 -162
  156. souleyez/licensing/__init__.py +6 -6
  157. souleyez/licensing/validator.py +17 -19
  158. souleyez/log_config.py +79 -54
  159. souleyez/main.py +1505 -687
  160. souleyez/migrations/fix_job_counter.py +16 -14
  161. souleyez/parsers/bloodhound_parser.py +41 -39
  162. souleyez/parsers/crackmapexec_parser.py +178 -111
  163. souleyez/parsers/dalfox_parser.py +72 -77
  164. souleyez/parsers/dnsrecon_parser.py +103 -91
  165. souleyez/parsers/enum4linux_parser.py +183 -153
  166. souleyez/parsers/ffuf_parser.py +29 -25
  167. souleyez/parsers/gobuster_parser.py +301 -41
  168. souleyez/parsers/hashcat_parser.py +324 -79
  169. souleyez/parsers/http_fingerprint_parser.py +350 -103
  170. souleyez/parsers/hydra_parser.py +131 -111
  171. souleyez/parsers/impacket_parser.py +231 -178
  172. souleyez/parsers/john_parser.py +98 -86
  173. souleyez/parsers/katana_parser.py +316 -0
  174. souleyez/parsers/msf_parser.py +943 -498
  175. souleyez/parsers/nikto_parser.py +346 -65
  176. souleyez/parsers/nmap_parser.py +262 -174
  177. souleyez/parsers/nuclei_parser.py +40 -44
  178. souleyez/parsers/responder_parser.py +26 -26
  179. souleyez/parsers/searchsploit_parser.py +74 -74
  180. souleyez/parsers/service_explorer_parser.py +279 -0
  181. souleyez/parsers/smbmap_parser.py +180 -124
  182. souleyez/parsers/sqlmap_parser.py +434 -308
  183. souleyez/parsers/theharvester_parser.py +75 -57
  184. souleyez/parsers/whois_parser.py +135 -94
  185. souleyez/parsers/wpscan_parser.py +278 -190
  186. souleyez/plugins/afp.py +44 -36
  187. souleyez/plugins/afp_brute.py +114 -46
  188. souleyez/plugins/ard.py +48 -37
  189. souleyez/plugins/bloodhound.py +95 -61
  190. souleyez/plugins/certipy.py +303 -0
  191. souleyez/plugins/crackmapexec.py +186 -85
  192. souleyez/plugins/dalfox.py +120 -59
  193. souleyez/plugins/dns_hijack.py +146 -41
  194. souleyez/plugins/dnsrecon.py +97 -61
  195. souleyez/plugins/enum4linux.py +91 -66
  196. souleyez/plugins/evil_winrm.py +291 -0
  197. souleyez/plugins/ffuf.py +166 -90
  198. souleyez/plugins/firmware_extract.py +133 -29
  199. souleyez/plugins/gobuster.py +387 -190
  200. souleyez/plugins/gpp_extract.py +393 -0
  201. souleyez/plugins/hashcat.py +100 -73
  202. souleyez/plugins/http_fingerprint.py +913 -267
  203. souleyez/plugins/hydra.py +566 -200
  204. souleyez/plugins/impacket_getnpusers.py +117 -69
  205. souleyez/plugins/impacket_psexec.py +84 -64
  206. souleyez/plugins/impacket_secretsdump.py +103 -69
  207. souleyez/plugins/impacket_smbclient.py +89 -75
  208. souleyez/plugins/john.py +86 -69
  209. souleyez/plugins/katana.py +313 -0
  210. souleyez/plugins/kerbrute.py +237 -0
  211. souleyez/plugins/lfi_extract.py +541 -0
  212. souleyez/plugins/macos_ssh.py +117 -48
  213. souleyez/plugins/mdns.py +35 -30
  214. souleyez/plugins/msf_auxiliary.py +253 -130
  215. souleyez/plugins/msf_exploit.py +239 -161
  216. souleyez/plugins/nikto.py +134 -78
  217. souleyez/plugins/nmap.py +275 -91
  218. souleyez/plugins/nuclei.py +180 -89
  219. souleyez/plugins/nxc.py +285 -0
  220. souleyez/plugins/plugin_base.py +35 -36
  221. souleyez/plugins/plugin_template.py +13 -5
  222. souleyez/plugins/rdp_sec_check.py +130 -0
  223. souleyez/plugins/responder.py +112 -71
  224. souleyez/plugins/router_http_brute.py +76 -65
  225. souleyez/plugins/router_ssh_brute.py +118 -41
  226. souleyez/plugins/router_telnet_brute.py +124 -42
  227. souleyez/plugins/routersploit.py +91 -59
  228. souleyez/plugins/routersploit_exploit.py +77 -55
  229. souleyez/plugins/searchsploit.py +91 -77
  230. souleyez/plugins/service_explorer.py +1160 -0
  231. souleyez/plugins/smbmap.py +122 -72
  232. souleyez/plugins/smbpasswd.py +215 -0
  233. souleyez/plugins/sqlmap.py +301 -113
  234. souleyez/plugins/theharvester.py +127 -75
  235. souleyez/plugins/tr069.py +79 -57
  236. souleyez/plugins/upnp.py +65 -47
  237. souleyez/plugins/upnp_abuse.py +73 -55
  238. souleyez/plugins/vnc_access.py +129 -42
  239. souleyez/plugins/vnc_brute.py +109 -38
  240. souleyez/plugins/web_login_test.py +417 -0
  241. souleyez/plugins/whois.py +77 -58
  242. souleyez/plugins/wpscan.py +219 -69
  243. souleyez/reporting/__init__.py +2 -1
  244. souleyez/reporting/attack_chain.py +411 -346
  245. souleyez/reporting/charts.py +436 -501
  246. souleyez/reporting/compliance_mappings.py +334 -201
  247. souleyez/reporting/detection_report.py +126 -125
  248. souleyez/reporting/formatters.py +828 -591
  249. souleyez/reporting/generator.py +386 -302
  250. souleyez/reporting/metrics.py +72 -75
  251. souleyez/scanner.py +35 -29
  252. souleyez/security/__init__.py +37 -11
  253. souleyez/security/scope_validator.py +175 -106
  254. souleyez/security/validation.py +237 -149
  255. souleyez/security.py +22 -6
  256. souleyez/storage/credentials.py +247 -186
  257. souleyez/storage/crypto.py +296 -129
  258. souleyez/storage/database.py +73 -50
  259. souleyez/storage/db.py +58 -36
  260. souleyez/storage/deliverable_evidence.py +177 -128
  261. souleyez/storage/deliverable_exporter.py +282 -246
  262. souleyez/storage/deliverable_templates.py +134 -116
  263. souleyez/storage/deliverables.py +135 -130
  264. souleyez/storage/engagements.py +109 -56
  265. souleyez/storage/evidence.py +181 -152
  266. souleyez/storage/execution_log.py +31 -17
  267. souleyez/storage/exploit_attempts.py +93 -57
  268. souleyez/storage/exploits.py +67 -36
  269. souleyez/storage/findings.py +48 -61
  270. souleyez/storage/hosts.py +176 -144
  271. souleyez/storage/migrate_to_engagements.py +43 -19
  272. souleyez/storage/migrations/_001_add_credential_enhancements.py +22 -12
  273. souleyez/storage/migrations/_002_add_status_tracking.py +10 -7
  274. souleyez/storage/migrations/_003_add_execution_log.py +14 -8
  275. souleyez/storage/migrations/_005_screenshots.py +13 -5
  276. souleyez/storage/migrations/_006_deliverables.py +13 -5
  277. souleyez/storage/migrations/_007_deliverable_templates.py +12 -7
  278. souleyez/storage/migrations/_008_add_nuclei_table.py +10 -4
  279. souleyez/storage/migrations/_010_evidence_linking.py +17 -10
  280. souleyez/storage/migrations/_011_timeline_tracking.py +20 -13
  281. souleyez/storage/migrations/_012_team_collaboration.py +34 -21
  282. souleyez/storage/migrations/_013_add_host_tags.py +12 -6
  283. souleyez/storage/migrations/_014_exploit_attempts.py +22 -10
  284. souleyez/storage/migrations/_015_add_mac_os_fields.py +15 -7
  285. souleyez/storage/migrations/_016_add_domain_field.py +10 -4
  286. souleyez/storage/migrations/_017_msf_sessions.py +16 -8
  287. souleyez/storage/migrations/_018_add_osint_target.py +10 -6
  288. souleyez/storage/migrations/_019_add_engagement_type.py +10 -6
  289. souleyez/storage/migrations/_020_add_rbac.py +36 -15
  290. souleyez/storage/migrations/_021_wazuh_integration.py +20 -8
  291. souleyez/storage/migrations/_022_wazuh_indexer_columns.py +6 -4
  292. souleyez/storage/migrations/_023_fix_detection_results_fk.py +16 -6
  293. souleyez/storage/migrations/_024_wazuh_vulnerabilities.py +26 -10
  294. souleyez/storage/migrations/_025_multi_siem_support.py +3 -5
  295. souleyez/storage/migrations/_026_add_engagement_scope.py +31 -12
  296. souleyez/storage/migrations/_027_multi_siem_persistence.py +32 -15
  297. souleyez/storage/migrations/__init__.py +26 -26
  298. souleyez/storage/migrations/migration_manager.py +19 -19
  299. souleyez/storage/msf_sessions.py +100 -65
  300. souleyez/storage/osint.py +17 -24
  301. souleyez/storage/recommendation_engine.py +269 -235
  302. souleyez/storage/screenshots.py +33 -32
  303. souleyez/storage/smb_shares.py +136 -92
  304. souleyez/storage/sqlmap_data.py +183 -128
  305. souleyez/storage/team_collaboration.py +135 -141
  306. souleyez/storage/timeline_tracker.py +122 -94
  307. souleyez/storage/wazuh_vulns.py +64 -66
  308. souleyez/storage/web_paths.py +33 -37
  309. souleyez/testing/credential_tester.py +221 -205
  310. souleyez/ui/__init__.py +1 -1
  311. souleyez/ui/ai_quotes.py +12 -12
  312. souleyez/ui/attack_surface.py +2439 -1516
  313. souleyez/ui/chain_rules_view.py +914 -382
  314. souleyez/ui/correlation_view.py +312 -230
  315. souleyez/ui/dashboard.py +2382 -1130
  316. souleyez/ui/deliverables_view.py +148 -62
  317. souleyez/ui/design_system.py +13 -13
  318. souleyez/ui/errors.py +49 -49
  319. souleyez/ui/evidence_linking_view.py +284 -179
  320. souleyez/ui/evidence_vault.py +393 -285
  321. souleyez/ui/exploit_suggestions_view.py +555 -349
  322. souleyez/ui/export_view.py +100 -66
  323. souleyez/ui/gap_analysis_view.py +315 -171
  324. souleyez/ui/help_system.py +105 -97
  325. souleyez/ui/intelligence_view.py +436 -293
  326. souleyez/ui/interactive.py +23034 -10679
  327. souleyez/ui/interactive_selector.py +75 -68
  328. souleyez/ui/log_formatter.py +47 -39
  329. souleyez/ui/menu_components.py +22 -13
  330. souleyez/ui/msf_auxiliary_menu.py +184 -133
  331. souleyez/ui/pending_chains_view.py +336 -172
  332. souleyez/ui/progress_indicators.py +5 -3
  333. souleyez/ui/recommendations_view.py +195 -137
  334. souleyez/ui/rule_builder.py +343 -225
  335. souleyez/ui/setup_wizard.py +678 -284
  336. souleyez/ui/shortcuts.py +217 -165
  337. souleyez/ui/splunk_gap_analysis_view.py +452 -270
  338. souleyez/ui/splunk_vulns_view.py +139 -86
  339. souleyez/ui/team_dashboard.py +498 -335
  340. souleyez/ui/template_selector.py +196 -105
  341. souleyez/ui/terminal.py +6 -6
  342. souleyez/ui/timeline_view.py +198 -127
  343. souleyez/ui/tool_setup.py +264 -164
  344. souleyez/ui/tutorial.py +202 -72
  345. souleyez/ui/tutorial_state.py +40 -40
  346. souleyez/ui/wazuh_vulns_view.py +235 -141
  347. souleyez/ui/wordlist_browser.py +260 -107
  348. souleyez/ui.py +464 -312
  349. souleyez/utils/tool_checker.py +427 -367
  350. souleyez/utils.py +33 -29
  351. souleyez/wordlists.py +134 -167
  352. {souleyez-2.43.29.dist-info → souleyez-3.0.0.dist-info}/METADATA +2 -2
  353. souleyez-3.0.0.dist-info/RECORD +443 -0
  354. {souleyez-2.43.29.dist-info → souleyez-3.0.0.dist-info}/WHEEL +1 -1
  355. souleyez-2.43.29.dist-info/RECORD +0 -379
  356. {souleyez-2.43.29.dist-info → souleyez-3.0.0.dist-info}/entry_points.txt +0 -0
  357. {souleyez-2.43.29.dist-info → souleyez-3.0.0.dist-info}/licenses/LICENSE +0 -0
  358. {souleyez-2.43.29.dist-info → souleyez-3.0.0.dist-info}/top_level.txt +0 -0
@@ -9,6 +9,7 @@ Columns added:
9
9
  - engagements.scope_enforcement: Enforcement mode (off, warn, block)
10
10
  - hosts.scope_status: Host scope status (in_scope, out_of_scope, unknown)
11
11
  """
12
+
12
13
  import os
13
14
 
14
15
 
@@ -16,7 +17,8 @@ def upgrade(conn):
16
17
  """Add scope validation tables and columns."""
17
18
 
18
19
  # Engagement scope definitions table
19
- conn.execute("""
20
+ conn.execute(
21
+ """
20
22
  CREATE TABLE IF NOT EXISTS engagement_scope (
21
23
  id INTEGER PRIMARY KEY AUTOINCREMENT,
22
24
  engagement_id INTEGER NOT NULL,
@@ -29,10 +31,12 @@ def upgrade(conn):
29
31
  FOREIGN KEY (engagement_id) REFERENCES engagements(id) ON DELETE CASCADE,
30
32
  UNIQUE(engagement_id, scope_type, value)
31
33
  )
32
- """)
34
+ """
35
+ )
33
36
 
34
37
  # Scope validation audit log
35
- conn.execute("""
38
+ conn.execute(
39
+ """
36
40
  CREATE TABLE IF NOT EXISTS scope_validation_log (
37
41
  id INTEGER PRIMARY KEY AUTOINCREMENT,
38
42
  engagement_id INTEGER NOT NULL,
@@ -45,11 +49,14 @@ def upgrade(conn):
45
49
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
46
50
  FOREIGN KEY (engagement_id) REFERENCES engagements(id) ON DELETE CASCADE
47
51
  )
48
- """)
52
+ """
53
+ )
49
54
 
50
55
  # Add scope_enforcement column to engagements
51
56
  try:
52
- conn.execute("ALTER TABLE engagements ADD COLUMN scope_enforcement TEXT DEFAULT 'off'")
57
+ conn.execute(
58
+ "ALTER TABLE engagements ADD COLUMN scope_enforcement TEXT DEFAULT 'off'"
59
+ )
53
60
  except Exception:
54
61
  pass # Column may already exist
55
62
 
@@ -60,16 +67,28 @@ def upgrade(conn):
60
67
  pass # Column may already exist
61
68
 
62
69
  # Indexes for performance
63
- conn.execute("CREATE INDEX IF NOT EXISTS idx_scope_engagement ON engagement_scope(engagement_id)")
64
- conn.execute("CREATE INDEX IF NOT EXISTS idx_scope_type ON engagement_scope(scope_type)")
65
- conn.execute("CREATE INDEX IF NOT EXISTS idx_scope_log_engagement ON scope_validation_log(engagement_id)")
66
- conn.execute("CREATE INDEX IF NOT EXISTS idx_scope_log_result ON scope_validation_log(validation_result)")
67
- conn.execute("CREATE INDEX IF NOT EXISTS idx_scope_log_timestamp ON scope_validation_log(created_at DESC)")
68
- conn.execute("CREATE INDEX IF NOT EXISTS idx_hosts_scope_status ON hosts(scope_status)")
70
+ conn.execute(
71
+ "CREATE INDEX IF NOT EXISTS idx_scope_engagement ON engagement_scope(engagement_id)"
72
+ )
73
+ conn.execute(
74
+ "CREATE INDEX IF NOT EXISTS idx_scope_type ON engagement_scope(scope_type)"
75
+ )
76
+ conn.execute(
77
+ "CREATE INDEX IF NOT EXISTS idx_scope_log_engagement ON scope_validation_log(engagement_id)"
78
+ )
79
+ conn.execute(
80
+ "CREATE INDEX IF NOT EXISTS idx_scope_log_result ON scope_validation_log(validation_result)"
81
+ )
82
+ conn.execute(
83
+ "CREATE INDEX IF NOT EXISTS idx_scope_log_timestamp ON scope_validation_log(created_at DESC)"
84
+ )
85
+ conn.execute(
86
+ "CREATE INDEX IF NOT EXISTS idx_hosts_scope_status ON hosts(scope_status)"
87
+ )
69
88
 
70
89
  conn.commit()
71
90
 
72
- if not os.environ.get('SOULEYEZ_MIGRATION_SILENT'):
91
+ if not os.environ.get("SOULEYEZ_MIGRATION_SILENT"):
73
92
  print("Migration 026: Engagement scope validation tables created")
74
93
 
75
94
 
@@ -6,6 +6,7 @@ Changes wazuh_config table to support multiple SIEM configs per engagement.
6
6
  - Adds UNIQUE constraint on (engagement_id, siem_type)
7
7
  - Allows each engagement to have separate configs for Wazuh, Splunk, etc.
8
8
  """
9
+
9
10
  import os
10
11
 
11
12
 
@@ -17,13 +18,16 @@ def upgrade(conn):
17
18
  cursor.execute("PRAGMA table_info(wazuh_config)")
18
19
  columns = [col[1] for col in cursor.fetchall()]
19
20
 
20
- if 'siem_type' not in columns:
21
- cursor.execute("ALTER TABLE wazuh_config ADD COLUMN siem_type TEXT DEFAULT 'wazuh'")
22
- if 'config_json' not in columns:
21
+ if "siem_type" not in columns:
22
+ cursor.execute(
23
+ "ALTER TABLE wazuh_config ADD COLUMN siem_type TEXT DEFAULT 'wazuh'"
24
+ )
25
+ if "config_json" not in columns:
23
26
  cursor.execute("ALTER TABLE wazuh_config ADD COLUMN config_json TEXT")
24
27
 
25
28
  # Create new table with correct constraint
26
- cursor.execute("""
29
+ cursor.execute(
30
+ """
27
31
  CREATE TABLE IF NOT EXISTS siem_config_new (
28
32
  id INTEGER PRIMARY KEY AUTOINCREMENT,
29
33
  engagement_id INTEGER NOT NULL,
@@ -42,10 +46,12 @@ def upgrade(conn):
42
46
  FOREIGN KEY (engagement_id) REFERENCES engagements(id) ON DELETE CASCADE,
43
47
  UNIQUE(engagement_id, siem_type)
44
48
  )
45
- """)
49
+ """
50
+ )
46
51
 
47
52
  # Copy existing data
48
- cursor.execute("""
53
+ cursor.execute(
54
+ """
49
55
  INSERT OR IGNORE INTO siem_config_new (
50
56
  id, engagement_id, siem_type, api_url, api_user, api_password,
51
57
  indexer_url, indexer_user, indexer_password, verify_ssl, enabled,
@@ -56,19 +62,24 @@ def upgrade(conn):
56
62
  indexer_url, indexer_user, indexer_password, verify_ssl, enabled,
57
63
  config_json, created_at, updated_at
58
64
  FROM wazuh_config
59
- """)
65
+ """
66
+ )
60
67
 
61
68
  # Drop old table and rename new one
62
69
  cursor.execute("DROP TABLE wazuh_config")
63
70
  cursor.execute("ALTER TABLE siem_config_new RENAME TO wazuh_config")
64
71
 
65
72
  # Recreate index
66
- cursor.execute("CREATE INDEX IF NOT EXISTS idx_wazuh_config_engagement ON wazuh_config(engagement_id)")
67
- cursor.execute("CREATE INDEX IF NOT EXISTS idx_wazuh_config_siem_type ON wazuh_config(siem_type)")
73
+ cursor.execute(
74
+ "CREATE INDEX IF NOT EXISTS idx_wazuh_config_engagement ON wazuh_config(engagement_id)"
75
+ )
76
+ cursor.execute(
77
+ "CREATE INDEX IF NOT EXISTS idx_wazuh_config_siem_type ON wazuh_config(siem_type)"
78
+ )
68
79
 
69
80
  conn.commit()
70
81
 
71
- if not os.environ.get('SOULEYEZ_MIGRATION_SILENT'):
82
+ if not os.environ.get("SOULEYEZ_MIGRATION_SILENT"):
72
83
  print("Migration 027: Multi-SIEM persistence enabled")
73
84
 
74
85
 
@@ -76,7 +87,8 @@ def downgrade(conn):
76
87
  """Revert to single SIEM per engagement (lossy - keeps only first config per engagement)."""
77
88
  cursor = conn.cursor()
78
89
 
79
- cursor.execute("""
90
+ cursor.execute(
91
+ """
80
92
  CREATE TABLE IF NOT EXISTS wazuh_config_old (
81
93
  id INTEGER PRIMARY KEY AUTOINCREMENT,
82
94
  engagement_id INTEGER NOT NULL UNIQUE,
@@ -94,10 +106,12 @@ def downgrade(conn):
94
106
  updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
95
107
  FOREIGN KEY (engagement_id) REFERENCES engagements(id) ON DELETE CASCADE
96
108
  )
97
- """)
109
+ """
110
+ )
98
111
 
99
112
  # Copy only first config per engagement
100
- cursor.execute("""
113
+ cursor.execute(
114
+ """
101
115
  INSERT OR IGNORE INTO wazuh_config_old (
102
116
  engagement_id, api_url, api_user, api_password,
103
117
  indexer_url, indexer_user, indexer_password, verify_ssl, enabled,
@@ -109,11 +123,14 @@ def downgrade(conn):
109
123
  siem_type, config_json, created_at, updated_at
110
124
  FROM wazuh_config
111
125
  GROUP BY engagement_id
112
- """)
126
+ """
127
+ )
113
128
 
114
129
  cursor.execute("DROP TABLE wazuh_config")
115
130
  cursor.execute("ALTER TABLE wazuh_config_old RENAME TO wazuh_config")
116
- cursor.execute("CREATE INDEX IF NOT EXISTS idx_wazuh_config_engagement ON wazuh_config(engagement_id)")
131
+ cursor.execute(
132
+ "CREATE INDEX IF NOT EXISTS idx_wazuh_config_engagement ON wazuh_config(engagement_id)"
133
+ )
117
134
 
118
135
  conn.commit()
119
136
  print("Migration 027: Reverted to single SIEM per engagement")
@@ -36,32 +36,32 @@ from . import (
36
36
 
37
37
  # Migration registry - maps version to module
38
38
  MIGRATIONS_REGISTRY = {
39
- '001': _001_add_credential_enhancements,
40
- '002': _002_add_status_tracking,
41
- '003': _003_add_execution_log,
42
- '005': _005_screenshots,
43
- '006': _006_deliverables,
44
- '007': _007_deliverable_templates,
45
- '008': _008_add_nuclei_table,
46
- '009': _009_add_cme_tables,
47
- '010': _010_evidence_linking,
48
- '011': _011_timeline_tracking,
49
- '012': _012_team_collaboration,
50
- '013': _013_add_host_tags,
51
- '014': _014_exploit_attempts,
52
- '015': _015_add_mac_os_fields,
53
- '016': _016_add_domain_field,
54
- '017': _017_msf_sessions,
55
- '018': _018_add_osint_target,
56
- '019': _019_add_engagement_type,
57
- '020': _020_add_rbac,
58
- '021': _021_wazuh_integration,
59
- '022': _022_wazuh_indexer_columns,
60
- '023': _023_fix_detection_results_fk,
61
- '024': _024_wazuh_vulnerabilities,
62
- '025': _025_multi_siem_support,
63
- '026': _026_add_engagement_scope,
64
- '027': _027_multi_siem_persistence,
39
+ "001": _001_add_credential_enhancements,
40
+ "002": _002_add_status_tracking,
41
+ "003": _003_add_execution_log,
42
+ "005": _005_screenshots,
43
+ "006": _006_deliverables,
44
+ "007": _007_deliverable_templates,
45
+ "008": _008_add_nuclei_table,
46
+ "009": _009_add_cme_tables,
47
+ "010": _010_evidence_linking,
48
+ "011": _011_timeline_tracking,
49
+ "012": _012_team_collaboration,
50
+ "013": _013_add_host_tags,
51
+ "014": _014_exploit_attempts,
52
+ "015": _015_add_mac_os_fields,
53
+ "016": _016_add_domain_field,
54
+ "017": _017_msf_sessions,
55
+ "018": _018_add_osint_target,
56
+ "019": _019_add_engagement_type,
57
+ "020": _020_add_rbac,
58
+ "021": _021_wazuh_integration,
59
+ "022": _022_wazuh_indexer_columns,
60
+ "023": _023_fix_detection_results_fk,
61
+ "024": _024_wazuh_vulnerabilities,
62
+ "025": _025_multi_siem_support,
63
+ "026": _026_add_engagement_scope,
64
+ "027": _027_multi_siem_persistence,
65
65
  }
66
66
 
67
67
 
@@ -20,14 +20,16 @@ class MigrationManager:
20
20
  def _ensure_migrations_table(self):
21
21
  """Create migrations tracking table if it doesn't exist."""
22
22
  conn = sqlite3.connect(self.db_path)
23
- conn.execute("""
23
+ conn.execute(
24
+ """
24
25
  CREATE TABLE IF NOT EXISTS schema_migrations (
25
26
  id INTEGER PRIMARY KEY AUTOINCREMENT,
26
27
  version TEXT UNIQUE NOT NULL,
27
28
  name TEXT NOT NULL,
28
29
  applied_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
29
30
  )
30
- """)
31
+ """
32
+ )
31
33
  conn.commit()
32
34
  conn.close()
33
35
 
@@ -48,12 +50,8 @@ class MigrationManager:
48
50
  for version in get_all_versions():
49
51
  if version not in applied:
50
52
  module = MIGRATIONS_REGISTRY[version]
51
- name = getattr(module, 'DESCRIPTION', version)
52
- pending.append({
53
- 'version': version,
54
- 'name': name,
55
- 'module': module
56
- })
53
+ name = getattr(module, "DESCRIPTION", version)
54
+ pending.append({"version": version, "name": name, "module": module})
57
55
 
58
56
  return pending
59
57
 
@@ -65,12 +63,13 @@ class MigrationManager:
65
63
  try:
66
64
  # Set environment variable for migration scripts to check
67
65
  import os
68
- old_silent = os.environ.get('SOULEYEZ_MIGRATION_SILENT')
66
+
67
+ old_silent = os.environ.get("SOULEYEZ_MIGRATION_SILENT")
69
68
  if silent:
70
- os.environ['SOULEYEZ_MIGRATION_SILENT'] = '1'
69
+ os.environ["SOULEYEZ_MIGRATION_SILENT"] = "1"
71
70
 
72
71
  # Get migration module from registry (already imported)
73
- migration_module = migration['module']
72
+ migration_module = migration["module"]
74
73
 
75
74
  # Execute upgrade function
76
75
  conn = sqlite3.connect(self.db_path)
@@ -79,7 +78,7 @@ class MigrationManager:
79
78
  # Record migration
80
79
  conn.execute(
81
80
  "INSERT INTO schema_migrations (version, name) VALUES (?, ?)",
82
- (migration['version'], migration['name'])
81
+ (migration["version"], migration["name"]),
83
82
  )
84
83
  conn.commit()
85
84
  conn.close()
@@ -87,9 +86,9 @@ class MigrationManager:
87
86
  # Restore environment variable
88
87
  if silent:
89
88
  if old_silent is None:
90
- os.environ.pop('SOULEYEZ_MIGRATION_SILENT', None)
89
+ os.environ.pop("SOULEYEZ_MIGRATION_SILENT", None)
91
90
  else:
92
- os.environ['SOULEYEZ_MIGRATION_SILENT'] = old_silent
91
+ os.environ["SOULEYEZ_MIGRATION_SILENT"] = old_silent
93
92
 
94
93
  if not silent:
95
94
  print(f"[{migration['version']}] ✅ Successfully applied")
@@ -157,7 +156,7 @@ class MigrationManager:
157
156
  continue
158
157
 
159
158
  # Check if downgrade function exists
160
- if not hasattr(migration_module, 'downgrade'):
159
+ if not hasattr(migration_module, "downgrade"):
161
160
  print(f"[{version}] ❌ No downgrade() function found")
162
161
  continue
163
162
 
@@ -167,8 +166,7 @@ class MigrationManager:
167
166
 
168
167
  # Remove from migrations table
169
168
  conn.execute(
170
- "DELETE FROM schema_migrations WHERE version = ?",
171
- (version,)
169
+ "DELETE FROM schema_migrations WHERE version = ?", (version,)
172
170
  )
173
171
  conn.commit()
174
172
  conn.close()
@@ -201,11 +199,13 @@ class MigrationManager:
201
199
  if applied:
202
200
  print("✅ Applied:")
203
201
  conn = sqlite3.connect(self.db_path)
204
- cursor = conn.execute("""
202
+ cursor = conn.execute(
203
+ """
205
204
  SELECT version, name, applied_at
206
205
  FROM schema_migrations
207
206
  ORDER BY version
208
- """)
207
+ """
208
+ )
209
209
  for row in cursor.fetchall():
210
210
  print(f" [{row[0]}] {row[1]} (applied: {row[2]})")
211
211
  conn.close()
@@ -25,7 +25,7 @@ def add_msf_session(
25
25
  port: Optional[int] = None,
26
26
  tunnel_peer: Optional[str] = None,
27
27
  opened_at: Optional[datetime] = None,
28
- notes: Optional[str] = None
28
+ notes: Optional[str] = None,
29
29
  ) -> int:
30
30
  """
31
31
  Add or update MSF session record
@@ -52,16 +52,20 @@ def add_msf_session(
52
52
  cursor = db.cursor()
53
53
 
54
54
  # Check if session already exists
55
- cursor.execute("""
55
+ cursor.execute(
56
+ """
56
57
  SELECT id FROM msf_sessions
57
58
  WHERE engagement_id = ? AND msf_session_id = ?
58
- """, (engagement_id, msf_session_id))
59
+ """,
60
+ (engagement_id, msf_session_id),
61
+ )
59
62
 
60
63
  existing = cursor.fetchone()
61
64
 
62
65
  if existing:
63
66
  # Update existing session
64
- cursor.execute("""
67
+ cursor.execute(
68
+ """
65
69
  UPDATE msf_sessions
66
70
  SET session_type = ?,
67
71
  via_exploit = ?,
@@ -76,34 +80,54 @@ def add_msf_session(
76
80
  is_active = 1,
77
81
  updated_at = CURRENT_TIMESTAMP
78
82
  WHERE id = ?
79
- """, (
80
- session_type, via_exploit, via_payload, platform, arch,
81
- username, port, tunnel_peer, opened_at, notes, existing[0]
82
- ))
83
+ """,
84
+ (
85
+ session_type,
86
+ via_exploit,
87
+ via_payload,
88
+ platform,
89
+ arch,
90
+ username,
91
+ port,
92
+ tunnel_peer,
93
+ opened_at,
94
+ notes,
95
+ existing[0],
96
+ ),
97
+ )
83
98
  return existing[0]
84
99
  else:
85
100
  # Insert new session
86
- cursor.execute("""
101
+ cursor.execute(
102
+ """
87
103
  INSERT INTO msf_sessions (
88
104
  engagement_id, host_id, msf_session_id,
89
105
  session_type, via_exploit, via_payload,
90
106
  platform, arch, username, port,
91
107
  tunnel_peer, opened_at, notes, is_active
92
108
  ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1)
93
- """, (
94
- engagement_id, host_id, msf_session_id,
95
- session_type, via_exploit, via_payload,
96
- platform, arch, username, port,
97
- tunnel_peer, opened_at, notes
98
- ))
109
+ """,
110
+ (
111
+ engagement_id,
112
+ host_id,
113
+ msf_session_id,
114
+ session_type,
115
+ via_exploit,
116
+ via_payload,
117
+ platform,
118
+ arch,
119
+ username,
120
+ port,
121
+ tunnel_peer,
122
+ opened_at,
123
+ notes,
124
+ ),
125
+ )
99
126
  return cursor.lastrowid
100
127
 
101
128
 
102
129
  def close_msf_session(
103
- db,
104
- engagement_id: int,
105
- msf_session_id: int,
106
- close_reason: Optional[str] = None
130
+ db, engagement_id: int, msf_session_id: int, close_reason: Optional[str] = None
107
131
  ) -> bool:
108
132
  """
109
133
  Mark MSF session as closed
@@ -119,23 +143,23 @@ def close_msf_session(
119
143
  """
120
144
  cursor = db.cursor()
121
145
 
122
- cursor.execute("""
146
+ cursor.execute(
147
+ """
123
148
  UPDATE msf_sessions
124
149
  SET is_active = 0,
125
150
  closed_at = CURRENT_TIMESTAMP,
126
151
  close_reason = ?,
127
152
  updated_at = CURRENT_TIMESTAMP
128
153
  WHERE engagement_id = ? AND msf_session_id = ?
129
- """, (close_reason, engagement_id, msf_session_id))
154
+ """,
155
+ (close_reason, engagement_id, msf_session_id),
156
+ )
130
157
 
131
158
  return cursor.rowcount > 0
132
159
 
133
160
 
134
161
  def get_msf_sessions(
135
- db,
136
- engagement_id: int,
137
- active_only: bool = True,
138
- host_id: Optional[int] = None
162
+ db, engagement_id: int, active_only: bool = True, host_id: Optional[int] = None
139
163
  ) -> List[Dict[str, Any]]:
140
164
  """
141
165
  Get MSF sessions
@@ -178,9 +202,7 @@ def get_msf_sessions(
178
202
 
179
203
 
180
204
  def get_msf_session_by_id(
181
- db,
182
- engagement_id: int,
183
- msf_session_id: int
205
+ db, engagement_id: int, msf_session_id: int
184
206
  ) -> Optional[Dict[str, Any]]:
185
207
  """
186
208
  Get MSF session by MSF session ID
@@ -195,7 +217,8 @@ def get_msf_session_by_id(
195
217
  """
196
218
  cursor = db.cursor()
197
219
 
198
- cursor.execute("""
220
+ cursor.execute(
221
+ """
199
222
  SELECT
200
223
  s.*,
201
224
  h.ip_address as host_ip,
@@ -203,7 +226,9 @@ def get_msf_session_by_id(
203
226
  FROM msf_sessions s
204
227
  JOIN hosts h ON s.host_id = h.id
205
228
  WHERE s.engagement_id = ? AND s.msf_session_id = ?
206
- """, (engagement_id, msf_session_id))
229
+ """,
230
+ (engagement_id, msf_session_id),
231
+ )
207
232
 
208
233
  row = cursor.fetchone()
209
234
  if row:
@@ -212,11 +237,7 @@ def get_msf_session_by_id(
212
237
  return None
213
238
 
214
239
 
215
- def update_session_last_seen(
216
- db,
217
- engagement_id: int,
218
- msf_session_id: int
219
- ) -> bool:
240
+ def update_session_last_seen(db, engagement_id: int, msf_session_id: int) -> bool:
220
241
  """
221
242
  Update session last_seen timestamp
222
243
 
@@ -230,12 +251,15 @@ def update_session_last_seen(
230
251
  """
231
252
  cursor = db.cursor()
232
253
 
233
- cursor.execute("""
254
+ cursor.execute(
255
+ """
234
256
  UPDATE msf_sessions
235
257
  SET last_seen = CURRENT_TIMESTAMP,
236
258
  updated_at = CURRENT_TIMESTAMP
237
259
  WHERE engagement_id = ? AND msf_session_id = ?
238
- """, (engagement_id, msf_session_id))
260
+ """,
261
+ (engagement_id, msf_session_id),
262
+ )
239
263
 
240
264
  return cursor.rowcount > 0
241
265
 
@@ -254,71 +278,79 @@ def get_session_stats(db, engagement_id: int) -> Dict[str, Any]:
254
278
  cursor = db.cursor()
255
279
 
256
280
  stats = {
257
- 'total': 0,
258
- 'active': 0,
259
- 'closed': 0,
260
- 'by_type': {},
261
- 'by_exploit': {},
262
- 'compromised_hosts': 0
281
+ "total": 0,
282
+ "active": 0,
283
+ "closed": 0,
284
+ "by_type": {},
285
+ "by_exploit": {},
286
+ "compromised_hosts": 0,
263
287
  }
264
288
 
265
289
  # Total and active sessions
266
- cursor.execute("""
290
+ cursor.execute(
291
+ """
267
292
  SELECT
268
293
  COUNT(*) as total,
269
294
  SUM(CASE WHEN is_active = 1 THEN 1 ELSE 0 END) as active
270
295
  FROM msf_sessions
271
296
  WHERE engagement_id = ?
272
- """, (engagement_id,))
297
+ """,
298
+ (engagement_id,),
299
+ )
273
300
 
274
301
  row = cursor.fetchone()
275
302
  if row:
276
- stats['total'] = row[0] or 0
277
- stats['active'] = row[1] or 0
278
- stats['closed'] = stats['total'] - stats['active']
303
+ stats["total"] = row[0] or 0
304
+ stats["active"] = row[1] or 0
305
+ stats["closed"] = stats["total"] - stats["active"]
279
306
 
280
307
  # Sessions by type
281
- cursor.execute("""
308
+ cursor.execute(
309
+ """
282
310
  SELECT session_type, COUNT(*) as count
283
311
  FROM msf_sessions
284
312
  WHERE engagement_id = ?
285
313
  GROUP BY session_type
286
- """, (engagement_id,))
314
+ """,
315
+ (engagement_id,),
316
+ )
287
317
 
288
318
  for row in cursor.fetchall():
289
- session_type = row[0] or 'unknown'
290
- stats['by_type'][session_type] = row[1]
319
+ session_type = row[0] or "unknown"
320
+ stats["by_type"][session_type] = row[1]
291
321
 
292
322
  # Sessions by exploit
293
- cursor.execute("""
323
+ cursor.execute(
324
+ """
294
325
  SELECT via_exploit, COUNT(*) as count
295
326
  FROM msf_sessions
296
327
  WHERE engagement_id = ? AND via_exploit IS NOT NULL
297
328
  GROUP BY via_exploit
298
- """, (engagement_id,))
329
+ """,
330
+ (engagement_id,),
331
+ )
299
332
 
300
333
  for row in cursor.fetchall():
301
- stats['by_exploit'][row[0]] = row[1]
334
+ stats["by_exploit"][row[0]] = row[1]
302
335
 
303
336
  # Compromised hosts (hosts with active sessions)
304
- cursor.execute("""
337
+ cursor.execute(
338
+ """
305
339
  SELECT COUNT(DISTINCT host_id)
306
340
  FROM msf_sessions
307
341
  WHERE engagement_id = ? AND is_active = 1
308
- """, (engagement_id,))
342
+ """,
343
+ (engagement_id,),
344
+ )
309
345
 
310
346
  row = cursor.fetchone()
311
347
  if row:
312
- stats['compromised_hosts'] = row[0] or 0
348
+ stats["compromised_hosts"] = row[0] or 0
313
349
 
314
350
  return stats
315
351
 
316
352
 
317
- def delete_msf_session(
318
- db,
319
- engagement_id: int,
320
- msf_session_id: int
321
- ) -> bool:
353
+ def delete_msf_session(db, engagement_id: int, msf_session_id: int) -> bool:
322
354
  """
323
355
  Delete MSF session record
324
356
 
@@ -332,9 +364,12 @@ def delete_msf_session(
332
364
  """
333
365
  cursor = db.cursor()
334
366
 
335
- cursor.execute("""
367
+ cursor.execute(
368
+ """
336
369
  DELETE FROM msf_sessions
337
370
  WHERE engagement_id = ? AND msf_session_id = ?
338
- """, (engagement_id, msf_session_id))
371
+ """,
372
+ (engagement_id, msf_session_id),
373
+ )
339
374
 
340
375
  return cursor.rowcount > 0