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
@@ -2,11 +2,13 @@
2
2
  """
3
3
  Input validation and sanitization for security-critical operations.
4
4
  """
5
- import re
5
+
6
6
  import ipaddress
7
+ import re
7
8
  import shlex
8
9
  from pathlib import Path
9
- from typing import List, Union, Optional
10
+ from typing import List, Optional, Union
11
+
10
12
  from souleyez.log_config import get_logger
11
13
 
12
14
  logger = get_logger(__name__)
@@ -14,21 +16,23 @@ logger = get_logger(__name__)
14
16
 
15
17
  class ValidationError(Exception):
16
18
  """Raised when input validation fails."""
19
+
17
20
  pass
18
21
 
19
22
 
20
23
  # ===== IP ADDRESS VALIDATION =====
21
24
 
25
+
22
26
  def validate_ip_address(ip: str) -> str:
23
27
  """
24
28
  Validate and normalize an IP address.
25
-
29
+
26
30
  Args:
27
31
  ip: IP address string
28
-
32
+
29
33
  Returns:
30
34
  Normalized IP address string
31
-
35
+
32
36
  Raises:
33
37
  ValidationError: If IP is invalid
34
38
  """
@@ -37,23 +41,20 @@ def validate_ip_address(ip: str) -> str:
37
41
  ip_obj = ipaddress.ip_address(ip)
38
42
  return str(ip_obj)
39
43
  except ValueError as e:
40
- logger.warning("Invalid IP address", extra={
41
- "input": ip,
42
- "error": str(e)
43
- })
44
+ logger.warning("Invalid IP address", extra={"input": ip, "error": str(e)})
44
45
  raise ValidationError(f"Invalid IP address: {ip}")
45
46
 
46
47
 
47
48
  def validate_cidr(cidr: str) -> str:
48
49
  """
49
50
  Validate and normalize a CIDR notation network.
50
-
51
+
51
52
  Args:
52
53
  cidr: CIDR string (e.g., "192.168.1.0/24")
53
-
54
+
54
55
  Returns:
55
56
  Normalized CIDR string
56
-
57
+
57
58
  Raises:
58
59
  ValidationError: If CIDR is invalid
59
60
  """
@@ -61,54 +62,52 @@ def validate_cidr(cidr: str) -> str:
61
62
  network = ipaddress.ip_network(cidr, strict=False)
62
63
  return str(network)
63
64
  except ValueError as e:
64
- logger.warning("Invalid CIDR notation", extra={
65
- "input": cidr,
66
- "error": str(e)
67
- })
65
+ logger.warning("Invalid CIDR notation", extra={"input": cidr, "error": str(e)})
68
66
  raise ValidationError(f"Invalid CIDR notation: {cidr}")
69
67
 
70
68
 
71
69
  def validate_hostname(hostname: str) -> str:
72
70
  """
73
71
  Validate hostname format.
74
-
72
+
75
73
  Args:
76
74
  hostname: Hostname string
77
-
75
+
78
76
  Returns:
79
77
  Validated hostname
80
-
78
+
81
79
  Raises:
82
80
  ValidationError: If hostname is invalid
83
81
  """
84
82
  # RFC 1123 hostname rules
85
83
  if not hostname or len(hostname) > 253:
86
84
  raise ValidationError("Hostname must be 1-253 characters")
87
-
85
+
88
86
  # Valid hostname regex
89
87
  hostname_pattern = re.compile(
90
- r'^(?!-)[A-Za-z0-9-]{1,63}(?<!-)(\.[A-Za-z0-9-]{1,63})*$'
88
+ r"^(?!-)[A-Za-z0-9-]{1,63}(?<!-)(\.[A-Za-z0-9-]{1,63})*$"
91
89
  )
92
-
90
+
93
91
  if not hostname_pattern.match(hostname):
94
92
  logger.warning("Invalid hostname format", extra={"input": hostname})
95
93
  raise ValidationError(f"Invalid hostname format: {hostname}")
96
-
94
+
97
95
  return hostname
98
96
 
99
97
 
100
98
  # ===== PORT VALIDATION =====
101
99
 
100
+
102
101
  def validate_port(port: Union[int, str]) -> int:
