souleyez 2.43.26__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.

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 +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 +23434 -10286
  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.26.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.26.dist-info → souleyez-2.43.34.dist-info}/WHEEL +1 -1
  355. souleyez-2.43.26.dist-info/RECORD +0 -379
  356. {souleyez-2.43.26.dist-info → souleyez-2.43.34.dist-info}/entry_points.txt +0 -0
  357. {souleyez-2.43.26.dist-info → souleyez-2.43.34.dist-info}/licenses/LICENSE +0 -0
  358. {souleyez-2.43.26.dist-info → souleyez-2.43.34.dist-info}/top_level.txt +0 -0
@@ -31,12 +31,12 @@ HELP = {
31
31
  "- Respect rate limits and the target's rules of engagement — brute forcing can trigger alerts.\n"
32
32
  "- Save findings (responses, status codes, and URLs) to the job log so you can convert them into Findings or follow-up tasks.\n"
33
33
  ),
34
- "usage": "souleyez jobs enqueue gobuster <target> --args \"dir -u <url> -w <wordlist> -t <threads>\"",
34
+ "usage": 'souleyez jobs enqueue gobuster <target> --args "dir -u <url> -w <wordlist> -t <threads>"',
35
35
  "examples": [
36
- "souleyez jobs enqueue gobuster http://example.com --args \"dir -u http://example.com -w data/wordlists/web_dirs_common.txt -t 10\"",
37
- "souleyez jobs enqueue gobuster http://example.com --args \"dir -u http://example.com -w data/wordlists/web_dirs_common.txt -x php,txt,html -t 20\"",
38
- "souleyez jobs enqueue gobuster example.com --args \"dns --domain example.com -w data/wordlists/subdomains_common.txt -t 15 --timeout 3s\"",
39
- "souleyez jobs enqueue gobuster http://example.com --args \"vhost -u http://example.com -w data/wordlists/subdomains_common.txt -t 50\"",
36
+ 'souleyez jobs enqueue gobuster http://example.com --args "dir -u http://example.com -w data/wordlists/web_dirs_common.txt -t 10"',
37
+ 'souleyez jobs enqueue gobuster http://example.com --args "dir -u http://example.com -w data/wordlists/web_dirs_common.txt -x php,txt,html -t 20"',
38
+ 'souleyez jobs enqueue gobuster example.com --args "dns --domain example.com -w data/wordlists/subdomains_common.txt -t 15 --timeout 3s"',
39
+ 'souleyez jobs enqueue gobuster http://example.com --args "vhost -u http://example.com -w data/wordlists/subdomains_common.txt -t 50"',
40
40
  ],
41
41
  "flags": [
42
42
  ["dir", "Directory/file enumeration mode"],
@@ -46,7 +46,10 @@ HELP = {
46
46
  ["--domain <domain>", "Target domain (dns mode)"],
47
47
  ["-w <wordlist>", "Wordlist path"],
48
48
  ["-t <threads>", "Number of threads"],
49
- ["--timeout <duration>", "DNS timeout per request (e.g., 3s, 5s) - useful to avoid DNS timeouts"],
49
+ [
50
+ "--timeout <duration>",
51
+ "DNS timeout per request (e.g., 3s, 5s) - useful to avoid DNS timeouts",
52
+ ],
50
53
  ["-x <extensions>", "File extensions to check (comma-separated)"],
51
54
  ["-b <codes>", "Status codes to blacklist"],
52
55
  ["--wildcard", "Force continued operation when wildcard found"],
@@ -55,96 +58,248 @@ HELP = {
55
58
  "directory_enum": [
56
59
  {
57
60
  "name": "Quick Scan",
58
- "args": ["dir", "-u", "<target>", "-w", "data/wordlists/web_dirs_common.txt", "-t", "10"],
59
- "desc": "Common wordlist (87 entries)"
61
+ "args": [
62
+ "dir",
63
+ "-u",
64
+ "<target>",
65
+ "-w",
66
+ "data/wordlists/web_dirs_common.txt",
67
+ "-t",
68
+ "10",
69
+ ],
70
+ "desc": "Common wordlist (87 entries)",
60
71
  },
61
72
  {
62
73
  "name": "Standard Scan",
63
- "args": ["dir", "-u", "<target>", "-w", "data/wordlists/web_dirs_extended.txt", "-t", "20"],
64
- "desc": "Extended wordlist (more coverage)"
74
+ "args": [
75
+ "dir",
76
+ "-u",
77
+ "<target>",
78
+ "-w",
79
+ "data/wordlists/web_dirs_extended.txt",
80
+ "-t",
81
+ "20",
82
+ ],
83
+ "desc": "Extended wordlist (more coverage)",
65
84
  },
66
85
  {
67
86
  "name": "Common Files (HTML/PHP/TXT)",
68
- "args": ["dir", "-u", "<target>", "-w", "data/wordlists/web_dirs_common.txt", "-x", "html,htm,php,txt,xml,json", "-t", "15"],
69
- "desc": "Common paths + web file extensions"
87
+ "args": [
88
+ "dir",
89
+ "-u",
90
+ "<target>",
91
+ "-w",
92
+ "data/wordlists/web_dirs_common.txt",
93
+ "-x",
94
+ "html,htm,php,txt,xml,json",
95
+ "-t",
96
+ "15",
97
+ ],
98
+ "desc": "Common paths + web file extensions",
70
99
  },
71
100
  {
72
101
  "name": "PHP Extensions",
73
- "args": ["dir", "-u", "<target>", "-w", "data/wordlists/web_dirs_common.txt", "-x", "php,phps,php3,php4,php5,phtml", "-t", "15"],
74
- "desc": "Common paths + PHP variants"
75
- }
102
+ "args": [
103
+ "dir",
104
+ "-u",
105
+ "<target>",
106
+ "-w",
107
+ "data/wordlists/web_dirs_common.txt",
108
+ "-x",
109
+ "php,phps,php3,php4,php5,phtml",
110
+ "-t",
111
+ "15",
112
+ ],
113
+ "desc": "Common paths + PHP variants",
114
+ },
76
115
  ],
77
116
  "subdomain_enum": [
78
117
  {
79
118
  "name": "Subdomain Scan (manual domain)",
80
- "args": ["dns", "--domain", "example.com", "-w", "data/wordlists/subdomains_common.txt", "-t", "15", "--timeout", "3s"],
81
- "desc": "Subdomain enumeration - EDIT example.com to your domain"
119
+ "args": [
120
+ "dns",
121
+ "--domain",
122
+ "example.com",
123
+ "-w",
124
+ "data/wordlists/subdomains_common.txt",
125
+ "-t",
126
+ "15",
127
+ "--timeout",
128
+ "3s",
129
+ ],
130
+ "desc": "Subdomain enumeration - EDIT example.com to your domain",
82
131
  }
83
132
  ],
84
133
  "vhost_discovery": [
85
134
  {
86
135
  "name": "Virtual Hosts",
87
- "args": ["vhost", "-u", "<target>", "-w", "data/wordlists/subdomains_common.txt", "-t", "50"],
88
- "desc": "Virtual host discovery"
136
+ "args": [
137
+ "vhost",
138
+ "-u",
139
+ "<target>",
140
+ "-w",
141
+ "data/wordlists/subdomains_common.txt",
142
+ "-t",
143
+ "50",
144
+ ],
145
+ "desc": "Virtual host discovery",
89
146
  }
90
- ]
147
+ ],
91
148
  },
