souleyez 2.27.0__py3-none-any.whl → 2.28.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/storage/hosts.py CHANGED
@@ -28,9 +28,12 @@ class HostManager:
28
28
  if not ip:
29
29
  raise ValueError("Host must have an IP address")
30
30
 
31
+ # Determine scope status for this host
32
+ scope_status = self._determine_scope_status(engagement_id, ip)
33
+
31
34
  # Check if host already exists
32
35
  existing = self.db.execute_one(
33
- "SELECT id FROM hosts WHERE engagement_id = ? AND ip_address = ?",
36
+ "SELECT id, scope_status FROM hosts WHERE engagement_id = ? AND ip_address = ?",
34
37
  (engagement_id, ip)
35
38
  )
36
39
 
@@ -43,6 +46,10 @@ class HostManager:
43
46
  # Always update status
44
47
  update_data['status'] = host_data.get('status', 'up')
45
48
 
49
+ # Update scope_status if it was unknown and we now have a determination
50
+ if existing.get('scope_status') == 'unknown' and scope_status != 'unknown':
51
+ update_data['scope_status'] = scope_status
52
+
46
53
  # Only update these fields if they have values
47
54
  if host_data.get('hostname'):
48
55
  update_data['hostname'] = host_data['hostname']
@@ -71,11 +78,89 @@ class HostManager:
71
78
  'os_name': host_data.get('os'),
72
79
  'mac_address': host_data.get('mac_address'),
73
80
  'os_accuracy': host_data.get('os_accuracy'),
74
- 'status': host_data.get('status', 'up')
81
+ 'status': host_data.get('status', 'up'),
82
+ 'scope_status': scope_status
75
83
  })
76
84
 
77
85
  return host_id
78
86
 
87
+ def _determine_scope_status(self, engagement_id: int, ip: str) -> str:
88
+ """
89
+ Determine scope status for a host based on engagement scope.
90
+
91
+ Args:
92
+ engagement_id: Engagement ID
93
+ ip: IP address to check
94
+
95
+ Returns:
96
+ 'in_scope', 'out_of_scope', or 'unknown'
97
+ """
98
+ try:
99
+ from souleyez.security.scope_validator import ScopeValidator
100
+ validator = ScopeValidator(engagement_id)
101
+ if validator.has_scope_defined():
102
+ result = validator.validate_ip(ip)
103
+ return 'in_scope' if result.is_in_scope else 'out_of_scope'
104
+ return 'unknown' # No scope defined
105
+ except Exception as e:
106
+ logger.warning(f"Failed to determine scope status for {ip}: {e}")
107
+ return 'unknown'
108
+
109
+ def update_scope_status(self, host_id: int, scope_status: str) -> bool:
110
+ """
111
+ Update scope status for a host.
112
+
113
+ Args:
114
+ host_id: Host ID
115
+ scope_status: 'in_scope', 'out_of_scope', or 'unknown'
116
+
117
+ Returns:
118
+ True if successful
119
+ """
120
+ valid_statuses = ['in_scope', 'out_of_scope', 'unknown']
121
+ if scope_status not in valid_statuses:
122
+ raise ValueError(f"Invalid scope_status: {scope_status}. Must be one of: {valid_statuses}")
123
+
124
+ try:
125
+ self.db.execute(
126
+ "UPDATE hosts SET scope_status = ? WHERE id = ?",
127
+ (scope_status, host_id)
128
+ )
129
+ return True
130
+ except Exception:
131
+ return False
132
+
133
+ def revalidate_scope_status(self, engagement_id: int) -> Dict[str, int]:
134
+ """
135
+ Revalidate scope status for all hosts in an engagement.
136
+
137
+ Call this after scope entries are added/modified to update all hosts.
138
+
139
+ Args:
140
+ engagement_id: Engagement ID
141
+
142
+ Returns:
143
+ {'updated': N, 'in_scope': X, 'out_of_scope': Y}
144
+ """
145
+ hosts = self.list_hosts(engagement_id)
146
+ updated = 0
147
+ in_scope = 0
148
+ out_of_scope = 0
149
+
150
+ for host in hosts:
151
+ ip = host.get('ip') or host.get('ip_address')
152
+ new_status = self._determine_scope_status(engagement_id, ip)
153
+ if new_status != host.get('scope_status'):
154
+ self.update_scope_status(host['id'], new_status)
155
+ updated += 1
156
+
157
+ if new_status == 'in_scope':
158
+ in_scope += 1
159
+ elif new_status == 'out_of_scope':
160
+ out_of_scope += 1
161
+
162
+ return {'updated': updated, 'in_scope': in_scope, 'out_of_scope': out_of_scope}
163
+
79
164
  def add_service(self, host_id: int, service_data: Dict[str, Any]) -> int:
