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.
Potentially problematic release.
This version of souleyez might be problematic. Click here for more details.
- 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/ai/chain_advisor.py
CHANGED
|
@@ -18,53 +18,56 @@ logger = logging.getLogger(__name__)
|
|
|
18
18
|
|
|
19
19
|
class ChainAdvisorMode(Enum):
|
|
20
20
|
"""AI chain recommendation modes."""
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
|
|
22
|
+
OFF = "off" # No AI involvement
|
|
23
|
+
SUGGEST = "suggest" # Show recommendations, don't auto-queue
|
|
24
|
+
AUTO = "auto" # Auto-queue AI recommendations
|
|
24
25
|
|
|
25
26
|
|
|
26
27
|
@dataclass
|
|
27
28
|
class AIChainRecommendation:
|
|
28
29
|
"""Single tool recommendation from LLM."""
|
|
30
|
+
|
|
29
31
|
tool: str
|
|
30
32
|
target: str
|
|
31
33
|
args: List[str] = field(default_factory=list)
|
|
32
|
-
priority: int = 5
|
|
33
|
-
rationale: str =
|
|
34
|
-
confidence: float = 0.7
|
|
35
|
-
expected_outcome: str =
|
|
36
|
-
risk_level: str =
|
|
34
|
+
priority: int = 5 # 1-10
|
|
35
|
+
rationale: str = ""
|
|
36
|
+
confidence: float = 0.7 # 0.0-1.0
|
|
37
|
+
expected_outcome: str = ""
|
|
38
|
+
risk_level: str = "medium" # low/medium/high
|
|
37
39
|
|
|
38
40
|
|
|
39
41
|
@dataclass
|
|
40
42
|
class AIChainAnalysis:
|
|
41
43
|
"""Complete AI analysis of scan results."""
|
|
44
|
+
|
|
42
45
|
recommendations: List[AIChainRecommendation]
|
|
43
|
-
summary: str =
|
|
46
|
+
summary: str = ""
|
|
44
47
|
static_rules_applied: List[str] = field(default_factory=list)
|
|
45
48
|
analysis_time_ms: int = 0
|
|
46
|
-
provider: str =
|
|
49
|
+
provider: str = ""
|
|
47
50
|
error: Optional[str] = None
|
|
48
51
|
|
|
49
52
|
|
|
50
53
|
# Available tools for recommendations (must match actual plugins in souleyez/plugins/)
|
|
51
54
|
AVAILABLE_TOOLS = {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
55
|
+
"nmap": "Network scanning, service detection, OS fingerprinting",
|
|
56
|
+
"nuclei": "Vulnerability scanning with templates (CVEs, misconfigs)",
|
|
57
|
+
"nikto": "Web server vulnerability scanner",
|
|
58
|
+
"gobuster": "Directory/file brute-forcing",
|
|
59
|
+
"ffuf": "Web fuzzing (parameters, directories)",
|
|
60
|
+
"sqlmap": "SQL injection detection and exploitation",
|
|
61
|
+
"wpscan": "WordPress vulnerability scanner",
|
|
62
|
+
"hydra": "Brute-force password cracking",
|
|
63
|
+
"enum4linux": "SMB/NetBIOS enumeration",
|
|
64
|
+
"smbmap": "SMB share enumeration",
|
|
65
|
+
"crackmapexec": "Windows/AD enumeration and exploitation",
|
|
66
|
+
"dnsrecon": "DNS enumeration and zone transfers",
|
|
67
|
+
"theharvester": "OSINT - emails, subdomains, IPs",
|
|
68
|
+
"dalfox": "XSS vulnerability scanner",
|
|
69
|
+
"searchsploit": "Exploit database search",
|
|
70
|
+
"bloodhound": "Active Directory attack path mapping",
|
|
68
71
|
}
|
|
69
72
|
|
|
70
73
|
|
|
@@ -121,7 +124,9 @@ Guidelines:
|
|
|
121
124
|
- Focus on high-value services (databases, admin panels, etc.)
|
|
122
125
|
"""
|
|
123
126
|
|
|
124
|
-
def __init__(
|
|
127
|
+
def __init__(
|
|
128
|
+
self, provider=None, mode: ChainAdvisorMode = ChainAdvisorMode.SUGGEST
|
|
129
|
+
):
|
|
125
130
|
"""
|
|
126
131
|
Initialize chain advisor.
|
|
127
132
|
|
|
@@ -153,7 +158,7 @@ Guidelines:
|
|
|
153
158
|
target: str,
|
|
154
159
|
parse_results: Dict[str, Any],
|
|
155
160
|
static_commands: List[Dict[str, Any]],
|
|
156
|
-
engagement_id: Optional[int] = None
|
|
161
|
+
engagement_id: Optional[int] = None,
|
|
157
162
|
) -> AIChainAnalysis:
|
|
158
163
|
"""
|
|
159
164
|
Analyze scan results and recommend next tools.
|
|
@@ -169,13 +174,13 @@ Guidelines:
|
|
|
169
174
|
AIChainAnalysis with recommendations, or error if failed
|
|
170
175
|
"""
|
|
171
176
|
import time
|
|
177
|
+
|
|
172
178
|
start_time = time.time()
|
|
173
179
|
|
|
174
180
|
# Check mode
|
|
175
181
|
if self.mode == ChainAdvisorMode.OFF:
|
|
176
182
|
return AIChainAnalysis(
|
|
177
|
-
recommendations=[],
|
|
178
|
-
error='AI chain analysis is disabled'
|
|
183
|
+
recommendations=[], error="AI chain analysis is disabled"
|
|
179
184
|
)
|
|
180
185
|
|
|
181
186
|
# Ensure provider is available
|
|
@@ -183,12 +188,13 @@ Guidelines:
|
|
|
183
188
|
if not self.provider or not self.provider.is_available():
|
|
184
189
|
logger.debug("AI provider not available for chain analysis")
|
|
185
190
|
return AIChainAnalysis(
|
|
186
|
-
recommendations=[],
|
|
187
|
-
error='AI provider not available'
|
|
191
|
+
recommendations=[], error="AI provider not available"
|
|
188
192
|
)
|
|
189
193
|
|
|
190
194
|
# Build prompt
|
|
191
|
-
prompt = self._build_analysis_prompt(
|
|
195
|
+
prompt = self._build_analysis_prompt(
|
|
196
|
+
tool, target, parse_results, static_commands
|
|
197
|
+
)
|
|
192
198
|
|
|
193
199
|
# Generate recommendations
|
|
194
200
|
try:
|
|
@@ -196,14 +202,13 @@ Guidelines:
|
|
|
196
202
|
prompt=prompt,
|
|
197
203
|
system_prompt=self.SYSTEM_PROMPT,
|
|
198
204
|
max_tokens=2000,
|
|
199
|
-
temperature=0.3 # Lower for consistent tool recommendations
|
|
205
|
+
temperature=0.3, # Lower for consistent tool recommendations
|
|
200
206
|
)
|
|
201
207
|
|
|
202
208
|
if not response:
|
|
203
209
|
logger.warning("AI returned empty chain analysis")
|
|
204
210
|
return AIChainAnalysis(
|
|
205
|
-
recommendations=[],
|
|
206
|
-
error='Empty response from AI provider'
|
|
211
|
+
recommendations=[], error="Empty response from AI provider"
|
|
207
212
|
)
|
|
208
213
|
|
|
209
214
|
# Parse recommendations
|
|
@@ -211,98 +216,103 @@ Guidelines:
|
|
|
211
216
|
|
|
212
217
|
# Filter out low-confidence recommendations
|
|
213
218
|
min_confidence = 0.6
|
|
214
|
-
recommendations = [
|
|
219
|
+
recommendations = [
|
|
220
|
+
r for r in recommendations if r.confidence >= min_confidence
|
|
221
|
+
]
|
|
215
222
|
|
|
216
223
|
# Filter duplicates with static commands
|
|
217
224
|
recommendations = self._filter_duplicates(recommendations, static_commands)
|
|
218
225
|
|
|
219
226
|
elapsed_ms = int((time.time() - start_time) * 1000)
|
|
220
|
-
provider_name = getattr(self.provider,
|
|
227
|
+
provider_name = getattr(self.provider, "provider_type", "unknown")
|
|
221
228
|
|
|
222
229
|
return AIChainAnalysis(
|
|
223
230
|
recommendations=recommendations,
|
|
224
231
|
summary=f"AI suggested {len(recommendations)} additional tools",
|
|
225
|
-
static_rules_applied=[
|
|
232
|
+
static_rules_applied=[
|
|
233
|
+
cmd.get("description", "") for cmd in static_commands
|
|
234
|
+
],
|
|
226
235
|
analysis_time_ms=elapsed_ms,
|
|
227
|
-
provider=str(provider_name)
|
|
236
|
+
provider=str(provider_name),
|
|
228
237
|
)
|
|
229
238
|
|
|
230
239
|
except Exception as e:
|
|
231
240
|
logger.error(f"AI chain analysis failed: {e}")
|
|
232
|
-
return AIChainAnalysis(
|
|
233
|
-
recommendations=[],
|
|
234
|
-
error=str(e)
|
|
235
|
-
)
|
|
241
|
+
return AIChainAnalysis(recommendations=[], error=str(e))
|
|
236
242
|
|
|
237
243
|
def _build_analysis_prompt(
|
|
238
244
|
self,
|
|
239
245
|
tool: str,
|
|
240
246
|
target: str,
|
|
241
247
|
parse_results: Dict[str, Any],
|
|
242
|
-
static_commands: List[Dict[str, Any]]
|
|
248
|
+
static_commands: List[Dict[str, Any]],
|
|
243
249
|
) -> str:
|
|
244
250
|
"""Build LLM prompt for chain analysis."""
|
|
245
251
|
# Format results summary
|
|
246
252
|
results_lines = []
|
|
247
253
|
|
|
248
254
|
# Add hosts
|
|
249
|
-
hosts = parse_results.get(
|
|
255
|
+
hosts = parse_results.get("hosts", [])
|
|
250
256
|
if hosts:
|
|
251
257
|
results_lines.append(f"Hosts discovered: {len(hosts)}")
|
|
252
258
|
for host in hosts[:5]: # Limit to first 5
|
|
253
|
-
ip = host.get(
|
|
254
|
-
os_name = host.get(
|
|
259
|
+
ip = host.get("ip", "unknown")
|
|
260
|
+
os_name = host.get("os", "unknown")
|
|
255
261
|
results_lines.append(f" - {ip} (OS: {os_name})")
|
|
256
262
|
|
|
257
263
|
# Add services
|
|
258
|
-
services = parse_results.get(
|
|
264
|
+
services = parse_results.get("services", [])
|
|
259
265
|
if services:
|
|
260
266
|
results_lines.append(f"\nServices found: {len(services)}")
|
|
261
267
|
for svc in services[:10]: # Limit to first 10
|
|
262
|
-
ip = svc.get(
|
|
263
|
-
port = svc.get(
|
|
264
|
-
name = svc.get(
|
|
265
|
-
product = svc.get(
|
|
266
|
-
version = svc.get(
|
|
267
|
-
ver_str = f" {product} {version}".strip() if product or version else
|
|
268
|
+
ip = svc.get("ip", target)
|
|
269
|
+
port = svc.get("port", "?")
|
|
270
|
+
name = svc.get("service_name", svc.get("service", "unknown"))
|
|
271
|
+
product = svc.get("product", "")
|
|
272
|
+
version = svc.get("version", "")
|
|
273
|
+
ver_str = f" {product} {version}".strip() if product or version else ""
|
|
268
274
|
results_lines.append(f" - {ip}:{port} - {name}{ver_str}")
|
|
269
275
|
|
|
270
276
|
# Add findings
|
|
271
|
-
findings = parse_results.get(
|
|
277
|
+
findings = parse_results.get("findings", [])
|
|
272
278
|
if findings:
|
|
273
279
|
results_lines.append(f"\nFindings: {len(findings)}")
|
|
274
280
|
for finding in findings[:5]: # Limit to first 5
|
|
275
|
-
title = finding.get(
|
|
276
|
-
severity = finding.get(
|
|
281
|
+
title = finding.get("title", "unknown")
|
|
282
|
+
severity = finding.get("severity", "info")
|
|
277
283
|
results_lines.append(f" - [{severity.upper()}] {title}")
|
|
278
284
|
|
|
279
285
|
# Add credentials
|
|
280
|
-
credentials = parse_results.get(
|
|
286
|
+
credentials = parse_results.get("credentials", [])
|
|
281
287
|
if credentials:
|
|
282
288
|
results_lines.append(f"\nCredentials found: {len(credentials)}")
|
|
283
289
|
|
|
284
|
-
results_summary =
|
|
290
|
+
results_summary = (
|
|
291
|
+
"\n".join(results_lines)
|
|
292
|
+
if results_lines
|
|
293
|
+
else "No structured results parsed."
|
|
294
|
+
)
|
|
285
295
|
|
|
286
296
|
# Format static commands
|
|
287
297
|
static_lines = []
|
|
288
298
|
for cmd in static_commands:
|
|
289
|
-
tool_name = cmd.get(
|
|
290
|
-
desc = cmd.get(
|
|
299
|
+
tool_name = cmd.get("tool", "unknown")
|
|
300
|
+
desc = cmd.get("description", "")
|
|
291
301
|
static_lines.append(f" - {tool_name}: {desc}")
|
|
292
|
-
static_summary =
|
|
302
|
+
static_summary = "\n".join(static_lines) if static_lines else " (none)"
|
|
293
303
|
|
|
294
304
|
# Format available tools
|
|
295
305
|
tools_lines = []
|
|
296
306
|
for tool_name, desc in AVAILABLE_TOOLS.items():
|
|
297
307
|
tools_lines.append(f" - {tool_name}: {desc}")
|
|
298
|
-
tools_summary =
|
|
308
|
+
tools_summary = "\n".join(tools_lines)
|
|
299
309
|
|
|
300
310
|
return self.ANALYSIS_TEMPLATE.format(
|
|
301
311
|
tool=tool,
|
|
302
312
|
target=target,
|
|
303
313
|
results_summary=results_summary,
|
|
304
314
|
static_commands=static_summary,
|
|
305
|
-
available_tools=tools_summary
|
|
315
|
+
available_tools=tools_summary,
|
|
306
316
|
)
|
|
307
317
|
|
|
308
318
|
def _parse_recommendations(self, response: str) -> List[AIChainRecommendation]:
|
|
@@ -310,11 +320,11 @@ Guidelines:
|
|
|
310
320
|
recommendations = []
|
|
311
321
|
|
|
312
322
|
# Check for no recommendations response
|
|
313
|
-
if
|
|
323
|
+
if "NO_RECOMMENDATIONS" in response.upper():
|
|
314
324
|
return []
|
|
315
325
|
|
|
316
326
|
# Split by separator
|
|
317
|
-
sections = response.split(
|
|
327
|
+
sections = response.split("---")
|
|
318
328
|
|
|
319
329
|
for section in sections:
|
|
320
330
|
section = section.strip()
|
|
@@ -331,17 +341,23 @@ Guidelines:
|
|
|
331
341
|
|
|
332
342
|
return recommendations
|
|
333
343
|
|
|
334
|
-
def _parse_single_recommendation(
|
|
344
|
+
def _parse_single_recommendation(
|
|
345
|
+
self, section: str
|
|
346
|
+
) -> Optional[AIChainRecommendation]:
|
|
335
347
|
"""Parse a single recommendation section."""
|
|
336
348
|
# Extract fields
|
|
337
|
-
tool_match = re.search(r
|
|
338
|
-
target_match = re.search(r
|
|
339
|
-
args_match = re.search(r
|
|
340
|
-
priority_match = re.search(r
|
|
341
|
-
rationale_match = re.search(
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
349
|
+
tool_match = re.search(r"TOOL:\s*(.+?)(?=\n|$)", section, re.IGNORECASE)
|
|
350
|
+
target_match = re.search(r"TARGET:\s*(.+?)(?=\n|$)", section, re.IGNORECASE)
|
|
351
|
+
args_match = re.search(r"ARGS:\s*(.+?)(?=\n|$)", section, re.IGNORECASE)
|
|
352
|
+
priority_match = re.search(r"PRIORITY:\s*(\d+)", section, re.IGNORECASE)
|
|
353
|
+
rationale_match = re.search(
|
|
354
|
+
r"RATIONALE:\s*(.+?)(?=\n[A-Z]+:|$)", section, re.DOTALL | re.IGNORECASE
|
|
355
|
+
)
|
|
356
|
+
confidence_match = re.search(r"CONFIDENCE:\s*([\d.]+)", section, re.IGNORECASE)
|
|
357
|
+
expected_match = re.search(
|
|
358
|
+
r"EXPECTED:\s*(.+?)(?=\n[A-Z]+:|$)", section, re.DOTALL | re.IGNORECASE
|
|
359
|
+
)
|
|
360
|
+
risk_match = re.search(r"RISK:\s*(\w+)", section, re.IGNORECASE)
|
|
345
361
|
|
|
346
362
|
# Require at least tool and target
|
|
347
363
|
if not tool_match or not target_match:
|
|
@@ -359,9 +375,9 @@ Guidelines:
|
|
|
359
375
|
args = []
|
|
360
376
|
if args_match:
|
|
361
377
|
args_str = args_match.group(1).strip()
|
|
362
|
-
if args_str and args_str.lower() not in [
|
|
378
|
+
if args_str and args_str.lower() not in ["none", "n/a", "-"]:
|
|
363
379
|
# Split by comma or space
|
|
364
|
-
args = [a.strip() for a in re.split(r
|
|
380
|
+
args = [a.strip() for a in re.split(r"[,\s]+", args_str) if a.strip()]
|
|
365
381
|
|
|
366
382
|
# Parse priority (default 5)
|
|
367
383
|
priority = 5
|
|
@@ -380,21 +396,21 @@ Guidelines:
|
|
|
380
396
|
pass
|
|
381
397
|
|
|
382
398
|
# Parse rationale
|
|
383
|
-
rationale =
|
|
399
|
+
rationale = ""
|
|
384
400
|
if rationale_match:
|
|
385
401
|
rationale = rationale_match.group(1).strip()
|
|
386
402
|
|
|
387
403
|
# Parse expected
|
|
388
|
-
expected =
|
|
404
|
+
expected = ""
|
|
389
405
|
if expected_match:
|
|
390
406
|
expected = expected_match.group(1).strip()
|
|
391
407
|
|
|
392
408
|
# Parse risk
|
|
393
|
-
risk =
|
|
409
|
+
risk = "medium"
|
|
394
410
|
if risk_match:
|
|
395
411
|
risk = risk_match.group(1).strip().lower()
|
|
396
|
-
if risk not in [
|
|
397
|
-
risk =
|
|
412
|
+
if risk not in ["low", "medium", "high"]:
|
|
413
|
+
risk = "medium"
|
|
398
414
|
|
|
399
415
|
return AIChainRecommendation(
|
|
400
416
|
tool=tool,
|
|
@@ -404,20 +420,20 @@ Guidelines:
|
|
|
404
420
|
rationale=rationale,
|
|
405
421
|
confidence=confidence,
|
|
406
422
|
expected_outcome=expected,
|
|
407
|
-
risk_level=risk
|
|
423
|
+
risk_level=risk,
|
|
408
424
|
)
|
|
409
425
|
|
|
410
426
|
def _filter_duplicates(
|
|
411
427
|
self,
|
|
412
428
|
recommendations: List[AIChainRecommendation],
|
|
413
|
-
static_commands: List[Dict[str, Any]]
|
|
429
|
+
static_commands: List[Dict[str, Any]],
|
|
414
430
|
) -> List[AIChainRecommendation]:
|
|
415
431
|
"""Remove AI recommendations that duplicate static rules."""
|
|
416
432
|
# Build set of (tool, target) pairs from static commands
|
|
417
433
|
static_pairs = set()
|
|
418
434
|
for cmd in static_commands:
|
|
419
|
-
tool = cmd.get(
|
|
420
|
-
target = cmd.get(
|
|
435
|
+
tool = cmd.get("tool", "").lower()
|
|
436
|
+
target = cmd.get("target", "").lower()
|
|
421
437
|
static_pairs.add((tool, target))
|
|
422
438
|
|
|
423
439
|
# Filter recommendations
|
|
@@ -427,14 +443,14 @@ Guidelines:
|
|
|
427
443
|
if pair not in static_pairs:
|
|
428
444
|
filtered.append(rec)
|
|
429
445
|
else:
|
|
430
|
-
logger.debug(
|
|
446
|
+
logger.debug(
|
|
447
|
+
f"Filtered duplicate AI recommendation: {rec.tool} -> {rec.target}"
|
|
448
|
+
)
|
|
431
449
|
|
|
432
450
|
return filtered
|
|
433
451
|
|
|
434
452
|
def to_chain_commands(
|
|
435
|
-
self,
|
|
436
|
-
recommendations: List[AIChainRecommendation],
|
|
437
|
-
context: Dict[str, Any]
|
|
453
|
+
self, recommendations: List[AIChainRecommendation], context: Dict[str, Any]
|
|
438
454
|
) -> List[Dict[str, Any]]:
|
|
439
455
|
"""
|
|
440
456
|
Convert AI recommendations to chain command format.
|
|
@@ -449,17 +465,17 @@ Guidelines:
|
|
|
449
465
|
commands = []
|
|
450
466
|
for rec in recommendations:
|
|
451
467
|
cmd = {
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
468
|
+
"tool": rec.tool,
|
|
469
|
+
"target": rec.target,
|
|
470
|
+
"args": rec.args,
|
|
471
|
+
"priority": rec.priority,
|
|
472
|
+
"description": f"[AI] {rec.rationale[:100]}",
|
|
473
|
+
"source": "ai_advisor",
|
|
474
|
+
"confidence": rec.confidence,
|
|
475
|
+
"risk_level": rec.risk_level,
|
|
460
476
|
}
|
|
461
477
|
commands.append(cmd)
|
|
462
478
|
|
|
463
479
|
# Sort by priority (highest first)
|
|
464
|
-
commands.sort(key=lambda c: c[
|
|
480
|
+
commands.sort(key=lambda c: c["priority"], reverse=True)
|
|
465
481
|
return commands
|
souleyez/ai/claude_provider.py
CHANGED
|
@@ -4,6 +4,7 @@ souleyez.ai.claude_provider - Anthropic Claude API provider implementation
|
|
|
4
4
|
Provides Claude LLM integration for high-quality AI report generation.
|
|
5
5
|
Requires API key stored securely via CryptoManager.
|
|
6
6
|
"""
|
|
7
|
+
|
|
7
8
|
import logging
|
|
8
9
|
from typing import Optional, Dict, Any
|
|
9
10
|
|
|
@@ -14,6 +15,7 @@ logger = logging.getLogger(__name__)
|
|
|
14
15
|
# Check if anthropic package is available
|
|
15
16
|
try:
|
|
16
17
|
import anthropic
|
|
18
|
+
|
|
17
19
|
ANTHROPIC_AVAILABLE = True
|
|
18
20
|
except ImportError:
|
|
19
21
|
anthropic = None
|
|
@@ -30,11 +32,7 @@ class ClaudeProvider(LLMProvider):
|
|
|
30
32
|
|
|
31
33
|
DEFAULT_MODEL = "claude-sonnet-4-20250514"
|
|
32
34
|
|
|
33
|
-
def __init__(
|
|
34
|
-
self,
|
|
35
|
-
api_key: Optional[str] = None,
|
|
36
|
-
model: Optional[str] = None
|
|
37
|
-
):
|
|
35
|
+
def __init__(self, api_key: Optional[str] = None, model: Optional[str] = None):
|
|
38
36
|
"""
|
|
39
37
|
Initialize Claude provider.
|
|
40
38
|
|
|
@@ -54,7 +52,7 @@ class ClaudeProvider(LLMProvider):
|
|
|
54
52
|
from souleyez.config import get
|
|
55
53
|
from souleyez.storage.crypto import get_crypto_manager
|
|
56
54
|
|
|
57
|
-
encrypted_key = get(
|
|
55
|
+
encrypted_key = get("ai.claude_api_key")
|
|
58
56
|
if not encrypted_key:
|
|
59
57
|
logger.debug("No Claude API key configured")
|
|
60
58
|
return None
|
|
@@ -74,7 +72,8 @@ class ClaudeProvider(LLMProvider):
|
|
|
74
72
|
def _load_model(self) -> str:
|
|
75
73
|
"""Load model from config."""
|
|
76
74
|
from souleyez.config import get
|
|
77
|
-
|
|
75
|
+
|
|
76
|
+
return get("ai.claude_model", self.DEFAULT_MODEL)
|
|
78
77
|
|
|
79
78
|
def _get_client(self):
|
|
80
79
|
"""Get or create Anthropic client."""
|
|
@@ -86,7 +85,9 @@ class ClaudeProvider(LLMProvider):
|
|
|
86
85
|
return None
|
|
87
86
|
|
|
88
87
|
if not ANTHROPIC_AVAILABLE:
|
|
89
|
-
logger.error(
|
|
88
|
+
logger.error(
|
|
89
|
+
"anthropic package not installed. Install with: pip install anthropic"
|
|
90
|
+
)
|
|
90
91
|
return None
|
|
91
92
|
|
|
92
93
|
try:
|
|
@@ -131,7 +132,7 @@ class ClaudeProvider(LLMProvider):
|
|
|
131
132
|
prompt: str,
|
|
132
133
|
system_prompt: Optional[str] = None,
|
|
133
134
|
max_tokens: int = 4096,
|
|
134
|
-
temperature: float = 0.3
|
|
135
|
+
temperature: float = 0.3,
|
|
135
136
|
) -> Optional[str]:
|
|
136
137
|
"""
|
|
137
138
|
Generate text using Claude API.
|
|
@@ -159,7 +160,7 @@ class ClaudeProvider(LLMProvider):
|
|
|
159
160
|
"model": self._model,
|
|
160
161
|
"max_tokens": max_tokens,
|
|
161
162
|
"temperature": temperature,
|
|
162
|
-
"messages": messages
|
|
163
|
+
"messages": messages,
|
|
163
164
|
}
|
|
164
165
|
|
|
165
166
|
if system_prompt:
|
|
@@ -198,33 +199,33 @@ class ClaudeProvider(LLMProvider):
|
|
|
198
199
|
has_key = api_key is not None
|
|
199
200
|
|
|
200
201
|
status = {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
202
|
+
"provider_type": self.provider_type.value,
|
|
203
|
+
"provider": "Claude",
|
|
204
|
+
"provider_name": "Claude (Anthropic)",
|
|
205
|
+
"model": self._model,
|
|
206
|
+
"api_key_configured": has_key,
|
|
207
|
+
"package_installed": ANTHROPIC_AVAILABLE,
|
|
208
|
+
"connected": False,
|
|
209
|
+
"error": None,
|
|
209
210
|
}
|
|
210
211
|
|
|
211
212
|
if not ANTHROPIC_AVAILABLE:
|
|
212
|
-
status[
|
|
213
|
+
status["error"] = "anthropic package not installed"
|
|
213
214
|
return status
|
|
214
215
|
|
|
215
216
|
if not has_key:
|
|
216
|
-
status[
|
|
217
|
+
status["error"] = "No API key configured"
|
|
217
218
|
return status
|
|
218
219
|
|
|
219
220
|
# Test connection
|
|
220
221
|
try:
|
|
221
222
|
client = self._get_client()
|
|
222
223
|
if client:
|
|
223
|
-
status[
|
|
224
|
+
status["connected"] = True
|
|
224
225
|
else:
|
|
225
|
-
status[
|
|
226
|
+
status["error"] = "Failed to create client"
|
|
226
227
|
except Exception as e:
|
|
227
|
-
status[
|
|
228
|
+
status["error"] = str(e)
|
|
228
229
|
|
|
229
230
|
return status
|
|
230
231
|
|
|
@@ -254,9 +255,9 @@ def set_claude_api_key(api_key: str) -> bool:
|
|
|
254
255
|
return False
|
|
255
256
|
|
|
256
257
|
cfg = read_config()
|
|
257
|
-
if
|
|
258
|
-
cfg[
|
|
259
|
-
cfg[
|
|
258
|
+
if "ai" not in cfg:
|
|
259
|
+
cfg["ai"] = {}
|
|
260
|
+
cfg["ai"]["claude_api_key"] = encrypted_key
|
|
260
261
|
write_config(cfg)
|
|
261
262
|
|
|
262
263
|
logger.info("Claude API key stored successfully")
|
|
@@ -277,8 +278,8 @@ def clear_claude_api_key() -> bool:
|
|
|
277
278
|
|
|
278
279
|
try:
|
|
279
280
|
cfg = read_config()
|
|
280
|
-
if
|
|
281
|
-
del cfg[
|
|
281
|
+
if "ai" in cfg and "claude_api_key" in cfg["ai"]:
|
|
282
|
+
del cfg["ai"]["claude_api_key"]
|
|
282
283
|
write_config(cfg)
|
|
283
284
|
logger.info("Claude API key removed")
|
|
284
285
|
return True
|