souleyez 2.43.29__py3-none-any.whl → 3.0.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 -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 +9564 -2881
- 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 +564 -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 +409 -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 +417 -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 +913 -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 +219 -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 +237 -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 +23034 -10679
- 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.29.dist-info → souleyez-3.0.0.dist-info}/METADATA +2 -2
- souleyez-3.0.0.dist-info/RECORD +443 -0
- {souleyez-2.43.29.dist-info → souleyez-3.0.0.dist-info}/WHEEL +1 -1
- souleyez-2.43.29.dist-info/RECORD +0 -379
- {souleyez-2.43.29.dist-info → souleyez-3.0.0.dist-info}/entry_points.txt +0 -0
- {souleyez-2.43.29.dist-info → souleyez-3.0.0.dist-info}/licenses/LICENSE +0 -0
- {souleyez-2.43.29.dist-info → souleyez-3.0.0.dist-info}/top_level.txt +0 -0
souleyez/ai/result_parser.py
CHANGED
|
@@ -12,7 +12,7 @@ logger = logging.getLogger(__name__)
|
|
|
12
12
|
class ResultParser:
|
|
13
13
|
"""
|
|
14
14
|
Parse command execution results and extract meaningful data.
|
|
15
|
-
|
|
15
|
+
|
|
16
16
|
Handles different command types (SSH, MySQL, nmap, etc.)
|
|
17
17
|
and extracts success status, access levels, and other metadata.
|
|
18
18
|
"""
|
|
@@ -22,61 +22,54 @@ class ResultParser:
|
|
|
22
22
|
pass
|
|
23
23
|
|
|
24
24
|
def parse_result(
|
|
25
|
-
self,
|
|
26
|
-
command: str,
|
|
27
|
-
stdout: str,
|
|
28
|
-
stderr: str,
|
|
29
|
-
exit_code: int
|
|
25
|
+
self, command: str, stdout: str, stderr: str, exit_code: int
|
|
30
26
|
) -> Dict[str, Any]:
|
|
31
27
|
"""
|
|
32
28
|
Parse command result based on command type.
|
|
33
|
-
|
|
29
|
+
|
|
34
30
|
Args:
|
|
35
31
|
command: Command that was executed
|
|
36
32
|
stdout: Standard output
|
|
37
33
|
stderr: Standard error
|
|
38
34
|
exit_code: Exit code
|
|
39
|
-
|
|
35
|
+
|
|
40
36
|
Returns:
|
|
41
37
|
Dict with parsed results
|
|
42
38
|
"""
|
|
43
39
|
cmd_lower = command.lower()
|
|
44
|
-
|
|
40
|
+
|
|
45
41
|
# Detect command type and parse accordingly
|
|
46
|
-
if
|
|
42
|
+
if "sshpass" in cmd_lower or ("ssh" in cmd_lower and "SSH_SUCCESS" in command):
|
|
47
43
|
return self.parse_ssh_result(stdout, stderr, exit_code)
|
|
48
|
-
elif
|
|
44
|
+
elif "smbclient" in cmd_lower:
|
|
49
45
|
return self.parse_smb_result(stdout, stderr, exit_code)
|
|
50
|
-
elif
|
|
46
|
+
elif "xfreerdp" in cmd_lower:
|
|
51
47
|
return self.parse_rdp_result(stdout, stderr, exit_code)
|
|
52
|
-
elif
|
|
48
|
+
elif "lftp" in cmd_lower:
|
|
53
49
|
return self.parse_ftp_result(stdout, stderr, exit_code)
|
|
54
|
-
elif
|
|
50
|
+
elif "psql" in cmd_lower or "PGPASSWORD" in command:
|
|
55
51
|
return self.parse_postgresql_result(stdout, stderr, exit_code)
|
|
56
|
-
elif
|
|
52
|
+
elif "mysql" in cmd_lower:
|
|
57
53
|
return self.parse_mysql_result(stdout, stderr, exit_code, command)
|
|
58
|
-
elif
|
|
54
|
+
elif "nmap" in cmd_lower:
|
|
59
55
|
return self.parse_nmap_result(stdout, stderr, exit_code)
|
|
60
|
-
elif
|
|
56
|
+
elif "curl" in cmd_lower:
|
|
61
57
|
return self.parse_http_result(stdout, stderr, exit_code)
|
|
62
58
|
else:
|
|
63
59
|
# Generic parsing
|
|
64
60
|
return self.parse_generic_result(stdout, stderr, exit_code)
|
|
65
61
|
|
|
66
62
|
def parse_ssh_result(
|
|
67
|
-
self,
|
|
68
|
-
stdout: str,
|
|
69
|
-
stderr: str,
|
|
70
|
-
exit_code: int
|
|
63
|
+
self, stdout: str, stderr: str, exit_code: int
|
|
71
64
|
) -> Dict[str, Any]:
|
|
72
65
|
"""
|
|
73
66
|
Parse SSH credential test result.
|
|
74
|
-
|
|
67
|
+
|
|
75
68
|
Args:
|
|
76
69
|
stdout: Command output
|
|
77
70
|
stderr: Error output
|
|
78
71
|
exit_code: Exit code
|
|
79
|
-
|
|
72
|
+
|
|
80
73
|
Returns:
|
|
81
74
|
Dict with:
|
|
82
75
|
- success: bool
|
|
@@ -86,59 +79,56 @@ class ResultParser:
|
|
|
86
79
|
- details: str
|
|
87
80
|
"""
|
|
88
81
|
result = {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
82
|
+
"success": False,
|
|
83
|
+
"credential_valid": False,
|
|
84
|
+
"access_level": "none",
|
|
85
|
+
"username": None,
|
|
86
|
+
"details": "",
|
|
94
87
|
}
|
|
95
|
-
|
|
88
|
+
|
|
96
89
|
# Check for success marker
|
|
97
|
-
if
|
|
98
|
-
result[
|
|
99
|
-
result[
|
|
100
|
-
|
|
90
|
+
if "SSH_SUCCESS" in stdout:
|
|
91
|
+
result["success"] = True
|
|
92
|
+
result["credential_valid"] = True
|
|
93
|
+
|
|
101
94
|
# Extract username from whoami output
|
|
102
|
-
whoami_match = re.search(r
|
|
95
|
+
whoami_match = re.search(r"SSH_SUCCESS\s+(\w+)", stdout)
|
|
103
96
|
if whoami_match:
|
|
104
|
-
result[
|
|
105
|
-
|
|
97
|
+
result["username"] = whoami_match.group(1)
|
|
98
|
+
|
|
106
99
|
# Check if root
|
|
107
|
-
if
|
|
108
|
-
result[
|
|
100
|
+
if "uid=0(root)" in stdout or result["username"] == "root":
|
|
101
|
+
result["access_level"] = "root"
|
|
109
102
|
else:
|
|
110
|
-
result[
|
|
111
|
-
|
|
112
|
-
result[
|
|
113
|
-
|
|
103
|
+
result["access_level"] = "user"
|
|
104
|
+
|
|
105
|
+
result["details"] = f"SSH login successful as {result['username']}"
|
|
106
|
+
|
|
114
107
|
elif exit_code == 0:
|
|
115
108
|
# Connection succeeded but no marker (shouldn't happen with our command)
|
|
116
|
-
result[
|
|
117
|
-
result[
|
|
118
|
-
result[
|
|
119
|
-
result[
|
|
120
|
-
|
|
109
|
+
result["success"] = True
|
|
110
|
+
result["credential_valid"] = True
|
|
111
|
+
result["access_level"] = "user"
|
|
112
|
+
result["details"] = "SSH connection successful"
|
|
113
|
+
|
|
121
114
|
else:
|
|
122
115
|
# Authentication failed or connection error
|
|
123
|
-
result[
|
|
124
|
-
result[
|
|
125
|
-
|
|
126
|
-
if
|
|
127
|
-
result[
|
|
128
|
-
elif
|
|
129
|
-
result[
|
|
130
|
-
elif
|
|
131
|
-
result[
|
|
116
|
+
result["success"] = False
|
|
117
|
+
result["credential_valid"] = False
|
|
118
|
+
|
|
119
|
+
if "Permission denied" in stderr or "Authentication failed" in stderr:
|
|
120
|
+
result["details"] = "Invalid credentials"
|
|
121
|
+
elif "Connection refused" in stderr:
|
|
122
|
+
result["details"] = "SSH service not available"
|
|
123
|
+
elif "Connection timed out" in stderr:
|
|
124
|
+
result["details"] = "Connection timeout"
|
|
132
125
|
else:
|
|
133
|
-
result[
|
|
134
|
-
|
|
126
|
+
result["details"] = f"SSH connection failed (exit code: {exit_code})"
|
|
127
|
+
|
|
135
128
|
return result
|
|
136
129
|
|
|
137
130
|
def parse_ftp_result(
|
|
138
|
-
self,
|
|
139
|
-
stdout: str,
|
|
140
|
-
stderr: str,
|
|
141
|
-
exit_code: int
|
|
131
|
+
self, stdout: str, stderr: str, exit_code: int
|
|
142
132
|
) -> Dict[str, Any]:
|
|
143
133
|
"""
|
|
144
134
|
Parse FTP credential test result.
|
|
@@ -151,40 +141,41 @@ class ResultParser:
|
|
|
151
141
|
- details: str
|
|
152
142
|
"""
|
|
153
143
|
result = {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
144
|
+
"success": False,
|
|
145
|
+
"credential_valid": False,
|
|
146
|
+
"access_level": "user", # FTP doesn't have root concept
|
|
147
|
+
"details": "",
|
|
158
148
|
}
|
|
159
149
|
|
|
160
150
|
# Check for failure marker
|
|
161
|
-
if
|
|
162
|
-
result[
|
|
151
|
+
if "FTP_FAILED" in stdout or "FTP_FAILED" in stderr:
|
|
152
|
+
result["details"] = "FTP connection failed"
|
|
163
153
|
return result
|
|
164
154
|
|
|
165
155
|
# Check for authentication errors
|
|
166
|
-
if
|
|
167
|
-
|
|
156
|
+
if (
|
|
157
|
+
"Login incorrect" in stderr
|
|
158
|
+
or "Login failed" in stderr
|
|
159
|
+
or "Authentication failed" in stderr
|
|
160
|
+
):
|
|
161
|
+
result["details"] = "Invalid credentials"
|
|
168
162
|
return result
|
|
169
163
|
|
|
170
164
|
# Check for connection errors
|
|
171
|
-
if
|
|
172
|
-
result[
|
|
165
|
+
if "Connection refused" in stderr or "Connection timed out" in stderr:
|
|
166
|
+
result["details"] = "Connection failed"
|
|
173
167
|
return result
|
|
174
168
|
|
|
175
169
|
# If we got directory listing, credentials worked
|
|
176
|
-
if exit_code == 0 or
|
|
177
|
-
result[
|
|
178
|
-
result[
|
|
179
|
-
result[
|
|
170
|
+
if exit_code == 0 or "drw" in stdout or "-rw" in stdout:
|
|
171
|
+
result["success"] = True
|
|
172
|
+
result["credential_valid"] = True
|
|
173
|
+
result["details"] = "FTP login successful"
|
|
180
174
|
|
|
181
175
|
return result
|
|
182
176
|
|
|
183
177
|
def parse_smb_result(
|
|
184
|
-
self,
|
|
185
|
-
stdout: str,
|
|
186
|
-
stderr: str,
|
|
187
|
-
exit_code: int
|
|
178
|
+
self, stdout: str, stderr: str, exit_code: int
|
|
188
179
|
) -> Dict[str, Any]:
|
|
189
180
|
"""
|
|
190
181
|
Parse SMB credential test result.
|
|
@@ -198,52 +189,52 @@ class ResultParser:
|
|
|
198
189
|
- details: str
|
|
199
190
|
"""
|
|
200
191
|
result = {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
192
|
+
"success": False,
|
|
193
|
+
"credential_valid": False,
|
|
194
|
+
"access_level": "user",
|
|
195
|
+
"shares": [],
|
|
196
|
+
"details": "",
|
|
206
197
|
}
|
|
207
198
|
|
|
208
199
|
# Check for failure marker
|
|
209
|
-
if
|
|
210
|
-
result[
|
|
200
|
+
if "SMB_FAILED" in stdout or "SMB_FAILED" in stderr:
|
|
201
|
+
result["details"] = "SMB connection failed"
|
|
211
202
|
return result
|
|
212
203
|
|
|
213
204
|
# Check for authentication errors
|
|
214
|
-
if
|
|
215
|
-
result[
|
|
205
|
+
if "NT_STATUS_LOGON_FAILURE" in stderr or "NT_STATUS_ACCESS_DENIED" in stderr:
|
|
206
|
+
result["details"] = "Invalid credentials"
|
|
216
207
|
return result
|
|
217
208
|
|
|
218
209
|
# Check for connection errors
|
|
219
|
-
if
|
|
220
|
-
result[
|
|
210
|
+
if "Connection refused" in stderr or "Connection timed out" in stderr:
|
|
211
|
+
result["details"] = "Connection failed"
|
|
221
212
|
return result
|
|
222
213
|
|
|
223
|
-
if
|
|
224
|
-
result[
|
|
214
|
+
if "NT_STATUS_IO_TIMEOUT" in stderr or "Unable to connect" in stderr:
|
|
215
|
+
result["details"] = "Connection timeout"
|
|
225
216
|
return result
|
|
226
217
|
|
|
227
218
|
# Check for successful share listing
|
|
228
|
-
if
|
|
229
|
-
result[
|
|
230
|
-
result[
|
|
231
|
-
result[
|
|
219
|
+
if "Disk|" in stdout or "IPC$" in stdout or "Sharename" in stdout:
|
|
220
|
+
result["success"] = True
|
|
221
|
+
result["credential_valid"] = True
|
|
222
|
+
result["details"] = "SMB login successful"
|
|
232
223
|
|
|
233
224
|
# Extract share names (optional enhancement)
|
|
234
225
|
import re
|
|
235
|
-
|
|
226
|
+
|
|
227
|
+
share_matches = re.findall(r"^\s+(\S+)\s+Disk", stdout, re.MULTILINE)
|
|
236
228
|
if share_matches:
|
|
237
|
-
result[
|
|
238
|
-
result[
|
|
229
|
+
result["shares"] = share_matches
|
|
230
|
+
result["details"] = (
|
|
231
|
+
f"SMB login successful, found {len(share_matches)} shares"
|
|
232
|
+
)
|
|
239
233
|
|
|
240
234
|
return result
|
|
241
235
|
|
|
242
236
|
def parse_rdp_result(
|
|
243
|
-
self,
|
|
244
|
-
stdout: str,
|
|
245
|
-
stderr: str,
|
|
246
|
-
exit_code: int
|
|
237
|
+
self, stdout: str, stderr: str, exit_code: int
|
|
247
238
|
) -> Dict[str, Any]:
|
|
248
239
|
"""
|
|
249
240
|
Parse RDP credential test result.
|
|
@@ -256,53 +247,60 @@ class ResultParser:
|
|
|
256
247
|
- details: str
|
|
257
248
|
"""
|
|
258
249
|
result = {
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
250
|
+
"success": False,
|
|
251
|
+
"credential_valid": False,
|
|
252
|
+
"access_level": "user",
|
|
253
|
+
"details": "",
|
|
263
254
|
}
|
|
264
255
|
|
|
265
256
|
# Check for failure marker
|
|
266
|
-
if
|
|
267
|
-
result[
|
|
257
|
+
if "RDP_FAILED" in stdout or "RDP_FAILED" in stderr:
|
|
258
|
+
result["details"] = "RDP connection failed"
|
|
268
259
|
return result
|
|
269
260
|
|
|
270
261
|
# Check for authentication errors
|
|
271
|
-
if
|
|
272
|
-
|
|
262
|
+
if (
|
|
263
|
+
"Authentication failure" in stderr
|
|
264
|
+
or "ERRCONNECT_AUTHENTICATION_FAILED" in stderr
|
|
265
|
+
):
|
|
266
|
+
result["details"] = "Invalid credentials"
|
|
273
267
|
return result
|
|
274
268
|
|
|
275
|
-
if
|
|
276
|
-
result[
|
|
269
|
+
if "Account restriction" in stderr or "ERRCONNECT_ACCOUNT_DISABLED" in stderr:
|
|
270
|
+
result["details"] = "Account disabled or restricted"
|
|
277
271
|
return result
|
|
278
272
|
|
|
279
273
|
# Check for connection errors
|
|
280
|
-
if
|
|
281
|
-
|
|
274
|
+
if (
|
|
275
|
+
"unable to connect" in stderr.lower()
|
|
276
|
+
or "connection failed" in stderr.lower()
|
|
277
|
+
):
|
|
278
|
+
result["details"] = "Connection failed"
|
|
282
279
|
return result
|
|
283
280
|
|
|
284
|
-
if
|
|
285
|
-
result[
|
|
281
|
+
if "Connection timeout" in stderr or "connection timed out" in stderr.lower():
|
|
282
|
+
result["details"] = "Connection timeout"
|
|
286
283
|
return result
|
|
287
284
|
|
|
288
285
|
# Check for successful authentication
|
|
289
286
|
# xfreerdp with +auth-only returns 0 on successful auth
|
|
290
|
-
if
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
287
|
+
if (
|
|
288
|
+
exit_code == 0
|
|
289
|
+
or "Authentication only" in stdout
|
|
290
|
+
or "connected" in stdout.lower()
|
|
291
|
+
):
|
|
292
|
+
result["success"] = True
|
|
293
|
+
result["credential_valid"] = True
|
|
294
|
+
result["details"] = "RDP authentication successful"
|
|
294
295
|
|
|
295
296
|
# Check if admin (optional - may not be detectable in auth-only mode)
|
|
296
|
-
if
|
|
297
|
-
result[
|
|
297
|
+
if "administrator" in stdout.lower():
|
|
298
|
+
result["access_level"] = "admin"
|
|
298
299
|
|
|
299
300
|
return result
|
|
300
301
|
|
|
301
302
|
def parse_postgresql_result(
|
|
302
|
-
self,
|
|
303
|
-
stdout: str,
|
|
304
|
-
stderr: str,
|
|
305
|
-
exit_code: int
|
|
303
|
+
self, stdout: str, stderr: str, exit_code: int
|
|
306
304
|
) -> Dict[str, Any]:
|
|
307
305
|
"""
|
|
308
306
|
Parse PostgreSQL credential test result.
|
|
@@ -315,171 +313,166 @@ class ResultParser:
|
|
|
315
313
|
- details: str
|
|
316
314
|
"""
|
|
317
315
|
result = {
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
316
|
+
"success": False,
|
|
317
|
+
"credential_valid": False,
|
|
318
|
+
"access_level": "user",
|
|
319
|
+
"details": "",
|
|
322
320
|
}
|
|
323
321
|
|
|
324
322
|
# Check for failure marker
|
|
325
|
-
if
|
|
326
|
-
result[
|
|
323
|
+
if "PSQL_FAILED" in stdout:
|
|
324
|
+
result["details"] = "PostgreSQL connection failed"
|
|
327
325
|
return result
|
|
328
326
|
|
|
329
327
|
# Check for authentication errors
|
|
330
|
-
if
|
|
331
|
-
|
|
328
|
+
if (
|
|
329
|
+
"authentication failed" in stderr.lower()
|
|
330
|
+
or "password authentication failed" in stderr.lower()
|
|
331
|
+
):
|
|
332
|
+
result["details"] = "Invalid credentials"
|
|
332
333
|
return result
|
|
333
334
|
|
|
334
335
|
# Check for connection errors
|
|
335
|
-
if
|
|
336
|
-
result[
|
|
336
|
+
if "could not connect" in stderr.lower() or "Connection refused" in stderr:
|
|
337
|
+
result["details"] = "Connection failed"
|
|
337
338
|
return result
|
|
338
339
|
|
|
339
340
|
# Check for version output (means connection worked)
|
|
340
|
-
if
|
|
341
|
-
result[
|
|
342
|
-
result[
|
|
341
|
+
if "PostgreSQL" in stdout or "version" in stdout.lower():
|
|
342
|
+
result["success"] = True
|
|
343
|
+
result["credential_valid"] = True
|
|
343
344
|
|
|
344
345
|
# Check if superuser
|
|
345
|
-
if
|
|
346
|
-
result[
|
|
346
|
+
if "superuser" in stdout.lower():
|
|
347
|
+
result["access_level"] = "admin"
|
|
347
348
|
|
|
348
|
-
result[
|
|
349
|
+
result["details"] = "PostgreSQL login successful"
|
|
349
350
|
|
|
350
351
|
return result
|
|
351
352
|
|
|
352
353
|
def parse_mysql_result(
|
|
353
|
-
self,
|
|
354
|
-
stdout: str,
|
|
355
|
-
stderr: str,
|
|
356
|
-
exit_code: int,
|
|
357
|
-
command: str
|
|
354
|
+
self, stdout: str, stderr: str, exit_code: int, command: str
|
|
358
355
|
) -> Dict[str, Any]:
|
|
359
356
|
"""
|
|
360
357
|
Parse MySQL command result.
|
|
361
|
-
|
|
358
|
+
|
|
362
359
|
Args:
|
|
363
360
|
stdout: Command output
|
|
364
361
|
stderr: Error output
|
|
365
362
|
exit_code: Exit code
|
|
366
363
|
command: Original command
|
|
367
|
-
|
|
364
|
+
|
|
368
365
|
Returns:
|
|
369
366
|
Dict with success status and details
|
|
370
367
|
"""
|
|
371
368
|
result = {
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
369
|
+
"success": False,
|
|
370
|
+
"credential_valid": False,
|
|
371
|
+
"details": "",
|
|
372
|
+
"databases": [],
|
|
373
|
+
"users": [],
|
|
377
374
|
}
|
|
378
|
-
|
|
375
|
+
|
|
379
376
|
# Check for version output (indicates successful connection)
|
|
380
|
-
if
|
|
381
|
-
result[
|
|
382
|
-
result[
|
|
383
|
-
|
|
384
|
-
version_match = re.search(r
|
|
377
|
+
if "version()" in stdout or re.search(r"\d+\.\d+\.\d+", stdout):
|
|
378
|
+
result["success"] = True
|
|
379
|
+
result["credential_valid"] = True
|
|
380
|
+
|
|
381
|
+
version_match = re.search(r"(\d+\.\d+\.\d+)", stdout)
|
|
385
382
|
if version_match:
|
|
386
|
-
result[
|
|
383
|
+
result["details"] = (
|
|
384
|
+
f"MySQL connection successful (version: {version_match.group(1)})"
|
|
385
|
+
)
|
|
387
386
|
else:
|
|
388
|
-
result[
|
|
389
|
-
|
|
387
|
+
result["details"] = "MySQL connection successful"
|
|
388
|
+
|
|
390
389
|
# Check for database enumeration
|
|
391
|
-
if
|
|
392
|
-
db_matches = re.findall(r
|
|
393
|
-
result[
|
|
394
|
-
|
|
390
|
+
if "SHOW DATABASES" in command.upper():
|
|
391
|
+
db_matches = re.findall(r"^([a-zA-Z_]\w*)$", stdout, re.MULTILINE)
|
|
392
|
+
result["databases"] = [
|
|
393
|
+
db for db in db_matches if db not in ["Database", "version()"]
|
|
394
|
+
]
|
|
395
|
+
|
|
395
396
|
# Check for user enumeration
|
|
396
|
-
if
|
|
397
|
-
user_matches = re.findall(r
|
|
398
|
-
result[
|
|
399
|
-
|
|
397
|
+
if "SELECT user" in command:
|
|
398
|
+
user_matches = re.findall(r"(\w+)\s+(%|[\w.]+)", stdout)
|
|
399
|
+
result["users"] = user_matches
|
|
400
|
+
|
|
400
401
|
# Check for errors
|
|
401
|
-
if exit_code != 0 or
|
|
402
|
-
result[
|
|
403
|
-
result[
|
|
404
|
-
|
|
405
|
-
if
|
|
406
|
-
result[
|
|
407
|
-
elif "Can't connect" in stderr or
|
|
408
|
-
result[
|
|
402
|
+
if exit_code != 0 or "ERROR" in stderr:
|
|
403
|
+
result["success"] = False
|
|
404
|
+
result["credential_valid"] = False
|
|
405
|
+
|
|
406
|
+
if "Access denied" in stderr:
|
|
407
|
+
result["details"] = "Invalid MySQL credentials"
|
|
408
|
+
elif "Can't connect" in stderr or "Connection refused" in stderr:
|
|
409
|
+
result["details"] = "MySQL service not available"
|
|
409
410
|
else:
|
|
410
|
-
result[
|
|
411
|
-
|
|
411
|
+
result["details"] = f"MySQL command failed: {stderr[:100]}"
|
|
412
|
+
|
|
412
413
|
return result
|
|
413
414
|
|
|
414
415
|
def parse_nmap_result(
|
|
415
|
-
self,
|
|
416
|
-
stdout: str,
|
|
417
|
-
stderr: str,
|
|
418
|
-
exit_code: int
|
|
416
|
+
self, stdout: str, stderr: str, exit_code: int
|
|
419
417
|
) -> Dict[str, Any]:
|
|
420
418
|
"""Parse nmap scan result."""
|
|
421
419
|
result = {
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
420
|
+
"success": exit_code == 0,
|
|
421
|
+
"open_ports": [],
|
|
422
|
+
"services": {},
|
|
423
|
+
"details": "",
|
|
426
424
|
}
|
|
427
|
-
|
|
425
|
+
|
|
428
426
|
if exit_code == 0:
|
|
429
427
|
# Extract open ports
|
|
430
|
-
port_lines = re.findall(r
|
|
428
|
+
port_lines = re.findall(r"^(\d+)/tcp\s+open\s+(.+)$", stdout, re.MULTILINE)
|
|
431
429
|
for port, service_info in port_lines:
|
|
432
|
-
result[
|
|
433
|
-
result[
|
|
434
|
-
|
|
435
|
-
result[
|
|
430
|
+
result["open_ports"].append(int(port))
|
|
431
|
+
result["services"][port] = service_info.strip()
|
|
432
|
+
|
|
433
|
+
result["details"] = (
|
|
434
|
+
f"Scan complete: {len(result['open_ports'])} open ports found"
|
|
435
|
+
)
|
|
436
436
|
else:
|
|
437
|
-
result[
|
|
438
|
-
|
|
437
|
+
result["details"] = "Nmap scan failed"
|
|
438
|
+
|
|
439
439
|
return result
|
|
440
440
|
|
|
441
441
|
def parse_http_result(
|
|
442
|
-
self,
|
|
443
|
-
stdout: str,
|
|
444
|
-
stderr: str,
|
|
445
|
-
exit_code: int
|
|
442
|
+
self, stdout: str, stderr: str, exit_code: int
|
|
446
443
|
) -> Dict[str, Any]:
|
|
447
444
|
"""Parse HTTP request result."""
|
|
448
|
-
result = {
|
|
449
|
-
|
|
450
|
-
'status_code': None,
|
|
451
|
-
'server': None,
|
|
452
|
-
'details': ''
|
|
453
|
-
}
|
|
454
|
-
|
|
445
|
+
result = {"success": False, "status_code": None, "server": None, "details": ""}
|
|
446
|
+
|
|
455
447
|
# Extract HTTP status code
|
|
456
|
-
status_match = re.search(r
|
|
448
|
+
status_match = re.search(r"HTTP/[\d.]+\s+(\d+)", stdout)
|
|
457
449
|
if status_match:
|
|
458
|
-
result[
|
|
459
|
-
result[
|
|
460
|
-
|
|
450
|
+
result["success"] = True
|
|
451
|
+
result["status_code"] = int(status_match.group(1))
|
|
452
|
+
|
|
461
453
|
# Extract server header
|
|
462
|
-
server_match = re.search(r
|
|
454
|
+
server_match = re.search(r"Server:\s*(.+)", stdout, re.IGNORECASE)
|
|
463
455
|
if server_match:
|
|
464
|
-
result[
|
|
465
|
-
|
|
466
|
-
if result[
|
|
467
|
-
result[
|
|
468
|
-
if result[
|
|
469
|
-
result[
|
|
456
|
+
result["server"] = server_match.group(1).strip()
|
|
457
|
+
|
|
458
|
+
if result["success"]:
|
|
459
|
+
result["details"] = f"HTTP {result['status_code']}"
|
|
460
|
+
if result["server"]:
|
|
461
|
+
result["details"] += f" - {result['server']}"
|
|
470
462
|
else:
|
|
471
|
-
result[
|
|
472
|
-
|
|
463
|
+
result["details"] = "HTTP request failed"
|
|
464
|
+
|
|
473
465
|
return result
|
|
474
466
|
|
|
475
467
|
def parse_generic_result(
|
|
476
|
-
self,
|
|
477
|
-
stdout: str,
|
|
478
|
-
stderr: str,
|
|
479
|
-
exit_code: int
|
|
468
|
+
self, stdout: str, stderr: str, exit_code: int
|
|
480
469
|
) -> Dict[str, Any]:
|
|
481
470
|
"""Generic result parsing for unknown command types."""
|
|
482
471
|
return {
|
|
483
|
-
|
|
484
|
-
|
|
472
|
+
"success": exit_code == 0,
|
|
473
|
+
"details": (
|
|
474
|
+
"Command executed"
|
|
475
|
+
if exit_code == 0
|
|
476
|
+
else f"Command failed (exit {exit_code})"
|
|
477
|
+
),
|
|
485
478
|
}
|