souleyez 2.43.28__py3-none-any.whl → 2.43.32__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 (356) 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 +9592 -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 +1238 -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 +2198 -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 +288 -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/whois_handler.py +277 -0
  126. souleyez/handlers/wpscan_handler.py +554 -0
  127. souleyez/history.py +32 -16
  128. souleyez/importers/msf_importer.py +106 -75
  129. souleyez/importers/smart_importer.py +208 -147
  130. souleyez/integrations/siem/__init__.py +10 -10
  131. souleyez/integrations/siem/base.py +17 -18
  132. souleyez/integrations/siem/elastic.py +108 -122
  133. souleyez/integrations/siem/factory.py +207 -80
  134. souleyez/integrations/siem/googlesecops.py +146 -154
  135. souleyez/integrations/siem/rule_mappings/__init__.py +1 -1
  136. souleyez/integrations/siem/rule_mappings/wazuh_rules.py +8 -5
  137. souleyez/integrations/siem/sentinel.py +107 -109
  138. souleyez/integrations/siem/splunk.py +246 -212
  139. souleyez/integrations/siem/wazuh.py +65 -71
  140. souleyez/integrations/wazuh/__init__.py +5 -5
  141. souleyez/integrations/wazuh/client.py +70 -93
  142. souleyez/integrations/wazuh/config.py +85 -57
  143. souleyez/integrations/wazuh/host_mapper.py +28 -36
  144. souleyez/integrations/wazuh/sync.py +78 -68
  145. souleyez/intelligence/__init__.py +4 -5
  146. souleyez/intelligence/correlation_analyzer.py +309 -295
  147. souleyez/intelligence/exploit_knowledge.py +661 -623
  148. souleyez/intelligence/exploit_suggestions.py +159 -139
  149. souleyez/intelligence/gap_analyzer.py +132 -97
  150. souleyez/intelligence/gap_detector.py +251 -214
  151. souleyez/intelligence/sensitive_tables.py +266 -129
  152. souleyez/intelligence/service_parser.py +137 -123
  153. souleyez/intelligence/surface_analyzer.py +407 -268
  154. souleyez/intelligence/target_parser.py +159 -162
  155. souleyez/licensing/__init__.py +6 -6
  156. souleyez/licensing/validator.py +17 -19
  157. souleyez/log_config.py +79 -54
  158. souleyez/main.py +1505 -687
  159. souleyez/migrations/fix_job_counter.py +16 -14
  160. souleyez/parsers/bloodhound_parser.py +41 -39
  161. souleyez/parsers/crackmapexec_parser.py +178 -111
  162. souleyez/parsers/dalfox_parser.py +72 -77
  163. souleyez/parsers/dnsrecon_parser.py +103 -91
  164. souleyez/parsers/enum4linux_parser.py +183 -153
  165. souleyez/parsers/ffuf_parser.py +29 -25
  166. souleyez/parsers/gobuster_parser.py +301 -41
  167. souleyez/parsers/hashcat_parser.py +324 -79
  168. souleyez/parsers/http_fingerprint_parser.py +350 -103
  169. souleyez/parsers/hydra_parser.py +131 -111
  170. souleyez/parsers/impacket_parser.py +231 -178
  171. souleyez/parsers/john_parser.py +98 -86
  172. souleyez/parsers/katana_parser.py +316 -0
  173. souleyez/parsers/msf_parser.py +943 -498
  174. souleyez/parsers/nikto_parser.py +346 -65
  175. souleyez/parsers/nmap_parser.py +262 -174
  176. souleyez/parsers/nuclei_parser.py +40 -44
  177. souleyez/parsers/responder_parser.py +26 -26
  178. souleyez/parsers/searchsploit_parser.py +74 -74
  179. souleyez/parsers/service_explorer_parser.py +279 -0
  180. souleyez/parsers/smbmap_parser.py +180 -124
  181. souleyez/parsers/sqlmap_parser.py +434 -308
  182. souleyez/parsers/theharvester_parser.py +75 -57
  183. souleyez/parsers/whois_parser.py +135 -94
  184. souleyez/parsers/wpscan_parser.py +278 -190
  185. souleyez/plugins/afp.py +44 -36
  186. souleyez/plugins/afp_brute.py +114 -46
  187. souleyez/plugins/ard.py +48 -37
  188. souleyez/plugins/bloodhound.py +95 -61
  189. souleyez/plugins/certipy.py +303 -0
  190. souleyez/plugins/crackmapexec.py +186 -85
  191. souleyez/plugins/dalfox.py +120 -59
  192. souleyez/plugins/dns_hijack.py +146 -41
  193. souleyez/plugins/dnsrecon.py +97 -61
  194. souleyez/plugins/enum4linux.py +91 -66
  195. souleyez/plugins/evil_winrm.py +291 -0
  196. souleyez/plugins/ffuf.py +166 -90
  197. souleyez/plugins/firmware_extract.py +133 -29
  198. souleyez/plugins/gobuster.py +387 -190
  199. souleyez/plugins/gpp_extract.py +393 -0
  200. souleyez/plugins/hashcat.py +100 -73
  201. souleyez/plugins/http_fingerprint.py +854 -267
  202. souleyez/plugins/hydra.py +566 -200
  203. souleyez/plugins/impacket_getnpusers.py +117 -69
  204. souleyez/plugins/impacket_psexec.py +84 -64
  205. souleyez/plugins/impacket_secretsdump.py +103 -69
  206. souleyez/plugins/impacket_smbclient.py +89 -75
  207. souleyez/plugins/john.py +86 -69
  208. souleyez/plugins/katana.py +313 -0
  209. souleyez/plugins/kerbrute.py +237 -0
  210. souleyez/plugins/lfi_extract.py +541 -0
  211. souleyez/plugins/macos_ssh.py +117 -48
  212. souleyez/plugins/mdns.py +35 -30
  213. souleyez/plugins/msf_auxiliary.py +253 -130
  214. souleyez/plugins/msf_exploit.py +239 -161
  215. souleyez/plugins/nikto.py +134 -78
  216. souleyez/plugins/nmap.py +275 -91
  217. souleyez/plugins/nuclei.py +180 -89
  218. souleyez/plugins/nxc.py +285 -0
  219. souleyez/plugins/plugin_base.py +35 -36
  220. souleyez/plugins/plugin_template.py +13 -5
  221. souleyez/plugins/rdp_sec_check.py +130 -0
  222. souleyez/plugins/responder.py +112 -71
  223. souleyez/plugins/router_http_brute.py +76 -65
  224. souleyez/plugins/router_ssh_brute.py +118 -41
  225. souleyez/plugins/router_telnet_brute.py +124 -42
  226. souleyez/plugins/routersploit.py +91 -59
  227. souleyez/plugins/routersploit_exploit.py +77 -55
  228. souleyez/plugins/searchsploit.py +91 -77
  229. souleyez/plugins/service_explorer.py +1160 -0
  230. souleyez/plugins/smbmap.py +122 -72
  231. souleyez/plugins/smbpasswd.py +215 -0
  232. souleyez/plugins/sqlmap.py +301 -113
  233. souleyez/plugins/theharvester.py +127 -75
  234. souleyez/plugins/tr069.py +79 -57
  235. souleyez/plugins/upnp.py +65 -47
  236. souleyez/plugins/upnp_abuse.py +73 -55
  237. souleyez/plugins/vnc_access.py +129 -42
  238. souleyez/plugins/vnc_brute.py +109 -38
  239. souleyez/plugins/whois.py +77 -58
  240. souleyez/plugins/wpscan.py +173 -69
  241. souleyez/reporting/__init__.py +2 -1
  242. souleyez/reporting/attack_chain.py +411 -346
  243. souleyez/reporting/charts.py +436 -501
  244. souleyez/reporting/compliance_mappings.py +334 -201
  245. souleyez/reporting/detection_report.py +126 -125
  246. souleyez/reporting/formatters.py +828 -591
  247. souleyez/reporting/generator.py +386 -302
  248. souleyez/reporting/metrics.py +72 -75
  249. souleyez/scanner.py +35 -29
  250. souleyez/security/__init__.py +37 -11
  251. souleyez/security/scope_validator.py +175 -106
  252. souleyez/security/validation.py +223 -149
  253. souleyez/security.py +22 -6
  254. souleyez/storage/credentials.py +247 -186
  255. souleyez/storage/crypto.py +296 -129
  256. souleyez/storage/database.py +73 -50
  257. souleyez/storage/db.py +58 -36
  258. souleyez/storage/deliverable_evidence.py +177 -128
  259. souleyez/storage/deliverable_exporter.py +282 -246
  260. souleyez/storage/deliverable_templates.py +134 -116
  261. souleyez/storage/deliverables.py +135 -130
  262. souleyez/storage/engagements.py +109 -56
  263. souleyez/storage/evidence.py +181 -152
  264. souleyez/storage/execution_log.py +31 -17
  265. souleyez/storage/exploit_attempts.py +93 -57
  266. souleyez/storage/exploits.py +67 -36
  267. souleyez/storage/findings.py +48 -61
  268. souleyez/storage/hosts.py +176 -144
  269. souleyez/storage/migrate_to_engagements.py +43 -19
  270. souleyez/storage/migrations/_001_add_credential_enhancements.py +22 -12
  271. souleyez/storage/migrations/_002_add_status_tracking.py +10 -7
  272. souleyez/storage/migrations/_003_add_execution_log.py +14 -8
  273. souleyez/storage/migrations/_005_screenshots.py +13 -5
  274. souleyez/storage/migrations/_006_deliverables.py +13 -5
  275. souleyez/storage/migrations/_007_deliverable_templates.py +12 -7
  276. souleyez/storage/migrations/_008_add_nuclei_table.py +10 -4
  277. souleyez/storage/migrations/_010_evidence_linking.py +17 -10
  278. souleyez/storage/migrations/_011_timeline_tracking.py +20 -13
  279. souleyez/storage/migrations/_012_team_collaboration.py +34 -21
  280. souleyez/storage/migrations/_013_add_host_tags.py +12 -6
  281. souleyez/storage/migrations/_014_exploit_attempts.py +22 -10
  282. souleyez/storage/migrations/_015_add_mac_os_fields.py +15 -7
  283. souleyez/storage/migrations/_016_add_domain_field.py +10 -4
  284. souleyez/storage/migrations/_017_msf_sessions.py +16 -8
  285. souleyez/storage/migrations/_018_add_osint_target.py +10 -6
  286. souleyez/storage/migrations/_019_add_engagement_type.py +10 -6
  287. souleyez/storage/migrations/_020_add_rbac.py +36 -15
  288. souleyez/storage/migrations/_021_wazuh_integration.py +20 -8
  289. souleyez/storage/migrations/_022_wazuh_indexer_columns.py +6 -4
  290. souleyez/storage/migrations/_023_fix_detection_results_fk.py +16 -6
  291. souleyez/storage/migrations/_024_wazuh_vulnerabilities.py +26 -10
  292. souleyez/storage/migrations/_025_multi_siem_support.py +3 -5
  293. souleyez/storage/migrations/_026_add_engagement_scope.py +31 -12
  294. souleyez/storage/migrations/_027_multi_siem_persistence.py +32 -15
  295. souleyez/storage/migrations/__init__.py +26 -26
  296. souleyez/storage/migrations/migration_manager.py +19 -19
  297. souleyez/storage/msf_sessions.py +100 -65
  298. souleyez/storage/osint.py +17 -24
  299. souleyez/storage/recommendation_engine.py +269 -235
  300. souleyez/storage/screenshots.py +33 -32
  301. souleyez/storage/smb_shares.py +136 -92
  302. souleyez/storage/sqlmap_data.py +183 -128
  303. souleyez/storage/team_collaboration.py +135 -141
  304. souleyez/storage/timeline_tracker.py +122 -94
  305. souleyez/storage/wazuh_vulns.py +64 -66
  306. souleyez/storage/web_paths.py +33 -37
  307. souleyez/testing/credential_tester.py +221 -205
  308. souleyez/ui/__init__.py +1 -1
  309. souleyez/ui/ai_quotes.py +12 -12
  310. souleyez/ui/attack_surface.py +2439 -1516
  311. souleyez/ui/chain_rules_view.py +914 -382
  312. souleyez/ui/correlation_view.py +312 -230
  313. souleyez/ui/dashboard.py +2382 -1130
  314. souleyez/ui/deliverables_view.py +148 -62
  315. souleyez/ui/design_system.py +13 -13
  316. souleyez/ui/errors.py +49 -49
  317. souleyez/ui/evidence_linking_view.py +284 -179
  318. souleyez/ui/evidence_vault.py +393 -285
  319. souleyez/ui/exploit_suggestions_view.py +555 -349
  320. souleyez/ui/export_view.py +100 -66
  321. souleyez/ui/gap_analysis_view.py +315 -171
  322. souleyez/ui/help_system.py +105 -97
  323. souleyez/ui/intelligence_view.py +436 -293
  324. souleyez/ui/interactive.py +23142 -10430
  325. souleyez/ui/interactive_selector.py +75 -68
  326. souleyez/ui/log_formatter.py +47 -39
  327. souleyez/ui/menu_components.py +22 -13
  328. souleyez/ui/msf_auxiliary_menu.py +184 -133
  329. souleyez/ui/pending_chains_view.py +336 -172
  330. souleyez/ui/progress_indicators.py +5 -3
  331. souleyez/ui/recommendations_view.py +195 -137
  332. souleyez/ui/rule_builder.py +343 -225
  333. souleyez/ui/setup_wizard.py +678 -284
  334. souleyez/ui/shortcuts.py +217 -165
  335. souleyez/ui/splunk_gap_analysis_view.py +452 -270
  336. souleyez/ui/splunk_vulns_view.py +139 -86
  337. souleyez/ui/team_dashboard.py +498 -335
  338. souleyez/ui/template_selector.py +196 -105
  339. souleyez/ui/terminal.py +6 -6
  340. souleyez/ui/timeline_view.py +198 -127
  341. souleyez/ui/tool_setup.py +264 -164
  342. souleyez/ui/tutorial.py +202 -72
  343. souleyez/ui/tutorial_state.py +40 -40
  344. souleyez/ui/wazuh_vulns_view.py +235 -141
  345. souleyez/ui/wordlist_browser.py +260 -107
  346. souleyez/ui.py +464 -312
  347. souleyez/utils/tool_checker.py +427 -367
  348. souleyez/utils.py +33 -29
  349. souleyez/wordlists.py +134 -167
  350. {souleyez-2.43.28.dist-info → souleyez-2.43.32.dist-info}/METADATA +1 -1
  351. souleyez-2.43.32.dist-info/RECORD +441 -0
  352. {souleyez-2.43.28.dist-info → souleyez-2.43.32.dist-info}/WHEEL +1 -1
  353. souleyez-2.43.28.dist-info/RECORD +0 -379
  354. {souleyez-2.43.28.dist-info → souleyez-2.43.32.dist-info}/entry_points.txt +0 -0
  355. {souleyez-2.43.28.dist-info → souleyez-2.43.32.dist-info}/licenses/LICENSE +0 -0
  356. {souleyez-2.43.28.dist-info → souleyez-2.43.32.dist-info}/top_level.txt +0 -0
