souleyez 2.23.0__py3-none-any.whl → 2.25.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- souleyez/__init__.py +1 -1
- souleyez/core/msf_sync_manager.py +15 -5
- souleyez/core/tool_chaining.py +2 -11
- souleyez/docs/README.md +2 -2
- souleyez/docs/user-guide/installation.md +11 -0
- souleyez/engine/background.py +17 -1
- souleyez/main.py +11 -4
- souleyez/plugins/nuclei.py +41 -17
- souleyez/ui/interactive.py +96 -4
- souleyez/ui/tool_setup.py +3 -0
- souleyez/utils/tool_checker.py +42 -2
- {souleyez-2.23.0.dist-info → souleyez-2.25.0.dist-info}/METADATA +14 -3
- {souleyez-2.23.0.dist-info → souleyez-2.25.0.dist-info}/RECORD +17 -17
- {souleyez-2.23.0.dist-info → souleyez-2.25.0.dist-info}/WHEEL +0 -0
- {souleyez-2.23.0.dist-info → souleyez-2.25.0.dist-info}/entry_points.txt +0 -0
- {souleyez-2.23.0.dist-info → souleyez-2.25.0.dist-info}/licenses/LICENSE +0 -0
- {souleyez-2.23.0.dist-info → souleyez-2.25.0.dist-info}/top_level.txt +0 -0
souleyez/__init__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = '2.
|
|
1
|
+
__version__ = '2.25.0'
|
|
@@ -29,15 +29,25 @@ logger = logging.getLogger(__name__)
|
|
|
29
29
|
|
|
30
30
|
def get_msf_database_config() -> Optional[Dict[str, Any]]:
|
|
31
31
|
"""
|
|
32
|
-
Get MSF database configuration from ~/.msf4/database.yml
|
|
32
|
+
Get MSF database configuration from ~/.msf4/database.yml or system-wide config.
|
|
33
|
+
|
|
34
|
+
Checks user config first, then falls back to system-wide config (Kali Linux).
|
|
33
35
|
|
|
34
36
|
Returns:
|
|
35
37
|
Dictionary with database config or None if not found/parseable
|
|
36
38
|
"""
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
# Check user config first, then system-wide config (Kali uses system-wide)
|
|
40
|
+
user_db_path = Path.home() / ".msf4" / "database.yml"
|
|
41
|
+
system_db_path = Path('/usr/share/metasploit-framework/config/database.yml')
|
|
42
|
+
|
|
43
|
+
db_yml_path = None
|
|
44
|
+
if user_db_path.exists():
|
|
45
|
+
db_yml_path = user_db_path
|
|
46
|
+
elif system_db_path.exists():
|
|
47
|
+
db_yml_path = system_db_path
|
|
48
|
+
|
|
49
|
+
if not db_yml_path:
|
|
50
|
+
logger.debug("MSF database.yml not found in user or system config")
|
|
41
51
|
return None
|
|
42
52
|
|
|
43
53
|
try:
|
souleyez/core/tool_chaining.py
CHANGED
|
@@ -746,17 +746,8 @@ class ToolChaining:
|
|
|
746
746
|
args_template=['-a', '{target}'],
|
|
747
747
|
description='SMB service detected, enumerating shares and users (runs after CrackMapExec)'
|
|
748
748
|
),
|
|
749
|
-
#
|
|
750
|
-
# Use crackmapexec/netexec --shares instead (rule
|
|
751
|
-
ChainRule(
|
|
752
|
-
trigger_tool='nmap',
|
|
753
|
-
trigger_condition='service:smb',
|
|
754
|
-
target_tool='smbmap',
|
|
755
|
-
priority=7,
|
|
756
|
-
enabled=False, # Disabled due to impacket pickling bug
|
|
757
|
-
args_template=['-H', '{target}'],
|
|
758
|
-
description='SMB service detected, mapping shares (DISABLED - use netexec)'
|
|
759
|
-
),
|
|
749
|
+
# NOTE: smbmap removed - has upstream impacket pickling bug on Python 3.13+
|
|
750
|
+
# Use crackmapexec/netexec --shares instead (enum4linux rule above)
|
|
760
751
|
])
|
|
761
752
|
|
|
762
753
|
# Active Directory attacks - smart chaining workflow
|
souleyez/docs/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# SoulEyez Documentation
|
|
2
2
|
|
|
3
|
-
**Version:** 2.
|
|
4
|
-
**Last Updated:** January
|
|
3
|
+
**Version:** 2.25.0
|
|
4
|
+
**Last Updated:** January 8, 2026
|
|
5
5
|
**Organization:** CyberSoul Security
|
|
6
6
|
|
|
7
7
|
Welcome to the SoulEyez documentation! This documentation covers architecture, development, user guides, and operational information for the SoulEyez penetration testing platform.
|
|
@@ -22,6 +22,17 @@ This guide walks you through installing souleyez on your system. The process tak
|
|
|
22
22
|
- **RAM Usage**: Running multiple heavy tools (Metasploit, SQLMap, Hashcat) simultaneously requires additional RAM
|
|
23
23
|
- **Disk I/O**: SSD recommended for database operations and log processing
|
|
24
24
|
|
|
25
|
+
> **🐉 Kali Linux Recommended**
|
|
26
|
+
>
|
|
27
|
+
> SoulEyez performs significantly better on **Kali Linux** than other distributions:
|
|
28
|
+
> - All pentesting tools pre-installed and optimized
|
|
29
|
+
> - Metasploit database and RPC already configured
|
|
30
|
+
> - Security-focused kernel and networking stack
|
|
31
|
+
> - No dependency hunting or version conflicts
|
|
32
|
+
> - Wordlists, databases, and tool configs ready to go
|
|
33
|
+
>
|
|
34
|
+
> While Ubuntu and other Debian-based distros are supported, you may experience slower setup times and occasional tool compatibility issues.
|
|
35
|
+
|
|
25
36
|
### Software Requirements
|
|
26
37
|
|
|
27
38
|
- **Operating System**: Linux (Kali Linux recommended, any Debian-based distro supported)
|
souleyez/engine/background.py
CHANGED
|
@@ -1039,7 +1039,8 @@ def _is_true_error_exit_code(rc: int, tool: str) -> bool:
|
|
|
1039
1039
|
# Tools that use non-zero exit codes for non-error conditions
|
|
1040
1040
|
# Parser will determine the actual status based on output
|
|
1041
1041
|
# msf_exploit returns 1 when no session opened (exploit ran but target not vulnerable)
|
|
1042
|
-
|
|
1042
|
+
# nikto returns non-zero when it finds vulnerabilities (not an error!)
|
|
1043
|
+
tools_with_nonzero_success = ['gobuster', 'hydra', 'medusa', 'msf_exploit', 'nikto']
|
|
1043
1044
|
|
|
1044
1045
|
if tool.lower() in tools_with_nonzero_success:
|
|
1045
1046
|
# Let parser determine status
|
|
@@ -1433,6 +1434,21 @@ def _detect_and_recover_stale_jobs() -> int:
|
|
|
1433
1434
|
"status": status,
|
|
1434
1435
|
"parse_result": parse_result
|
|
1435
1436
|
})
|
|
1437
|
+
|
|
1438
|
+
# Mark for auto-chaining if conditions are met
|
|
1439
|
+
try:
|
|
1440
|
+
from souleyez.core.tool_chaining import ToolChaining
|
|
1441
|
+
chaining = ToolChaining()
|
|
1442
|
+
if chaining.is_enabled() and is_chainable(status):
|
|
1443
|
+
_update_job(jid, chainable=True)
|
|
1444
|
+
_append_worker_log(f"job {jid} stale recovery marked as chainable")
|
|
1445
|
+
logger.info("Stale job marked as chainable", extra={
|
|
1446
|
+
"job_id": jid,
|
|
1447
|
+
"tool": tool,
|
|
1448
|
+
"status": status
|
|
1449
|
+
})
|
|
1450
|
+
except Exception as chain_err:
|
|
1451
|
+
_append_worker_log(f"job {jid} stale recovery chainable error: {chain_err}")
|
|
1436
1452
|
except Exception as parse_err:
|
|
1437
1453
|
_append_worker_log(f"job {jid} stale recovery parse exception: {parse_err}")
|
|
1438
1454
|
|
souleyez/main.py
CHANGED
|
@@ -173,7 +173,7 @@ def _check_privileged_tools():
|
|
|
173
173
|
|
|
174
174
|
|
|
175
175
|
@click.group()
|
|
176
|
-
@click.version_option(version='2.
|
|
176
|
+
@click.version_option(version='2.25.0')
|
|
177
177
|
def cli():
|
|
178
178
|
"""SoulEyez - AI-Powered Pentesting Platform by CyberSoul Security"""
|
|
179
179
|
from souleyez.log_config import init_logging
|
|
@@ -1388,19 +1388,24 @@ def _run_doctor(fix=False, verbose=False):
|
|
|
1388
1388
|
path_dirs = os.environ.get('PATH', '').split(':')
|
|
1389
1389
|
pipx_bin = str(Path.home() / '.local' / 'bin')
|
|
1390
1390
|
go_bin = str(Path.home() / 'go' / 'bin')
|
|
1391
|
+
|
|
1392
|
+
# Detect shell config file (zsh for Kali, bash for others)
|
|
1393
|
+
shell = os.environ.get('SHELL', '/bin/bash')
|
|
1394
|
+
shell_rc = '~/.zshrc' if 'zsh' in shell else '~/.bashrc'
|
|
1395
|
+
|
|
1391
1396
|
if pipx_bin in path_dirs:
|
|
1392
1397
|
if verbose:
|
|
1393
1398
|
check_pass("PATH includes ~/.local/bin (pipx)")
|
|
1394
1399
|
else:
|
|
1395
1400
|
if Path(pipx_bin).exists() and any(Path(pipx_bin).iterdir()):
|
|
1396
|
-
check_warn("~/.local/bin not in PATH", "Add to
|
|
1401
|
+
check_warn("~/.local/bin not in PATH", f"Add to {shell_rc}: export PATH=\"$HOME/.local/bin:$PATH\"")
|
|
1397
1402
|
|
|
1398
1403
|
if go_bin in path_dirs:
|
|
1399
1404
|
if verbose:
|
|
1400
1405
|
check_pass("PATH includes ~/go/bin")
|
|
1401
1406
|
else:
|
|
1402
1407
|
if Path(go_bin).exists() and any(Path(go_bin).iterdir()):
|
|
1403
|
-
check_warn("~/go/bin not in PATH", "Add to
|
|
1408
|
+
check_warn("~/go/bin not in PATH", f"Add to {shell_rc}: export PATH=\"$HOME/go/bin:$PATH\"")
|
|
1404
1409
|
|
|
1405
1410
|
# Check database is writable
|
|
1406
1411
|
if db_path.exists():
|
|
@@ -1430,8 +1435,10 @@ def _run_doctor(fix=False, verbose=False):
|
|
|
1430
1435
|
# Section 7: MSF Database (if msfconsole available)
|
|
1431
1436
|
if shutil.which('msfconsole'):
|
|
1432
1437
|
click.echo(click.style("Metasploit", bold=True))
|
|
1438
|
+
# Check user config first, then system-wide config (Kali uses system-wide)
|
|
1433
1439
|
msf_db = Path.home() / '.msf4' / 'database.yml'
|
|
1434
|
-
|
|
1440
|
+
system_msf_db = Path('/usr/share/metasploit-framework/config/database.yml')
|
|
1441
|
+
if msf_db.exists() or system_msf_db.exists():
|
|
1435
1442
|
check_pass("MSF database configured")
|
|
1436
1443
|
else:
|
|
1437
1444
|
check_fail("MSF database not initialized", "msfdb init")
|
souleyez/plugins/nuclei.py
CHANGED
|
@@ -207,19 +207,51 @@ class NucleiPlugin(PluginBase):
|
|
|
207
207
|
return True
|
|
208
208
|
return False
|
|
209
209
|
|
|
210
|
-
def
|
|
211
|
-
"""
|
|
212
|
-
|
|
210
|
+
def _normalize_target(self, target: str, args: List[str] = None, log_path: str = None) -> str:
|
|
211
|
+
"""
|
|
212
|
+
Normalize target for Nuclei scanning.
|
|
213
|
+
|
|
214
|
+
- URLs are validated and passed through
|
|
215
|
+
- Bare IPs/domains get http:// prepended for web scanning
|
|
216
|
+
|
|
217
|
+
This fixes the issue where nmap chains pass bare IPs but Nuclei
|
|
218
|
+
needs URLs to properly scan web services.
|
|
219
|
+
"""
|
|
220
|
+
import re
|
|
221
|
+
|
|
222
|
+
# Already a URL - validate and return
|
|
213
223
|
if target.startswith(('http://', 'https://')):
|
|
214
224
|
try:
|
|
215
|
-
|
|
225
|
+
return validate_url(target)
|
|
216
226
|
except ValidationError as e:
|
|
217
227
|
if log_path:
|
|
218
228
|
with open(log_path, 'w') as f:
|
|
219
229
|
f.write(f"ERROR: Invalid URL: {e}\n")
|
|
220
230
|
return None
|
|
221
231
|
|
|
232
|
+
# Bare IP or domain - prepend http:// for web scanning
|
|
233
|
+
# This is needed because Nuclei web templates require a URL
|
|
234
|
+
ip_pattern = r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(:\d+)?$'
|
|
235
|
+
domain_pattern = r'^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?)*$'
|
|
236
|
+
|
|
237
|
+
if re.match(ip_pattern, target) or re.match(domain_pattern, target):
|
|
238
|
+
# Log the conversion
|
|
239
|
+
if log_path:
|
|
240
|
+
with open(log_path, 'a') as f:
|
|
241
|
+
f.write(f"NOTE: Converting bare target '{target}' to 'http://{target}' for web scanning\n")
|
|
242
|
+
return f"http://{target}"
|
|
243
|
+
|
|
244
|
+
# Unknown format - return as-is
|
|
245
|
+
return target
|
|
246
|
+
|
|
247
|
+
def build_command(self, target: str, args: List[str] = None, label: str = "", log_path: str = None):
|
|
248
|
+
"""Build nuclei command for background execution with PID tracking."""
|
|
222
249
|
args = args or []
|
|
250
|
+
|
|
251
|
+
# Normalize target (convert bare IPs to URLs)
|
|
252
|
+
target = self._normalize_target(target, args, log_path)
|
|
253
|
+
if target is None:
|
|
254
|
+
return None
|
|
223
255
|
args = [arg.replace("<target>", target) for arg in args]
|
|
224
256
|
|
|
225
257
|
cmd = ["nuclei", "-target", target]
|
|
@@ -252,21 +284,13 @@ class NucleiPlugin(PluginBase):
|
|
|
252
284
|
|
|
253
285
|
def run(self, target: str, args: List[str] = None, label: str = "", log_path: str = None) -> int:
|
|
254
286
|
"""Execute nuclei scan and write JSON output to log_path."""
|
|
255
|
-
|
|
256
|
-
# For URLs, validate them. For bare IPs/domains, let Nuclei auto-detect protocols
|
|
257
|
-
if target.startswith(('http://', 'https://')):
|
|
258
|
-
try:
|
|
259
|
-
target = validate_url(target)
|
|
260
|
-
except ValidationError as e:
|
|
261
|
-
if log_path:
|
|
262
|
-
with open(log_path, 'w') as f:
|
|
263
|
-
f.write(f"ERROR: Invalid URL: {e}\n")
|
|
264
|
-
return 1
|
|
265
|
-
raise ValueError(f"Invalid URL: {e}")
|
|
266
|
-
# Otherwise keep target as-is (IP or domain) for Nuclei auto-detect protocols
|
|
267
|
-
|
|
268
287
|
args = args or []
|
|
269
288
|
|
|
289
|
+
# Normalize target (convert bare IPs to URLs)
|
|
290
|
+
target = self._normalize_target(target, args, log_path)
|
|
291
|
+
if target is None:
|
|
292
|
+
return 1
|
|
293
|
+
|
|
270
294
|
# Replace <target> placeholder
|
|
271
295
|
args = [arg.replace("<target>", target) for arg in args]
|
|
272
296
|
|
souleyez/ui/interactive.py
CHANGED
|
@@ -130,6 +130,63 @@ def render_standard_header(title: str, width: int = None) -> None:
|
|
|
130
130
|
click.echo()
|
|
131
131
|
|
|
132
132
|
|
|
133
|
+
def parse_syslog_description(desc: str) -> str:
|
|
134
|
+
"""
|
|
135
|
+
Extract meaningful message from syslog-formatted descriptions.
|
|
136
|
+
|
|
137
|
+
Syslog format: <timestamp> <host> [timestamp] <program>[pid]: <message>
|
|
138
|
+
Example input: "Jan 8 07:00:05 192.168.1.111 Jan 8 07:00:05 eyez CRON[537281]: pam_unix(cron:session): session closed for user yoda"
|
|
139
|
+
Example output: "CRON: pam_unix(cron:session): session closed for user yoda"
|
|
140
|
+
"""
|
|
141
|
+
import re
|
|
142
|
+
|
|
143
|
+
if not desc:
|
|
144
|
+
return 'No description'
|
|
145
|
+
|
|
146
|
+
# Try to find the actual message after common syslog patterns
|
|
147
|
+
# Pattern 1: Look for process name with PID followed by colon (e.g., "CRON[537281]:")
|
|
148
|
+
pid_match = re.search(r'([A-Za-z_][A-Za-z0-9_-]*)\[(\d+)\]:\s*(.+)$', desc)
|
|
149
|
+
if pid_match:
|
|
150
|
+
process_name = pid_match.group(1)
|
|
151
|
+
message = pid_match.group(3)
|
|
152
|
+
return f"{process_name}: {message}"
|
|
153
|
+
|
|
154
|
+
# Pattern 2: Look for systemd-style messages (e.g., "systemd[1]: Started...")
|
|
155
|
+
systemd_match = re.search(r'(systemd(?:-[a-z]+)?)\[?\d*\]?:\s*(.+)$', desc, re.IGNORECASE)
|
|
156
|
+
if systemd_match:
|
|
157
|
+
return f"{systemd_match.group(1)}: {systemd_match.group(2)}"
|
|
158
|
+
|
|
159
|
+
# Pattern 3: Look for kernel messages
|
|
160
|
+
kernel_match = re.search(r'kernel:\s*(.+)$', desc)
|
|
161
|
+
if kernel_match:
|
|
162
|
+
return f"kernel: {kernel_match.group(1)}"
|
|
163
|
+
|
|
164
|
+
# Pattern 4: Generic - find content after last colon that has substance
|
|
165
|
+
colon_parts = desc.split(': ')
|
|
166
|
+
if len(colon_parts) > 1:
|
|
167
|
+
# Get the meaningful part (usually after the first "process:" pattern)
|
|
168
|
+
for i, part in enumerate(colon_parts):
|
|
169
|
+
# Skip parts that look like timestamps or IPs
|
|
170
|
+
if not re.match(r'^(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec|\d{1,3}\.\d{1,3}|\d{4}-\d{2})', part):
|
|
171
|
+
# Found something meaningful - join from here
|
|
172
|
+
meaningful = ': '.join(colon_parts[i:])
|
|
173
|
+
if len(meaningful) > 10: # Ensure it's substantial
|
|
174
|
+
return meaningful
|
|
175
|
+
|
|
176
|
+
# Pattern 5: Strip leading timestamp patterns
|
|
177
|
+
# Remove patterns like "Jan 8 07:00:05 192.168.1.111 Jan 8 07:00:05 hostname"
|
|
178
|
+
stripped = re.sub(
|
|
179
|
+
r'^(?:[A-Z][a-z]{2}\s+\d+\s+\d{2}:\d{2}:\d{2}\s+\S+\s*)+',
|
|
180
|
+
'', desc
|
|
181
|
+
).strip()
|
|
182
|
+
|
|
183
|
+
if stripped and len(stripped) > 5:
|
|
184
|
+
return stripped
|
|
185
|
+
|
|
186
|
+
# Fallback: return original if no patterns matched
|
|
187
|
+
return desc
|
|
188
|
+
|
|
189
|
+
|
|
133
190
|
def _show_upgrade_prompt(feature_name: str):
|
|
134
191
|
"""Show upgrade prompt when FREE user tries to access Pro feature."""
|
|
135
192
|
from rich.panel import Panel
|
|
@@ -5345,7 +5402,7 @@ def view_job_detail(job_id: int):
|
|
|
5345
5402
|
|
|
5346
5403
|
# Check if tool has a parser - if yes, hide raw logs by default
|
|
5347
5404
|
tool = job.get('tool', '')
|
|
5348
|
-
has_parser = tool in ['dnsrecon', 'nmap', 'nuclei', 'nikto', 'dalfox', 'theharvester', 'sqlmap', 'ffuf', 'gobuster', 'wpscan', 'crackmapexec', 'hydra', 'whois', 'smbmap', 'enum4linux', 'msf_auxiliary', 'searchsploit']
|
|
5405
|
+
has_parser = tool in ['dnsrecon', 'nmap', 'ard', 'nuclei', 'nikto', 'dalfox', 'theharvester', 'sqlmap', 'ffuf', 'gobuster', 'wpscan', 'crackmapexec', 'hydra', 'whois', 'smbmap', 'enum4linux', 'msf_auxiliary', 'searchsploit']
|
|
5349
5406
|
|
|
5350
5407
|
# Show log file if exists
|
|
5351
5408
|
log_path = job.get('log')
|
|
@@ -5941,7 +5998,9 @@ def view_job_detail(job_id: int):
|
|
|
5941
5998
|
pass
|
|
5942
5999
|
|
|
5943
6000
|
# Parse and display Nmap results if available (only when not showing raw logs)
|
|
5944
|
-
|
|
6001
|
+
# ARD plugin uses nmap under the hood, so include it here
|
|
6002
|
+
nmap_based_tools = ['nmap', 'ard']
|
|
6003
|
+
if not show_raw_logs and job.get('tool') in nmap_based_tools and job.get('status') in ['done', 'completed'] and log_path and os.path.exists(log_path):
|
|
5945
6004
|
try:
|
|
5946
6005
|
from souleyez.parsers.nmap_parser import parse_nmap_output
|
|
5947
6006
|
with open(log_path, 'r', encoding='utf-8', errors='replace') as f:
|
|
@@ -8898,7 +8957,8 @@ def _view_wazuh_alerts(engagement_id: int):
|
|
|
8898
8957
|
icon = get_level_icon(level)
|
|
8899
8958
|
rule_id = str(alert.get('rule_id', 'N/A'))[:10]
|
|
8900
8959
|
agent_name = alert.get('agent_name', 'N/A')[:15]
|
|
8901
|
-
|
|
8960
|
+
raw_desc = alert.get('description') or 'No description'
|
|
8961
|
+
desc = parse_syslog_description(raw_desc)[:45]
|
|
8902
8962
|
ts = alert.get('timestamp', 'N/A')
|
|
8903
8963
|
if hasattr(ts, 'strftime'):
|
|
8904
8964
|
ts = ts.strftime('%Y-%m-%d %H:%M:%S')
|
|
@@ -9075,7 +9135,8 @@ def _view_alert_detail(alert: dict):
|
|
|
9075
9135
|
|
|
9076
9136
|
# Get values from normalized format first, then fall back to raw_data
|
|
9077
9137
|
rule_id = alert.get('rule_id') or rule.get('id', 'N/A')
|
|
9078
|
-
|
|
9138
|
+
raw_description = alert.get('description') or rule.get('description', 'N/A')
|
|
9139
|
+
description = parse_syslog_description(raw_description)
|
|
9079
9140
|
level = alert.get('level', 0) or rule.get('level', 0)
|
|
9080
9141
|
severity = alert.get('severity', 'info')
|
|
9081
9142
|
|
|
@@ -15790,6 +15851,37 @@ def view_findings(engagement_id: int):
|
|
|
15790
15851
|
summary_parts.append(f"Filters: {', '.join(active_filters)}")
|
|
15791
15852
|
|
|
15792
15853
|
click.echo(" " + " | ".join(summary_parts))
|
|
15854
|
+
|
|
15855
|
+
# Show tool distribution legend
|
|
15856
|
+
if findings:
|
|
15857
|
+
tool_counts = {}
|
|
15858
|
+
for f in findings:
|
|
15859
|
+
tool = f.get('tool') or 'unknown'
|
|
15860
|
+
tool_counts[tool] = tool_counts.get(tool, 0) + 1
|
|
15861
|
+
|
|
15862
|
+
# Sort by count (descending) and format
|
|
15863
|
+
sorted_tools = sorted(tool_counts.items(), key=lambda x: x[1], reverse=True)
|
|
15864
|
+
tool_parts = [f"{tool}({count})" for tool, count in sorted_tools]
|
|
15865
|
+
|
|
15866
|
+
# Display on one or more lines if needed
|
|
15867
|
+
tool_legend = " Tools: " + " | ".join(tool_parts)
|
|
15868
|
+
if len(tool_legend) > width - 4:
|
|
15869
|
+
# Wrap to multiple lines if too long
|
|
15870
|
+
lines = []
|
|
15871
|
+
current_line = " Tools: "
|
|
15872
|
+
for i, part in enumerate(tool_parts):
|
|
15873
|
+
test_line = current_line + part + (" | " if i < len(tool_parts) - 1 else "")
|
|
15874
|
+
if len(test_line) > width - 4 and current_line != " Tools: ":
|
|
15875
|
+
lines.append(current_line.rstrip(" | "))
|
|
15876
|
+
current_line = " " + part + (" | " if i < len(tool_parts) - 1 else "")
|
|
15877
|
+
else:
|
|
15878
|
+
current_line = test_line
|
|
15879
|
+
lines.append(current_line.rstrip(" | "))
|
|
15880
|
+
for line in lines:
|
|
15881
|
+
click.echo(click.style(line, fg='cyan'))
|
|
15882
|
+
else:
|
|
15883
|
+
click.echo(click.style(tool_legend, fg='cyan'))
|
|
15884
|
+
|
|
15793
15885
|
click.echo()
|
|
15794
15886
|
|
|
15795
15887
|
if not findings:
|
souleyez/ui/tool_setup.py
CHANGED
|
@@ -542,6 +542,9 @@ def run_tool_setup(check_only: bool = False, install_all: bool = False):
|
|
|
542
542
|
|
|
543
543
|
def _run_post_install_tasks(console, distro: str):
|
|
544
544
|
"""Run tasks that should happen after tool installation or when all tools are present."""
|
|
545
|
+
# Ensure PATH is configured in shell rc files
|
|
546
|
+
_add_paths_to_shell_rc()
|
|
547
|
+
|
|
545
548
|
# Configure passwordless sudo for privileged scans
|
|
546
549
|
_configure_sudoers(console)
|
|
547
550
|
|
souleyez/utils/tool_checker.py
CHANGED
|
@@ -721,6 +721,7 @@ def check_msfdb_status() -> Dict[str, any]:
|
|
|
721
721
|
- message: str - Human-readable status message
|
|
722
722
|
"""
|
|
723
723
|
import subprocess
|
|
724
|
+
from pathlib import Path
|
|
724
725
|
|
|
725
726
|
result = {
|
|
726
727
|
'initialized': False,
|
|
@@ -734,6 +735,32 @@ def check_msfdb_status() -> Dict[str, any]:
|
|
|
734
735
|
result['message'] = 'msfdb command not found - Metasploit may not be installed'
|
|
735
736
|
return result
|
|
736
737
|
|
|
738
|
+
# Helper to check if PostgreSQL is running
|
|
739
|
+
def check_postgresql_running() -> bool:
|
|
740
|
+
try:
|
|
741
|
+
proc = subprocess.run(
|
|
742
|
+
['systemctl', 'is-active', 'postgresql'],
|
|
743
|
+
capture_output=True,
|
|
744
|
+
text=True,
|
|
745
|
+
timeout=5
|
|
746
|
+
)
|
|
747
|
+
return proc.returncode == 0 and 'active' in proc.stdout.lower()
|
|
748
|
+
except Exception:
|
|
749
|
+
return False
|
|
750
|
+
|
|
751
|
+
# Helper to check system-wide MSF database config (Kali fallback)
|
|
752
|
+
def check_system_config() -> bool:
|
|
753
|
+
"""Check if system-wide database.yml exists with valid PostgreSQL config."""
|
|
754
|
+
config_path = Path('/usr/share/metasploit-framework/config/database.yml')
|
|
755
|
+
if config_path.exists():
|
|
756
|
+
try:
|
|
757
|
+
content = config_path.read_text()
|
|
758
|
+
# Check for PostgreSQL adapter configuration
|
|
759
|
+
return 'adapter: postgresql' in content and 'database: msf' in content
|
|
760
|
+
except Exception:
|
|
761
|
+
return False
|
|
762
|
+
return False
|
|
763
|
+
|
|
737
764
|
try:
|
|
738
765
|
# Run msfdb status
|
|
739
766
|
proc = subprocess.run(
|
|
@@ -743,10 +770,23 @@ def check_msfdb_status() -> Dict[str, any]:
|
|
|
743
770
|
timeout=10
|
|
744
771
|
)
|
|
745
772
|
output = proc.stdout + proc.stderr
|
|
746
|
-
|
|
747
|
-
# Parse output for status indicators
|
|
748
773
|
output_lower = output.lower()
|
|
749
774
|
|
|
775
|
+
# Check if msfdb requires root (common on Kali)
|
|
776
|
+
if 'run as root' in output_lower or (proc.returncode != 0 and 'error' in output_lower):
|
|
777
|
+
# Fall back to checking system config file and PostgreSQL status
|
|
778
|
+
result['running'] = check_postgresql_running()
|
|
779
|
+
if check_system_config():
|
|
780
|
+
result['initialized'] = True
|
|
781
|
+
if result['running']:
|
|
782
|
+
result['connected'] = True
|
|
783
|
+
result['message'] = 'Database initialized and running'
|
|
784
|
+
else:
|
|
785
|
+
result['message'] = 'Database initialized but PostgreSQL not running - run: sudo systemctl start postgresql'
|
|
786
|
+
else:
|
|
787
|
+
result['message'] = 'Need sudo to verify - run: sudo msfdb status'
|
|
788
|
+
return result
|
|
789
|
+
|
|
750
790
|
# Check if database is initialized
|
|
751
791
|
if 'no database' in output_lower or 'not initialized' in output_lower:
|
|
752
792
|
result['message'] = 'Database not initialized - run: msfdb init'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: souleyez
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.25.0
|
|
4
4
|
Summary: AI-Powered Penetration Testing Platform with 40+ integrated tools
|
|
5
5
|
Author-email: CyberSoul Security <contact@cybersoulsecurity.com>
|
|
6
6
|
Maintainer-email: CyberSoul Security <contact@cybersoulsecurity.com>
|
|
@@ -72,7 +72,7 @@ Welcome to the SoulEyez beta! Thank you for helping us test and improve this pen
|
|
|
72
72
|
|
|
73
73
|
> ⚠️ **Important**: Only use SoulEyez on systems you have explicit authorization to test.
|
|
74
74
|
|
|
75
|
-
## Version: 2.
|
|
75
|
+
## Version: 2.25.0
|
|
76
76
|
|
|
77
77
|
### What's Included
|
|
78
78
|
|
|
@@ -110,6 +110,17 @@ Welcome to the SoulEyez beta! Thank you for helping us test and improve this pen
|
|
|
110
110
|
- **Python**: 3.8 or newer
|
|
111
111
|
- **Storage**: ~500MB for SoulEyez + tools
|
|
112
112
|
|
|
113
|
+
> **🐉 Kali Linux Recommended**
|
|
114
|
+
>
|
|
115
|
+
> SoulEyez performs significantly better on **Kali Linux** than other distributions:
|
|
116
|
+
> - All pentesting tools pre-installed and optimized
|
|
117
|
+
> - Metasploit database and RPC already configured
|
|
118
|
+
> - Security-focused kernel and networking stack
|
|
119
|
+
> - No dependency hunting or version conflicts
|
|
120
|
+
> - Wordlists, databases, and tool configs ready to go
|
|
121
|
+
>
|
|
122
|
+
> While Ubuntu and other Debian-based distros are supported, you may experience slower setup times and occasional tool compatibility issues.
|
|
123
|
+
|
|
113
124
|
### Known Issues
|
|
114
125
|
|
|
115
126
|
- Very large scan outputs (>10MB) may slow the interface
|
|
@@ -299,4 +310,4 @@ Happy hacking! 🛡️
|
|
|
299
310
|
|
|
300
311
|
---
|
|
301
312
|
|
|
302
|
-
**Version**: 2.
|
|
313
|
+
**Version**: 2.24.0 | **Release Date**: January 2026 | **Maintainer**: CyberSoul Security
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
souleyez/__init__.py,sha256=
|
|
1
|
+
souleyez/__init__.py,sha256=Gwu_1X8_-6D9YneU0PXTLXImdMbVJ586ZzZwZ1kCbd8,23
|
|
2
2
|
souleyez/config.py,sha256=av357I3GYRWAklv8Dto-9-5Db699Wq5znez7zo7241Q,11595
|
|
3
3
|
souleyez/devtools.py,sha256=rptmUY4a5eVvYjdEc6273MSagL-D9xibPOFgohVqUno,3508
|
|
4
4
|
souleyez/feature_flags.py,sha256=mo6YAq07lc6sR3lEFKmIwTKxXZ2JPxwa5X97uR_mu50,4642
|
|
5
5
|
souleyez/history.py,sha256=gzs5I_j-3OigIP6yfmBChdqxaFmyUIxvTpzWUPe_Q6c,2853
|
|
6
6
|
souleyez/log_config.py,sha256=MMhPAJOqgXDfuE-xm5g0RxAfWndcmbhFHvIEMm1a_Wo,5830
|
|
7
|
-
souleyez/main.py,sha256=
|
|
7
|
+
souleyez/main.py,sha256=ePJkT92BAJJ5XnTGiwbdYwVs0_CpT4HJoWeSU4rLxxA,119362
|
|
8
8
|
souleyez/scanner.py,sha256=U3IWHRrJ5aQ32dSHiVAHB60w1R_z0E0QxfM99msYNlw,3124
|
|
9
9
|
souleyez/security.py,sha256=S84m1QmnKz_6NgH2I6IBIAorMHxRPNYVFSnks5xjihQ,2479
|
|
10
10
|
souleyez/ui.py,sha256=15pfsqoDPnojAqr5S0TZHJE2ZkSHzkHpNVfVvsRj66A,34301
|
|
@@ -52,12 +52,12 @@ souleyez/core/msf_database.py,sha256=xaGt0wMX15CQv3-s2NobLK8niHgrE98qAkmS9zhrLe8
|
|
|
52
52
|
souleyez/core/msf_integration.py,sha256=J9EXecxq72q65Itv1lBqjSkhh8Zx6SbZO2VPtlZXuOg,64842
|
|
53
53
|
souleyez/core/msf_rpc_client.py,sha256=DL-_uJz_6G1pQud8iTg3_SjRJmgl4-W1YWmb0xg6f8Q,15994
|
|
54
54
|
souleyez/core/msf_rpc_manager.py,sha256=8irWzXdiASVIokGSTf8DV57Uh_DUJ3Q6L-2oR9y8qeI,15572
|
|
55
|
-
souleyez/core/msf_sync_manager.py,sha256=
|
|
55
|
+
souleyez/core/msf_sync_manager.py,sha256=1alAqM2jHiMxbBdPTQL9Vjzbl8XVhrnS2VYhVdfNwUw,27779
|
|
56
56
|
souleyez/core/network_utils.py,sha256=-4WgUE91RBzyXDFgGTxMa0zsWowJ47cEOAKXNeVa-Wc,4555
|
|
57
57
|
souleyez/core/parser_handler.py,sha256=cyZtEDctqMdWgubsU0Jg6o4XqBgyfaJ_AeBHQmmv4hM,5564
|
|
58
58
|
souleyez/core/pending_chains.py,sha256=Dnka7JK7A8gTWCGpTu6qrIgIDIXprkZmwJ0Rm2oWqRE,10972
|
|
59
59
|
souleyez/core/templates.py,sha256=DzlXlAz8_lwAFjjUWPp3r81KCCzbNeK-bkN1IlgQBSU,18112
|
|
60
|
-
souleyez/core/tool_chaining.py,sha256=
|
|
60
|
+
souleyez/core/tool_chaining.py,sha256=WPUJk0ArOFcF8kDUt5ZXgCnAc7LrPE16ETzpDgqFfcg,270028
|
|
61
61
|
souleyez/core/version_utils.py,sha256=UOrOa3qfUdLKdzWT6GAGNV9TauwinXyLyelS8sOk0eE,11769
|
|
62
62
|
souleyez/core/vuln_correlation.py,sha256=U69MSI5I-AtiyOAbXohGDKMpEHRW9y4G_0M1ppRGX18,14765
|
|
63
63
|
souleyez/core/web_utils.py,sha256=f-Dqa6tH8ROnygn6-k7J1y8Qz2f1FmeJnPjPE0WRn34,4902
|
|
@@ -102,7 +102,7 @@ souleyez/detection/__init__.py,sha256=QIhvXjFdjrquQ6A0VQ7GZQkK_EXB59t8Dv9PKXhEUe
|
|
|
102
102
|
souleyez/detection/attack_signatures.py,sha256=akgWwiIkh6WYnghCuLhRV0y6FS0SQ0caGF8tZUc49oA,6965
|
|
103
103
|
souleyez/detection/mitre_mappings.py,sha256=xejE80YK-g8kKaeQoo-vBl8P3t8RTTItbfN0NaVZw6s,20558
|
|
104
104
|
souleyez/detection/validator.py,sha256=-AJ7QSJ3-6jFKLnPG_Rc34IXyF4JPyI82BFUgTA9zw0,15641
|
|
105
|
-
souleyez/docs/README.md,sha256=
|
|
105
|
+
souleyez/docs/README.md,sha256=2xyQnpisNZMMTAmWc1YuRi0KIkUc56X96-qzqKnBATY,7183
|
|
106
106
|
souleyez/docs/api-reference/cli-commands.md,sha256=lTLFnILN3YRVdqCaag7WgsYXfDGglb1TuPexkxDsVdE,12917
|
|
107
107
|
souleyez/docs/api-reference/engagement-api.md,sha256=nd-EvQMtiJrobg2bzFEADp853HP1Uhb9dmgok0_-neE,11672
|
|
108
108
|
souleyez/docs/api-reference/integration-guide.md,sha256=c96uX79ukHyYotLa54wZ20Kx-EUZnrKegTeGkfLD-pw,16285
|
|
@@ -132,7 +132,7 @@ souleyez/docs/user-guide/dependencies.md,sha256=WOPilg0W0U3KnsdGREkM5_gAG7Rr5P10
|
|
|
132
132
|
souleyez/docs/user-guide/evidence-vault.md,sha256=PNg7cIUlVXr41iMJTi66j4qUV2fkrPATljunx0pD5sI,9454
|
|
133
133
|
souleyez/docs/user-guide/exploit-suggestions.md,sha256=Qv9CPwDe9ypKoeUG3XAL6dtg4YA5PmlE5DA6JVHK4Nk,17971
|
|
134
134
|
souleyez/docs/user-guide/getting-started.md,sha256=4QfZiQlYnReORAUpsy2gWzKe1uFHOePZyzDSz8zldgc,20932
|
|
135
|
-
souleyez/docs/user-guide/installation.md,sha256=
|
|
135
|
+
souleyez/docs/user-guide/installation.md,sha256=uM_qBpgG5YjkxSf8u6gUJh0TGYtPlo4pbn_wEXJHX0Q,14431
|
|
136
136
|
souleyez/docs/user-guide/metasploit-integration.md,sha256=kSCai2PO4kiv3BEUSXa6mIC3vCdqIA70GyLVQH_Kaj4,10026
|
|
137
137
|
souleyez/docs/user-guide/rbac.md,sha256=ULY5IbTCoNGOnvRrT1oH5XayCqD10PKoUwRDBRzpK2g,21055
|
|
138
138
|
souleyez/docs/user-guide/report-generation.md,sha256=7Qe47jfPxmZ4U1uuM3kggPLQ6JM7_TCOOhYIvYen4Ao,20754
|
|
@@ -143,7 +143,7 @@ souleyez/docs/user-guide/uninstall.md,sha256=gDknetFhjZ0tnYk4JqhLa369NT4bIRb50rm
|
|
|
143
143
|
souleyez/docs/user-guide/worker-management.md,sha256=hNu6eSTVb6XM4Zbb0I9Y5aL4AA2EiWOSFI6iGjn17kU,12035
|
|
144
144
|
souleyez/docs/user-guide/workflows.md,sha256=4EyZKWRyuWf9wrENJwtidWKN25PGis1Pk33HIHk5UHM,22261
|
|
145
145
|
souleyez/engine/__init__.py,sha256=THI_89hQfAPJDsfzDcur6H9sEGhGAnTxSNim7UOExYc,110
|
|
146
|
-
souleyez/engine/background.py,sha256=
|
|
146
|
+
souleyez/engine/background.py,sha256=Wm7dBzwq8qTi1vYvJk7T2TRktkdNzk50YQGZddcI3NU,66278
|
|
147
147
|
souleyez/engine/base.py,sha256=G35U1d-fygUvzmHH8zxLXw-vyQ9JzcfhGaSYOsHJtzQ,728
|
|
148
148
|
souleyez/engine/job_status.py,sha256=OAEf2rAzapm55m4tc3PSilotdA5ONX15JavUMLre0is,2685
|
|
149
149
|
souleyez/engine/loader.py,sha256=ke6QQVVWozDnqGNBotajC3RBYOa2_DZmv5DAnDZVgIc,2769
|
|
@@ -236,7 +236,7 @@ souleyez/plugins/msf_auxiliary.py,sha256=pOL9yLJr_L0niwaHLPguM2Gaunr3iAe7HdIFbDL
|
|
|
236
236
|
souleyez/plugins/msf_exploit.py,sha256=BNAZz5EO4jgwx64dEB4OE-CQdYkqJk5N4eXT140DRqs,23765
|
|
237
237
|
souleyez/plugins/nikto.py,sha256=_BPzypwNTliBg2Tr6sPGMKYFTvnQgqEHO1xesDZ-6uo,9957
|
|
238
238
|
souleyez/plugins/nmap.py,sha256=Bh3xauEfsDw_hxSarNfLN-J6tNda-mG3DqHHkphpttE,15997
|
|
239
|
-
souleyez/plugins/nuclei.py,sha256=
|
|
239
|
+
souleyez/plugins/nuclei.py,sha256=A2pbSVFGJCu1QlokpV4-d4SJWw4Yv8FrQiLHE9lwRfc,15862
|
|
240
240
|
souleyez/plugins/plugin_base.py,sha256=zB0wzZBBx5V63Ipc7CUEApYADLC8T-A__CLnTaXb49A,6731
|
|
241
241
|
souleyez/plugins/plugin_template.py,sha256=Tcd_JrCBNgT1o88On4vjG5Y7mlGSVGh-hXyUak_KXlE,1786
|
|
242
242
|
souleyez/plugins/responder.py,sha256=ImhISPLtvzqvJSUQrgavlZ_h9ZrJ4s9NrTkPAhEcHQM,13160
|
|
@@ -338,7 +338,7 @@ souleyez/ui/export_view.py,sha256=0nQvVsKk7FU4uRzSfJ_qBZh_Lfn8hgGA2rbJ5bNg5-Y,65
|
|
|
338
338
|
souleyez/ui/gap_analysis_view.py,sha256=AytAOEBq010wwo9hne1TE-uJpY_xicjLrFANbvN3r3w,30727
|
|
339
339
|
souleyez/ui/help_system.py,sha256=nKGxLaMi-TKYs6xudTyw_tZqBb1cGFEuYYh6N-MAsJE,16648
|
|
340
340
|
souleyez/ui/intelligence_view.py,sha256=VeAQ-3mANRnLIVpRqocL3JV0HUmJtADdxDeC5lzQhE0,32168
|
|
341
|
-
souleyez/ui/interactive.py,sha256=
|
|
341
|
+
souleyez/ui/interactive.py,sha256=d-fGuSDG1n2a5GeF1aY_mZMTDWqMsQfBB2_-1kpSobw,1375354
|
|
342
342
|
souleyez/ui/interactive_selector.py,sha256=6A51fgmFRnemBY0aCPHIhK2Rpba16NjSGKLzC0Q5vI8,16407
|
|
343
343
|
souleyez/ui/log_formatter.py,sha256=akhIkYoO_cCaKxS1V5N3iPmIrHzgsU7pmsedx70s9TI,3845
|
|
344
344
|
souleyez/ui/menu_components.py,sha256=N8zq2QXGmfaLJ08l53MMYt1y-5LRWgpZH6r8nXHonj8,3519
|
|
@@ -355,15 +355,15 @@ souleyez/ui/team_dashboard.py,sha256=ejM_44nbJbEIPxxxdEK7SCPcqQtcuJLjoO-C53qED2Y
|
|
|
355
355
|
souleyez/ui/template_selector.py,sha256=qQJkFNnVjYctb-toeYlupP_U1asGrJWYi5-HR89Ab9g,19103
|
|
356
356
|
souleyez/ui/terminal.py,sha256=Sw9ma1-DZclJE1sENjTZ3Q7r-Ct1NiB3Lpmv-RZW5tE,2372
|
|
357
357
|
souleyez/ui/timeline_view.py,sha256=Ze8Mev9VE4_ECdNFEJwZK2V42EBguR83uCCdwAbJqmc,11111
|
|
358
|
-
souleyez/ui/tool_setup.py,sha256=
|
|
358
|
+
souleyez/ui/tool_setup.py,sha256=pkOUr-1inZlYnvaIc8Kj-Qaxk2KHWR2opJAgI_Er-Wo,32591
|
|
359
359
|
souleyez/ui/tutorial.py,sha256=GGbBsze0ioL00WBWKEwPKy1ikegP1eusI2REDVMx4gY,14262
|
|
360
360
|
souleyez/ui/tutorial_state.py,sha256=Thf7_qCj4VKjG7UqgJqa9kjIqiFUU-7Q7kG4v-u2B4A,8123
|
|
361
361
|
souleyez/ui/wazuh_vulns_view.py,sha256=3vJJEmrjgS2wD6EDB7ZV7WxgytBHTm-1WqNDjp7lVEI,21830
|
|
362
362
|
souleyez/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
363
|
-
souleyez/utils/tool_checker.py,sha256=
|
|
364
|
-
souleyez-2.
|
|
365
|
-
souleyez-2.
|
|
366
|
-
souleyez-2.
|
|
367
|
-
souleyez-2.
|
|
368
|
-
souleyez-2.
|
|
369
|
-
souleyez-2.
|
|
363
|
+
souleyez/utils/tool_checker.py,sha256=kQcXJVY5NiO-orQAUnpHhpQvR5UOBNHJ0PaT0fBxYoQ,30782
|
|
364
|
+
souleyez-2.25.0.dist-info/licenses/LICENSE,sha256=J7vDD5QMF4w2oSDm35eBgosATE70ah1M40u9W4EpTZs,1090
|
|
365
|
+
souleyez-2.25.0.dist-info/METADATA,sha256=CqQ6AFaFoE6vTx2bU3l9vXeH16I3rd9o8qtIGmvL4Yo,10691
|
|
366
|
+
souleyez-2.25.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
367
|
+
souleyez-2.25.0.dist-info/entry_points.txt,sha256=bN5W1dhjDZJl3TKclMjRpfQvGPmyrJLwwDuCj_X39HE,48
|
|
368
|
+
souleyez-2.25.0.dist-info/top_level.txt,sha256=afAMzS9p4lcdBNxhGo6jl3ipQE9HUvvNIPOdjtPjr_Q,9
|
|
369
|
+
souleyez-2.25.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|