103
102
  """
104
103
  Validate port number.
105
-
104
+
106
105
  Args:
107
106
  port: Port number (1-65535)
108
-
107
+
109
108
  Returns:
110
109
  Validated port as integer
111
-
110
+
112
111
  Raises:
113
112
  ValidationError: If port is invalid
114
113
  """
@@ -124,28 +123,28 @@ def validate_port(port: Union[int, str]) -> int:
124
123
  def validate_port_list(ports: str) -> str:
125
124
  """
126
125
  Validate and sanitize port list for nmap.
127
-
126
+
128
127
  Args:
129
128
  ports: Port specification (e.g., "80,443", "1-1000", "80,443,8000-9000")
130
-
129
+
131
130
  Returns:
132
131
  Validated port string
133
-
132
+
134
133
  Raises:
135
134
  ValidationError: If port specification is invalid
136
135
  """
137
136
  # Allow comma-separated ports and ranges
138
- port_pattern = re.compile(r'^[0-9,\-]+$')
139
-
137
+ port_pattern = re.compile(r"^[0-9,\-]+$")
138
+
140
139
  if not port_pattern.match(ports):
141
140
  raise ValidationError(f"Invalid port specification: {ports}")
142
-
141
+
143
142
  # Validate each component
144
- for part in ports.split(','):
145
- if '-' in part:
143
+ for part in ports.split(","):
144
+ if "-" in part:
146
145
  # Range
147
146
  try:
148
- start, end = part.split('-')
147
+ start, end = part.split("-")
149
148
  start_port = validate_port(start)
150
149
  end_port = validate_port(end)
151
150
  if start_port > end_port:
@@ -155,25 +154,29 @@ def validate_port_list(ports: str) -> str:
155
154
  else:
156
155
  # Single port
157
156
  validate_port(part)
158
-
157
+
159
158
  return ports
160
159
 
161
160
 
162
161
  # ===== PATH VALIDATION =====
163
162
 
164
- def validate_file_path(path: Union[str, Path], must_exist: bool = False,
165
- allowed_dirs: Optional[List[Path]] = None) -> Path:
163
+
164
+ def validate_file_path(
165
+ path: Union[str, Path],
166
+ must_exist: bool = False,
167
+ allowed_dirs: Optional[List[Path]] = None,
168
+ ) -> Path:
166
169
  """
167
170
  Validate and sanitize file path to prevent directory traversal.
168
-
171
+
169
172
  Args:
170
173
  path: File path to validate
171
174
  must_exist: If True, path must exist
172
175
  allowed_dirs: List of allowed parent directories (None = user's .souleyez dir only)
173
-
176
+
174
177
  Returns:
175
178
  Resolved absolute Path object
176
-
179
+
177
180
  Raises:
178
181
  ValidationError: If path is invalid or unsafe
179
182
  """