92
149
  "presets": [
93
150
  # Flattened list for backward compatibility
94
- {"name": "Quick Scan", "args": ["dir", "-u", "<target>", "-w", "data/wordlists/web_dirs_common.txt", "-t", "10"], "desc": "Common wordlist (87 entries)"},
95
- {"name": "Standard Scan", "args": ["dir", "-u", "<target>", "-w", "data/wordlists/web_dirs_extended.txt", "-t", "20"], "desc": "Extended wordlist (more coverage)"},
96
- {"name": "Common Files (HTML/PHP/TXT)", "args": ["dir", "-u", "<target>", "-w", "data/wordlists/web_dirs_common.txt", "-x", "html,htm,php,txt,xml,json", "-t", "15"], "desc": "Common paths + web file extensions"},
97
- {"name": "PHP Extensions", "args": ["dir", "-u", "<target>", "-w", "data/wordlists/web_dirs_common.txt", "-x", "php,phps,php3,php4,php5,phtml", "-t", "15"], "desc": "Common paths + PHP variants"},
98
- {"name": "Subdomain Scan (manual domain)", "args": ["dns", "--domain", "example.com", "-w", "data/wordlists/subdomains_common.txt", "-t", "15", "--timeout", "3s"], "desc": "Subdomain enumeration - EDIT example.com to your domain"},
99
- {"name": "Virtual Hosts", "args": ["vhost", "-u", "<target>", "-w", "data/wordlists/subdomains_common.txt", "-t", "50"], "desc": "Virtual host discovery"}
151
+ {
152
+ "name": "Quick Scan",
153
+ "args": [
154
+ "dir",
155
+ "-u",
156
+ "<target>",
157
+ "-w",
158
+ "data/wordlists/web_dirs_common.txt",
159
+ "-t",
160
+ "10",
161
+ ],
162
+ "desc": "Common wordlist (87 entries)",
163
+ },
164
+ {
165
+ "name": "Standard Scan",
166
+ "args": [
167
+ "dir",
168
+ "-u",
169
+ "<target>",
170
+ "-w",
171
+ "data/wordlists/web_dirs_extended.txt",
172
+ "-t",
173
+ "20",
174
+ ],
175
+ "desc": "Extended wordlist (more coverage)",
176
+ },
177
+ {
178
+ "name": "Common Files (HTML/PHP/TXT)",
179
+ "args": [
180
+ "dir",
181
+ "-u",
182
+ "<target>",
183
+ "-w",
184
+ "data/wordlists/web_dirs_common.txt",
185
+ "-x",
186
+ "html,htm,php,txt,xml,json",
187
+ "-t",
188
+ "15",
189
+ ],
190
+ "desc": "Common paths + web file extensions",
191
+ },
192
+ {
193
+ "name": "PHP Extensions",
194
+ "args": [
195
+ "dir",
196
+ "-u",
197
+ "<target>",
198
+ "-w",
199
+ "data/wordlists/web_dirs_common.txt",
200
+ "-x",
201
+ "php,phps,php3,php4,php5,phtml",
202
+ "-t",
203
+ "15",
204
+ ],
205
+ "desc": "Common paths + PHP variants",
206
+ },
207
+ {
208
+ "name": "Subdomain Scan (manual domain)",
209
+ "args": [
210
+ "dns",
211
+ "--domain",
212
+ "example.com",
213
+ "-w",
214
+ "data/wordlists/subdomains_common.txt",
215
+ "-t",
216
+ "15",
217
+ "--timeout",
218
+ "3s",
219
+ ],
220
+ "desc": "Subdomain enumeration - EDIT example.com to your domain",
221
+ },
222
+ {
223
+ "name": "Virtual Hosts",
224
+ "args": [
225
+ "vhost",
226
+ "-u",
227
+ "<target>",
228
+ "-w",
229
+ "data/wordlists/subdomains_common.txt",
230
+ "-t",
231
+ "50",
232
+ ],
233
+ "desc": "Virtual host discovery",
234
+ },
100
235
  ],
