souleyez 2.43.26__py3-none-any.whl → 2.43.34__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- souleyez/__init__.py +1 -2
- souleyez/ai/__init__.py +21 -15
- souleyez/ai/action_mapper.py +249 -150
- souleyez/ai/chain_advisor.py +116 -100
- souleyez/ai/claude_provider.py +29 -28
- souleyez/ai/context_builder.py +80 -62
- souleyez/ai/executor.py +158 -117
- souleyez/ai/feedback_handler.py +136 -121
- souleyez/ai/llm_factory.py +27 -20
- souleyez/ai/llm_provider.py +4 -2
- souleyez/ai/ollama_provider.py +6 -9
- souleyez/ai/ollama_service.py +44 -37
- souleyez/ai/path_scorer.py +91 -76
- souleyez/ai/recommender.py +176 -144
- souleyez/ai/report_context.py +74 -73
- souleyez/ai/report_service.py +84 -66
- souleyez/ai/result_parser.py +222 -229
- souleyez/ai/safety.py +67 -44
- souleyez/auth/__init__.py +23 -22
- souleyez/auth/audit.py +36 -26
- souleyez/auth/engagement_access.py +65 -48
- souleyez/auth/permissions.py +14 -3
- souleyez/auth/session_manager.py +54 -37
- souleyez/auth/user_manager.py +109 -64
- souleyez/commands/audit.py +40 -43
- souleyez/commands/auth.py +35 -15
- souleyez/commands/deliverables.py +55 -50
- souleyez/commands/engagement.py +47 -28
- souleyez/commands/license.py +32 -23
- souleyez/commands/screenshots.py +36 -32
- souleyez/commands/user.py +82 -36
- souleyez/config.py +52 -44
- souleyez/core/credential_tester.py +87 -81
- souleyez/core/cve_mappings.py +179 -192
- souleyez/core/cve_matcher.py +162 -148
- souleyez/core/msf_auto_mapper.py +100 -83
- souleyez/core/msf_chain_engine.py +294 -256
- souleyez/core/msf_database.py +153 -70
- souleyez/core/msf_integration.py +679 -673
- souleyez/core/msf_rpc_client.py +40 -42
- souleyez/core/msf_rpc_manager.py +77 -79
- souleyez/core/msf_sync_manager.py +241 -181
- souleyez/core/network_utils.py +22 -15
- souleyez/core/parser_handler.py +34 -25
- souleyez/core/pending_chains.py +114 -63
- souleyez/core/templates.py +158 -107
- souleyez/core/tool_chaining.py +9526 -2879
- souleyez/core/version_utils.py +79 -94
- souleyez/core/vuln_correlation.py +136 -89
- souleyez/core/web_utils.py +33 -32
- souleyez/data/wordlists/ad_users.txt +378 -0
- souleyez/data/wordlists/api_endpoints_large.txt +769 -0
- souleyez/data/wordlists/home_dir_sensitive.txt +39 -0
- souleyez/data/wordlists/lfi_payloads.txt +82 -0
- souleyez/data/wordlists/passwords_brute.txt +1548 -0
- souleyez/data/wordlists/passwords_crack.txt +2479 -0
- souleyez/data/wordlists/passwords_spray.txt +386 -0
- souleyez/data/wordlists/subdomains_large.txt +5057 -0
- souleyez/data/wordlists/usernames_common.txt +694 -0
- souleyez/data/wordlists/web_dirs_large.txt +4769 -0
- souleyez/detection/__init__.py +1 -1
- souleyez/detection/attack_signatures.py +12 -17
- souleyez/detection/mitre_mappings.py +61 -55
- souleyez/detection/validator.py +97 -86
- souleyez/devtools.py +23 -10
- souleyez/docs/README.md +4 -4
- souleyez/docs/api-reference/cli-commands.md +2 -2
- souleyez/docs/developer-guide/adding-new-tools.md +562 -0
- souleyez/docs/user-guide/auto-chaining.md +30 -8
- souleyez/docs/user-guide/getting-started.md +1 -1
- souleyez/docs/user-guide/installation.md +26 -3
- souleyez/docs/user-guide/metasploit-integration.md +2 -2
- souleyez/docs/user-guide/rbac.md +1 -1
- souleyez/docs/user-guide/scope-management.md +1 -1
- souleyez/docs/user-guide/siem-integration.md +1 -1
- souleyez/docs/user-guide/tools-reference.md +1 -8
- souleyez/docs/user-guide/worker-management.md +1 -1
- souleyez/engine/background.py +1239 -535
- souleyez/engine/base.py +4 -1
- souleyez/engine/job_status.py +17 -49
- souleyez/engine/log_sanitizer.py +103 -77
- souleyez/engine/manager.py +38 -7
- souleyez/engine/result_handler.py +2200 -1550
- souleyez/engine/worker_manager.py +50 -41
- souleyez/export/evidence_bundle.py +72 -62
- souleyez/feature_flags/features.py +16 -20
- souleyez/feature_flags.py +5 -9
- souleyez/handlers/__init__.py +11 -0
- souleyez/handlers/base.py +188 -0
- souleyez/handlers/bash_handler.py +277 -0
- souleyez/handlers/bloodhound_handler.py +243 -0
- souleyez/handlers/certipy_handler.py +311 -0
- souleyez/handlers/crackmapexec_handler.py +486 -0
- souleyez/handlers/dnsrecon_handler.py +344 -0
- souleyez/handlers/enum4linux_handler.py +400 -0
- souleyez/handlers/evil_winrm_handler.py +493 -0
- souleyez/handlers/ffuf_handler.py +815 -0
- souleyez/handlers/gobuster_handler.py +1114 -0
- souleyez/handlers/gpp_extract_handler.py +334 -0
- souleyez/handlers/hashcat_handler.py +444 -0
- souleyez/handlers/hydra_handler.py +563 -0
- souleyez/handlers/impacket_getuserspns_handler.py +343 -0
- souleyez/handlers/impacket_psexec_handler.py +222 -0
- souleyez/handlers/impacket_secretsdump_handler.py +426 -0
- souleyez/handlers/john_handler.py +286 -0
- souleyez/handlers/katana_handler.py +425 -0
- souleyez/handlers/kerbrute_handler.py +298 -0
- souleyez/handlers/ldapsearch_handler.py +636 -0
- souleyez/handlers/lfi_extract_handler.py +464 -0
- souleyez/handlers/msf_auxiliary_handler.py +408 -0
- souleyez/handlers/msf_exploit_handler.py +380 -0
- souleyez/handlers/nikto_handler.py +413 -0
- souleyez/handlers/nmap_handler.py +821 -0
- souleyez/handlers/nuclei_handler.py +359 -0
- souleyez/handlers/nxc_handler.py +371 -0
- souleyez/handlers/rdp_sec_check_handler.py +353 -0
- souleyez/handlers/registry.py +292 -0
- souleyez/handlers/responder_handler.py +232 -0
- souleyez/handlers/service_explorer_handler.py +434 -0
- souleyez/handlers/smbclient_handler.py +344 -0
- souleyez/handlers/smbmap_handler.py +510 -0
- souleyez/handlers/smbpasswd_handler.py +296 -0
- souleyez/handlers/sqlmap_handler.py +1116 -0
- souleyez/handlers/theharvester_handler.py +601 -0
- souleyez/handlers/web_login_test_handler.py +327 -0
- souleyez/handlers/whois_handler.py +277 -0
- souleyez/handlers/wpscan_handler.py +554 -0
- souleyez/history.py +32 -16
- souleyez/importers/msf_importer.py +106 -75
- souleyez/importers/smart_importer.py +208 -147
- souleyez/integrations/siem/__init__.py +10 -10
- souleyez/integrations/siem/base.py +17 -18
- souleyez/integrations/siem/elastic.py +108 -122
- souleyez/integrations/siem/factory.py +207 -80
- souleyez/integrations/siem/googlesecops.py +146 -154
- souleyez/integrations/siem/rule_mappings/__init__.py +1 -1
- souleyez/integrations/siem/rule_mappings/wazuh_rules.py +8 -5
- souleyez/integrations/siem/sentinel.py +107 -109
- souleyez/integrations/siem/splunk.py +246 -212
- souleyez/integrations/siem/wazuh.py +65 -71
- souleyez/integrations/wazuh/__init__.py +5 -5
- souleyez/integrations/wazuh/client.py +70 -93
- souleyez/integrations/wazuh/config.py +85 -57
- souleyez/integrations/wazuh/host_mapper.py +28 -36
- souleyez/integrations/wazuh/sync.py +78 -68
- souleyez/intelligence/__init__.py +4 -5
- souleyez/intelligence/correlation_analyzer.py +309 -295
- souleyez/intelligence/exploit_knowledge.py +661 -623
- souleyez/intelligence/exploit_suggestions.py +159 -139
- souleyez/intelligence/gap_analyzer.py +132 -97
- souleyez/intelligence/gap_detector.py +251 -214
- souleyez/intelligence/sensitive_tables.py +266 -129
- souleyez/intelligence/service_parser.py +137 -123
- souleyez/intelligence/surface_analyzer.py +407 -268
- souleyez/intelligence/target_parser.py +159 -162
- souleyez/licensing/__init__.py +6 -6
- souleyez/licensing/validator.py +17 -19
- souleyez/log_config.py +79 -54
- souleyez/main.py +1505 -687
- souleyez/migrations/fix_job_counter.py +16 -14
- souleyez/parsers/bloodhound_parser.py +41 -39
- souleyez/parsers/crackmapexec_parser.py +178 -111
- souleyez/parsers/dalfox_parser.py +72 -77
- souleyez/parsers/dnsrecon_parser.py +103 -91
- souleyez/parsers/enum4linux_parser.py +183 -153
- souleyez/parsers/ffuf_parser.py +29 -25
- souleyez/parsers/gobuster_parser.py +301 -41
- souleyez/parsers/hashcat_parser.py +324 -79
- souleyez/parsers/http_fingerprint_parser.py +350 -103
- souleyez/parsers/hydra_parser.py +131 -111
- souleyez/parsers/impacket_parser.py +231 -178
- souleyez/parsers/john_parser.py +98 -86
- souleyez/parsers/katana_parser.py +316 -0
- souleyez/parsers/msf_parser.py +943 -498
- souleyez/parsers/nikto_parser.py +346 -65
- souleyez/parsers/nmap_parser.py +262 -174
- souleyez/parsers/nuclei_parser.py +40 -44
- souleyez/parsers/responder_parser.py +26 -26
- souleyez/parsers/searchsploit_parser.py +74 -74
- souleyez/parsers/service_explorer_parser.py +279 -0
- souleyez/parsers/smbmap_parser.py +180 -124
- souleyez/parsers/sqlmap_parser.py +434 -308
- souleyez/parsers/theharvester_parser.py +75 -57
- souleyez/parsers/whois_parser.py +135 -94
- souleyez/parsers/wpscan_parser.py +278 -190
- souleyez/plugins/afp.py +44 -36
- souleyez/plugins/afp_brute.py +114 -46
- souleyez/plugins/ard.py +48 -37
- souleyez/plugins/bloodhound.py +95 -61
- souleyez/plugins/certipy.py +303 -0
- souleyez/plugins/crackmapexec.py +186 -85
- souleyez/plugins/dalfox.py +120 -59
- souleyez/plugins/dns_hijack.py +146 -41
- souleyez/plugins/dnsrecon.py +97 -61
- souleyez/plugins/enum4linux.py +91 -66
- souleyez/plugins/evil_winrm.py +291 -0
- souleyez/plugins/ffuf.py +166 -90
- souleyez/plugins/firmware_extract.py +133 -29
- souleyez/plugins/gobuster.py +387 -190
- souleyez/plugins/gpp_extract.py +393 -0
- souleyez/plugins/hashcat.py +100 -73
- souleyez/plugins/http_fingerprint.py +854 -267
- souleyez/plugins/hydra.py +566 -200
- souleyez/plugins/impacket_getnpusers.py +117 -69
- souleyez/plugins/impacket_psexec.py +84 -64
- souleyez/plugins/impacket_secretsdump.py +103 -69
- souleyez/plugins/impacket_smbclient.py +89 -75
- souleyez/plugins/john.py +86 -69
- souleyez/plugins/katana.py +313 -0
- souleyez/plugins/kerbrute.py +237 -0
- souleyez/plugins/lfi_extract.py +541 -0
- souleyez/plugins/macos_ssh.py +117 -48
- souleyez/plugins/mdns.py +35 -30
- souleyez/plugins/msf_auxiliary.py +253 -130
- souleyez/plugins/msf_exploit.py +239 -161
- souleyez/plugins/nikto.py +134 -78
- souleyez/plugins/nmap.py +275 -91
- souleyez/plugins/nuclei.py +180 -89
- souleyez/plugins/nxc.py +285 -0
- souleyez/plugins/plugin_base.py +35 -36
- souleyez/plugins/plugin_template.py +13 -5
- souleyez/plugins/rdp_sec_check.py +130 -0
- souleyez/plugins/responder.py +112 -71
- souleyez/plugins/router_http_brute.py +76 -65
- souleyez/plugins/router_ssh_brute.py +118 -41
- souleyez/plugins/router_telnet_brute.py +124 -42
- souleyez/plugins/routersploit.py +91 -59
- souleyez/plugins/routersploit_exploit.py +77 -55
- souleyez/plugins/searchsploit.py +91 -77
- souleyez/plugins/service_explorer.py +1160 -0
- souleyez/plugins/smbmap.py +122 -72
- souleyez/plugins/smbpasswd.py +215 -0
- souleyez/plugins/sqlmap.py +301 -113
- souleyez/plugins/theharvester.py +127 -75
- souleyez/plugins/tr069.py +79 -57
- souleyez/plugins/upnp.py +65 -47
- souleyez/plugins/upnp_abuse.py +73 -55
- souleyez/plugins/vnc_access.py +129 -42
- souleyez/plugins/vnc_brute.py +109 -38
- souleyez/plugins/web_login_test.py +417 -0
- souleyez/plugins/whois.py +77 -58
- souleyez/plugins/wpscan.py +173 -69
- souleyez/reporting/__init__.py +2 -1
- souleyez/reporting/attack_chain.py +411 -346
- souleyez/reporting/charts.py +436 -501
- souleyez/reporting/compliance_mappings.py +334 -201
- souleyez/reporting/detection_report.py +126 -125
- souleyez/reporting/formatters.py +828 -591
- souleyez/reporting/generator.py +386 -302
- souleyez/reporting/metrics.py +72 -75
- souleyez/scanner.py +35 -29
- souleyez/security/__init__.py +37 -11
- souleyez/security/scope_validator.py +175 -106
- souleyez/security/validation.py +223 -149
- souleyez/security.py +22 -6
- souleyez/storage/credentials.py +247 -186
- souleyez/storage/crypto.py +296 -129
- souleyez/storage/database.py +73 -50
- souleyez/storage/db.py +58 -36
- souleyez/storage/deliverable_evidence.py +177 -128
- souleyez/storage/deliverable_exporter.py +282 -246
- souleyez/storage/deliverable_templates.py +134 -116
- souleyez/storage/deliverables.py +135 -130
- souleyez/storage/engagements.py +109 -56
- souleyez/storage/evidence.py +181 -152
- souleyez/storage/execution_log.py +31 -17
- souleyez/storage/exploit_attempts.py +93 -57
- souleyez/storage/exploits.py +67 -36
- souleyez/storage/findings.py +48 -61
- souleyez/storage/hosts.py +176 -144
- souleyez/storage/migrate_to_engagements.py +43 -19
- souleyez/storage/migrations/_001_add_credential_enhancements.py +22 -12
- souleyez/storage/migrations/_002_add_status_tracking.py +10 -7
- souleyez/storage/migrations/_003_add_execution_log.py +14 -8
- souleyez/storage/migrations/_005_screenshots.py +13 -5
- souleyez/storage/migrations/_006_deliverables.py +13 -5
- souleyez/storage/migrations/_007_deliverable_templates.py +12 -7
- souleyez/storage/migrations/_008_add_nuclei_table.py +10 -4
- souleyez/storage/migrations/_010_evidence_linking.py +17 -10
- souleyez/storage/migrations/_011_timeline_tracking.py +20 -13
- souleyez/storage/migrations/_012_team_collaboration.py +34 -21
- souleyez/storage/migrations/_013_add_host_tags.py +12 -6
- souleyez/storage/migrations/_014_exploit_attempts.py +22 -10
- souleyez/storage/migrations/_015_add_mac_os_fields.py +15 -7
- souleyez/storage/migrations/_016_add_domain_field.py +10 -4
- souleyez/storage/migrations/_017_msf_sessions.py +16 -8
- souleyez/storage/migrations/_018_add_osint_target.py +10 -6
- souleyez/storage/migrations/_019_add_engagement_type.py +10 -6
- souleyez/storage/migrations/_020_add_rbac.py +36 -15
- souleyez/storage/migrations/_021_wazuh_integration.py +20 -8
- souleyez/storage/migrations/_022_wazuh_indexer_columns.py +6 -4
- souleyez/storage/migrations/_023_fix_detection_results_fk.py +16 -6
- souleyez/storage/migrations/_024_wazuh_vulnerabilities.py +26 -10
- souleyez/storage/migrations/_025_multi_siem_support.py +3 -5
- souleyez/storage/migrations/_026_add_engagement_scope.py +31 -12
- souleyez/storage/migrations/_027_multi_siem_persistence.py +32 -15
- souleyez/storage/migrations/__init__.py +26 -26
- souleyez/storage/migrations/migration_manager.py +19 -19
- souleyez/storage/msf_sessions.py +100 -65
- souleyez/storage/osint.py +17 -24
- souleyez/storage/recommendation_engine.py +269 -235
- souleyez/storage/screenshots.py +33 -32
- souleyez/storage/smb_shares.py +136 -92
- souleyez/storage/sqlmap_data.py +183 -128
- souleyez/storage/team_collaboration.py +135 -141
- souleyez/storage/timeline_tracker.py +122 -94
- souleyez/storage/wazuh_vulns.py +64 -66
- souleyez/storage/web_paths.py +33 -37
- souleyez/testing/credential_tester.py +221 -205
- souleyez/ui/__init__.py +1 -1
- souleyez/ui/ai_quotes.py +12 -12
- souleyez/ui/attack_surface.py +2439 -1516
- souleyez/ui/chain_rules_view.py +914 -382
- souleyez/ui/correlation_view.py +312 -230
- souleyez/ui/dashboard.py +2382 -1130
- souleyez/ui/deliverables_view.py +148 -62
- souleyez/ui/design_system.py +13 -13
- souleyez/ui/errors.py +49 -49
- souleyez/ui/evidence_linking_view.py +284 -179
- souleyez/ui/evidence_vault.py +393 -285
- souleyez/ui/exploit_suggestions_view.py +555 -349
- souleyez/ui/export_view.py +100 -66
- souleyez/ui/gap_analysis_view.py +315 -171
- souleyez/ui/help_system.py +105 -97
- souleyez/ui/intelligence_view.py +436 -293
- souleyez/ui/interactive.py +23434 -10286
- souleyez/ui/interactive_selector.py +75 -68
- souleyez/ui/log_formatter.py +47 -39
- souleyez/ui/menu_components.py +22 -13
- souleyez/ui/msf_auxiliary_menu.py +184 -133
- souleyez/ui/pending_chains_view.py +336 -172
- souleyez/ui/progress_indicators.py +5 -3
- souleyez/ui/recommendations_view.py +195 -137
- souleyez/ui/rule_builder.py +343 -225
- souleyez/ui/setup_wizard.py +678 -284
- souleyez/ui/shortcuts.py +217 -165
- souleyez/ui/splunk_gap_analysis_view.py +452 -270
- souleyez/ui/splunk_vulns_view.py +139 -86
- souleyez/ui/team_dashboard.py +498 -335
- souleyez/ui/template_selector.py +196 -105
- souleyez/ui/terminal.py +6 -6
- souleyez/ui/timeline_view.py +198 -127
- souleyez/ui/tool_setup.py +264 -164
- souleyez/ui/tutorial.py +202 -72
- souleyez/ui/tutorial_state.py +40 -40
- souleyez/ui/wazuh_vulns_view.py +235 -141
- souleyez/ui/wordlist_browser.py +260 -107
- souleyez/ui.py +464 -312
- souleyez/utils/tool_checker.py +427 -367
- souleyez/utils.py +33 -29
- souleyez/wordlists.py +134 -167
- {souleyez-2.43.26.dist-info → souleyez-2.43.34.dist-info}/METADATA +1 -1
- souleyez-2.43.34.dist-info/RECORD +443 -0
- {souleyez-2.43.26.dist-info → souleyez-2.43.34.dist-info}/WHEEL +1 -1
- souleyez-2.43.26.dist-info/RECORD +0 -379
- {souleyez-2.43.26.dist-info → souleyez-2.43.34.dist-info}/entry_points.txt +0 -0
- {souleyez-2.43.26.dist-info → souleyez-2.43.34.dist-info}/licenses/LICENSE +0 -0
- {souleyez-2.43.26.dist-info → souleyez-2.43.34.dist-info}/top_level.txt +0 -0
souleyez/core/network_utils.py
CHANGED
|
@@ -4,6 +4,7 @@ Network utilities for security validation.
|
|
|
4
4
|
Provides functions for detecting VM host (gateway) and validating
|
|
5
5
|
that Ollama connections only go to trusted destinations.
|
|
6
6
|
"""
|
|
7
|
+
|
|
7
8
|
import socket
|
|
8
9
|
import subprocess
|
|
9
10
|
import re
|
|
@@ -23,14 +24,14 @@ def get_default_gateway() -> Optional[str]:
|
|
|
23
24
|
try:
|
|
24
25
|
# Try using 'ip route' (Linux)
|
|
25
26
|
result = subprocess.run(
|
|
26
|
-
[
|
|
27
|
+
["ip", "route", "show", "default"],
|
|
27
28
|
capture_output=True,
|
|
28
29
|
text=True,
|
|
29
|
-
timeout=5
|
|
30
|
+
timeout=5,
|
|
30
31
|
)
|
|
31
32
|
if result.returncode == 0:
|
|
32
33
|
# Parse: "default via 10.0.0.1 dev eth0 ..."
|
|
33
|
-
match = re.search(r
|
|
34
|
+
match = re.search(r"default via (\d+\.\d+\.\d+\.\d+)", result.stdout)
|
|
34
35
|
if match:
|
|
35
36
|
return match.group(1)
|
|
36
37
|
except (subprocess.TimeoutExpired, FileNotFoundError):
|
|
@@ -39,14 +40,13 @@ def get_default_gateway() -> Optional[str]:
|
|
|
39
40
|
try:
|
|
40
41
|
# Fallback: 'route -n' (older Linux)
|
|
41
42
|
result = subprocess.run(
|
|
42
|
-
[
|
|
43
|
-
capture_output=True,
|
|
44
|
-
text=True,
|
|
45
|
-
timeout=5
|
|
43
|
+
["route", "-n"], capture_output=True, text=True, timeout=5
|
|
46
44
|
)
|
|
47
45
|
if result.returncode == 0:
|
|
48
|
-
for line in result.stdout.split(
|
|
49
|
-
if line.startswith(
|
|
46
|
+
for line in result.stdout.split("\n"):
|
|
47
|
+
if line.startswith(
|
|
48
|
+
"0.0.0.0"
|
|
49
|
+
): # nosec B104 - parsing route output, not binding
|
|
50
50
|
parts = line.split()
|
|
51
51
|
if len(parts) >= 2:
|
|
52
52
|
return parts[1]
|
|
@@ -56,8 +56,9 @@ def get_default_gateway() -> Optional[str]:
|
|
|
56
56
|
try:
|
|
57
57
|
# Fallback: netifaces if available
|
|
58
58
|
import netifaces
|
|
59
|
+
|
|
59
60
|
gateways = netifaces.gateways()
|
|
60
|
-
default = gateways.get(
|
|
61
|
+
default = gateways.get("default", {})
|
|
61
62
|
if netifaces.AF_INET in default:
|
|
62
63
|
return default[netifaces.AF_INET][0]
|
|
63
64
|
except ImportError:
|
|
@@ -87,7 +88,7 @@ def is_localhost(host: str) -> bool:
|
|
|
87
88
|
"""Check if host is localhost."""
|
|
88
89
|
if not host:
|
|
89
90
|
return False
|
|
90
|
-
return host in (
|
|
91
|
+
return host in ("localhost", "127.0.0.1", "::1")
|
|
91
92
|
|
|
92
93
|
|
|
93
94
|
def is_private_ip(ip: str) -> bool:
|
|
@@ -109,7 +110,7 @@ def is_private_ip(ip: str) -> bool:
|
|
|
109
110
|
return False
|
|
110
111
|
|
|
111
112
|
try:
|
|
112
|
-
parts = ip.split(
|
|
113
|
+
parts = ip.split(".")
|
|
113
114
|
if len(parts) != 4:
|
|
114
115
|
return False
|
|
115
116
|
|
|
@@ -173,7 +174,13 @@ def get_ollama_host_info() -> dict:
|
|
|
173
174
|
Dict with allowed host info
|
|
174
175
|
"""
|
|
175
176
|
return {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
177
|
+
"allowed_hosts": [
|
|
178
|
+
"localhost",
|
|
179
|
+
"127.0.0.1",
|
|
180
|
+
"10.x.x.x",
|
|
181
|
+
"172.16-31.x.x",
|
|
182
|
+
"192.168.x.x",
|
|
183
|
+
],
|
|
184
|
+
"description": "Localhost and private network IPs (RFC 1918)",
|
|
185
|
+
"blocked": "Public internet IPs and hostnames",
|
|
179
186
|
}
|
souleyez/core/parser_handler.py
CHANGED
|
@@ -33,37 +33,40 @@ class ParserHandler:
|
|
|
33
33
|
def _register_parsers(self):
|
|
34
34
|
"""Register all available parsers."""
|
|
35
35
|
parser_modules = {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
'dalfox': 'souleyez.parsers.dalfox_parser',
|
|
36
|
+
"nmap": "souleyez.parsers.nmap_parser",
|
|
37
|
+
"enum4linux": "souleyez.parsers.enum4linux_parser",
|
|
38
|
+
"gobuster": "souleyez.parsers.gobuster_parser",
|
|
39
|
+
"sqlmap": "souleyez.parsers.sqlmap_parser",
|
|
40
|
+
"smbmap": "souleyez.parsers.smbmap_parser",
|
|
41
|
+
"theharvester": "souleyez.parsers.theharvester_parser",
|
|
42
|
+
"msf": "souleyez.parsers.msf_parser",
|
|
43
|
+
"whois": "souleyez.parsers.whois_parser",
|
|
44
|
+
"wpscan": "souleyez.parsers.wpscan_parser",
|
|
45
|
+
"hydra": "souleyez.parsers.hydra_parser",
|
|
46
|
+
"dnsrecon": "souleyez.parsers.dnsrecon_parser",
|
|
47
|
+
"nikto": "souleyez.parsers.nikto_parser",
|
|
48
|
+
"dalfox": "souleyez.parsers.dalfox_parser",
|
|
50
49
|
}
|
|
51
50
|
|
|
52
51
|
for tool, module_path in parser_modules.items():
|
|
53
52
|
try:
|
|
54
|
-
module = __import__(module_path, fromlist=[
|
|
53
|
+
module = __import__(module_path, fromlist=[""])
|
|
55
54
|
parse_func_name = f"parse_{tool}_output"
|
|
56
55
|
if hasattr(module, parse_func_name):
|
|
57
56
|
self.parsers[tool] = getattr(module, parse_func_name)
|
|
58
57
|
logger.debug(f"Registered parser for {tool}")
|
|
59
58
|
else:
|
|
60
|
-
logger.warning(
|
|
59
|
+
logger.warning(
|
|
60
|
+
f"Parser module {module_path} missing {parse_func_name}"
|
|
61
|
+
)
|
|
61
62
|
except ImportError as e:
|
|
62
63
|
logger.warning(f"Could not import parser for {tool}: {e}")
|
|
63
64
|
except Exception as e:
|
|
64
65
|
logger.error(f"Error registering parser for {tool}: {e}")
|
|
65
66
|
|
|
66
|
-
def parse(
|
|
67
|
+
def parse(
|
|
68
|
+
self, tool: str, output: str, target: str = "", **kwargs
|
|
69
|
+
) -> Optional[Dict[str, Any]]:
|
|
67
70
|
"""
|
|
68
71
|
Parse tool output with error handling.
|
|
69
72
|
|
|
@@ -92,13 +95,15 @@ class ParserHandler:
|
|
|
92
95
|
logger.error(f"Parser error for {tool}: {e}", exc_info=True)
|
|
93
96
|
# Return basic structure with error information
|
|
94
97
|
return {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
98
|
+
"error": str(e),
|
|
99
|
+
"tool": tool,
|
|
100
|
+
"target": target,
|
|
101
|
+
"raw_output": output[:500], # First 500 chars for debugging
|
|
99
102
|
}
|
|
100
103
|
|
|
101
|
-
def parse_file(
|
|
104
|
+
def parse_file(
|
|
105
|
+
self, tool: str, log_path: str, target: str = "", **kwargs
|
|
106
|
+
) -> Optional[Dict[str, Any]]:
|
|
102
107
|
"""
|
|
103
108
|
Parse tool output from file with error handling.
|
|
104
109
|
|
|
@@ -117,7 +122,7 @@ class ParserHandler:
|
|
|
117
122
|
logger.error(f"Log file does not exist: {log_path}")
|
|
118
123
|
return None
|
|
119
124
|
|
|
120
|
-
with open(log_path,
|
|
125
|
+
with open(log_path, "r", encoding="utf-8", errors="replace") as f:
|
|
121
126
|
output = f.read()
|
|
122
127
|
|
|
123
128
|
return self.parse(tool, output, target, **kwargs)
|
|
@@ -148,13 +153,17 @@ def get_parser_handler() -> ParserHandler:
|
|
|
148
153
|
|
|
149
154
|
|
|
150
155
|
# Convenience functions
|
|
151
|
-
def parse_output(
|
|
156
|
+
def parse_output(
|
|
157
|
+
tool: str, output: str, target: str = "", **kwargs
|
|
158
|
+
) -> Optional[Dict[str, Any]]:
|
|
152
159
|
"""Parse tool output using the global parser handler."""
|
|
153
160
|
handler = get_parser_handler()
|
|
154
161
|
return handler.parse(tool, output, target, **kwargs)
|
|
155
162
|
|
|
156
163
|
|
|
157
|
-
def parse_file(
|
|
164
|
+
def parse_file(
|
|
165
|
+
tool: str, log_path: str, target: str = "", **kwargs
|
|
166
|
+
) -> Optional[Dict[str, Any]]:
|
|
158
167
|
"""Parse tool output file using the global parser handler."""
|
|
159
168
|
handler = get_parser_handler()
|
|
160
169
|
return handler.parse_file(tool, log_path, target, **kwargs)
|
souleyez/core/pending_chains.py
CHANGED
|
@@ -22,10 +22,10 @@ _lock = threading.RLock()
|
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
# Chain status constants
|
|
25
|
-
CHAIN_PENDING =
|
|
26
|
-
CHAIN_APPROVED =
|
|
27
|
-
CHAIN_REJECTED =
|
|
28
|
-
CHAIN_EXECUTED =
|
|
25
|
+
CHAIN_PENDING = "pending" # Awaiting user decision
|
|
26
|
+
CHAIN_APPROVED = "approved" # User approved, ready to execute
|
|
27
|
+
CHAIN_REJECTED = "rejected" # User rejected, will not execute
|
|
28
|
+
CHAIN_EXECUTED = "executed" # Approved and job created
|
|
29
29
|
|
|
30
30
|
|
|
31
31
|
def _ensure_dirs():
|
|
@@ -48,7 +48,9 @@ def _read_chains() -> List[Dict[str, Any]]:
|
|
|
48
48
|
def _write_chains(chains: List[Dict[str, Any]]):
|
|
49
49
|
"""Write chains to storage atomically."""
|
|
50
50
|
_ensure_dirs()
|
|
51
|
-
tmp = tempfile.NamedTemporaryFile(
|
|
51
|
+
tmp = tempfile.NamedTemporaryFile(
|
|
52
|
+
"w", delete=False, dir=CHAINS_DIR, encoding="utf-8"
|
|
53
|
+
)
|
|
52
54
|
try:
|
|
53
55
|
json.dump(chains, tmp, indent=2, ensure_ascii=False)
|
|
54
56
|
tmp.flush()
|
|
@@ -69,7 +71,7 @@ def _next_chain_id(chains: List[Dict[str, Any]]) -> int:
|
|
|
69
71
|
|
|
70
72
|
try:
|
|
71
73
|
if os.path.exists(counter_file):
|
|
72
|
-
with open(counter_file,
|
|
74
|
+
with open(counter_file, "r") as f:
|
|
73
75
|
next_id = int(f.read().strip())
|
|
74
76
|
else:
|
|
75
77
|
maxid = 0
|
|
@@ -78,7 +80,7 @@ def _next_chain_id(chains: List[Dict[str, Any]]) -> int:
|
|
|
78
80
|
maxid = c["id"]
|
|
79
81
|
next_id = maxid + 1
|
|
80
82
|
|
|
81
|
-
with open(counter_file,
|
|
83
|
+
with open(counter_file, "w") as f:
|
|
82
84
|
f.write(str(next_id + 1))
|
|
83
85
|
|
|
84
86
|
return next_id
|
|
@@ -98,7 +100,7 @@ def add_pending_chain(
|
|
|
98
100
|
args: List[str],
|
|
99
101
|
priority: int,
|
|
100
102
|
engagement_id: Optional[int] = None,
|
|
101
|
-
metadata: Optional[Dict[str, Any]] = None
|
|
103
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
102
104
|
) -> int:
|
|
103
105
|
"""
|
|
104
106
|
Add a chain to the pending approval queue.
|
|
@@ -122,20 +124,20 @@ def add_pending_chain(
|
|
|
122
124
|
now = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
|
|
123
125
|
|
|
124
126
|
chain = {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
127
|
+
"id": chain_id,
|
|
128
|
+
"parent_job_id": parent_job_id,
|
|
129
|
+
"rule_description": rule_description,
|
|
130
|
+
"tool": tool,
|
|
131
|
+
"target": target,
|
|
132
|
+
"args": args or [],
|
|
133
|
+
"priority": priority,
|
|
134
|
+
"status": CHAIN_PENDING,
|
|
135
|
+
"created_at": now,
|
|
136
|
+
"decided_at": None,
|
|
137
|
+
"executed_at": None,
|
|
138
|
+
"job_id": None, # Set when executed
|
|
139
|
+
"engagement_id": engagement_id,
|
|
140
|
+
"metadata": metadata or {},
|
|
139
141
|
}
|
|
140
142
|
|
|
141
143
|
chains.append(chain)
|
|
@@ -148,7 +150,7 @@ def list_pending_chains(
|
|
|
148
150
|
status: Optional[str] = None,
|
|
149
151
|
engagement_id: Optional[int] = None,
|
|
150
152
|
limit: int = 100,
|
|
151
|
-
offset: int = 0
|
|
153
|
+
offset: int = 0,
|
|
152
154
|
) -> List[Dict[str, Any]]:
|
|
153
155
|
"""
|
|
154
156
|
List chains, optionally filtered by status and engagement.
|
|
@@ -166,21 +168,21 @@ def list_pending_chains(
|
|
|
166
168
|
|
|
167
169
|
# Apply filters
|
|
168
170
|
if status:
|
|
169
|
-
chains = [c for c in chains if c.get(
|
|
171
|
+
chains = [c for c in chains if c.get("status") == status]
|
|
170
172
|
if engagement_id is not None:
|
|
171
|
-
chains = [c for c in chains if c.get(
|
|
173
|
+
chains = [c for c in chains if c.get("engagement_id") == engagement_id]
|
|
172
174
|
|
|
173
175
|
# Sort by priority (desc) then created_at (asc)
|
|
174
|
-
chains.sort(key=lambda c: (-c.get(
|
|
176
|
+
chains.sort(key=lambda c: (-c.get("priority", 5), c.get("created_at", "")))
|
|
175
177
|
|
|
176
|
-
return chains[offset:offset + limit]
|
|
178
|
+
return chains[offset : offset + limit]
|
|
177
179
|
|
|
178
180
|
|
|
179
181
|
def get_pending_chain(chain_id: int) -> Optional[Dict[str, Any]]:
|
|
180
182
|
"""Get a specific chain by ID."""
|
|
181
183
|
chains = _read_chains()
|
|
182
184
|
for c in chains:
|
|
183
|
-
if c.get(
|
|
185
|
+
if c.get("id") == chain_id:
|
|
184
186
|
return c
|
|
185
187
|
return None
|
|
186
188
|
|
|
@@ -195,11 +197,11 @@ def approve_chain(chain_id: int) -> bool:
|
|
|
195
197
|
with _lock:
|
|
196
198
|
chains = _read_chains()
|
|
197
199
|
for c in chains:
|
|
198
|
-
if c.get(
|
|
199
|
-
if c.get(
|
|
200
|
+
if c.get("id") == chain_id:
|
|
201
|
+
if c.get("status") != CHAIN_PENDING:
|
|
200
202
|
return False # Already decided
|
|
201
|
-
c[
|
|
202
|
-
c[
|
|
203
|
+
c["status"] = CHAIN_APPROVED
|
|
204
|
+
c["decided_at"] = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
|
|
203
205
|
_write_chains(chains)
|
|
204
206
|
return True
|
|
205
207
|
return False
|
|
@@ -215,11 +217,11 @@ def reject_chain(chain_id: int) -> bool:
|
|
|
215
217
|
with _lock:
|
|
216
218
|
chains = _read_chains()
|
|
217
219
|
for c in chains:
|
|
218
|
-
if c.get(
|
|
219
|
-
if c.get(
|
|
220
|
+
if c.get("id") == chain_id:
|
|
221
|
+
if c.get("status") != CHAIN_PENDING:
|
|
220
222
|
return False
|
|
221
|
-
c[
|
|
222
|
-
c[
|
|
223
|
+
c["status"] = CHAIN_REJECTED
|
|
224
|
+
c["decided_at"] = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
|
|
223
225
|
_write_chains(chains)
|
|
224
226
|
return True
|
|
225
227
|
return False
|
|
@@ -241,10 +243,10 @@ def approve_all_pending(engagement_id: Optional[int] = None) -> int:
|
|
|
241
243
|
approved = 0
|
|
242
244
|
|
|
243
245
|
for c in chains:
|
|
244
|
-
if c.get(
|
|
245
|
-
if engagement_id is None or c.get(
|
|
246
|
-
c[
|
|
247
|
-
c[
|
|
246
|
+
if c.get("status") == CHAIN_PENDING:
|
|
247
|
+
if engagement_id is None or c.get("engagement_id") == engagement_id:
|
|
248
|
+
c["status"] = CHAIN_APPROVED
|
|
249
|
+
c["decided_at"] = now
|
|
248
250
|
approved += 1
|
|
249
251
|
|
|
250
252
|
if approved > 0:
|
|
@@ -269,10 +271,10 @@ def reject_all_pending(engagement_id: Optional[int] = None) -> int:
|
|
|
269
271
|
rejected = 0
|
|
270
272
|
|
|
271
273
|
for c in chains:
|
|
272
|
-
if c.get(
|
|
273
|
-
if engagement_id is None or c.get(
|
|
274
|
-
c[
|
|
275
|
-
c[
|
|
274
|
+
if c.get("status") == CHAIN_PENDING:
|
|
275
|
+
if engagement_id is None or c.get("engagement_id") == engagement_id:
|
|
276
|
+
c["status"] = CHAIN_REJECTED
|
|
277
|
+
c["decided_at"] = now
|
|
276
278
|
rejected += 1
|
|
277
279
|
|
|
278
280
|
if rejected > 0:
|
|
@@ -295,16 +297,18 @@ def mark_chain_executed(chain_id: int, job_id: int) -> bool:
|
|
|
295
297
|
with _lock:
|
|
296
298
|
chains = _read_chains()
|
|
297
299
|
for c in chains:
|
|
298
|
-
if c.get(
|
|
299
|
-
c[
|
|
300
|
-
c[
|
|
301
|
-
c[
|
|
300
|
+
if c.get("id") == chain_id:
|
|
301
|
+
c["status"] = CHAIN_EXECUTED
|
|
302
|
+
c["executed_at"] = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
|
|
303
|
+
c["job_id"] = job_id
|
|
302
304
|
_write_chains(chains)
|
|
303
305
|
return True
|
|
304
306
|
return False
|
|
305
307
|
|
|
306
308
|
|
|
307
|
-
def get_approved_chains(
|
|
309
|
+
def get_approved_chains(
|
|
310
|
+
engagement_id: Optional[int] = None, limit: int = 50
|
|
311
|
+
) -> List[Dict[str, Any]]:
|
|
308
312
|
"""
|
|
309
313
|
Get approved chains ready for execution.
|
|
310
314
|
|
|
@@ -315,7 +319,9 @@ def get_approved_chains(engagement_id: Optional[int] = None, limit: int = 50) ->
|
|
|
315
319
|
Returns:
|
|
316
320
|
List of approved chains, sorted by priority
|
|
317
321
|
"""
|
|
318
|
-
return list_pending_chains(
|
|
322
|
+
return list_pending_chains(
|
|
323
|
+
status=CHAIN_APPROVED, engagement_id=engagement_id, limit=limit
|
|
324
|
+
)
|
|
319
325
|
|
|
320
326
|
|
|
321
327
|
def purge_old_chains(days: int = 7) -> int:
|
|
@@ -337,10 +343,11 @@ def purge_old_chains(days: int = 7) -> int:
|
|
|
337
343
|
|
|
338
344
|
original_count = len(chains)
|
|
339
345
|
chains = [
|
|
340
|
-
c
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
346
|
+
c
|
|
347
|
+
for c in chains
|
|
348
|
+
if c.get("status") == CHAIN_PENDING # Keep pending
|
|
349
|
+
or c.get("status") == CHAIN_APPROVED # Keep approved (not yet executed)
|
|
350
|
+
or c.get("created_at", "") > cutoff_str # Keep recent
|
|
344
351
|
]
|
|
345
352
|
|
|
346
353
|
purged = original_count - len(chains)
|
|
@@ -353,9 +360,9 @@ def purge_old_chains(days: int = 7) -> int:
|
|
|
353
360
|
def get_pending_count(engagement_id: Optional[int] = None) -> int:
|
|
354
361
|
"""Get count of pending chains awaiting approval."""
|
|
355
362
|
chains = _read_chains()
|
|
356
|
-
pending = [c for c in chains if c.get(
|
|
363
|
+
pending = [c for c in chains if c.get("status") == CHAIN_PENDING]
|
|
357
364
|
if engagement_id is not None:
|
|
358
|
-
pending = [c for c in pending if c.get(
|
|
365
|
+
pending = [c for c in pending if c.get("engagement_id") == engagement_id]
|
|
359
366
|
return len(pending)
|
|
360
367
|
|
|
361
368
|
|
|
@@ -369,19 +376,63 @@ def get_chain_stats(engagement_id: Optional[int] = None) -> Dict[str, int]:
|
|
|
369
376
|
chains = _read_chains()
|
|
370
377
|
|
|
371
378
|
if engagement_id is not None:
|
|
372
|
-
chains = [c for c in chains if c.get(
|
|
379
|
+
chains = [c for c in chains if c.get("engagement_id") == engagement_id]
|
|
373
380
|
|
|
374
381
|
stats = {
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
382
|
+
"pending": 0,
|
|
383
|
+
"approved": 0,
|
|
384
|
+
"rejected": 0,
|
|
385
|
+
"executed": 0,
|
|
386
|
+
"total": len(chains),
|
|
380
387
|
}
|
|
381
388
|
|
|
382
389
|
for c in chains:
|
|
383
|
-
status = c.get(
|
|
390
|
+
status = c.get("status", "")
|
|
384
391
|
if status in stats:
|
|
385
392
|
stats[status] += 1
|
|
386
393
|
|
|
387
394
|
return stats
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
def purge_orphaned_chains() -> int:
|
|
398
|
+
"""
|
|
399
|
+
Remove chains that reference non-existent engagements.
|
|
400
|
+
|
|
401
|
+
This can happen when engagements are deleted but their pending
|
|
402
|
+
chains weren't cleaned up. Orphaned chains waste resources and
|
|
403
|
+
will never be processed.
|
|
404
|
+
|
|
405
|
+
Returns:
|
|
406
|
+
Number of orphaned chains removed
|
|
407
|
+
"""
|
|
408
|
+
import sqlite3
|
|
409
|
+
|
|
410
|
+
with _lock:
|
|
411
|
+
chains = _read_chains()
|
|
412
|
+
if not chains:
|
|
413
|
+
return 0
|
|
414
|
+
|
|
415
|
+
# Get all valid engagement IDs from database
|
|
416
|
+
try:
|
|
417
|
+
db_path = os.path.join(os.path.expanduser("~"), ".souleyez", "souleyez.db")
|
|
418
|
+
conn = sqlite3.connect(db_path)
|
|
419
|
+
cursor = conn.execute("SELECT id FROM engagements")
|
|
420
|
+
valid_ids = {row[0] for row in cursor.fetchall()}
|
|
421
|
+
conn.close()
|
|
422
|
+
except Exception:
|
|
423
|
+
return 0 # Can't verify, don't purge
|
|
424
|
+
|
|
425
|
+
original_count = len(chains)
|
|
426
|
+
|
|
427
|
+
# Keep only chains with valid engagement IDs (or None)
|
|
428
|
+
chains = [
|
|
429
|
+
c
|
|
430
|
+
for c in chains
|
|
431
|
+
if c.get("engagement_id") is None or c.get("engagement_id") in valid_ids
|
|
432
|
+
]
|
|
433
|
+
|
|
434
|
+
purged = original_count - len(chains)
|
|
435
|
+
if purged > 0:
|
|
436
|
+
_write_chains(chains)
|
|
437
|
+
|
|
438
|
+
return purged
|