@@ -47,102 +47,120 @@ def parse_theharvester_output(output: str, target: str = "") -> Dict[str, Any]:
47
47
  }
48
48
  """
49
49
  result = {
50
- 'target': target,
51
- 'emails': [],
52
- 'hosts': [],
53
- 'ips': [],
54
- 'urls': [],
55
- 'asns': []
50
+ "target": target,
51
+ "emails": [],
52
+ "hosts": [],
53
+ "ips": [],
54
+ "urls": [],
55
+ "asns": [],
56
56
  }
57
57
 
58
- lines = output.split('\n')
58
+ lines = output.split("\n")
59
59
  current_section = None
60
60
 
61
61
  for line in lines:
62
62
  line = line.strip()
63
63
 
64
64
  # Detect target
65
- if line.startswith('[*] Target:'):
66
- target_match = re.search(r'\[?\*\]?\s*Target:\s*(\S+)', line)
65
+ if line.startswith("[*] Target:"):
66
+ target_match = re.search(r"\[?\*\]?\s*Target:\s*(\S+)", line)
67
67
  if target_match:
68
- result['target'] = target_match.group(1)
68
+ result["target"] = target_match.group(1)
69
69
 
70
70
  # Detect section headers (case-insensitive, multiple format variations)
71
71
  line_lower = line.lower()
72
- if any(x in line_lower for x in ['asns found', 'asn found', 'autonomous system']):
73
- current_section = 'asns'
74
- elif any(x in line_lower for x in ['urls found', 'interesting urls', 'url found']):
75
- current_section = 'urls'
76
- elif any(x in line_lower for x in ['ips found', 'ip found', 'ip addresses']):
77
- current_section = 'ips'
78
- elif any(x in line_lower for x in ['emails found', 'email found', 'email addresses']):
79
- current_section = 'emails'
80
- elif any(x in line_lower for x in ['hosts found', 'host found', 'subdomains found', 'subdomain found']):
81
- current_section = 'hosts'
82
- elif any(x in line_lower for x in ['people found', 'no people found', 'linkedin']):
83
- current_section = 'people' # We'll skip this for now
72
+ if any(
73
+ x in line_lower for x in ["asns found", "asn found", "autonomous system"]
74
+ ):
75
+ current_section = "asns"
76
+ elif any(
77
+ x in line_lower for x in ["urls found", "interesting urls", "url found"]
78
+ ):
79
+ current_section = "urls"
80
+ elif any(x in line_lower for x in ["ips found", "ip found", "ip addresses"]):
81
+ current_section = "ips"
82
+ elif any(
83
+ x in line_lower for x in ["emails found", "email found", "email addresses"]
84
+ ):
85
+ current_section = "emails"
86
+ elif any(
87
+ x in line_lower
88
+ for x in [
89
+ "hosts found",
90
+ "host found",
91
+ "subdomains found",
92
+ "subdomain found",
93
+ ]
94
+ ):
95
+ current_section = "hosts"
96
+ elif any(
97
+ x in line_lower for x in ["people found", "no people found", "linkedin"]
98
+ ):
99
+ current_section = "people" # We'll skip this for now
84
100
 
85
101
  # Skip separator lines and empty lines
86
- elif line.startswith('---') or not line:
102
+ elif line.startswith("---") or not line:
87
103
  continue
88
104
 
89
105
  # Skip "No X found" messages
90
- elif '[*] No' in line:
106
+ elif "[*] No" in line:
91
107
  current_section = None
92
108
  continue
93
109
 
94
110
  # Skip header/banner lines
95
- elif line.startswith('*') or line.startswith('[*] Searching'):
111
+ elif line.startswith("*") or line.startswith("[*] Searching"):
96
112
  continue
97
113
 
98
114
  # Parse data based on current section
99
- elif current_section == 'asns':
115
+ elif current_section == "asns":
100
116
  # ASN format: AS12345
101
- if line.startswith('AS') and line[2:].isdigit():
102
- result['asns'].append(line)
117
+ if line.startswith("AS") and line[2:].isdigit():
118
+ result["asns"].append(line)
103
119
 
104
- elif current_section == 'urls':
120
+ elif current_section == "urls":
105
121
  # URL format: http(s)://...
106
- if line.startswith('http://') or line.startswith('https://'):
122
+ if line.startswith("http://") or line.startswith("https://"):
107
123
  # Clean up trailing punctuation
108
- url = line.rstrip('.,;)')
109
- if url not in result['urls']:
110
- result['urls'].append(url)
124
+ url = line.rstrip(".,;)")
125
+ if url not in result["urls"]:
126
+ result["urls"].append(url)
111
127
 
112
- elif current_section == 'ips':
128
+ elif current_section == "ips":
113
129
  # IP format: N.N.N.N
114
- if re.match(r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$', line):
115
- if line not in result['ips']:
116
- result['ips'].append(line)
130
+ if re.match(r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$", line):
131
+ if line not in result["ips"]:
132
+ result["ips"].append(line)
117
133
 
118
- elif current_section == 'emails':
134
+ elif current_section == "emails":
119
135
  # Email format: user@domain
120
- if '@' in line and '.' in line:
136
+ if "@" in line and "." in line:
121
137
  # More permissive email validation (supports international domains)
122
138
  # Pattern allows: standard emails, plus-addressing, dots, underscores
123
139
  email = line.strip().lower()
124
140
  # Remove any leading/trailing brackets or quotes
125
- email = re.sub(r'^[\[\(<\'\"]+|[\]\)>\'\"]$', '', email)
126
- if re.match(r'^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$', email):
127
- if email not in result['emails']:
128
- result['emails'].append(email)
129
-
130
- elif current_section == 'hosts':
141
+ email = re.sub(r"^[\[\(<\'\"]+|[\]\)>\'\"]$", "", email)
142
+ if re.match(
143
+ r"^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$", email
144
+ ):
145
+ if email not in result["emails"]:
146
+ result["emails"].append(email)
147
+
148
+ elif current_section == "hosts":
131
149
  # Host format: subdomain.domain.tld
132
- if '.' in line and not line.startswith('http'):
150
+ if "." in line and not line.startswith("http"):
133
151
  # Clean and validate hostname
134
152
  host = line.strip().lower()
135
153
  # Remove any leading/trailing brackets, quotes, or trailing dots
136
- host = re.sub(r'^[\[\(<\'\"]+|[\]\)>\'\".]+$', '', host)
154
+ host = re.sub(r"^[\[\(<\'\"]+|[\]\)>\'\".]+$", "", host)
137
155
  # More permissive validation: allows underscores (common in some hosts)
138
156
  # and longer TLDs (some are 4+ chars)
139
- if re.match(r'^[a-zA-Z0-9._-]+\.[a-zA-Z]{2,}$', host) and len(host) > 3:
140
- if host not in result['hosts']:
141
- result['hosts'].append(host)
157
+ if re.match(r"^[a-zA-Z0-9._-]+\.[a-zA-Z]{2,}$", host) and len(host) > 3:
158
+ if host not in result["hosts"]:
159
+ result["hosts"].append(host)
142
160
 
143
161
  # Add alias fields for backward compatibility with display code
144
- result['subdomains'] = result['hosts'] # Alias for display
145
- result['base_urls'] = result['urls'] # Alias for display
162
+ result["subdomains"] = result["hosts"] # Alias for display
163
+ result["base_urls"] = result["urls"] # Alias for display
146
164
 
147
165
  return result
148
166
 
@@ -158,9 +176,9 @@ def get_osint_stats(parsed: Dict[str, Any]) -> Dict[str, int]:
158
176
  Dict with counts: {'emails': 5, 'hosts': 10, ...}
159
177
  """