101
236
  "help_sections": [
102
237
  {
103
238
  "title": "What is Gobuster?",
104
239
  "color": "cyan",
105
240
  "content": [
106
- {"title": "Overview", "desc": "Gobuster is a fast directory, file, and DNS/vhost brute-forcing tool perfect for discovering hidden web content and subdomains."},
107
- {"title": "Use Cases", "desc": "Essential for web application reconnaissance", "tips": [
108
- "Find hidden directories and files (admin panels, config files)",
109
- "Discover DNS subdomains",
110
- "Enumerate virtual hosts",
111
- "Identify forgotten endpoints and backups"
112
- ]}
113
- ]
241
+ {
242
+ "title": "Overview",
243
+ "desc": "Gobuster is a fast directory, file, and DNS/vhost brute-forcing tool perfect for discovering hidden web content and subdomains.",
244
+ },
245
+ {
246
+ "title": "Use Cases",
247
+ "desc": "Essential for web application reconnaissance",
248
+ "tips": [
249
+ "Find hidden directories and files (admin panels, config files)",
250
+ "Discover DNS subdomains",
251
+ "Enumerate virtual hosts",
252
+ "Identify forgotten endpoints and backups",
253
+ ],
254
+ },
255
+ ],
114
256
  },
115
257
  {
116
258
  "title": "How to Use",
117
259
  "color": "green",
118
260
  "content": [
119
- {"title": "Basic Workflow", "desc": "1. Start with common wordlist for quick wins\n 2. Use file extensions (-x) for targeted discovery\n 3. Expand to larger wordlists if needed\n 4. Follow up discovered paths with manual testing"},
120
- {"title": "Modes & Features", "desc": "Three main scanning modes", "tips": [
121
- "dir: Directory/file enumeration on web servers",
122
- "dns: Subdomain brute-forcing",
123
- "vhost: Virtual host discovery",
124
- "Use -b to blacklist status codes (403, 404)"
125
- ]}
126
- ]
261
+ {
262
+ "title": "Basic Workflow",
263
+ "desc": "1. Start with common wordlist for quick wins\n 2. Use file extensions (-x) for targeted discovery\n 3. Expand to larger wordlists if needed\n 4. Follow up discovered paths with manual testing",
264
+ },
265
+ {
266
+ "title": "Modes & Features",
267
+ "desc": "Three main scanning modes",
268
+ "tips": [
269
+ "dir: Directory/file enumeration on web servers",
270
+ "dns: Subdomain brute-forcing",
271
+ "vhost: Virtual host discovery",
272
+ "Use -b to blacklist status codes (403, 404)",
273
+ ],
274
+ },
275
+ ],
127
276
  },
128
277
  {
129
278
  "title": "Tips & Best Practices",
130
279
  "color": "yellow",
131
280
  "content": [
132
- ("Best Practices:", [
133
- "Start with small wordlists, expand as needed",
134
- "Use -x for file extensions (php,txt,html)",
135
- "Respect rate limits to avoid overwhelming targets",
136
- "Save results and convert findings to job log",
137
- "Combine with Nmap service scans for context"
138
- ]),
139
- ("Common Issues:", [
140
- "Wildcard responses: Use -b to filter status codes",
141
- "Too many results: Filter by size (-fs) or words (-fw)",
142
- "DNS timeouts: Add --timeout 3s for DNS mode",
143
- "No results: Try different wordlists or extensions"
144
- ])
145
- ]
146
- }
147
- ]
281
+ (
282
+ "Best Practices:",
283
+ [
284
+ "Start with small wordlists, expand as needed",
285
+ "Use -x for file extensions (php,txt,html)",
286
+ "Respect rate limits to avoid overwhelming targets",
287
+ "Save results and convert findings to job log",
288
+ "Combine with Nmap service scans for context",
289
+ ],
290
+ ),
291
+ (
292
+ "Common Issues:",
293
+ [
294
+ "Wildcard responses: Use -b to filter status codes",
295
+ "Too many results: Filter by size (-fs) or words (-fw)",
296
+ "DNS timeouts: Add --timeout 3s for DNS mode",
297
+ "No results: Try different wordlists or extensions",
298
+ ],
299
+ ),
300
+ ],
301
+ },
302
+ ],
148
303
  }
149
304
 
150
305
 
@@ -167,18 +322,17 @@ class GobusterPlugin(PluginBase):
167
322
  try:
168
323
  # Use -v flag (not 'version' subcommand) - works on v3.x
169
324
  result = subprocess.run(
170
- ["gobuster", "-v"],
171
- capture_output=True,
172
- text=True,
173
- timeout=10
325
+ ["gobuster", "-v"], capture_output=True, text=True, timeout=10
174
326
  )
175
327
  output = result.stdout + result.stderr
176
328
 
177
329
  # Parse version from output like "gobuster version 3.8.2"
178
- version_match = re.search(r'version\s+(\d+\.\d+\.\d+)', output, re.IGNORECASE)
330
+ version_match = re.search(
331
+ r"version\s+(\d+\.\d+\.\d+)", output, re.IGNORECASE
332
+ )
179
333
  if version_match:
180
334
  version = version_match.group(1)
181
- major = int(version.split('.')[0])
335
+ major = int(version.split(".")[0])
182
336
  if major >= 3:
183
337
  return (True, version, None)
184
338
  else:
@@ -186,16 +340,15 @@ class GobusterPlugin(PluginBase):
186
340
 
