souleyez 2.16.0__py3-none-any.whl → 2.22.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/tool_chaining.py +99 -7
- souleyez/docs/README.md +2 -2
- souleyez/engine/background.py +9 -1
- souleyez/engine/result_handler.py +40 -0
- souleyez/integrations/siem/splunk.py +58 -11
- souleyez/main.py +1 -1
- souleyez/parsers/nmap_parser.py +97 -1
- souleyez/parsers/smbmap_parser.py +30 -2
- souleyez/parsers/sqlmap_parser.py +54 -17
- souleyez/plugins/gobuster.py +96 -3
- souleyez/plugins/msf_exploit.py +6 -3
- souleyez/ui/interactive.py +31 -13
- souleyez/ui/setup_wizard.py +331 -53
- souleyez/utils/tool_checker.py +30 -8
- {souleyez-2.16.0.dist-info → souleyez-2.22.0.dist-info}/METADATA +3 -3
- {souleyez-2.16.0.dist-info → souleyez-2.22.0.dist-info}/RECORD +21 -21
- {souleyez-2.16.0.dist-info → souleyez-2.22.0.dist-info}/WHEEL +0 -0
- {souleyez-2.16.0.dist-info → souleyez-2.22.0.dist-info}/entry_points.txt +0 -0
- {souleyez-2.16.0.dist-info → souleyez-2.22.0.dist-info}/licenses/LICENSE +0 -0
- {souleyez-2.16.0.dist-info → souleyez-2.22.0.dist-info}/top_level.txt +0 -0
souleyez/ui/setup_wizard.py
CHANGED
|
@@ -7,7 +7,9 @@ import time
|
|
|
7
7
|
import click
|
|
8
8
|
import getpass
|
|
9
9
|
import shutil
|
|
10
|
+
import subprocess
|
|
10
11
|
from pathlib import Path
|
|
12
|
+
from typing import List, Dict, Optional
|
|
11
13
|
from souleyez.ui.design_system import DesignSystem
|
|
12
14
|
|
|
13
15
|
|
|
@@ -26,16 +28,268 @@ def mark_wizard_completed():
|
|
|
26
28
|
WIZARD_STATE_FILE.touch()
|
|
27
29
|
|
|
28
30
|
|
|
31
|
+
def _check_disk_space(min_mb: int = 500) -> bool:
|
|
32
|
+
"""Check if there's enough disk space for installs."""
|
|
33
|
+
try:
|
|
34
|
+
usage = shutil.disk_usage('/')
|
|
35
|
+
free_mb = usage.free // (1024 * 1024)
|
|
36
|
+
return free_mb >= min_mb
|
|
37
|
+
except Exception:
|
|
38
|
+
return True # Assume OK if we can't check
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _configure_sudoers(tool_name: str, tool_path: str) -> bool:
|
|
42
|
+
"""
|
|
43
|
+
Add NOPASSWD sudoers entry for privileged tool.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
tool_name: Name of the tool (used for sudoers filename)
|
|
47
|
+
tool_path: Absolute path to tool binary
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
True if configured successfully, False otherwise
|
|
51
|
+
"""
|
|
52
|
+
# Skip if running as root
|
|
53
|
+
if os.geteuid() == 0:
|
|
54
|
+
return True
|
|
55
|
+
|
|
56
|
+
username = getpass.getuser()
|
|
57
|
+
sudoers_file = f"/etc/sudoers.d/{tool_name}"
|
|
58
|
+
sudoers_line = f"{username} ALL=(ALL) NOPASSWD: {tool_path}"
|
|
59
|
+
tmp_file = f"/tmp/sudoers_{tool_name}_{os.getpid()}"
|
|
60
|
+
|
|
61
|
+
try:
|
|
62
|
+
# Write to temp file first
|
|
63
|
+
with open(tmp_file, 'w') as f:
|
|
64
|
+
f.write(sudoers_line + '\n')
|
|
65
|
+
|
|
66
|
+
# Validate syntax with visudo before applying
|
|
67
|
+
result = subprocess.run(
|
|
68
|
+
['sudo', 'visudo', '-c', '-f', tmp_file],
|
|
69
|
+
capture_output=True,
|
|
70
|
+
timeout=30
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
if result.returncode != 0:
|
|
74
|
+
click.echo(f" {click.style('!', fg='yellow')} Invalid sudoers syntax for {tool_name}, skipping")
|
|
75
|
+
os.unlink(tmp_file)
|
|
76
|
+
return False
|
|
77
|
+
|
|
78
|
+
# Safe to move to sudoers.d
|
|
79
|
+
subprocess.run(['sudo', 'mv', tmp_file, sudoers_file], check=True, timeout=30)
|
|
80
|
+
subprocess.run(['sudo', 'chmod', '0440', sudoers_file], check=True, timeout=30)
|
|
81
|
+
|
|
82
|
+
return True
|
|
83
|
+
|
|
84
|
+
except subprocess.TimeoutExpired:
|
|
85
|
+
click.echo(f" {click.style('!', fg='yellow')} Timeout configuring sudoers for {tool_name}")
|
|
86
|
+
return False
|
|
87
|
+
except Exception as e:
|
|
88
|
+
click.echo(f" {click.style('!', fg='yellow')} Error configuring sudoers for {tool_name}: {e}")
|
|
89
|
+
# Clean up temp file if it exists
|
|
90
|
+
if os.path.exists(tmp_file):
|
|
91
|
+
try:
|
|
92
|
+
os.unlink(tmp_file)
|
|
93
|
+
except Exception:
|
|
94
|
+
pass
|
|
95
|
+
return False
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _run_tool_installs(
|
|
99
|
+
missing_tools: List[Dict],
|
|
100
|
+
wrong_version_tools: List[Dict],
|
|
101
|
+
distro: str
|
|
102
|
+
) -> bool:
|
|
103
|
+
"""
|
|
104
|
+
Prompt user and run install/upgrade commands.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
missing_tools: List of missing tool dicts with 'name', 'install', 'tool_info'
|
|
108
|
+
wrong_version_tools: List of wrong version tool dicts
|
|
109
|
+
distro: Detected distribution
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
True if any installs were run
|
|
113
|
+
"""
|
|
114
|
+
from souleyez.utils.tool_checker import EXTERNAL_TOOLS, get_upgrade_command
|
|
115
|
+
|
|
116
|
+
all_installs = []
|
|
117
|
+
|
|
118
|
+
# Add wrong version tools (upgrades)
|
|
119
|
+
for tool in wrong_version_tools:
|
|
120
|
+
# Try to get upgrade command, fall back to install command
|
|
121
|
+
tool_info = tool.get('tool_info', {})
|
|
122
|
+
upgrade_cmd = get_upgrade_command(tool_info, distro) if tool_info else None
|
|
123
|
+
install_cmd = upgrade_cmd or tool.get('install', '')
|
|
124
|
+
|
|
125
|
+
all_installs.append({
|
|
126
|
+
'name': tool['name'],
|
|
127
|
+
'cmd': install_cmd,
|
|
128
|
+
'action': 'upgrade',
|
|
129
|
+
'tool_info': tool_info
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
# Add missing tools (installs)
|
|
133
|
+
for tool in missing_tools:
|
|
134
|
+
all_installs.append({
|
|
135
|
+
'name': tool['name'],
|
|
136
|
+
'cmd': tool['install'],
|
|
137
|
+
'action': 'install',
|
|
138
|
+
'tool_info': tool.get('tool_info', {})
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
if not all_installs:
|
|
142
|
+
return False
|
|
143
|
+
|
|
144
|
+
# Check disk space
|
|
145
|
+
if not _check_disk_space(500):
|
|
146
|
+
click.echo()
|
|
147
|
+
click.echo(f" {click.style('!', fg='yellow')} Low disk space (<500MB free). Installs may fail.")
|
|
148
|
+
if not click.confirm(" Continue anyway?", default=False):
|
|
149
|
+
return False
|
|
150
|
+
|
|
151
|
+
# Show what will be installed/upgraded
|
|
152
|
+
click.echo()
|
|
153
|
+
click.echo(f" {len(all_installs)} tool(s) to install/upgrade:")
|
|
154
|
+
for item in all_installs:
|
|
155
|
+
action_color = 'cyan' if item['action'] == 'upgrade' else 'green'
|
|
156
|
+
click.echo(f" - {item['name']} ({click.style(item['action'], fg=action_color)})")
|
|
157
|
+
|
|
158
|
+
click.echo()
|
|
159
|
+
if not click.confirm(" Install/upgrade now?", default=False):
|
|
160
|
+
click.echo(" Skipped. You can run 'souleyez setup' later to install tools.")
|
|
161
|
+
return False
|
|
162
|
+
|
|
163
|
+
# Request sudo upfront
|
|
164
|
+
click.echo()
|
|
165
|
+
click.echo(" Requesting sudo access...")
|
|
166
|
+
result = subprocess.run(['sudo', '-v'], check=False)
|
|
167
|
+
if result.returncode != 0:
|
|
168
|
+
click.echo(f" {click.style('x', fg='red')} Could not obtain sudo. Aborting installs.")
|
|
169
|
+
return False
|
|
170
|
+
|
|
171
|
+
# Update apt cache first (for apt-based installs)
|
|
172
|
+
has_apt_installs = any('apt' in item['cmd'] for item in all_installs)
|
|
173
|
+
if has_apt_installs:
|
|
174
|
+
click.echo()
|
|
175
|
+
click.echo(" Updating package lists...")
|
|
176
|
+
result = subprocess.run(['sudo', 'apt', 'update'], capture_output=True)
|
|
177
|
+
if result.returncode != 0:
|
|
178
|
+
click.echo(f" {click.style('!', fg='yellow')} apt update had issues, continuing anyway...")
|
|
179
|
+
|
|
180
|
+
# Track results for summary
|
|
181
|
+
results = []
|
|
182
|
+
|
|
183
|
+
# Run each install command
|
|
184
|
+
for item in all_installs:
|
|
185
|
+
click.echo()
|
|
186
|
+
click.echo(f" {click.style(item['action'].capitalize() + 'ing', fg='cyan')} {item['name']}...")
|
|
187
|
+
|
|
188
|
+
try:
|
|
189
|
+
# Run install command
|
|
190
|
+
proc = subprocess.run(
|
|
191
|
+
item['cmd'],
|
|
192
|
+
shell=True, # nosec B602 - commands from trusted EXTERNAL_TOOLS
|
|
193
|
+
timeout=600 # 10 minute timeout per tool
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
if proc.returncode == 0:
|
|
197
|
+
click.echo(f" {click.style('✓', fg='green')} {item['name']} {item['action']} complete")
|
|
198
|
+
results.append({'name': item['name'], 'success': True, 'tool_info': item['tool_info']})
|
|
199
|
+
else:
|
|
200
|
+
click.echo(f" {click.style('✗', fg='red')} {item['name']} failed (exit {proc.returncode})")
|
|
201
|
+
results.append({'name': item['name'], 'success': False, 'tool_info': item['tool_info']})
|
|
202
|
+
|
|
203
|
+
except subprocess.TimeoutExpired:
|
|
204
|
+
click.echo(f" {click.style('✗', fg='red')} {item['name']} timed out")
|
|
205
|
+
results.append({'name': item['name'], 'success': False, 'tool_info': item['tool_info']})
|
|
206
|
+
except Exception as e:
|
|
207
|
+
click.echo(f" {click.style('✗', fg='red')} {item['name']} error: {e}")
|
|
208
|
+
results.append({'name': item['name'], 'success': False, 'tool_info': item['tool_info']})
|
|
209
|
+
|
|
210
|
+
# Configure sudoers for privileged tools that installed successfully
|
|
211
|
+
click.echo()
|
|
212
|
+
click.echo(" Configuring permissions for privileged tools...")
|
|
213
|
+
|
|
214
|
+
sudoers_configured = False
|
|
215
|
+
for res in results:
|
|
216
|
+
if not res['success']:
|
|
217
|
+
continue
|
|
218
|
+
|
|
219
|
+
tool_info = res['tool_info']
|
|
220
|
+
if not tool_info.get('needs_sudo'):
|
|
221
|
+
continue
|
|
222
|
+
|
|
223
|
+
sudoers_configured = True
|
|
224
|
+
# Find the actual binary path
|
|
225
|
+
command = tool_info.get('command', res['name'])
|
|
226
|
+
tool_path = shutil.which(command)
|
|
227
|
+
|
|
228
|
+
# Check alt_commands if primary not found
|
|
229
|
+
if not tool_path and tool_info.get('alt_commands'):
|
|
230
|
+
for alt in tool_info['alt_commands']:
|
|
231
|
+
tool_path = shutil.which(alt)
|
|
232
|
+
if tool_path:
|
|
233
|
+
break
|
|
234
|
+
|
|
235
|
+
if tool_path:
|
|
236
|
+
if _configure_sudoers(res['name'], tool_path):
|
|
237
|
+
click.echo(f" {click.style('✓', fg='green')} {res['name']} configured for passwordless sudo")
|
|
238
|
+
else:
|
|
239
|
+
click.echo(f" {click.style('!', fg='yellow')} {res['name']} sudoers config failed")
|
|
240
|
+
else:
|
|
241
|
+
click.echo(f" {click.style('!', fg='yellow')} {res['name']} binary not found, skipping sudoers")
|
|
242
|
+
|
|
243
|
+
if not sudoers_configured:
|
|
244
|
+
click.echo(" No privileged tools needed configuration.")
|
|
245
|
+
|
|
246
|
+
# Re-verify installed tools
|
|
247
|
+
click.echo()
|
|
248
|
+
click.echo(" Verifying installations...")
|
|
249
|
+
|
|
250
|
+
success_count = 0
|
|
251
|
+
fail_count = 0
|
|
252
|
+
|
|
253
|
+
for res in results:
|
|
254
|
+
tool_info = res['tool_info']
|
|
255
|
+
command = tool_info.get('command', res['name'])
|
|
256
|
+
alt_commands = tool_info.get('alt_commands')
|
|
257
|
+
|
|
258
|
+
# Check if tool is now available
|
|
259
|
+
found = shutil.which(command) is not None
|
|
260
|
+
if not found and alt_commands:
|
|
261
|
+
for alt in alt_commands:
|
|
262
|
+
if shutil.which(alt):
|
|
263
|
+
found = True
|
|
264
|
+
break
|
|
265
|
+
|
|
266
|
+
if found:
|
|
267
|
+
click.echo(f" {click.style('✓', fg='green')} {res['name']} verified")
|
|
268
|
+
success_count += 1
|
|
269
|
+
else:
|
|
270
|
+
click.echo(f" {click.style('✗', fg='red')} {res['name']} not found after install")
|
|
271
|
+
fail_count += 1
|
|
272
|
+
|
|
273
|
+
# Summary
|
|
274
|
+
click.echo()
|
|
275
|
+
if fail_count == 0:
|
|
276
|
+
click.echo(f" {click.style('✓', fg='green')} All {success_count} tool(s) installed successfully!")
|
|
277
|
+
else:
|
|
278
|
+
click.echo(f" {click.style('!', fg='yellow')} {success_count} succeeded, {fail_count} failed")
|
|
279
|
+
click.echo(" Failed tools may need manual installation.")
|
|
280
|
+
|
|
281
|
+
return True
|
|
282
|
+
|
|
29
283
|
|
|
30
284
|
def _show_wizard_banner():
|
|
31
285
|
"""Display the SoulEyez ASCII banner for wizard steps."""
|
|
32
286
|
click.echo()
|
|
33
|
-
click.echo(click.style(" ███████╗ ██████╗ ██╗ ██╗██╗ ███████╗██╗ ██╗███████╗███████╗", fg='bright_cyan', bold=True))
|
|
34
|
-
click.echo(click.style(" ██╔════╝██╔═══██╗██║ ██║██║ ██╔════╝╚██╗ ██╔╝██╔════╝╚══███╔╝", fg='bright_cyan', bold=True))
|
|
35
|
-
click.echo(click.style(" ███████╗██║ ██║██║ ██║██║ █████╗ ╚████╔╝ █████╗ ███╔╝ ", fg='bright_cyan', bold=True))
|
|
36
|
-
click.echo(click.style(" ╚════██║██║ ██║██║ ██║██║ ██╔══╝ ╚██╔╝ ██╔══╝ ███╔╝ ", fg='bright_cyan', bold=True))
|
|
37
|
-
click.echo(click.style(" ███████║╚██████╔╝╚██████╔╝███████╗███████╗ ██║ ███████╗███████╗", fg='bright_cyan', bold=True))
|
|
38
|
-
click.echo(click.style(" ╚══════╝ ╚═════╝ ╚═════╝ ╚══════╝╚══════╝ ╚═╝ ╚══════╝╚══════╝", fg='bright_cyan', bold=True))
|
|
287
|
+
click.echo(click.style(" ███████╗ ██████╗ ██╗ ██╗██╗ ███████╗██╗ ██╗███████╗███████╗", fg='bright_cyan', bold=True) + click.style(" ▄██▄", fg='bright_blue', bold=True))
|
|
288
|
+
click.echo(click.style(" ██╔════╝██╔═══██╗██║ ██║██║ ██╔════╝╚██╗ ██╔╝██╔════╝╚══███╔╝", fg='bright_cyan', bold=True) + click.style(" ▄█▀ ▀█▄", fg='bright_blue', bold=True))
|
|
289
|
+
click.echo(click.style(" ███████╗██║ ██║██║ ██║██║ █████╗ ╚████╔╝ █████╗ ███╔╝ ", fg='bright_cyan', bold=True) + click.style(" █ ◉ █", fg='bright_blue', bold=True))
|
|
290
|
+
click.echo(click.style(" ╚════██║██║ ██║██║ ██║██║ ██╔══╝ ╚██╔╝ ██╔══╝ ███╔╝ ", fg='bright_cyan', bold=True) + click.style(" █ ═══ █", fg='bright_blue', bold=True))
|
|
291
|
+
click.echo(click.style(" ███████║╚██████╔╝╚██████╔╝███████╗███████╗ ██║ ███████╗███████╗", fg='bright_cyan', bold=True) + click.style(" ▀█▄ ▄█▀", fg='bright_blue', bold=True))
|
|
292
|
+
click.echo(click.style(" ╚══════╝ ╚═════╝ ╚═════╝ ╚══════╝╚══════╝ ╚═╝ ╚══════╝╚══════╝", fg='bright_cyan', bold=True) + click.style(" ▀██▀", fg='bright_blue', bold=True))
|
|
39
293
|
click.echo()
|
|
40
294
|
click.echo(click.style(" Created by CyberSoul SecurITy", fg='bright_blue'))
|
|
41
295
|
click.echo()
|
|
@@ -44,7 +298,7 @@ def _show_wizard_banner():
|
|
|
44
298
|
def run_setup_wizard() -> bool:
|
|
45
299
|
"""
|
|
46
300
|
Run the setup wizard for new users.
|
|
47
|
-
|
|
301
|
+
|
|
48
302
|
Returns:
|
|
49
303
|
bool: True if wizard completed, False if skipped/cancelled
|
|
50
304
|
"""
|
|
@@ -53,20 +307,20 @@ def run_setup_wizard() -> bool:
|
|
|
53
307
|
from souleyez.auth import get_current_user, Tier
|
|
54
308
|
user = get_current_user()
|
|
55
309
|
is_pro = user and user.tier == Tier.PRO
|
|
56
|
-
|
|
310
|
+
|
|
57
311
|
# Step 1: Welcome
|
|
58
312
|
if not _wizard_welcome(is_pro):
|
|
59
313
|
mark_wizard_completed() # Mark as completed even if skipped
|
|
60
314
|
return False
|
|
61
|
-
|
|
315
|
+
|
|
62
316
|
# Step 2: Encryption Setup
|
|
63
317
|
encryption_enabled = _wizard_encryption_setup()
|
|
64
|
-
|
|
318
|
+
|
|
65
319
|
# Step 3: Create First Engagement
|
|
66
320
|
engagement_info = _wizard_create_engagement()
|
|
67
321
|
if not engagement_info:
|
|
68
322
|
return False
|
|
69
|
-
|
|
323
|
+
|
|
70
324
|
# Step 4: Tool Check
|
|
71
325
|
tool_status = _wizard_tool_check()
|
|
72
326
|
|
|
@@ -78,7 +332,7 @@ def run_setup_wizard() -> bool:
|
|
|
78
332
|
automation_prefs = _wizard_automation_prefs()
|
|
79
333
|
else:
|
|
80
334
|
automation_prefs = {'enabled': False, 'mode': None}
|
|
81
|
-
|
|
335
|
+
|
|
82
336
|
# Step 7: Deliverable Templates (Pro only)
|
|
83
337
|
if is_pro:
|
|
84
338
|
templates = _wizard_deliverables(engagement_info.get('type'))
|
|
@@ -95,10 +349,10 @@ def run_setup_wizard() -> bool:
|
|
|
95
349
|
templates,
|
|
96
350
|
is_pro
|
|
97
351
|
)
|
|
98
|
-
|
|
352
|
+
|
|
99
353
|
mark_wizard_completed()
|
|
100
354
|
return True
|
|
101
|
-
|
|
355
|
+
|
|
102
356
|
except (KeyboardInterrupt, click.Abort):
|
|
103
357
|
click.echo(click.style("\n\n Setup wizard cancelled.", fg='yellow'))
|
|
104
358
|
mark_wizard_completed() # Don't show wizard again
|
|
@@ -110,13 +364,13 @@ def _wizard_welcome(is_pro: bool = False) -> bool:
|
|
|
110
364
|
"""Show welcome screen with ASCII banner."""
|
|
111
365
|
DesignSystem.clear_screen()
|
|
112
366
|
width = DesignSystem.get_terminal_width()
|
|
113
|
-
|
|
367
|
+
|
|
114
368
|
# Header
|
|
115
369
|
click.echo("\n┌" + "─" * (width - 2) + "┐")
|
|
116
370
|
click.echo("│" + click.style(" WELCOME TO SOULEYEZ - SETUP WIZARD ".center(width - 2), bold=True, fg='cyan') + "│")
|
|
117
371
|
click.echo("└" + "─" * (width - 2) + "┘")
|
|
118
372
|
click.echo()
|
|
119
|
-
|
|
373
|
+
|
|
120
374
|
# ASCII Art Banner - SOULEYEZ with all-seeing eye on the right
|
|
121
375
|
click.echo(click.style(" ███████╗ ██████╗ ██╗ ██╗██╗ ███████╗██╗ ██╗███████╗███████╗", fg='bright_cyan', bold=True) + click.style(" ▄██▄", fg='bright_blue', bold=True))
|
|
122
376
|
click.echo(click.style(" ██╔════╝██╔═══██╗██║ ██║██║ ██╔════╝╚██╗ ██╔╝██╔════╝╚══███╔╝", fg='bright_cyan', bold=True) + click.style(" ▄█▀ ▀█▄", fg='bright_blue', bold=True))
|
|
@@ -166,15 +420,15 @@ def _wizard_encryption_setup() -> bool:
|
|
|
166
420
|
click.echo("│" + click.style(" STEP 2: ENCRYPTION SETUP ".center(width - 2), bold=True, fg='cyan') + "│")
|
|
167
421
|
click.echo("└" + "─" * (width - 2) + "┘")
|
|
168
422
|
click.echo()
|
|
169
|
-
|
|
423
|
+
|
|
170
424
|
crypto = CryptoManager()
|
|
171
|
-
|
|
425
|
+
|
|
172
426
|
if crypto.is_encryption_enabled():
|
|
173
427
|
click.echo(" " + click.style("✓ Encryption already configured", fg='green'))
|
|
174
428
|
click.echo()
|
|
175
429
|
click.pause(" Press any key to continue...")
|
|
176
430
|
return True
|
|
177
|
-
|
|
431
|
+
|
|
178
432
|
click.echo(" SoulEyez encrypts all credentials (passwords, API keys, tokens)")
|
|
179
433
|
click.echo(" with a master password. This is " + click.style("required", fg='green', bold=True) + " for security.")
|
|
180
434
|
click.echo()
|
|
@@ -204,46 +458,46 @@ def _wizard_encryption_setup() -> bool:
|
|
|
204
458
|
click.echo(" • At least one number")
|
|
205
459
|
click.echo(" • At least one special character (!@#$%^&*)")
|
|
206
460
|
click.echo()
|
|
207
|
-
click.echo(" " + click.style("⚠️ If you lose this password, encrypted credentials cannot be recovered!",
|
|
461
|
+
click.echo(" " + click.style("⚠️ If you lose this password, encrypted credentials cannot be recovered!",
|
|
208
462
|
fg='yellow', bold=True))
|
|
209
463
|
click.echo()
|
|
210
|
-
|
|
464
|
+
|
|
211
465
|
import re
|
|
212
466
|
password_set = False
|
|
213
467
|
while not password_set:
|
|
214
468
|
password = getpass.getpass(" Enter master password: ")
|
|
215
|
-
|
|
469
|
+
|
|
216
470
|
# Validate password strength
|
|
217
471
|
if len(password) < 12:
|
|
218
472
|
click.echo(click.style(" ✗ Password must be at least 12 characters.", fg='red'))
|
|
219
473
|
continue
|
|
220
|
-
|
|
474
|
+
|
|
221
475
|
if not re.search(r'[a-z]', password):
|
|
222
476
|
click.echo(click.style(" ✗ Password must contain at least one lowercase letter.", fg='red'))
|
|
223
477
|
continue
|
|
224
|
-
|
|
478
|
+
|
|
225
479
|
if not re.search(r'[A-Z]', password):
|
|
226
480
|
click.echo(click.style(" ✗ Password must contain at least one uppercase letter.", fg='red'))
|
|
227
481
|
continue
|
|
228
|
-
|
|
482
|
+
|
|
229
483
|
if not re.search(r'\d', password):
|
|
230
484
|
click.echo(click.style(" ✗ Password must contain at least one number.", fg='red'))
|
|
231
485
|
continue
|
|
232
|
-
|
|
486
|
+
|
|
233
487
|
if not re.search(r'[!@#$%^&*()_+\-=\[\]{};:\'",.<>?/\\|`~]', password):
|
|
234
488
|
click.echo(click.style(" ✗ Password must contain at least one special character.", fg='red'))
|
|
235
489
|
continue
|
|
236
|
-
|
|
490
|
+
|
|
237
491
|
password_confirm = getpass.getpass(" Confirm master password: ")
|
|
238
492
|
if password != password_confirm:
|
|
239
493
|
click.echo(click.style(" ✗ Passwords don't match!", fg='red'))
|
|
240
494
|
continue
|
|
241
|
-
|
|
495
|
+
|
|
242
496
|
password_set = True
|
|
243
|
-
|
|
497
|
+
|
|
244
498
|
click.echo()
|
|
245
499
|
click.echo(" Enabling encryption...")
|
|
246
|
-
|
|
500
|
+
|
|
247
501
|
try:
|
|
248
502
|
if crypto.enable_encryption(password):
|
|
249
503
|
click.echo(" " + click.style("✓ Encryption enabled!", fg='green'))
|
|
@@ -255,7 +509,7 @@ def _wizard_encryption_setup() -> bool:
|
|
|
255
509
|
click.echo(" " + click.style(f"✗ Error: {e}", fg='red'))
|
|
256
510
|
click.pause(" Press any key to continue...")
|
|
257
511
|
return False
|
|
258
|
-
|
|
512
|
+
|
|
259
513
|
click.pause(" Press any key to continue...")
|
|
260
514
|
return True
|
|
261
515
|
|
|
@@ -293,9 +547,9 @@ def _wizard_create_engagement() -> dict:
|
|
|
293
547
|
click.echo()
|
|
294
548
|
click.echo(" " + click.style("NOTE:", fg='yellow') + " Type affects default automation and scan aggressiveness")
|
|
295
549
|
click.echo()
|
|
296
|
-
|
|
550
|
+
|
|
297
551
|
type_choice = click.prompt(" Select option", type=click.IntRange(1, 5), default=1, show_default=False)
|
|
298
|
-
|
|
552
|
+
|
|
299
553
|
engagement_types = {
|
|
300
554
|
1: 'penetration_test',
|
|
301
555
|
2: 'bug_bounty',
|
|
@@ -303,7 +557,7 @@ def _wizard_create_engagement() -> dict:
|
|
|
303
557
|
4: 'red_team',
|
|
304
558
|
5: 'custom'
|
|
305
559
|
}
|
|
306
|
-
|
|
560
|
+
|
|
307
561
|
engagement_type = engagement_types[type_choice]
|
|
308
562
|
|
|
309
563
|
# Create engagement and set it as active
|
|
@@ -338,7 +592,8 @@ def _wizard_create_engagement() -> dict:
|
|
|
338
592
|
def _wizard_tool_check() -> dict:
|
|
339
593
|
"""Check installed tools using the centralized tool_checker module."""
|
|
340
594
|
from souleyez.utils.tool_checker import (
|
|
341
|
-
check_tool_version, EXTERNAL_TOOLS, get_install_command, detect_distro
|
|
595
|
+
check_tool_version, EXTERNAL_TOOLS, get_install_command, detect_distro,
|
|
596
|
+
get_tool_version, get_upgrade_command
|
|
342
597
|
)
|
|
343
598
|
|
|
344
599
|
DesignSystem.clear_screen()
|
|
@@ -373,6 +628,7 @@ def _wizard_tool_check() -> dict:
|
|
|
373
628
|
total = len(wizard_tools)
|
|
374
629
|
tool_status = {}
|
|
375
630
|
wrong_version_tools = []
|
|
631
|
+
missing_tools = []
|
|
376
632
|
|
|
377
633
|
for display_name, (category, tool_key) in wizard_tools.items():
|
|
378
634
|
tool_info = EXTERNAL_TOOLS[category][tool_key]
|
|
@@ -390,7 +646,6 @@ def _wizard_tool_check() -> dict:
|
|
|
390
646
|
version_str = version_status['version']
|
|
391
647
|
if not version_str:
|
|
392
648
|
# Try to get version using the actual found command
|
|
393
|
-
from souleyez.utils.tool_checker import get_tool_version
|
|
394
649
|
version_str = get_tool_version(actual_cmd)
|
|
395
650
|
|
|
396
651
|
# Check if version is OK
|
|
@@ -410,8 +665,9 @@ def _wizard_tool_check() -> dict:
|
|
|
410
665
|
'name': display_name,
|
|
411
666
|
'installed': ver_display,
|
|
412
667
|
'required': min_ver,
|
|
413
|
-
'install': get_install_command(tool_info, distro),
|
|
414
|
-
'note': version_status.get('version_note')
|
|
668
|
+
'install': get_upgrade_command(tool_info, distro) or get_install_command(tool_info, distro),
|
|
669
|
+
'note': version_status.get('version_note'),
|
|
670
|
+
'tool_info': tool_info
|
|
415
671
|
})
|
|
416
672
|
found += 1 # Still counts as found, just needs upgrade
|
|
417
673
|
else:
|
|
@@ -423,6 +679,11 @@ def _wizard_tool_check() -> dict:
|
|
|
423
679
|
else:
|
|
424
680
|
click.echo(f" {click.style('✗', fg='red')} {display_name:<15} NOT FOUND")
|
|
425
681
|
tool_status[display_name] = {'found': False, 'path': None}
|
|
682
|
+
missing_tools.append({
|
|
683
|
+
'name': display_name,
|
|
684
|
+
'install': get_install_command(tool_info, distro),
|
|
685
|
+
'tool_info': tool_info
|
|
686
|
+
})
|
|
426
687
|
|
|
427
688
|
click.echo()
|
|
428
689
|
click.echo(f" Found {found}/{total} recommended tools")
|
|
@@ -435,17 +696,34 @@ def _wizard_tool_check() -> dict:
|
|
|
435
696
|
click.echo(f" - {tool['name']}: installed v{tool['installed']}, needs v{tool['required']}+")
|
|
436
697
|
if tool.get('note'):
|
|
437
698
|
click.echo(f" {click.style(tool['note'], fg='bright_black')}")
|
|
699
|
+
|
|
700
|
+
# Offer to install/upgrade tools
|
|
701
|
+
if missing_tools or wrong_version_tools:
|
|
438
702
|
click.echo()
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
703
|
+
# Run the install flow (will prompt user)
|
|
704
|
+
if _run_tool_installs(missing_tools, wrong_version_tools, distro):
|
|
705
|
+
# Re-check tools after install
|
|
706
|
+
click.echo()
|
|
707
|
+
click.echo(" Re-checking tool availability...")
|
|
708
|
+
click.echo()
|
|
442
709
|
|
|
443
|
-
|
|
710
|
+
found = 0
|
|
711
|
+
wrong_version_tools = []
|
|
712
|
+
for display_name, (category, tool_key) in wizard_tools.items():
|
|
713
|
+
tool_info = EXTERNAL_TOOLS[category][tool_key]
|
|
714
|
+
version_status = check_tool_version(tool_info)
|
|
715
|
+
|
|
716
|
+
if version_status['installed'] and not version_status['needs_upgrade']:
|
|
717
|
+
tool_status[display_name] = {'found': True, 'path': shutil.which(tool_info['command'])}
|
|
718
|
+
found += 1
|
|
719
|
+
elif version_status['installed'] and version_status['needs_upgrade']:
|
|
720
|
+
tool_status[display_name] = {'found': True, 'needs_upgrade': True}
|
|
721
|
+
wrong_version_tools.append({'name': display_name})
|
|
722
|
+
found += 1
|
|
723
|
+
else:
|
|
724
|
+
tool_status[display_name] = {'found': False, 'path': None}
|
|
444
725
|
|
|
445
|
-
|
|
446
|
-
click.echo(" " + click.style("TIP:", fg='yellow', bold=True) +
|
|
447
|
-
" You can install missing tools later. SoulEyez will work")
|
|
448
|
-
click.echo(" with the tools you have available.")
|
|
726
|
+
click.echo(f" Now have {found}/{total} tools available")
|
|
449
727
|
|
|
450
728
|
click.echo()
|
|
451
729
|
click.pause(" Press any key to continue...")
|
|
@@ -722,7 +1000,7 @@ def _wizard_deliverables(engagement_type: str) -> list:
|
|
|
722
1000
|
DesignSystem.clear_screen()
|
|
723
1001
|
_show_wizard_banner()
|
|
724
1002
|
width = 60
|
|
725
|
-
|
|
1003
|
+
|
|
726
1004
|
click.echo("┌" + "─" * (width - 2) + "┐")
|
|
727
1005
|
click.echo("│" + click.style(" STEP 7: REPORT TEMPLATES ".center(width - 2), bold=True, fg='cyan') + "│")
|
|
728
1006
|
click.echo("└" + "─" * (width - 2) + "┘")
|
|
@@ -732,7 +1010,7 @@ def _wizard_deliverables(engagement_type: str) -> list:
|
|
|
732
1010
|
click.echo()
|
|
733
1011
|
click.echo(" " + click.style("NOTE:", fg='yellow') + " Templates help generate professional reports from your findings")
|
|
734
1012
|
click.echo()
|
|
735
|
-
|
|
1013
|
+
|
|
736
1014
|
# Default templates based on engagement type
|
|
737
1015
|
type_templates = {
|
|
738
1016
|
'penetration_test': ['Executive Summary', 'Technical Findings Report', 'Vulnerability Details'],
|
|
@@ -741,9 +1019,9 @@ def _wizard_deliverables(engagement_type: str) -> list:
|
|
|
741
1019
|
'red_team': ['Attack Narrative', 'Remediation Roadmap'],
|
|
742
1020
|
'custom': ['Technical Findings Report']
|
|
743
1021
|
}
|
|
744
|
-
|
|
1022
|
+
|
|
745
1023
|
recommended = type_templates.get(engagement_type, ['Technical Findings Report'])
|
|
746
|
-
|
|
1024
|
+
|
|
747
1025
|
click.echo(f" Recommended for {engagement_type.replace('_', ' ').title()}:")
|
|
748
1026
|
for template in recommended:
|
|
749
1027
|
click.echo(f" {click.style('✓', fg='green')} {template}")
|
|
@@ -785,7 +1063,7 @@ def _wizard_summary(encryption_enabled, engagement_info, tool_status, ai_enabled
|
|
|
785
1063
|
click.echo(f" {click.style('✓', fg='green')} AI Features: Enabled (Ollama)")
|
|
786
1064
|
else:
|
|
787
1065
|
click.echo(f" {click.style('○', fg='bright_black')} AI Features: Not configured")
|
|
788
|
-
|
|
1066
|
+
|
|
789
1067
|
# Show tier status
|
|
790
1068
|
if is_pro:
|
|
791
1069
|
click.echo(f" {click.style('💎', fg='magenta')} License: PRO")
|
|
@@ -794,7 +1072,7 @@ def _wizard_summary(encryption_enabled, engagement_info, tool_status, ai_enabled
|
|
|
794
1072
|
click.echo(f" {click.style('✓', fg='green')} Auto-Chain: ON ({auto_mode} mode)")
|
|
795
1073
|
else:
|
|
796
1074
|
click.echo(f" {click.style('✓', fg='green')} Auto-Chain: OFF")
|
|
797
|
-
|
|
1075
|
+
|
|
798
1076
|
if templates:
|
|
799
1077
|
click.echo(f" {click.style('✓', fg='green')} Templates: {len(templates)} selected")
|
|
800
1078
|
else:
|
|
@@ -806,7 +1084,7 @@ def _wizard_summary(encryption_enabled, engagement_info, tool_status, ai_enabled
|
|
|
806
1084
|
click.echo(" • MSF Integration - Advanced attack chains")
|
|
807
1085
|
click.echo(" • Reports - Professional deliverables")
|
|
808
1086
|
click.echo(f" {click.style('→ cybersoulsecurity.com/upgrade', fg='cyan')}")
|
|
809
|
-
|
|
1087
|
+
|
|
810
1088
|
click.echo()
|
|
811
1089
|
click.echo(" " + click.style("You're ready to start!", bold=True, fg='cyan'))
|
|
812
1090
|
click.echo()
|