160
178
  return {
161
- 'emails': len(parsed.get('emails', [])),
162
- 'hosts': len(parsed.get('hosts', [])),
163
- 'ips': len(parsed.get('ips', [])),
164
- 'urls': len(parsed.get('urls', [])),
165
- 'asns': len(parsed.get('asns', []))
179
+ "emails": len(parsed.get("emails", [])),
180
+ "hosts": len(parsed.get("hosts", [])),
181
+ "ips": len(parsed.get("ips", [])),
182
+ "urls": len(parsed.get("urls", [])),
183
+ "asns": len(parsed.get("asns", [])),
166
184
  }
@@ -51,123 +51,149 @@ def parse_whois_output(output: str, target: str = "") -> Dict[str, Any]:
51
51
  }
52
52
  """
53
53
  result = {
54
- 'domain': target,
55
- 'registrar': None,
56
- 'registrant': {},
57
- 'admin_contact': {},
58
- 'tech_contact': {},
59
- 'dates': {},
60
- 'nameservers': [],
61
- 'status': [],
62
- 'dnssec': None
54
+ "domain": target,
55
+ "registrar": None,
56
+ "registrant": {},
57
+ "admin_contact": {},
58
+ "tech_contact": {},
59
+ "dates": {},
60
+ "nameservers": [],
61
+ "status": [],
62
+ "dnssec": None,
63
63
  }
64
64
 
65
- lines = output.split('\n')
65
+ lines = output.split("\n")
66
66
  current_section = None
67
67
 
68
68
  for line in lines:
69
69
  line_stripped = line.strip()
70
70
 
71
71
  # Skip comments and empty lines
72
- if not line_stripped or line_stripped.startswith('%') or line_stripped.startswith('#'):
72
+ if (
73
+ not line_stripped
74
+ or line_stripped.startswith("%")
75
+ or line_stripped.startswith("#")
76
+ ):
73
77
  continue
74
78
 
75
79
  # Convert to lowercase for matching
76
80
  line_lower = line_stripped.lower()
77
81
 
78
82
  # Extract domain name
79
- if not result['domain'] or result['domain'] == '':
80
- domain_match = re.match(r'domain name:\s+(.+)', line_stripped, re.IGNORECASE)
83
+ if not result["domain"] or result["domain"] == "":
84
+ domain_match = re.match(
85
+ r"domain name:\s+(.+)", line_stripped, re.IGNORECASE
86
+ )
81
87
  if domain_match:
82
- result['domain'] = domain_match.group(1).strip()
88
+ result["domain"] = domain_match.group(1).strip()
83
89
 
84
90
  # Extract registrar
85
- if 'registrar:' in line_lower and not result['registrar']:
86
- registrar_match = re.search(r'registrar:\s+(.+)', line_stripped, re.IGNORECASE)
91
+ if "registrar:" in line_lower and not result["registrar"]:
92
+ registrar_match = re.search(
93
+ r"registrar:\s+(.+)", line_stripped, re.IGNORECASE
94
+ )
87
95
  if registrar_match:
88
- result['registrar'] = registrar_match.group(1).strip()
96
+ result["registrar"] = registrar_match.group(1).strip()
89
97
 
90
98
  # Extract dates
91
- if 'creation date' in line_lower or 'created' in line_lower:
92
- date_match = re.search(r':\s+(\d{4}-\d{2}-\d{2}|\d{2}/\d{2}/\d{4})', line_stripped)
99
+ if "creation date" in line_lower or "created" in line_lower:
100
+ date_match = re.search(
101
+ r":\s+(\d{4}-\d{2}-\d{2}|\d{2}/\d{2}/\d{4})", line_stripped
102
+ )
93
103
  if date_match:
94
- result['dates']['created'] = date_match.group(1).strip()
95
-
96
- if 'updated date' in line_lower or 'last updated' in line_lower or 'modified' in line_lower:
97
- date_match = re.search(r':\s+(\d{4}-\d{2}-\d{2}|\d{2}/\d{2}/\d{4})', line_stripped)
104
+ result["dates"]["created"] = date_match.group(1).strip()
105
+
106
+ if (
107
+ "updated date" in line_lower
108
+ or "last updated" in line_lower
109
+ or "modified" in line_lower
110
+ ):
111
+ date_match = re.search(
112
+ r":\s+(\d{4}-\d{2}-\d{2}|\d{2}/\d{2}/\d{4})", line_stripped
113
+ )
98
114
  if date_match:
99
- result['dates']['updated'] = date_match.group(1).strip()
115
+ result["dates"]["updated"] = date_match.group(1).strip()
100
116
 
101
- if 'expir' in line_lower:
102
- date_match = re.search(r':\s+(\d{4}-\d{2}-\d{2}|\d{2}/\d{2}/\d{4})', line_stripped)
117
+ if "expir" in line_lower:
118
+ date_match = re.search(
119
+ r":\s+(\d{4}-\d{2}-\d{2}|\d{2}/\d{2}/\d{4})", line_stripped
120
+ )
103
121
  if date_match:
104
- result['dates']['expires'] = date_match.group(1).strip()
122
+ result["dates"]["expires"] = date_match.group(1).strip()
105
123
 
106
124
  # Detect contact sections
107
- if 'registrant' in line_lower and 'name:' in line_lower:
108
- current_section = 'registrant'
109
- elif 'admin' in line_lower and ('name:' in line_lower or 'contact' in line_lower):
110
- current_section = 'admin'
111
- elif 'tech' in line_lower and ('name:' in line_lower or 'contact' in line_lower):
112
- current_section = 'tech'
125
+ if "registrant" in line_lower and "name:" in line_lower:
126
+ current_section = "registrant"
127
+ elif "admin" in line_lower and (
128
+ "name:" in line_lower or "contact" in line_lower
129
+ ):
130
+ current_section = "admin"
131
+ elif "tech" in line_lower and (
132
+ "name:" in line_lower or "contact" in line_lower
133
+ ):
134
+ current_section = "tech"
113
135
 
114
136
  # Extract contact information based on current section
115
137
  if current_section:
116
138
  contact_dict = _get_contact_dict(result, current_section)
117
139
 
118
- if 'name:' in line_lower and 'domain name' not in line_lower:
119
- name_match = re.search(r'name:\s+(.+)', line_stripped, re.IGNORECASE)
140
+ if "name:" in line_lower and "domain name" not in line_lower:
141
+ name_match = re.search(r"name:\s+(.+)", line_stripped, re.IGNORECASE)
120
142
  if name_match:
121
- contact_dict['name'] = name_match.group(1).strip()
143
+ contact_dict["name"] = name_match.group(1).strip()
122
144
 
123
- if 'organi' in line_lower:
124
- org_match = re.search(r'organi[zs]ation:\s+(.+)', line_stripped, re.IGNORECASE)
145
+ if "organi" in line_lower:
146
+ org_match = re.search(
147
+ r"organi[zs]ation:\s+(.+)", line_stripped, re.IGNORECASE
148
+ )
125
149
  if org_match:
126
- contact_dict['organization'] = org_match.group(1).strip()
150
+ contact_dict["organization"] = org_match.group(1).strip()
127
151
 
128
- if 'email' in line_lower:
129
- email_match = re.search(r'email:\s+(.+)', line_stripped, re.IGNORECASE)
152
+ if "email" in line_lower:
153
+ email_match = re.search(r"email:\s+(.+)", line_stripped, re.IGNORECASE)
130
154
  if email_match:
131
- contact_dict['email'] = email_match.group(1).strip()
155
+ contact_dict["email"] = email_match.group(1).strip()
132
156
 
133
- if 'phone' in line_lower:
134
- phone_match = re.search(r'phone:\s+(.+)', line_stripped, re.IGNORECASE)
157
+ if "phone" in line_lower:
158
+ phone_match = re.search(r"phone:\s+(.+)", line_stripped, re.IGNORECASE)
135
159
  if phone_match:
136
- contact_dict['phone'] = phone_match.group(1).strip()
160
+ contact_dict["phone"] = phone_match.group(1).strip()
137
161
 
138
162
  # Extract nameservers
139
- if 'name server' in line_lower or 'nserver' in line_lower:
140
- ns_match = re.search(r'(?:name server|nserver):\s+(.+)', line_stripped, re.IGNORECASE)
163
+ if "name server" in line_lower or "nserver" in line_lower:
164
+ ns_match = re.search(
165
+ r"(?:name server|nserver):\s+(.+)", line_stripped, re.IGNORECASE
166
+ )
141
167
  if ns_match:
142
168
  nameserver = ns_match.group(1).strip().lower()
143
- if nameserver not in result['nameservers']:
144
- result['nameservers'].append(nameserver)
169
+ if nameserver not in result["nameservers"]:
170
+ result["nameservers"].append(nameserver)
145
171
 
146
172
  # Extract status
147
- if 'status:' in line_lower:
148
- status_match = re.search(r'status:\s+(.+)', line_stripped, re.IGNORECASE)
173
+ if "status:" in line_lower:
174
+ status_match = re.search(r"status:\s+(.+)", line_stripped, re.IGNORECASE)
149
175
  if status_match:
150
176
  status = status_match.group(1).strip()
151
- if status not in result['status']:
152
- result['status'].append(status)
177
+ if status not in result["status"]:
178
+ result["status"].append(status)
153
179
 
154
180
  # Extract DNSSEC
155
- if 'dnssec:' in line_lower:
156
- dnssec_match = re.search(r'dnssec:\s+(.+)', line_stripped, re.IGNORECASE)
181
+ if "dnssec:" in line_lower:
182
+ dnssec_match = re.search(r"dnssec:\s+(.+)", line_stripped, re.IGNORECASE)
157
183
  if dnssec_match:
158
- result['dnssec'] = dnssec_match.group(1).strip()
184
+ result["dnssec"] = dnssec_match.group(1).strip()
159
185
 
160
186
  return result
161
187
 
162
188
 
163
189
  def _get_contact_dict(result: Dict[str, Any], section: str) -> Dict[str, Any]:
164
190
  """Get the appropriate contact dictionary based on section."""
165
- if section == 'registrant':
166
- return result['registrant']
167
- elif section == 'admin':
168
- return result['admin_contact']
169
- elif section == 'tech':
170
- return result['tech_contact']
191
+ if section == "registrant":
192
+ return result["registrant"]
193
+ elif section == "admin":
194
+ return result["admin_contact"]
195
+ elif section == "tech":
196
+ return result["tech_contact"]
171
197
  return {}
172
198
 
173
199
 
@@ -183,17 +209,21 @@ def extract_emails(parsed_data: Dict[str, Any]) -> List[str]:
183
209
  """