187
341
  # Also try --version flag as fallback
188
342
  result2 = subprocess.run(
189
- ["gobuster", "--version"],
190
- capture_output=True,
191
- text=True,
192
- timeout=10
343
+ ["gobuster", "--version"], capture_output=True, text=True, timeout=10
193
344
  )
194
345
  output2 = result2.stdout + result2.stderr
195
- version_match2 = re.search(r'version\s+(\d+\.\d+\.\d+)', output2, re.IGNORECASE)
346
+ version_match2 = re.search(
347
+ r"version\s+(\d+\.\d+\.\d+)", output2, re.IGNORECASE
348
+ )
196
349
  if version_match2:
197
350
  version = version_match2.group(1)
198
- major = int(version.split('.')[0])
351
+ major = int(version.split(".")[0])
199
352
  if major >= 3:
200
353
  return (True, version, None)
201
354
  else:
@@ -210,7 +363,11 @@ class GobusterPlugin(PluginBase):
210
363
  return (True, "3.x", None)
211
364
 
212
365
  except FileNotFoundError:
213
- return (False, None, "ERROR: gobuster not found. Install with: sudo apt install gobuster")
366
+ return (
367
+ False,
368
+ None,
369
+ "ERROR: gobuster not found. Install with: sudo apt install gobuster",
370
+ )
214
371
  except subprocess.TimeoutExpired:
215
372
  return (True, "unknown", None) # Assume it works
216
373
  except Exception as e:
@@ -231,7 +388,9 @@ class GobusterPlugin(PluginBase):
231
388
  f"After upgrading, verify with: gobuster version\n"
232
389
  )
233
390
 
234
- def _preflight_check(self, base_url: str, timeout: float = 5.0, log_path: str = None) -> Dict[str, Optional[str]]:
391
+ def _preflight_check(
392
+ self, base_url: str, timeout: float = 5.0, log_path: str = None
393
+ ) -> Dict[str, Optional[str]]:
235
394
  """
236
395
  Probe target with random UUID path to detect false positive responses.
237
396
 
@@ -248,7 +407,12 @@ class GobusterPlugin(PluginBase):
248
407
  - reason: str or None (explanation)
249
408
  - redirect_host: str or None (suggested target if host redirect detected)
250
409
  """
251
- result = {'exclude_length': None, 'exclude_status': None, 'reason': None, 'redirect_host': None}
410
+ result = {
411
+ "exclude_length": None,
412
+ "exclude_status": None,
413
+ "reason": None,
414
+ "redirect_host": None,
415
+ }
252
416
 
253
417
  # Generate random UUID path that definitely doesn't exist
254
418
  test_path = str(uuid.uuid4())
@@ -263,7 +427,7 @@ class GobusterPlugin(PluginBase):
263
427
  test_url,
264
428
  timeout=timeout,
265
429
  allow_redirects=False,
266
- headers={'User-Agent': 'gobuster/3.6'} # Match gobuster's UA
430
+ headers={"User-Agent": "gobuster/3.6"}, # Match gobuster's UA
267
431
  )
268
432
 
269
433
  # 404 is expected for non-existent paths - no action needed
@@ -272,7 +436,7 @@ class GobusterPlugin(PluginBase):
272
436
 
273
437
  # Check for host-level redirects (301/302/307/308)
274
438
  if resp.status_code in [301, 302, 307, 308]:
275
- location = resp.headers.get('Location', '')
439
+ location = resp.headers.get("Location", "")
276
440
  if location:
277
441
  # Parse the redirect location
278
442
  redirect_parsed = urlparse(location)
@@ -282,24 +446,30 @@ class GobusterPlugin(PluginBase):
282
446
  if redirect_host and redirect_host != original_host:
283
447
  # This is a host-level redirect (e.g., non-www to www)
284
448
  suggested_url = f"{redirect_parsed.scheme or original_parsed.scheme}://{redirect_host}"