80
165
  """
81
166
  Add or update a service for a host.
@@ -0,0 +1,87 @@
1
+ """
2
+ Migration 026: Add Engagement Scope Validation
3
+
4
+ Tables created:
5
+ - engagement_scope: Structured scope definitions (CIDR, domains, URLs)
6
+ - scope_validation_log: Audit trail of scope validation decisions
7
+
8
+ Columns added:
9
+ - engagements.scope_enforcement: Enforcement mode (off, warn, block)
10
+ - hosts.scope_status: Host scope status (in_scope, out_of_scope, unknown)
11
+ """
12
+ import os
13
+
14
+
15
+ def upgrade(conn):
16
+ """Add scope validation tables and columns."""
17
+
18
+ # Engagement scope definitions table
19
+ conn.execute("""
20
+ CREATE TABLE IF NOT EXISTS engagement_scope (
21
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
22
+ engagement_id INTEGER NOT NULL,
23
+ scope_type TEXT NOT NULL,
24
+ value TEXT NOT NULL,
25
+ is_excluded BOOLEAN DEFAULT 0,
26
+ description TEXT,
27
+ added_by TEXT,
28
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
29
+ FOREIGN KEY (engagement_id) REFERENCES engagements(id) ON DELETE CASCADE,
30
+ UNIQUE(engagement_id, scope_type, value)
31
+ )
32
+ """)
33
+
34
+ # Scope validation audit log
35
+ conn.execute("""
36
+ CREATE TABLE IF NOT EXISTS scope_validation_log (
37
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
38
+ engagement_id INTEGER NOT NULL,
39
+ job_id INTEGER,
40
+ target TEXT NOT NULL,
41
+ validation_result TEXT NOT NULL,
42
+ action_taken TEXT NOT NULL,
43
+ matched_scope_id INTEGER,
44
+ user_id TEXT,
45
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
46
+ FOREIGN KEY (engagement_id) REFERENCES engagements(id) ON DELETE CASCADE
47
+ )
48
+ """)
49
+
50
+ # Add scope_enforcement column to engagements
51
+ try:
52
+ conn.execute("ALTER TABLE engagements ADD COLUMN scope_enforcement TEXT DEFAULT 'off'")
53
+ except Exception:
54
+ pass # Column may already exist
55
+
56
+ # Add scope_status column to hosts
57
+ try:
58
+ conn.execute("ALTER TABLE hosts ADD COLUMN scope_status TEXT DEFAULT 'unknown'")
59
+ except Exception:
60
+ pass # Column may already exist
61
+
62
+ # Indexes for performance
63
+ conn.execute("CREATE INDEX IF NOT EXISTS idx_scope_engagement ON engagement_scope(engagement_id)")
64
+ conn.execute("CREATE INDEX IF NOT EXISTS idx_scope_type ON engagement_scope(scope_type)")
65
+ conn.execute("CREATE INDEX IF NOT EXISTS idx_scope_log_engagement ON scope_validation_log(engagement_id)")
66
+ conn.execute("CREATE INDEX IF NOT EXISTS idx_scope_log_result ON scope_validation_log(validation_result)")
67
+ conn.execute("CREATE INDEX IF NOT EXISTS idx_scope_log_timestamp ON scope_validation_log(created_at DESC)")
68
+ conn.execute("CREATE INDEX IF NOT EXISTS idx_hosts_scope_status ON hosts(scope_status)")
69
+
70
+ conn.commit()
71
+
72
+ if not os.environ.get('SOULEYEZ_MIGRATION_SILENT'):
73
+ print("Migration 026: Engagement scope validation tables created")
74
+
75
+
76
+ def downgrade(conn):
77
+ """Remove scope validation tables."""
78
+ conn.execute("DROP TABLE IF EXISTS scope_validation_log")
79
+ conn.execute("DROP TABLE IF EXISTS engagement_scope")
80
+ conn.execute("DROP INDEX IF EXISTS idx_scope_engagement")
81
+ conn.execute("DROP INDEX IF EXISTS idx_scope_type")
82
+ conn.execute("DROP INDEX IF EXISTS idx_scope_log_engagement")
83
+ conn.execute("DROP INDEX IF EXISTS idx_scope_log_result")
84
+ conn.execute("DROP INDEX IF EXISTS idx_scope_log_timestamp")
85
+ conn.execute("DROP INDEX IF EXISTS idx_hosts_scope_status")
86
+ # Note: Cannot easily drop columns in SQLite, they remain but unused
87
+ print("Migration 026: Engagement scope validation tables dropped")
@@ -7172,6 +7172,275 @@ def view_results_menu():
7172
7172
  return
7173
7173
 
7174
7174
 
7175
+ def _scope_management_menu(engagement_id: int, engagement_name: str):
7176
+ """Interactive scope management menu for an engagement."""
7177
+ from souleyez.security.scope_validator import ScopeManager, ScopeValidator
7178
+ from souleyez.storage.hosts import HostManager
7179
+ from rich.console import Console
7180
+ from rich.table import Table
7181
+
7182
+ manager = ScopeManager()
7183
+ validator = ScopeValidator(engagement_id)
7184
+
7185
+ while True:
7186
+ DesignSystem.clear_screen()
7187
+ render_standard_header(f"SCOPE MANAGEMENT - {engagement_name}")
7188
+
7189
+ # Get current scope info
7190
+ entries = manager.list_scope(engagement_id)
7191
+ enforcement = validator.get_enforcement_mode()
7192
+
7193
+ # Show enforcement status
7194
+ if enforcement == 'block':
7195
+ enf_color = 'red'
7196
+ enf_desc = 'Block out-of-scope targets'
7197
+ elif enforcement == 'warn':
7198
+ enf_color = 'yellow'
7199
+ enf_desc = 'Warn but allow'
7200
+ else:
7201
+ enf_color = 'bright_black'
7202
+ enf_desc = 'No validation'
7203
+
7204
+ click.echo()
7205
+ click.echo(f" Enforcement: {click.style(enforcement.upper(), fg=enf_color, bold=True)} - {enf_desc}")
7206
+ click.echo()
7207
+
7208
+ # Display scope entries
7209
+ if not entries:
7210
+ click.echo(click.style(" No scope entries defined - all targets allowed", fg='yellow'))
7211
+ click.echo()
7212
+ else:
7213
+ console = Console()
7214
+ table = Table(show_header=True, header_style="bold", box=DesignSystem.TABLE_BOX, padding=(0, 1))
7215
+
7216
+ table.add_column("ID", width=4, justify="right")
7217
+ table.add_column("Type", width=10)
7218
+ table.add_column("Value", min_width=30)
7219
+ table.add_column("Status", width=10)
7220
+ table.add_column("Description", max_width=25)
7221
+
7222
+ for entry in entries:
7223
+ status = "[red]EXCLUDE[/red]" if entry.get('is_excluded') else "[green]INCLUDE[/green]"
7224
+ table.add_row(
7225
+ str(entry['id']),
7226
+ entry['scope_type'],
7227
+ entry['value'],
7228
+ status,
7229
+ entry.get('description', '') or ''
7230
+ )
7231
+
7232
+ console.print(table)
7233
+ click.echo()
7234
+
7235
+ click.echo("─" * get_terminal_width())
7236
+ click.echo()
7237
+ click.echo(" [+] Add scope entry")
7238
+ click.echo(" [-] Remove scope entry")
7239
+ click.echo(" [e] Change enforcement mode")
7240
+ click.echo(" [t] Test target validation")
7241
+ click.echo(" [r] Revalidate all hosts")
7242
+ click.echo(" [l] View validation log")
7243
+ click.echo(" [q] Back")
7244
+ click.echo()
7245
+
7246
+ try:
7247
+ choice = click.prompt(" Select option", type=str, default='q', show_default=False).strip().lower()
7248
+
7249
+ if choice == 'q':
7250
+ return
7251
+
7252
+ elif choice == '+':
7253
+ # Add scope entry
7254
+ click.echo()
7255
+ click.echo(click.style(" Add Scope Entry", fg='cyan', bold=True))
7256
+ click.echo()
7257
+ click.echo(" Type:")
7258
+ click.echo(" [1] CIDR range (e.g., 192.168.1.0/24)")
7259
+ click.echo(" [2] Domain pattern (e.g., *.example.com)")
7260
+ click.echo(" [3] URL (e.g., https://app.example.com)")
7261
+ click.echo(" [4] Hostname/IP (exact match)")
7262
+ click.echo(" [c] Cancel")
7263
+ click.echo()
7264
+
7265
+ type_choice = click.prompt(" Select type", type=str, default='c').strip().lower()
7266
+
7267
+ if type_choice == 'c':
7268
+ continue
7269
+
7270
+ type_map = {'1': 'cidr', '2': 'domain', '3': 'url', '4': 'hostname'}
7271
+ scope_type = type_map.get(type_choice)
7272
+
7273
+ if not scope_type:
7274
+ click.echo(click.style("\n Invalid choice!", fg='red'))
7275
+ click.pause()
7276
+ continue
7277
+
7278
+ # Get value
7279
+ if scope_type == 'cidr':
7280
+ value_hint = "192.168.1.0/24"
7281
+ elif scope_type == 'domain':
7282
+ value_hint = "*.example.com or example.com"
7283
+ elif scope_type == 'url':
7284
+ value_hint = "https://app.example.com"
7285
+ else:
7286
+ value_hint = "10.0.0.1 or server.local"
7287
+
7288
+ value = click.prompt(f"\n Enter {scope_type} ({value_hint})", type=str).strip()
7289
+ if not value:
7290
+ continue
7291
+
7292
+ # Ask if exclusion
7293
+ is_excluded = click.confirm(" Is this an EXCLUSION (deny rule)?", default=False)
7294
+
7295
+ # Optional description
7296
+ description = click.prompt(" Description (optional)", type=str, default="").strip()
7297
+
7298
+ try:
7299
+ scope_id = manager.add_scope(
7300
+ engagement_id=engagement_id,
7301
+ scope_type=scope_type,
7302
+ value=value,
7303
+ is_excluded=is_excluded,
7304
+ description=description or None
7305
+ )
7306
+ entry_type = "exclusion" if is_excluded else "scope entry"
7307
+ click.echo(click.style(f"\n ✓ Added {entry_type}: {scope_type}={value} (ID: {scope_id})", fg='green'))
7308
+ except ValueError as e:
7309
+ click.echo(click.style(f"\n ✗ Invalid value: {e}", fg='red'))
7310
+ except Exception as e:
7311
+ if "UNIQUE constraint" in str(e):
7312
+ click.echo(click.style(f"\n ✗ This scope entry already exists!", fg='red'))
7313
+ else:
7314
+ click.echo(click.style(f"\n ✗ Error: {e}", fg='red'))
7315
+ click.pause()
7316
+
7317
+ elif choice == '-':
7318
+ # Remove scope entry
7319
+ if not entries:
7320
+ click.echo(click.style("\n No scope entries to remove!", fg='yellow'))
7321
+ click.pause()
7322
+ continue
7323
+
7324
+ try:
7325
+ scope_id = click.prompt("\n Enter scope ID to remove", type=int)
7326
+ entry = next((e for e in entries if e['id'] == scope_id), None)
7327
+
7328
+ if entry:
7329
+ if click.confirm(f" Remove '{entry['scope_type']}={entry['value']}'?", default=False):
7330
+ if manager.remove_scope(scope_id):
7331
+ click.echo(click.style(f"\n ✓ Removed scope entry {scope_id}", fg='green'))
7332
+ else:
7333
+ click.echo(click.style(f"\n ✗ Failed to remove scope entry!", fg='red'))
7334
+ else:
7335
+ click.echo(click.style(f"\n ✗ Scope ID {scope_id} not found!", fg='red'))
7336
+ except ValueError:
7337
+ click.echo(click.style("\n ✗ Invalid ID!", fg='red'))
7338
+ click.pause()
7339
+
7340
+ elif choice == 'e':
7341
+ # Change enforcement mode
7342
+ click.echo()
7343
+ click.echo(click.style(" Enforcement Mode", fg='cyan', bold=True))
7344
+ click.echo()
7345
+ click.echo(f" Current: {click.style(enforcement.upper(), fg=enf_color, bold=True)}")
7346
+ click.echo()
7347
+ click.echo(" [1] OFF - No scope validation")
7348
+ click.echo(" [2] WARN - Allow but log warning")
7349
+ click.echo(" [3] BLOCK - Reject out-of-scope targets")
7350
+ click.echo(" [c] Cancel")
7351
+ click.echo()
7352
+
7353
+ mode_choice = click.prompt(" Select mode", type=str, default='c').strip().lower()
7354
+
7355
+ mode_map = {'1': 'off', '2': 'warn', '3': 'block'}
7356
+ new_mode = mode_map.get(mode_choice)
7357
+
7358
+ if new_mode:
7359
+ if manager.set_enforcement(engagement_id, new_mode):
7360
+ click.echo(click.style(f"\n ✓ Enforcement mode set to {new_mode.upper()}", fg='green'))
7361
+ # Refresh validator cache
7362
+ validator = ScopeValidator(engagement_id)
7363
+ else:
7364
+ click.echo(click.style("\n ✗ Failed to set enforcement mode!", fg='red'))
7365
+ click.pause()
7366
+
7367
+ elif choice == 't':
7368
+ # Test target validation
7369
+ click.echo()
7370
+ target = click.prompt(" Enter target to test", type=str).strip()
7371
+ if target:
7372
+ result = validator.validate_target(target)
7373
+ click.echo()
7374
+ if result.is_in_scope:
7375
+ click.echo(click.style(f" ✓ IN SCOPE: {target}", fg='green', bold=True))
7376
+ if result.matched_entry:
7377
+ click.echo(f" Matched: {result.matched_entry.get('value')}")
7378
+ else:
7379
+ click.echo(click.style(f" ✗ OUT OF SCOPE: {target}", fg='red', bold=True))
7380
+ click.echo(f" Reason: {result.reason}")
7381
+ click.echo(f" Enforcement: {enforcement}")
7382
+ click.pause()
7383
+
7384
+ elif choice == 'r':
7385
+ # Revalidate all hosts
7386
+ click.echo()
7387
+ if click.confirm(" Revalidate scope status for all hosts in this engagement?", default=True):
7388
+ hm = HostManager()
7389
+ result = hm.revalidate_scope_status(engagement_id)
7390
+ click.echo()
7391
+ click.echo(click.style(" Revalidation complete:", fg='green'))
7392
+ click.echo(f" Updated: {result['updated']}")
7393
+ click.echo(f" In scope: {result['in_scope']}")
7394
+ click.echo(f" Out of scope: {result['out_of_scope']}")
7395
+ click.pause()
7396
+
7397
+ elif choice == 'l':
7398
+ # View validation log
7399
+ log_entries = manager.get_validation_log(engagement_id, limit=30)
7400
+ click.echo()
7401
+ click.echo(click.style(" Recent Scope Validation Log (last 30)", fg='cyan', bold=True))
7402
+ click.echo()
7403
+
7404
+ if not log_entries:
7405
+ click.echo(" No validation log entries yet.")
7406
+ else:
7407
+ console = Console()
7408
+ table = Table(show_header=True, header_style="bold", box=DesignSystem.TABLE_BOX, padding=(0, 1))
7409
+
7410
+ table.add_column("Time", width=19)
7411
+ table.add_column("Target", min_width=20)
7412
+ table.add_column("Result", width=12)
7413
+ table.add_column("Action", width=10)
7414
+ table.add_column("Job", width=6)
7415
+
7416
+ for entry in log_entries:
7417
+ timestamp = entry.get('created_at', '')[:19]
7418
+ target = entry.get('target', '')
7419
+ result_val = entry.get('validation_result', '')
7420
+ action = entry.get('action_taken', '')
7421
+ job_id = str(entry.get('job_id', '-') or '-')
7422
+
7423
+ # Color code results
7424
+ if result_val == 'in_scope':
7425
+ result_display = "[green]in_scope[/green]"
7426
+ elif result_val == 'out_of_scope':
7427
+ result_display = "[red]out_of_scope[/red]"
7428
+ else:
7429
+ result_display = result_val
7430
+
7431
+ table.add_row(timestamp, target, result_display, action, job_id)
7432
+
7433
+ console.print(table)
7434
+ click.pause()
7435
+
7436
+ else:
7437
+ click.echo(click.style("\n Invalid choice!", fg='red'))
7438
+ click.pause()
7439
+
7440
+ except (KeyboardInterrupt, EOFError):
7441
+ return
7442
+
7443
+
7175
7444
  def _engagements_bulk_action_menu(selected_ids: set, em, current_id: int):
7176
7445
  """Show bulk action menu for selected engagements."""
7177
7446
  click.echo()
@@ -7426,6 +7695,7 @@ def manage_engagements_menu():
7426
7695
  click.echo(" [a] All - Toggle pagination")
7427
7696
  click.echo(" [+] Create - Create new engagement")
7428
7697
  click.echo(" [-] Delete - Delete engagement(s)")
7698
+ click.echo(" [s] Scope - Manage target scope")
7429
7699
  click.echo(" [d] Deliverables - Track progress")
7430
7700
  click.echo(" [q] Back")
7431
7701
 
@@ -7460,6 +7730,15 @@ def manage_engagements_menu():
7460
7730
  current_page = 0
7461
7731
  continue
7462
7732
 
7733
+ elif choice_input == 's':
7734
+ # Scope Management
7735
+ if not current_ws:
7736
+ click.echo(click.style("\n ⚠️ Please select an engagement first!", fg='yellow'))
7737
+ click.pause()
7738
+ continue
7739
+ _scope_management_menu(current_ws['id'], current_ws['name'])
7740
+ continue
7741
+
7463
7742
  elif choice_input == 'd':
7464
7743
  # Deliverables Tracker
7465
7744
  if not current_ws:
@@ -30962,31 +31241,36 @@ def view_documentation_menu():
30962
31241
  'name': 'Keyboard Shortcuts',
30963
31242
  'file': None, # Special handler
30964
31243
  'description': 'Command Center and Dashboard shortcuts'
31244
+ },
31245
+ '8': {
31246
+ 'name': 'Scope Management Guide',
31247
+ 'file': 'docs/user-guide/scope-management.md',
31248
+ 'description': 'Target validation and boundary enforcement'
30965
31249
  }
30966
31250
  }
30967
31251
 
30968
31252
  # Add PRO-only documentation
30969
31253
  if is_pro:
30970
31254
  docs.update({
30971
- '8': {
31255
+ '9': {
30972
31256
  'name': 'MSF Integration Guide',
30973
31257
  'file': None, # Special handler
30974
31258
  'description': 'Metasploit Framework workflows',
30975
31259
  'pro': True
30976
31260
  },
30977
- '9': {
31261
+ '10': {
30978
31262
  'name': 'RBAC & User Management',
30979
31263
  'file': 'docs/user-guide/rbac.md',
30980
31264
  'description': 'Roles, permissions, and audit logging',
30981
31265
  'pro': True
30982
31266
  },
30983
- '10': {
31267
+ '11': {
30984
31268
  'name': 'SIEM Integration Guide',
30985
31269
  'file': 'docs/user-guide/siem-integration.md',
30986
31270
  'description': 'Splunk, Wazuh, Elastic, Sentinel setup',
30987
31271
  'pro': True
30988
31272
  },
30989
- '11': {
31273
+ '12': {
30990
31274
  'name': 'AI Integration Guide',
30991
31275
  'file': 'docs/user-guide/ai-integration.md',
30992
31276
  'description': 'AI providers and autonomous execution',
@@ -31055,7 +31339,7 @@ def view_documentation_menu():
31055
31339
  continue
31056
31340
 
31057
31341
  # Special handler for MSF Integration Guide
31058
- if choice == '8':
31342
+ if choice == '9':
31059
31343
  _show_msf_help()
31060
31344
  continue
31061
31345
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: souleyez
3
- Version: 2.27.0
3
+ Version: 2.28.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>
@@ -51,6 +51,12 @@ Dynamic: license-file
51
51
 
52
52
  # SoulEyez Beta Program
53
53
 
54
+ [![CI](https://github.com/cyber-soul-security/souleyez/actions/workflows/python-ci.yml/badge.svg)](https://github.com/cyber-soul-security/souleyez/actions/workflows/python-ci.yml)
55
+ [![codecov](https://codecov.io/gh/cyber-soul-security/souleyez/branch/main/graph/badge.svg)](https://codecov.io/gh/cyber-soul-security/souleyez)
56
+ [![Python 3.9+](https://img.shields.io/badge/python-3.9+-blue.svg)](https://www.python.org/downloads/)
57
+ [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
58
+ [![Security: bandit](https://img.shields.io/badge/security-bandit-yellow.svg)](https://github.com/PyCQA/bandit)
59
+
54
60
  Welcome to the SoulEyez beta! Thank you for helping us test and improve this penetration testing management platform.
55
61
 
56
62
  ---
@@ -72,7 +78,7 @@ Welcome to the SoulEyez beta! Thank you for helping us test and improve this pen
72
78
 
73
79
  > ⚠️ **Important**: Only use SoulEyez on systems you have explicit authorization to test.
74
80
 
75
- ## Version: 2.27.0
81
+ ## Version: 2.28.0
76
82
 
77
83
  ### What's Included
78
84
 
@@ -310,4 +316,4 @@ Happy hacking! 🛡️
310
316
 
311
317
  ---
312
318
 
313
- **Version**: 2.27.0 | **Release Date**: January 2026 | **Maintainer**: CyberSoul Security
319
+ **Version**: 2.28.0 | **Release Date**: January 2026 | **Maintainer**: CyberSoul Security