@@ -181,11 +184,11 @@ def validate_file_path(path: Union[str, Path], must_exist: bool = False,
181
184
  path_obj = Path(path).expanduser().resolve()
182
185
  except Exception as e:
183
186
  raise ValidationError(f"Invalid path: {path} - {e}")
184
-
187
+
185
188
  # Default to only allowing .souleyez directory
186
189
  if allowed_dirs is None:
187
- allowed_dirs = [Path.home() / '.souleyez']
188
-
190
+ allowed_dirs = [Path.home() / ".souleyez"]
191
+
189
192
  # Ensure resolved path is within allowed directories
190
193
  is_allowed = False
191
194
  for allowed_dir in allowed_dirs:
@@ -196,30 +199,34 @@ def validate_file_path(path: Union[str, Path], must_exist: bool = False,
196
199
  break
197
200
  except ValueError:
198
201
  continue
199
-
202
+
200
203
  if not is_allowed:
201
- logger.warning("Path traversal attempt blocked", extra={
202
- "requested_path": str(path),
203
- "resolved_path": str(path_obj),
204
- "allowed_dirs": [str(d) for d in allowed_dirs]
205
- })
204
+ logger.warning(
205
+ "Path traversal attempt blocked",
206
+ extra={
207
+ "requested_path": str(path),
208
+ "resolved_path": str(path_obj),
209
+ "allowed_dirs": [str(d) for d in allowed_dirs],
210
+ },
211
+ )
206
212
  raise ValidationError(f"Path outside allowed directories: {path}")
207
-
213
+
208
214
  if must_exist and not path_obj.exists():
209
215
  raise ValidationError(f"Path does not exist: {path}")
210
-
216
+
211
217
  return path_obj
212
218
 
213
219
 
214
220
  # ===== COMMAND INJECTION PREVENTION =====
215
221
 
222
+
216
223
  def sanitize_command_arg(arg: str) -> str:
217
224
  """
218
225
  Sanitize a single command argument using shlex.
219
-
226
+
220
227
  Args:
221
228
  arg: Command argument to sanitize
222
-
229
+
223
230
  Returns:
224
231
  Safely quoted argument
225
232
  """
@@ -229,72 +236,73 @@ def sanitize_command_arg(arg: str) -> str:
229
236
  def validate_nmap_args(args: List[str]) -> List[str]:
230
237
  """
231
238
  Validate nmap arguments to prevent command injection.
232
-
239
+
233
240
  Args:
234
241
  args: List of nmap arguments
235
-
242
+
236
243
  Returns:
237
244
  Validated argument list
238
-
245
+
239
246
  Raises:
240
247
  ValidationError: If dangerous arguments detected
241
248
  """
242
249
  # Dangerous patterns that could lead to command injection
243
250
  # These patterns should only match within script arguments
244
251
  dangerous_patterns = [
245
- r'--script[=\s].*&&', # Command chaining in scripts
246
- r'--script[=\s].*;', # Command separator in scripts
247
- r'--script[=\s].*\|', # Pipe in scripts
248
- r'--script[=\s].*`', # Command substitution in scripts
249
- r'--script[=\s].*\$', # Variable expansion in scripts
252
+ r"--script[=\s].*&&", # Command chaining in scripts
253
+ r"--script[=\s].*;", # Command separator in scripts
254
+ r"--script[=\s].*\|", # Pipe in scripts
255
+ r"--script[=\s].*`", # Command substitution in scripts
256
+ r"--script[=\s].*\$", # Variable expansion in scripts
250
257
  ]
251
-
258
+
252
259
  # Blocked arguments that could be abused
253
260
  blocked_args = [
254
- '--interactive', # Interactive mode
255
- '--iflist', # Could reveal sensitive info
261
+ "--interactive", # Interactive mode
262
+ "--iflist", # Could reveal sensitive info
256
263
  ]
257
-
264
+
258
265
  validated_args = []
259
266
  for arg in args:
260
267
  # Check for dangerous patterns
261
268
  for pattern in dangerous_patterns:
262
269
  if re.search(pattern, arg):
263
- logger.warning("Blocked dangerous nmap argument", extra={
264
- "argument": arg,
265
- "pattern": pattern
266
- })
270
+ logger.warning(
271
+ "Blocked dangerous nmap argument",
272
+ extra={"argument": arg, "pattern": pattern},
273
+ )
267
274
  raise ValidationError(f"Dangerous nmap argument blocked: {arg}")
268
-
275
+
269
276
  # Check for blocked arguments
270
277
  arg_lower = arg.lower()
271
278
  for blocked in blocked_args:
272
279
  if arg_lower.startswith(blocked):
273
280
  logger.warning("Blocked nmap argument", extra={"argument": arg})
274
281
  raise ValidationError(f"Blocked nmap argument: {arg}")
275
-
282
+
276
283
  validated_args.append(arg)
277
-
284
+
278
285
  return validated_args
279
286
 
280
287
 
281
288
  # ===== SQL INJECTION PREVENTION =====
282
289
 
290
+
283
291
  def validate_table_name(table: str) -> str:
284
292
  """
285
293
  Validate table name to prevent SQL injection.
286
294
  Only alphanumeric and underscore allowed.
287
-
295
+
288
296
  Args:
289
297
  table: Table name
290
-
298
+
291
299
  Returns:
292
300
  Validated table name
293
-
301
+
294
302
  Raises:
295
303
  ValidationError: If table name is invalid
296
304
  """
297
- if not re.match(r'^[a-zA-Z_][a-zA-Z0-9_]*$', table):
305
+ if not re.match(r"^[a-zA-Z_][a-zA-Z0-9_]*$", table):
298
306
  raise ValidationError(f"Invalid table name: {table}")
299
307
  return table
300
308
 
@@ -303,17 +311,17 @@ def validate_column_name(column: str) -> str:
303
311
  """
304
312
  Validate column name to prevent SQL injection.
305
313
  Only alphanumeric and underscore allowed.
306
-
314
+
307
315
  Args:
308
316
  column: Column name
309
-
317
+
310
318
  Returns:
311
319
  Validated column name
312
-
320
+
313
321
  Raises:
314
322
  ValidationError: If column name is invalid
315
323
  """
316
- if not re.match(r'^[a-zA-Z_][a-zA-Z0-9_]*$', column):
324
+ if not re.match(r"^[a-zA-Z_][a-zA-Z0-9_]*$", column):
317
325
  raise ValidationError(f"Invalid column name: {column}")
318
326
  return column
319
327
 
@@ -321,186 +329,195 @@ def validate_column_name(column: str) -> str:
321
329
  def validate_severity(severity: str) -> str:
322
330
  """
323
331
  Validate finding severity level.
324
-
332
+
325
333
  Args:
326
334
  severity: Severity string
327
-
335
+
328
336
  Returns:
329
337
  Validated severity (lowercase)
330
-
338
+
331
339
  Raises:
332
340
  ValidationError: If severity is invalid
333
341
  """
334
- valid_severities = ['critical', 'high', 'medium', 'low', 'info']
342
+ valid_severities = ["critical", "high", "medium", "low", "info"]
335
343
  severity_lower = severity.lower()
336
-
344
+
337
345
  if severity_lower not in valid_severities:
338
- raise ValidationError(f"Invalid severity: {severity}. Must be one of: {valid_severities}")
339
-
346
+ raise ValidationError(
347
+ f"Invalid severity: {severity}. Must be one of: {valid_severities}"
348
+ )
349
+
340
350
  return severity_lower
341
351
 
342
352
 
343
353
  # ===== HTML/XSS PREVENTION =====
344
354
 
355
+
345
356
  def escape_html(text: str) -> str:
346
357
  """
347
358
  Escape HTML special characters to prevent XSS.
348
-
359
+
349
360
  Args:
350
361
  text: Text to escape
351
-
362
+
352
363
  Returns:
353
364
  HTML-escaped text
354
365
  """
355
366
  if text is None:
356
- return ''
357
-
367
+ return ""
368
+
358
369
  escape_dict = {
359
- '&': '&amp;',
360
- '<': '&lt;',
361
- '>': '&gt;',
362
- '"': '&quot;',
363
- "'": '&#x27;',
364
- '/': '&#x2F;',
370
+ "&": "&amp;",
371
+ "<": "&lt;",
372
+ ">": "&gt;",
373
+ '"': "&quot;",
374
+ "'": "&#x27;",
375
+ "/": "&#x2F;",
365
376
  }
366
-
367
- return ''.join(escape_dict.get(c, c) for c in str(text))
377
+
378
+ return "".join(escape_dict.get(c, c) for c in str(text))
368
379
 
369
380
 
370
381
  def sanitize_for_json(text: str) -> str:
371
382
  """
372
383
  Sanitize text for safe inclusion in JSON.
373
-
384
+
374
385
  Args:
375
386
  text: Text to sanitize
376
-
387
+
377
388
  Returns:
378
389
  Sanitized text
379
390
  """
380
391
  if text is None:
381
- return ''
382
-
392
+ return ""
393
+
383
394
  # Remove control characters
384
- text = re.sub(r'[\x00-\x1f\x7f-\x9f]', '', str(text))
395
+ text = re.sub(r"[\x00-\x1f\x7f-\x9f]", "", str(text))
385
396
  return text
386
397
 
387
398
 
388
399
  # ===== ENGAGEMENT NAME VALIDATION =====
389
400
 
401
+
390
402
  def validate_engagement_name(name: str) -> str:
391
403
  """
392
404
  Validate engagement name.
393
-
405
+
394
406
  Args:
395
407
  name: Engagement name
396
-
408
+
397
409
  Returns:
398
410
  Validated name
399
-
411
+
400
412
  Raises:
401
413
  ValidationError: If name is invalid
402
414
  """
403
415
  if not name or len(name) < 1:
404
416
  raise ValidationError("Engagement name cannot be empty")
405
-
417
+
406
418
  if len(name) > 255:
407
419
  raise ValidationError("Engagement name too long (max 255 characters)")
408
-
420
+
409
421
  # Prevent path traversal in names
410
- if '/' in name or '\\' in name or '..' in name:
422
+ if "/" in name or "\\" in name or ".." in name:
411
423
  raise ValidationError("Engagement name cannot contain path separators")
412
-
424
+
413
425
  return name.strip()
414
426
 
415
427
 
416
428
  # ===== PLUGIN NAME VALIDATION =====
417
429
 
430
+
418
431
  def validate_plugin_name(plugin: str) -> str:
419
432
  """
420
433
  Validate plugin name to prevent code injection.
421
-
434
+
422
435
  Args:
423
436
  plugin: Plugin name
424
-
437
+
425
438
  Returns:
426
439
  Validated plugin name
427
-
440
+
428
441
  Raises:
429
442
  ValidationError: If plugin name is invalid
430
443
  """
431
444
  # Only lowercase letters, numbers, underscores
432
- if not re.match(r'^[a-z][a-z0-9_]*$', plugin):
445
+ if not re.match(r"^[a-z][a-z0-9_]*$", plugin):
433
446
  raise ValidationError(f"Invalid plugin name: {plugin}")
434
-
447
+
435
448
  if len(plugin) > 50:
436
449
  raise ValidationError("Plugin name too long")
437
-
450
+
438
451
  return plugin
439
452
 
440
453
 
441
454
  # ===== PROTOCOL VALIDATION =====
442
455
 
456
+
443
457
  def validate_protocol(protocol: str) -> str:
444
458
  """
445
459
  Validate network protocol.
446
-
460
+
447
461
  Args:
448
462
  protocol: Protocol string (tcp, udp, etc.)
449
-
463
+
450
464
  Returns:
451
465
  Validated protocol (lowercase)
452
-
466
+
453
467
  Raises:
454
468
  ValidationError: If protocol is invalid
455
469
  """
456
- valid_protocols = ['tcp', 'udp', 'icmp', 'sctp']
470
+ valid_protocols = ["tcp", "udp", "icmp", "sctp"]
457
471
  protocol_lower = protocol.lower()
458
-
472
+
459
473
  if protocol_lower not in valid_protocols:
460
- raise ValidationError(f"Invalid protocol: {protocol}. Must be one of: {valid_protocols}")
461
-
474
+ raise ValidationError(
475
+ f"Invalid protocol: {protocol}. Must be one of: {valid_protocols}"
476
+ )
477
+
462
478
  return protocol_lower
463
479
 
464
480
 
465
481
  # ===== UNIVERSAL TARGET VALIDATION =====
466
482
 
483
+
467
484
  def validate_target(target: str) -> str:
468
485
  """
469
486
  Validate a target which can be IP, CIDR, or hostname.
470
487
  Tries each format in order and returns the first match.
471
-
488
+
472
489
  Args:
473
490
  target: Target string (IP, CIDR, or hostname)
474
-
491
+
475
492
  Returns:
476
493
  Validated target string
477
-
494
+
478
495
  Raises:
479
496
  ValidationError: If target is not valid in any format
480
497
  """
481
498
  if not target or not target.strip():
482
499
  raise ValidationError("Target cannot be empty")
483
-
500
+
484
501
  target = target.strip()
485
-
502
+
486
503
  # Try IP address
487
504
  try:
488
505
  return validate_ip_address(target)
489
506
  except ValidationError:
490
507
  pass
491
-
508
+
492
509
  # Try CIDR notation
493
510
  try:
494
511
  return validate_cidr(target)
495
512
  except ValidationError:
496
513
  pass
497
-
514
+
498
515
  # Try hostname
499
516
  try:
500
517
  return validate_hostname(target)
501
518
  except ValidationError:
502
519
  pass
503
-
520
+
504
521
  # Nothing matched
505
522
  raise ValidationError(
506
523
  f"Invalid target: '{target}'. Must be IP address, CIDR notation, or valid hostname"
@@ -509,40 +526,97 @@ def validate_target(target: str) -> str:
509
526
 
510
527
  # ===== URL VALIDATION =====
511
528
 
529
+
512
530
  def validate_url(url: str) -> str:
513
531
  """
514
532
  Validate URL format for web-based tools.
515
-
533
+
516
534
  Args:
517
535
  url: URL string
518
-
536
+
519
537
  Returns:
520
538
  Validated URL
521
-
539
+
522
540
  Raises:
523
541
  ValidationError: If URL is invalid
524
542
  """
525
543
  if not url or not url.strip():
526
544
  raise ValidationError("URL cannot be empty")
527
-
545
+
528
546
  url = url.strip()
529
-
547
+
530
548
  # Basic URL pattern (http/https only)
531
549
  import re
550
+
532
551
  url_pattern = re.compile(
533
- r'^https?://' # http:// or https://
534
- r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+[A-Z]{2,6}\.?|' # domain
535
- r'localhost|' # localhost
536
- r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})' # or IP
537
- r'(?::\d+)?' # optional port
538
- r'(?:/?|[/?]\S+)$', re.IGNORECASE # optional path
552
+ r"^https?://" # http:// or https://
553
+ r"(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+[A-Z]{2,6}\.?|" # domain
554
+ r"localhost|" # localhost
555
+ r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})" # or IP
556
+ r"(?::\d+)?" # optional port
557
+ r"(?:/?|[/?]\S+)$",
558
+ re.IGNORECASE, # optional path
539
559
  )