184
210
  emails = []
185
211
 
186
- for contact in [parsed_data.get('registrant', {}),
187
- parsed_data.get('admin_contact', {}),
188
- parsed_data.get('tech_contact', {})]:
189
- email = contact.get('email')
190
- if email and email not in emails and '@' in email:
212
+ for contact in [
213
+ parsed_data.get("registrant", {}),
214
+ parsed_data.get("admin_contact", {}),
215
+ parsed_data.get("tech_contact", {}),
216
+ ]:
217
+ email = contact.get("email")
218
+ if email and email not in emails and "@" in email:
191
219
  emails.append(email)
192
220
 
193
221
  return emails
194
222
 
195
223
 
196
- def map_to_osint_data(parsed_data: Dict[str, Any], engagement_id: int) -> Dict[str, Any]:
224
+ def map_to_osint_data(
225
+ parsed_data: Dict[str, Any], engagement_id: int
226
+ ) -> Dict[str, Any]:
197
227
  """
198
228
  Convert parsed WHOIS data into OSINT record for database storage.
199
229
 
@@ -209,36 +239,42 @@ def map_to_osint_data(parsed_data: Dict[str, Any], engagement_id: int) -> Dict[s
209
239
  # Extract key information for quick reference
210
240
  summary_parts = []
211
241
 
212
- if parsed_data.get('registrar'):
242
+ if parsed_data.get("registrar"):
213
243
  summary_parts.append(f"Registrar: {parsed_data['registrar']}")
214
244
 
215
- if parsed_data.get('dates', {}).get('created'):
245
+ if parsed_data.get("dates", {}).get("created"):
216
246
  summary_parts.append(f"Created: {parsed_data['dates']['created']}")
217
247
 
218
- if parsed_data.get('dates', {}).get('expires'):
248
+ if parsed_data.get("dates", {}).get("expires"):
219
249
  summary_parts.append(f"Expires: {parsed_data['dates']['expires']}")
220
250
 
221
- if parsed_data.get('registrant', {}).get('organization'):
251
+ if parsed_data.get("registrant", {}).get("organization"):
222
252
  summary_parts.append(f"Org: {parsed_data['registrant']['organization']}")
223
253
 
224
- summary = " | ".join(summary_parts) if summary_parts else "Domain registration information"
254
+ summary = (
255
+ " | ".join(summary_parts)
256
+ if summary_parts
257
+ else "Domain registration information"
258
+ )
225
259
 
226
260
  # Extract emails for OSINT correlation
227
261
  emails = extract_emails(parsed_data)
228
262
 
229
263
  osint_record = {
230
- 'target': parsed_data.get('domain', ''),
231
- 'data_type': 'domain_info',
232
- 'source': 'whois',
233
- 'content': json.dumps(parsed_data, indent=2),
234
- 'summary': summary,
235
- 'metadata': json.dumps({
236
- 'registrar': parsed_data.get('registrar'),
237
- 'nameservers': parsed_data.get('nameservers', []),
238
- 'emails': emails,
239
- 'expiration_date': parsed_data.get('dates', {}).get('expires'),
240
- 'dnssec': parsed_data.get('dnssec')
241
- })
264
+ "target": parsed_data.get("domain", ""),
265
+ "data_type": "domain_info",
266
+ "source": "whois",
267
+ "content": json.dumps(parsed_data, indent=2),
268
+ "summary": summary,
269
+ "metadata": json.dumps(
270
+ {
271
+ "registrar": parsed_data.get("registrar"),
272
+ "nameservers": parsed_data.get("nameservers", []),
273
+ "emails": emails,
274
+ "expiration_date": parsed_data.get("dates", {}).get("expires"),
275
+ "dnssec": parsed_data.get("dnssec"),
276
+ }
277
+ ),
242
278
  }
243
279
 
244
280
  return osint_record
@@ -255,23 +291,23 @@ def check_privacy_protection(parsed_data: Dict[str, Any]) -> bool:
255
291
  True if privacy protection detected
256
292
  """