285
- result['redirect_host'] = suggested_url
286
- result['exclude_status'] = str(resp.status_code)
287
- result['reason'] = (
449
+ result["redirect_host"] = suggested_url
450
+ result["exclude_status"] = str(resp.status_code)
451
+ result["reason"] = (
288
452
  f"Host redirect detected: {original_host} → {redirect_host}"
289
453
  )
290
454
 
291
455
  if log_path:
292
- with open(log_path, 'a') as f:
456
+ with open(log_path, "a") as f:
293
457
  f.write(f"\n{'=' * 70}\n")
294
458
  f.write("⚠️ HOST-LEVEL REDIRECT DETECTED\n")
295
459
  f.write(f"{'=' * 70}\n")
296
460
  f.write(f"Target: {base_url}\n")
297
461
  f.write(f"Redirects to: {redirect_host}\n")
298
462
  f.write(f"Status: {resp.status_code}\n\n")
299
- f.write("The server redirects ALL requests to a different host.\n")
463
+ f.write(
464
+ "The server redirects ALL requests to a different host.\n"
465
+ )
300
466
  f.write("This causes unreliable results because:\n")
301
- f.write(" - Response sizes vary based on path length in redirect URL\n")
302
- f.write(" - Gobuster may report false positives or miss real paths\n\n")
467
+ f.write(
468
+ " - Response sizes vary based on path length in redirect URL\n"
469
+ )
470
+ f.write(
471
+ " - Gobuster may report false positives or miss real paths\n\n"
472
+ )
303
473
  # Parseable marker for auto-retry
304
474
  f.write(f"HOST_REDIRECT_TARGET: {suggested_url}\n\n")
305
475
  f.write("Auto-retrying with corrected target...\n")
@@ -307,27 +477,29 @@ class GobusterPlugin(PluginBase):
307
477
 
308
478
  # Still try to exclude the response length for this scan
309
479
  content_length = len(resp.content)
310
- result['exclude_length'] = str(content_length)
480
+ result["exclude_length"] = str(content_length)
311
481
  return result
312
482
 
313
483
  # Any other status for a random UUID = false positive indicator
314
484
  # Common: 403 (blocked), 401 (auth required), 200 (catch-all), 500 (error page)
315
485
  if resp.status_code in [200, 301, 302, 400, 401, 403, 500, 503]:
316
486
  content_length = len(resp.content)
317
- result['exclude_length'] = str(content_length)
318
- result['exclude_status'] = str(resp.status_code)
319
- result['reason'] = (
487
+ result["exclude_length"] = str(content_length)
488
+ result["exclude_status"] = str(resp.status_code)
489
+ result["reason"] = (
320
490
  f"Pre-flight: Random path returned {resp.status_code} "
321
491
  f"({content_length} bytes) - auto-excluding"
322
492
  )
323
493
 
324
494
  if log_path:
325
- with open(log_path, 'a') as f:
495
+ with open(log_path, "a") as f:
326
496
  f.write(f"\n{'=' * 60}\n")
327
497
  f.write("PRE-FLIGHT CHECK\n")
328
498
  f.write(f"{'=' * 60}\n")
329
499
  f.write(f"Tested: {test_url}\n")
330
- f.write(f"Result: {resp.status_code} ({content_length} bytes)\n")
500
+ f.write(
501
+ f"Result: {resp.status_code} ({content_length} bytes)\n"
502
+ )
331
503
  f.write(f"Action: Adding --exclude-length {content_length}\n")
332
504
  f.write(f"{'=' * 60}\n\n")
333
505
 
@@ -336,7 +508,7 @@ class GobusterPlugin(PluginBase):
336
508
  except requests.exceptions.Timeout:
337
509
  # Target too slow, skip preflight
338
510
  if log_path:
339
- with open(log_path, 'a') as f:
511
+ with open(log_path, "a") as f:
340
512
  f.write("Pre-flight: Target timeout, skipping check\n")
341
513
  return result
342
514
  except requests.exceptions.ConnectionError:
@@ -346,7 +518,9 @@ class GobusterPlugin(PluginBase):
346
518
  # Any other error, continue without exclusions
347
519
  return result
348
520
 
349
- def build_command(self, target: str, args: List[str] = None, label: str = "", log_path: str = None):
521
+ def build_command(
522
+ self, target: str, args: List[str] = None, label: str = "", log_path: str = None
523
+ ):
350
524
  """Build gobuster command for background execution with PID tracking."""
351
525
  args = args or []
352
526
 
@@ -354,55 +528,57 @@ class GobusterPlugin(PluginBase):
354
528
  meets_req, version, error_msg = self._check_version()
355
529
  if not meets_req:
356
530
  if log_path:
357
- with open(log_path, 'w') as f:
531
+ with open(log_path, "w") as f:
358
532
  f.write(error_msg)
359
533
  return None
360
534
 
361
535
  # Detect the mode from args
362
536
  mode = None
363
- if 'dir' in args:
364
- mode = 'dir'
365
- elif 'dns' in args:
366
- mode = 'dns'
367
- elif 'vhost' in args:
368
- mode = 'vhost'
369
-
537
+ if "dir" in args:
538
+ mode = "dir"
539
+ elif "dns" in args:
540
+ mode = "dns"
541
+ elif "vhost" in args:
542
+ mode = "vhost"
543
+
370
544
  # Validate target and mode compatibility (same validation as run())
371
- if mode == 'dns':
372
- if target.startswith(('http://', 'https://')):
545
+ if mode == "dns":
546
+ if target.startswith(("http://", "https://")):
373
547
  if log_path:
374
- with open(log_path, 'w') as f:
548
+ with open(log_path, "w") as f:
375
549
  f.write("ERROR: DNS mode requires a domain name, not a URL\n")
376
550
  return None
377
-
378
- ip_pattern = re.compile(r'^(\d{1,3}\.){3}\d{1,3}$')
551
+
552
+ ip_pattern = re.compile(r"^(\d{1,3}\.){3}\d{1,3}$")
379
553
  if ip_pattern.match(target):
380
554
  if log_path:
381
- with open(log_path, 'w') as f:
382
- f.write("ERROR: DNS mode requires a domain name, not an IP address\n")
555
+ with open(log_path, "w") as f:
556
+ f.write(
557
+ "ERROR: DNS mode requires a domain name, not an IP address\n"
558
+ )
383
559
  return None
384
-
385
- elif mode in ['dir', 'vhost']:
386
- if not target.startswith(('http://', 'https://')):
560
+
561
+ elif mode in ["dir", "vhost"]:
562
+ if not target.startswith(("http://", "https://")):
387
563
  target = f"http://{target}"
388
-
564
+
389
565
  try:
390
566
  target = validate_url(target)
391
567
  except ValidationError as e:
392
568
  if log_path:
393
- with open(log_path, 'w') as f:
569
+ with open(log_path, "w") as f:
394
570
  f.write(f"ERROR: Invalid URL: {e}\n")
395
571
  return None
396
-
572
+
397
573
  else:
398
- if not target.startswith(('http://', 'https://')):
574
+ if not target.startswith(("http://", "https://")):
399
575
  target = f"http://{target}"
400
-
576
+
401
577
  try:
402
578
  target = validate_url(target)
403
579
  except ValidationError as e:
404
580
  if log_path:
405
- with open(log_path, 'w') as f:
581
+ with open(log_path, "w") as f:
406
582
  f.write(f"ERROR: Invalid URL: {e}\n")
407
583
  return None
408
584
 
@@ -410,11 +586,11 @@ class GobusterPlugin(PluginBase):
410
586
 
411
587
  # Pre-flight check for dir/vhost modes: detect false positive responses
412
588
  # This prevents gobuster from failing on servers that return 403/401/200 for all paths
413
- if mode in ['dir', 'vhost', None]: # None = default to dir behavior
589
+ if mode in ["dir", "vhost", None]: # None = default to dir behavior
414
590
  # Extract base URL from -u argument or use target
415
591
  base_url = target
416
592
  for i, arg in enumerate(processed_args):
417
- if arg == '-u' and i + 1 < len(processed_args):
593
+ if arg == "-u" and i + 1 < len(processed_args):
418
594
  base_url = processed_args[i + 1]
419
595
  break
420
596
 
@@ -423,49 +599,54 @@ class GobusterPlugin(PluginBase):
423
599
 
424
600
  # If host redirect detected, abort scan immediately
425
601
  # Don't waste time running on wrong target - result_handler will spawn retry
426
- if preflight.get('redirect_host'):
602
+ if preflight.get("redirect_host"):
427
603
  if log_path:
428
- with open(log_path, 'a') as f:
604
+ with open(log_path, "a") as f:
429
605
  f.write("\n=== SCAN ABORTED ===\n")
430
- f.write("Host redirect detected. Aborting to avoid wasted scan time.\n")
431
- f.write("A retry job will be auto-queued with the correct target.\n")
432
- f.write(f"=== Completed: {time.strftime('%Y-%m-%d %H:%M:%S UTC', time.gmtime())} ===\n")
606
+ f.write(
607
+ "Host redirect detected. Aborting to avoid wasted scan time.\n"
608
+ )
609
+ f.write(
610
+ "A retry job will be auto-queued with the correct target.\n"
611
+ )
612
+ f.write(
613
+ f"=== Completed: {time.strftime('%Y-%m-%d %H:%M:%S UTC', time.gmtime())} ===\n"
614
+ )
433
615
  return None # Abort - background.py will check log for HOST_REDIRECT_TARGET
434
616
 
435
- if preflight['exclude_length']:
617
+ if preflight["exclude_length"]:
436
618
  # Collect existing exclusions
437
619
  existing_excludes = set()
438
620
  exclude_idx = None
439
621
  for i, arg in enumerate(processed_args):
440
- if arg == '--exclude-length' and i + 1 < len(processed_args):
622
+ if arg == "--exclude-length" and i + 1 < len(processed_args):
441
623
  exclude_idx = i
442
- existing_excludes.update(processed_args[i + 1].split(','))
624
+ existing_excludes.update(processed_args[i + 1].split(","))
443
625
 
444
626
  # Add detected length if not already excluded
445
- if preflight['exclude_length'] not in existing_excludes:
446
- existing_excludes.add(preflight['exclude_length'])
447
- merged = ','.join(sorted(existing_excludes))
627
+ if preflight["exclude_length"] not in existing_excludes:
628
+ existing_excludes.add(preflight["exclude_length"])
629
+ merged = ",".join(sorted(existing_excludes))
448
630
 
449
631
  if exclude_idx is not None:
450
632
  # Update existing --exclude-length value
451
633
  processed_args[exclude_idx + 1] = merged
452
634
  else:
453
635
  # Add new --exclude-length
454
- processed_args.extend(['--exclude-length', merged])
636
+ processed_args.extend(["--exclude-length", merged])
455
637
 
456
638
  # Add --no-progress to suppress verbose progress output (gobuster v3.6+)
457
639
  # This prevents thousands of "Progress: X / Y" lines in output
458
- if '--no-progress' not in processed_args:
459
- processed_args.append('--no-progress')
640
+ if "--no-progress" not in processed_args:
641
+ processed_args.append("--no-progress")
460
642
 
461
643
  cmd = ["gobuster"] + processed_args
462
644
 
463
- return {
464
- 'cmd': cmd,
465
- 'timeout': 1800 # 30 minutes
466
- }
645
+ return {"cmd": cmd, "timeout": 1800} # 30 minutes
467
646
 
468
- def run(self, target: str, args: List[str] = None, label: str = "", log_path: str = None) -> int:
647
+ def run(
648
+ self, target: str, args: List[str] = None, label: str = "", log_path: str = None
649
+ ) -> int:
469
650
  """Execute gobuster scan and write output to log_path."""
470
651
 
471
652
  args = args or []
@@ -474,23 +655,23 @@ class GobusterPlugin(PluginBase):
474
655
  meets_req, version, error_msg = self._check_version()
475
656
  if not meets_req:
476
657
  if log_path:
477
- with open(log_path, 'w') as f:
658
+ with open(log_path, "w") as f:
478
659
  f.write(error_msg)
479
660
  return 1
480
661
 
481
662
  # Detect the mode from args
482
663
  mode = None
483
- if 'dir' in args:
484
- mode = 'dir'
485
- elif 'dns' in args:
486
- mode = 'dns'
487
- elif 'vhost' in args:
488
- mode = 'vhost'
489
-
664
+ if "dir" in args:
665
+ mode = "dir"
666
+ elif "dns" in args:
667
+ mode = "dns"
668
+ elif "vhost" in args:
669
+ mode = "vhost"
670
+
490
671
  # Validate target and mode compatibility
491
- if mode == 'dns':
672
+ if mode == "dns":
492
673
  # DNS mode requires a domain name, not an IP or URL
493
- if target.startswith(('http://', 'https://')):
674
+ if target.startswith(("http://", "https://")):
494
675
  error_msg = (
495
676
  "ERROR: DNS mode requires a domain name, not a URL.\n\n"
496
677
  f"You provided: {target}\n"
@@ -498,19 +679,19 @@ class GobusterPlugin(PluginBase):
498
679
  "FIXES:\n"
499
680
  "1. For DNS enumeration:\n"
500
681
  " - Use: souleyez jobs enqueue gobuster <domain>\n"
501
- " - Example: souleyez jobs enqueue gobuster vulnweb.com --args \"dns --domain vulnweb.com -w <wordlist>\"\n\n"
682
+ ' - Example: souleyez jobs enqueue gobuster vulnweb.com --args "dns --domain vulnweb.com -w <wordlist>"\n\n'
502
683
  "2. For directory enumeration of an IP/URL:\n"
503
- " - Use: souleyez jobs enqueue gobuster <ip-or-url> --args \"dir -u http://<ip> -w <wordlist>\"\n"
504
- " - Example: souleyez jobs enqueue gobuster 44.228.249.3 --args \"dir -u http://44.228.249.3 -w <wordlist>\"\n"
684
+ ' - Use: souleyez jobs enqueue gobuster <ip-or-url> --args "dir -u http://<ip> -w <wordlist>"\n'
685
+ ' - Example: souleyez jobs enqueue gobuster 44.228.249.3 --args "dir -u http://44.228.249.3 -w <wordlist>"\n'
505
686
  )
506
687
  if log_path:
507
- with open(log_path, 'w') as f:
688
+ with open(log_path, "w") as f:
508
689
  f.write(error_msg)
509
690
  return 1
510
691
  raise ValueError(error_msg)
511
-
692
+
512
693
  # Check if target is an IP address
513
- ip_pattern = re.compile(r'^(\d{1,3}\.){3}\d{1,3}$')
694
+ ip_pattern = re.compile(r"^(\d{1,3}\.){3}\d{1,3}$")
514
695
  if ip_pattern.match(target):
515
696
  error_msg = (
516
697
  f"ERROR: DNS mode requires a domain name, not an IP address.\n\n"
@@ -519,41 +700,41 @@ class GobusterPlugin(PluginBase):
519
700
  "IP addresses don't have subdomains.\n\n"
520
701
  "FIXES:\n"
521
702
  "1. For directory enumeration of the IP:\n"
522
- f" souleyez jobs enqueue gobuster {target} --args \"dir -u http://{target} -w <wordlist>\"\n\n"
703
+ f' souleyez jobs enqueue gobuster {target} --args "dir -u http://{target} -w <wordlist>"\n\n'
523
704
  "2. For DNS enumeration (if you have a domain):\n"
524
- " souleyez jobs enqueue gobuster example.com --args \"dns --domain example.com -w <wordlist>\"\n"
705
+ ' souleyez jobs enqueue gobuster example.com --args "dns --domain example.com -w <wordlist>"\n'
525
706
  )
526
707
  if log_path:
527
- with open(log_path, 'w') as f:
708
+ with open(log_path, "w") as f:
528
709
  f.write(error_msg)
529
710
  return 1
530
711
  raise ValueError(error_msg)
531
-
532
- elif mode in ['dir', 'vhost']:
712
+
713
+ elif mode in ["dir", "vhost"]:
533
714
  # Dir/vhost modes need a URL with protocol
534
- if not target.startswith(('http://', 'https://')):
715
+ if not target.startswith(("http://", "https://")):
535
716
  target = f"http://{target}"
536
-
717
+
537
718
  # Validate URL
538
719
  try:
539
720
  target = validate_url(target)
540
721
  except ValidationError as e:
541
722
  if log_path:
542
- with open(log_path, 'w') as f:
723
+ with open(log_path, "w") as f:
543
724
  f.write(f"ERROR: Invalid URL: {e}\n")
544
725
  return 1
545
726
  raise ValueError(f"Invalid URL: {e}")
546
-
727
+
547
728
  else:
548
729
  # No mode detected, try to handle as URL
549
- if not target.startswith(('http://', 'https://')):
730
+ if not target.startswith(("http://", "https://")):
550
731
  target = f"http://{target}"
551
-
732
+
552
733
  try:
553
734
  target = validate_url(target)
554
735
  except ValidationError as e:
555
736
  if log_path:
556
- with open(log_path, 'w') as f:
737
+ with open(log_path, "w") as f:
557
738
  f.write(f"ERROR: Invalid URL: {e}\n")
558
739
  return 1
559
740
  raise ValueError(f"Invalid URL: {e}")
@@ -561,42 +742,44 @@ class GobusterPlugin(PluginBase):
561
742
  processed_args = [arg.replace("<target>", target) for arg in args]
562
743
 
563
744
  # Pre-flight check for dir/vhost modes: detect false positive responses
564
- if mode in ['dir', 'vhost', None]:
745
+ if mode in ["dir", "vhost", None]:
565
746
  base_url = target
566
747
  for i, arg in enumerate(processed_args):
567
- if arg == '-u' and i + 1 < len(processed_args):
748
+ if arg == "-u" and i + 1 < len(processed_args):
568
749
  base_url = processed_args[i + 1]
569
750
  break
570
751
 
571
752
  # Always run preflight - merge detected length with any existing exclusions
572
753
  preflight = self._preflight_check(base_url, timeout=5.0, log_path=log_path)
573
- if preflight['exclude_length']:
754
+ if preflight["exclude_length"]:
574
755
  existing_excludes = set()
575
756
  exclude_idx = None
576
757
  for i, arg in enumerate(processed_args):
577
- if arg == '--exclude-length' and i + 1 < len(processed_args):
758
+ if arg == "--exclude-length" and i + 1 < len(processed_args):
578
759
  exclude_idx = i
579
- existing_excludes.update(processed_args[i + 1].split(','))
760
+ existing_excludes.update(processed_args[i + 1].split(","))
580
761
 
581
- if preflight['exclude_length'] not in existing_excludes:
582
- existing_excludes.add(preflight['exclude_length'])
583
- merged = ','.join(sorted(existing_excludes))
762
+ if preflight["exclude_length"] not in existing_excludes:
763
+ existing_excludes.add(preflight["exclude_length"])
764
+ merged = ",".join(sorted(existing_excludes))
584
765
 
585
766
  if exclude_idx is not None:
586
767
  processed_args[exclude_idx + 1] = merged
587
768
  else:
588
- processed_args.extend(['--exclude-length', merged])
769
+ processed_args.extend(["--exclude-length", merged])
589
770
 
590
771
  # Add --no-progress to suppress verbose progress output (gobuster v3.6+)
591
772
  # This prevents thousands of "Progress: X / Y" lines in output
592
- if '--no-progress' not in processed_args:
593
- processed_args.append('--no-progress')
773
+ if "--no-progress" not in processed_args:
774
+ processed_args.append("--no-progress")
594
775
 
595
776
  cmd = ["gobuster"] + processed_args
596
777
 
597
778
  if not log_path:
598
779
  try:
599
- proc = subprocess.run(cmd, capture_output=True, timeout=300, check=False)
780
+ proc = subprocess.run(
781
+ cmd, capture_output=True, timeout=300, check=False
782
+ )
600
783
  return proc.returncode
601
784
  except Exception:
602
785
  return 1
@@ -604,7 +787,9 @@ class GobusterPlugin(PluginBase):
604
787
  try:
605
788
  with open(log_path, "a", encoding="utf-8", errors="replace") as fh:
606
789
  fh.write(f"Command: {' '.join(cmd)}\n")
607
- fh.write(f"Started: {time.strftime('%Y-%m-%d %H:%M:%S UTC', time.gmtime())}\n\n")
790
+ fh.write(
791
+ f"Started: {time.strftime('%Y-%m-%d %H:%M:%S UTC', time.gmtime())}\n\n"
792
+ )
608
793
  fh.flush()
609
794
 
610
795
  proc = subprocess.run(
@@ -613,29 +798,39 @@ class GobusterPlugin(PluginBase):
613
798
  stderr=subprocess.STDOUT,
614
799
  timeout=300,
615
800
  check=False,
616
- text=True
801
+ text=True,
617
802
  )
618
-
803
+
619
804
  output = proc.stdout
620
805
  fh.write(output)
621
806
 
622
- fh.write(f"\nCompleted: {time.strftime('%Y-%m-%d %H:%M:%S UTC', time.gmtime())}\n")
807
+ fh.write(
808
+ f"\nCompleted: {time.strftime('%Y-%m-%d %H:%M:%S UTC', time.gmtime())}\n"
809
+ )
623
810
  fh.write(f"Exit Code: {proc.returncode}\n")
624
-
811
+
625
812
  # Check for wildcard error in output
626
- if proc.returncode == 1 and "the server returns a status code that matches" in output.lower():
813
+ if (
814
+ proc.returncode == 1
815
+ and "the server returns a status code that matches"
816
+ in output.lower()
817
+ ):
627
818
  # Extract details from error message
628
- status_match = re.search(r'=> (\d{3})', output)
629
- length_match = re.search(r'\(Length: (\d+)\)', output)
630
-
819
+ status_match = re.search(r"=> (\d{3})", output)
820
+ length_match = re.search(r"\(Length: (\d+)\)", output)
821
+
631
822
  status_code = status_match.group(1) if status_match else "403"
632
823
  length = length_match.group(1) if length_match else "unknown"
633
-
824
+
634
825
  fh.write("\n" + "=" * 70 + "\n")
635
826
  fh.write("⚠️ WILDCARD RESPONSE DETECTED\n")
636
827
  fh.write("=" * 70 + "\n\n")
637
- fh.write("The server is returning the same response for all URLs (wildcard).\n")
638
- fh.write("Gobuster cannot differentiate between real and fake directories.\n\n")
828
+ fh.write(
829
+ "The server is returning the same response for all URLs (wildcard).\n"
830
+ )
831
+ fh.write(
832
+ "Gobuster cannot differentiate between real and fake directories.\n\n"
833
+ )
639
834
  fh.write(f"Detected Response:\n")
640
835
  fh.write(f" Status Code: {status_code}\n")
641
836
  fh.write(f" Response Length: {length} bytes\n\n")
@@ -648,12 +843,14 @@ class GobusterPlugin(PluginBase):
648
843
  fh.write(" Add: --wildcard\n\n")
649
844
  fh.write("RETRY COMMAND:\n")
650
845
  retry_cmd = cmd.copy()
651
- retry_cmd.extend(['-b', status_code])
846
+ retry_cmd.extend(["-b", status_code])
652
847
  fh.write(f" {' '.join(retry_cmd)}\n\n")
653
- fh.write("TIP: You can add these flags when configuring the scan in the\n")
848
+ fh.write(
849
+ "TIP: You can add these flags when configuring the scan in the\n"
850
+ )
654
851
  fh.write(" interactive menu under 'Additional flags'.\n")
655
852
  fh.write("=" * 70 + "\n")
656
-
853
+
657
854
  # Treat wildcard detection (exit 1) as informational success
658
855
  # since we've provided helpful guidance
659
856
  return 0 if proc.returncode in [0, 1] else proc.returncode