540
-
560
+
541
561
  if not url_pattern.match(url):
542
- raise ValidationError(f"Invalid URL format: {url}. Must start with http:// or https://")
543
-
562
+ raise ValidationError(
563
+ f"Invalid URL format: {url}. Must start with http:// or https://"
564
+ )
565
+
544
566
  # Block dangerous protocols
545
- if url.lower().startswith(('file://', 'ftp://', 'javascript:', 'data:')):
567
+ if url.lower().startswith(("file://", "ftp://", "javascript:", "data:")):
546
568
  raise ValidationError(f"Dangerous URL protocol blocked: {url}")
547
-
569
+
548
570
  return url
571
+
572
+
573
+ def validate_target_or_url(target: str) -> str:
574
+ """
575
+ Validate a target which can be IP, CIDR, hostname, or URL.
576
+ Tries each format in order and returns the first match.
577
+
578
+ Args:
579
+ target: Target string (IP, CIDR, hostname, or URL)
580
+
581
+ Returns:
582
+ Validated target string
583
+
584
+ Raises:
585
+ ValidationError: If target is not valid in any format
586
+ """
587
+ if not target or not target.strip():
588
+ raise ValidationError("Target cannot be empty")
589
+
590
+ target = target.strip()
591
+
592
+ # Try URL first (if it has a scheme)
593
+ if target.startswith(("http://", "https://")):
594
+ try:
595
+ return validate_url(target)
596
+ except ValidationError:
597
+ pass
598
+
599
+ # Try IP address
600
+ try:
601
+ return validate_ip_address(target)
602
+ except ValidationError:
603
+ pass
604
+
605
+ # Try CIDR notation
606
+ try:
607
+ return validate_cidr(target)
608
+ except ValidationError:
609
+ pass
610
+
611
+ # Try hostname (more lenient pattern for domains)
612
+ # Allow single-part hostnames and domains
613
+ hostname_pattern = re.compile(
614
+ r"^(?!-)[A-Za-z0-9-]{1,63}(?<!-)(\.[A-Za-z0-9-]{1,63})*\.?$"
615
+ )
616
+ if hostname_pattern.match(target) and len(target) <= 253:
617
+ return target
618
+
619
+ # Nothing matched
620
+ raise ValidationError(
621
+ f"Invalid target: '{target}'. Must be IP address, CIDR notation, hostname, or URL"
622
+ )