257
293
  privacy_indicators = [
258
- 'privacy',
259
- 'redacted',
260
- 'protected',
261
- 'whoisguard',
262
- 'domains by proxy',
263
- 'private registration'
294
+ "privacy",
295
+ "redacted",
296
+ "protected",
297
+ "whoisguard",
298
+ "domains by proxy",
299
+ "private registration",
264
300
  ]
265
301
 
266
302
  # Check registrant info
267
- registrant_text = str(parsed_data.get('registrant', {})).lower()
303
+ registrant_text = str(parsed_data.get("registrant", {})).lower()
268
304
 
269
305
  for indicator in privacy_indicators:
270
306
  if indicator in registrant_text:
271
307
  return True
272
308
 
273
309
  # Check registrar
274
- registrar = str(parsed_data.get('registrar', '')).lower()
310
+ registrar = str(parsed_data.get("registrar", "")).lower()
275
311
  for indicator in privacy_indicators:
276
312
  if indicator in registrar:
277
313
  return True
@@ -280,4 +316,9 @@ def check_privacy_protection(parsed_data: Dict[str, Any]) -> bool:
280
316
 
281
317
 
282
318
  # Export the main functions
283
- __all__ = ['parse_whois_output', 'map_to_osint_data', 'extract_emails', 'check_privacy_protection']
319
+ __all__ = [
320
+ "parse_whois_output",
321
+ "map_to_osint_data",
322
+ "extract_emails",
323
+ "check_privacy_protection",
324
+ ]