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/ui/chain_rules_view.py
CHANGED
|
@@ -25,7 +25,13 @@ def manage_chain_rules():
|
|
|
25
25
|
|
|
26
26
|
# Header
|
|
27
27
|
click.echo("\nā" + "ā" * (width - 2) + "ā")
|
|
28
|
-
click.echo(
|
|
28
|
+
click.echo(
|
|
29
|
+
"ā"
|
|
30
|
+
+ click.style(
|
|
31
|
+
" CHAIN RULE MANAGEMENT ".center(width - 2), bold=True, fg="cyan"
|
|
32
|
+
)
|
|
33
|
+
+ "ā"
|
|
34
|
+
)
|
|
29
35
|
click.echo("ā" + "ā" * (width - 2) + "ā")
|
|
30
36
|
click.echo()
|
|
31
37
|
|
|
@@ -44,11 +50,18 @@ def manage_chain_rules():
|
|
|
44
50
|
tool_list = sorted(rules_by_tool.keys())
|
|
45
51
|
|
|
46
52
|
# Page info
|
|
47
|
-
click.echo(
|
|
53
|
+
click.echo(
|
|
54
|
+
f" Total Rules: {total_rules} ā ā Enabled: {enabled_rules} ā ā Disabled: {disabled_rules}"
|
|
55
|
+
)
|
|
48
56
|
click.echo()
|
|
49
57
|
|
|
50
58
|
# Table header with checkbox and category columns
|
|
51
|
-
click.echo(
|
|
59
|
+
click.echo(
|
|
60
|
+
click.style(
|
|
61
|
+
f" ā {'Tool':<20} ā Enabled ā Disabled ā Total ā šÆ ā š¢ ā āļø ā Status",
|
|
62
|
+
bold=True,
|
|
63
|
+
)
|
|
64
|
+
)
|
|
52
65
|
click.echo(" " + "ā" * (width - 2))
|
|
53
66
|
|
|
54
67
|
# Display tool groups
|
|
@@ -59,32 +72,50 @@ def manage_chain_rules():
|
|
|
59
72
|
total = len(rules)
|
|
60
73
|
|
|
61
74
|
# Category counts
|
|
62
|
-
ctf_count = sum(1 for r in rules if r.category ==
|
|
63
|
-
enterprise_count = sum(1 for r in rules if r.category ==
|
|
64
|
-
general_count = sum(1 for r in rules if r.category ==
|
|
75
|
+
ctf_count = sum(1 for r in rules if r.category == "ctf")
|
|
76
|
+
enterprise_count = sum(1 for r in rules if r.category == "enterprise")
|
|
77
|
+
general_count = sum(1 for r in rules if r.category == "general")
|
|
65
78
|
|
|
66
79
|
# Checkbox for multi-select
|
|
67
|
-
checkbox =
|
|
80
|
+
checkbox = "ā" if tool in selected_tools else "ā"
|
|
68
81
|
|
|
69
82
|
# Icon based on status
|
|
70
83
|
if tool_disabled == 0:
|
|
71
|
-
icon = click.style("ā", fg=
|
|
84
|
+
icon = click.style("ā", fg="green", bold=True)
|
|
72
85
|
elif tool_enabled == 0:
|
|
73
|
-
icon = click.style("ā", fg=
|
|
86
|
+
icon = click.style("ā", fg="red")
|
|
74
87
|
else:
|
|
75
|
-
icon = click.style("ā", fg=
|
|
88
|
+
icon = click.style("ā", fg="yellow")
|
|
76
89
|
|
|
77
90
|
# Tool name
|
|
78
91
|
tool_display = f"{tool.upper():<20}"
|
|
79
|
-
on_display = click.style(f"{tool_enabled:>7}", fg=
|
|
80
|
-
off_display =
|
|
92
|
+
on_display = click.style(f"{tool_enabled:>7}", fg="green")
|
|
93
|
+
off_display = (
|
|
94
|
+
click.style(f"{tool_disabled:>8}", fg="yellow")
|
|
95
|
+
if tool_disabled > 0
|
|
96
|
+
else f"{tool_disabled:>8}"
|
|
97
|
+
)
|
|
81
98
|
|
|
82
99
|
# Category displays (dim if 0)
|
|
83
|
-
ctf_display =
|
|
84
|
-
|
|
85
|
-
|
|
100
|
+
ctf_display = (
|
|
101
|
+
click.style(f"{ctf_count:>4}", fg="bright_black")
|
|
102
|
+
if ctf_count == 0
|
|
103
|
+
else f"{ctf_count:>4}"
|
|
104
|
+
)
|
|
105
|
+
ent_display = (
|
|
106
|
+
click.style(f"{enterprise_count:>4}", fg="bright_black")
|
|
107
|
+
if enterprise_count == 0
|
|
108
|
+
else f"{enterprise_count:>4}"
|
|
109
|
+
)
|
|
110
|
+
gen_display = (
|
|
111
|
+
click.style(f"{general_count:>4}", fg="bright_black")
|
|
112
|
+
if general_count == 0
|
|
113
|
+
else f"{general_count:>4}"
|
|
114
|
+
)
|
|
86
115
|
|
|
87
|
-
click.echo(
|
|
116
|
+
click.echo(
|
|
117
|
+
f" {checkbox} {click.style(tool_display, fg='cyan')} ā {on_display} ā {off_display} ā {total:>5} ā {ctf_display} ā {ent_display} ā {gen_display} ā {icon}"
|
|
118
|
+
)
|
|
88
119
|
|
|
89
120
|
click.echo()
|
|
90
121
|
|
|
@@ -93,9 +124,17 @@ def manage_chain_rules():
|
|
|
93
124
|
click.echo()
|
|
94
125
|
|
|
95
126
|
# Brute-force warning
|
|
96
|
-
brute_force_enabled = sum(
|
|
127
|
+
brute_force_enabled = sum(
|
|
128
|
+
1 for r in chaining.rules if r.target_tool == "hydra" and r.enabled
|
|
129
|
+
)
|
|
97
130
|
if brute_force_enabled > 0:
|
|
98
|
-
click.echo(
|
|
131
|
+
click.echo(
|
|
132
|
+
click.style(
|
|
133
|
+
f" ā ļø WARNING: {brute_force_enabled} brute-force rules are ACTIVE!",
|
|
134
|
+
fg="red",
|
|
135
|
+
bold=True,
|
|
136
|
+
)
|
|
137
|
+
)
|
|
99
138
|
click.echo()
|
|
100
139
|
|
|
101
140
|
# Separator + inline menu
|
|
@@ -112,29 +151,34 @@ def manage_chain_rules():
|
|
|
112
151
|
click.echo()
|
|
113
152
|
|
|
114
153
|
try:
|
|
115
|
-
choice =
|
|
154
|
+
choice = (
|
|
155
|
+
click.prompt("Select option", default="0", show_default=False)
|
|
156
|
+
.strip()
|
|
157
|
+
.lower()
|
|
158
|
+
)
|
|
116
159
|
|
|
117
|
-
if choice ==
|
|
160
|
+
if choice == "?":
|
|
118
161
|
# Show auto-chaining help guide
|
|
119
162
|
from souleyez.ui.interactive import show_auto_chaining_help
|
|
163
|
+
|
|
120
164
|
show_auto_chaining_help()
|
|
121
165
|
continue
|
|
122
|
-
elif choice ==
|
|
166
|
+
elif choice == "q":
|
|
123
167
|
return
|
|
124
|
-
elif choice ==
|
|
168
|
+
elif choice == "i":
|
|
125
169
|
# Interactive multi-select mode for tool groups
|
|
126
170
|
_run_tool_groups_interactive(chaining, rules_by_tool, selected_tools)
|
|
127
|
-
elif choice ==
|
|
171
|
+
elif choice == "t":
|
|
128
172
|
_toggle_rule_interactive(chaining)
|
|
129
|
-
elif choice ==
|
|
173
|
+
elif choice == "v":
|
|
130
174
|
_view_rule_details(chaining)
|
|
131
|
-
elif choice ==
|
|
175
|
+
elif choice == "f":
|
|
132
176
|
_filter_by_status(chaining)
|
|
133
|
-
elif choice ==
|
|
177
|
+
elif choice == "e":
|
|
134
178
|
_enable_all_rules(chaining)
|
|
135
|
-
elif choice ==
|
|
179
|
+
elif choice == "d":
|
|
136
180
|
_disable_all_rules(chaining)
|
|
137
|
-
elif choice ==
|
|
181
|
+
elif choice == "r":
|
|
138
182
|
_reset_to_defaults(chaining)
|
|
139
183
|
|
|
140
184
|
except (KeyboardInterrupt, EOFError):
|
|
@@ -146,17 +190,33 @@ def _toggle_single_rule(chaining, rule_idx: int):
|
|
|
146
190
|
rule = chaining.rules[rule_idx]
|
|
147
191
|
|
|
148
192
|
# Safety warning for enabling brute-force rules
|
|
149
|
-
if not rule.enabled and rule.target_tool ==
|
|
193
|
+
if not rule.enabled and rule.target_tool == "hydra":
|
|
150
194
|
click.echo()
|
|
151
|
-
click.echo(
|
|
152
|
-
|
|
195
|
+
click.echo(
|
|
196
|
+
click.style(
|
|
197
|
+
"ā ļø WARNING: You are about to enable a BRUTE-FORCE rule!",
|
|
198
|
+
fg="red",
|
|
199
|
+
bold=True,
|
|
200
|
+
)
|
|
201
|
+
)
|
|
202
|
+
click.echo(
|
|
203
|
+
click.style(
|
|
204
|
+
" This may cause account lockouts or trigger security alerts.",
|
|
205
|
+
fg="red",
|
|
206
|
+
)
|
|
207
|
+
)
|
|
153
208
|
click.echo()
|
|
154
209
|
click.echo(f" Rule: {rule.trigger_tool}ā{rule.target_tool}")
|
|
155
210
|
click.echo(f" Description: {rule.description}")
|
|
156
211
|
click.echo()
|
|
157
212
|
|
|
158
|
-
if not click.confirm(
|
|
159
|
-
click.
|
|
213
|
+
if not click.confirm(
|
|
214
|
+
click.style(
|
|
215
|
+
"Are you sure you want to enable this rule?", fg="yellow", bold=True
|
|
216
|
+
),
|
|
217
|
+
default=False,
|
|
218
|
+
):
|
|
219
|
+
click.echo(click.style("\nā Rule toggle cancelled", fg="green"))
|
|
160
220
|
click.pause()
|
|
161
221
|
return
|
|
162
222
|
|
|
@@ -165,10 +225,16 @@ def _toggle_single_rule(chaining, rule_idx: int):
|
|
|
165
225
|
chaining.save_rules()
|
|
166
226
|
|
|
167
227
|
status = "ENABLED" if rule.enabled else "DISABLED"
|
|
168
|
-
status_color =
|
|
228
|
+
status_color = "green" if rule.enabled else "yellow"
|
|
169
229
|
|
|
170
230
|
click.echo()
|
|
171
|
-
click.echo(
|
|
231
|
+
click.echo(
|
|
232
|
+
click.style(
|
|
233
|
+
f"ā Rule {status}: {rule.trigger_tool}ā{rule.target_tool}",
|
|
234
|
+
fg=status_color,
|
|
235
|
+
bold=True,
|
|
236
|
+
)
|
|
237
|
+
)
|
|
172
238
|
click.pause()
|
|
173
239
|
|
|
174
240
|
|
|
@@ -191,72 +257,79 @@ def _run_tool_groups_interactive(chaining, rules_by_tool: dict, selected_tools:
|
|
|
191
257
|
else:
|
|
192
258
|
status = "ā"
|
|
193
259
|
|
|
194
|
-
tool_items.append(
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
260
|
+
tool_items.append(
|
|
261
|
+
{
|
|
262
|
+
"tool": tool,
|
|
263
|
+
"enabled": tool_enabled,
|
|
264
|
+
"disabled": tool_disabled,
|
|
265
|
+
"total": len(rules),
|
|
266
|
+
"status": status,
|
|
267
|
+
}
|
|
268
|
+
)
|
|
201
269
|
|
|
202
270
|
columns = [
|
|
203
|
-
{
|
|
204
|
-
{
|
|
205
|
-
{
|
|
206
|
-
{
|
|
207
|
-
{
|
|
271
|
+
{"name": "Tool", "width": 20, "key": "tool"},
|
|
272
|
+
{"name": "Enabled", "width": 8, "key": "enabled", "justify": "right"},
|
|
273
|
+
{"name": "Disabled", "width": 9, "key": "disabled", "justify": "right"},
|
|
274
|
+
{"name": "Total", "width": 6, "key": "total", "justify": "right"},
|
|
275
|
+
{"name": "Status", "width": 8, "key": "status"},
|
|
208
276
|
]
|
|
209
277
|
|
|
210
278
|
def format_tool_cell(item: dict, key: str) -> str:
|
|
211
279
|
value = item.get(key)
|
|
212
|
-
if key ==
|
|
280
|
+
if key == "tool":
|
|
213
281
|
return value.upper()
|
|
214
|
-
if key ==
|
|
215
|
-
return f
|
|
216
|
-
if key ==
|
|
217
|
-
return f
|
|
218
|
-
if key ==
|
|
219
|
-
if value ==
|
|
220
|
-
return
|
|
221
|
-
elif value ==
|
|
222
|
-
return
|
|
282
|
+
if key == "enabled":
|
|
283
|
+
return f"[green]{value}[/green]"
|
|
284
|
+
if key == "disabled":
|
|
285
|
+
return f"[yellow]{value}[/yellow]" if value > 0 else str(value)
|
|
286
|
+
if key == "status":
|
|
287
|
+
if value == "ā":
|
|
288
|
+
return "[green]ā[/green]"
|
|
289
|
+
elif value == "ā":
|
|
290
|
+
return "[red]ā[/red]"
|
|
223
291
|
else:
|
|
224
|
-
return
|
|
225
|
-
return str(value) if value is not None else
|
|
292
|
+
return "[yellow]ā[/yellow]"
|
|
293
|
+
return str(value) if value is not None else "-"
|
|
226
294
|
|
|
227
295
|
while True:
|
|
228
296
|
interactive_select(
|
|
229
297
|
items=tool_items,
|
|
230
298
|
columns=columns,
|
|
231
299
|
selected_ids=selected_tools,
|
|
232
|
-
get_id=lambda t: t.get(
|
|
233
|
-
title=
|
|
234
|
-
format_cell=format_tool_cell
|
|
300
|
+
get_id=lambda t: t.get("tool"),
|
|
301
|
+
title="SELECT TOOL GROUPS",
|
|
302
|
+
format_cell=format_tool_cell,
|
|
235
303
|
)
|
|
236
304
|
|
|
237
305
|
if not selected_tools:
|
|
238
306
|
return
|
|
239
307
|
|
|
240
308
|
result = _tool_groups_bulk_action_menu(chaining, rules_by_tool, selected_tools)
|
|
241
|
-
if result ==
|
|
309
|
+
if result == "back":
|
|
242
310
|
return
|
|
243
|
-
elif result ==
|
|
311
|
+
elif result == "clear":
|
|
244
312
|
selected_tools.clear()
|
|
245
313
|
|
|
246
314
|
|
|
247
|
-
def _tool_groups_bulk_action_menu(
|
|
315
|
+
def _tool_groups_bulk_action_menu(
|
|
316
|
+
chaining, rules_by_tool: dict, selected_tools: set
|
|
317
|
+
) -> str:
|
|
248
318
|
"""Show bulk action menu for selected tool groups."""
|
|
249
319
|
from rich.console import Console
|
|
320
|
+
|
|
250
321
|
console = Console()
|
|
251
322
|
|
|
252
323
|
if not selected_tools:
|
|
253
|
-
return
|
|
324
|
+
return "continue"
|
|
254
325
|
|
|
255
326
|
# Count rules in selected groups
|
|
256
327
|
total_rules = sum(len(rules_by_tool.get(tool, [])) for tool in selected_tools)
|
|
257
328
|
|
|
258
329
|
console.print()
|
|
259
|
-
console.print(
|
|
330
|
+
console.print(
|
|
331
|
+
f" [bold]Selected: {len(selected_tools)} tool group(s) ({total_rules} rules)[/bold]"
|
|
332
|
+
)
|
|
260
333
|
console.print(" \\[v] View All Rules")
|
|
261
334
|
console.print(" \\[e] Enable All Rules")
|
|
262
335
|
console.print(" \\[d] Disable All Rules")
|
|
@@ -264,34 +337,58 @@ def _tool_groups_bulk_action_menu(chaining, rules_by_tool: dict, selected_tools:
|
|
|
264
337
|
console.print()
|
|
265
338
|
|
|
266
339
|
try:
|
|
267
|
-
action =
|
|
340
|
+
action = (
|
|
341
|
+
click.prompt(" Select option", default="0", show_default=False)
|
|
342
|
+
.strip()
|
|
343
|
+
.lower()
|
|
344
|
+
)
|
|
268
345
|
|
|
269
|
-
if action ==
|
|
270
|
-
return
|
|
271
|
-
elif action ==
|
|
346
|
+
if action == "q":
|
|
347
|
+
return "back"
|
|
348
|
+
elif action == "v":
|
|
272
349
|
# View all rules in selected groups
|
|
273
350
|
DesignSystem.clear_screen()
|
|
274
351
|
width = DesignSystem.get_terminal_width()
|
|
275
352
|
|
|
276
353
|
click.echo("\nā" + "ā" * (width - 2) + "ā")
|
|
277
|
-
click.echo(
|
|
354
|
+
click.echo(
|
|
355
|
+
"ā"
|
|
356
|
+
+ click.style(
|
|
357
|
+
" SELECTED TOOL GROUPS ".center(width - 2), bold=True, fg="cyan"
|
|
358
|
+
)
|
|
359
|
+
+ "ā"
|
|
360
|
+
)
|
|
278
361
|
click.echo("ā" + "ā" * (width - 2) + "ā")
|
|
279
362
|
click.echo()
|
|
280
363
|
|
|
281
364
|
for tool in sorted(selected_tools):
|
|
282
365
|
rules = rules_by_tool.get(tool, [])
|
|
283
|
-
click.echo(
|
|
366
|
+
click.echo(
|
|
367
|
+
click.style(
|
|
368
|
+
f" {tool.upper()} ({len(rules)} rules)", bold=True, fg="cyan"
|
|
369
|
+
)
|
|
370
|
+
)
|
|
284
371
|
|
|
285
372
|
for rule in rules:
|
|
286
|
-
status =
|
|
287
|
-
|
|
288
|
-
|
|
373
|
+
status = (
|
|
374
|
+
click.style("ON", fg="green")
|
|
375
|
+
if rule.enabled
|
|
376
|
+
else click.style("OFF", fg="red")
|
|
377
|
+
)
|
|
378
|
+
warning = (
|
|
379
|
+
click.style(" ā ļø", fg="red")
|
|
380
|
+
if rule.target_tool == "hydra"
|
|
381
|
+
else ""
|
|
382
|
+
)
|
|
383
|
+
click.echo(
|
|
384
|
+
f" ā {rule.target_tool}{warning} | {rule.trigger_condition} | {status}"
|
|
385
|
+
)
|
|
289
386
|
|
|
290
387
|
click.echo()
|
|
291
388
|
|
|
292
389
|
click.pause()
|
|
293
|
-
return
|
|
294
|
-
elif action ==
|
|
390
|
+
return "continue"
|
|
391
|
+
elif action == "e":
|
|
295
392
|
# Enable all rules in selected groups
|
|
296
393
|
enabled = 0
|
|
297
394
|
for tool in selected_tools:
|
|
@@ -301,10 +398,15 @@ def _tool_groups_bulk_action_menu(chaining, rules_by_tool: dict, selected_tools:
|
|
|
301
398
|
enabled += 1
|
|
302
399
|
|
|
303
400
|
chaining.save_rules()
|
|
304
|
-
click.echo(
|
|
401
|
+
click.echo(
|
|
402
|
+
click.style(
|
|
403
|
+
f"\nā Enabled {enabled} rule(s) in {len(selected_tools)} group(s)",
|
|
404
|
+
fg="green",
|
|
405
|
+
)
|
|
406
|
+
)
|
|
305
407
|
click.pause()
|
|
306
|
-
return
|
|
307
|
-
elif action ==
|
|
408
|
+
return "clear"
|
|
409
|
+
elif action == "d":
|
|
308
410
|
# Disable all rules in selected groups
|
|
309
411
|
disabled = 0
|
|
310
412
|
for tool in selected_tools:
|
|
@@ -314,14 +416,19 @@ def _tool_groups_bulk_action_menu(chaining, rules_by_tool: dict, selected_tools:
|
|
|
314
416
|
disabled += 1
|
|
315
417
|
|
|
316
418
|
chaining.save_rules()
|
|
317
|
-
click.echo(
|
|
419
|
+
click.echo(
|
|
420
|
+
click.style(
|
|
421
|
+
f"\nā Disabled {disabled} rule(s) in {len(selected_tools)} group(s)",
|
|
422
|
+
fg="yellow",
|
|
423
|
+
)
|
|
424
|
+
)
|
|
318
425
|
click.pause()
|
|
319
|
-
return
|
|
426
|
+
return "clear"
|
|
320
427
|
|
|
321
428
|
except (KeyboardInterrupt, EOFError):
|
|
322
429
|
pass
|
|
323
430
|
|
|
324
|
-
return
|
|
431
|
+
return "continue"
|
|
325
432
|
|
|
326
433
|
|
|
327
434
|
def _display_rules_dashboard(chaining: ToolChaining):
|
|
@@ -332,7 +439,11 @@ def _display_rules_dashboard(chaining: ToolChaining):
|
|
|
332
439
|
|
|
333
440
|
# Header
|
|
334
441
|
click.echo("\nā" + "ā" * (width - 2) + "ā")
|
|
335
|
-
click.echo(
|
|
442
|
+
click.echo(
|
|
443
|
+
"ā"
|
|
444
|
+
+ click.style(" CHAIN RULE MANAGEMENT ".center(width - 2), bold=True, fg="cyan")
|
|
445
|
+
+ "ā"
|
|
446
|
+
)
|
|
336
447
|
click.echo("ā" + "ā" * (width - 2) + "ā")
|
|
337
448
|
click.echo()
|
|
338
449
|
|
|
@@ -342,14 +453,16 @@ def _display_rules_dashboard(chaining: ToolChaining):
|
|
|
342
453
|
disabled_rules = total_rules - enabled_rules
|
|
343
454
|
|
|
344
455
|
# Categorize rules
|
|
345
|
-
brute_force_rules = [r for r in chaining.rules if r.target_tool ==
|
|
456
|
+
brute_force_rules = [r for r in chaining.rules if r.target_tool == "hydra"]
|
|
346
457
|
brute_force_disabled = [r for r in brute_force_rules if not r.enabled]
|
|
347
458
|
brute_force_enabled = len(brute_force_rules) - len(brute_force_disabled)
|
|
348
459
|
|
|
349
|
-
click.echo(click.style("š OVERVIEW", bold=True, fg=
|
|
460
|
+
click.echo(click.style("š OVERVIEW", bold=True, fg="cyan"))
|
|
350
461
|
click.echo("ā" * width)
|
|
351
462
|
click.echo(f" Total Rules: {total_rules}")
|
|
352
|
-
click.echo(
|
|
463
|
+
click.echo(
|
|
464
|
+
f" ā Enabled: {click.style(str(enabled_rules), fg='green', bold=True)}"
|
|
465
|
+
)
|
|
353
466
|
click.echo(f" ā Disabled: {click.style(str(disabled_rules), fg='yellow')}")
|
|
354
467
|
click.echo()
|
|
355
468
|
|
|
@@ -361,9 +474,17 @@ def _display_rules_dashboard(chaining: ToolChaining):
|
|
|
361
474
|
rules_by_tool[rule.trigger_tool].append(rule)
|
|
362
475
|
|
|
363
476
|
# Display collapsed tool summary as table (FIRST)
|
|
364
|
-
click.echo(
|
|
477
|
+
click.echo(
|
|
478
|
+
click.style("ā” TOOL GROUPS", bold=True, fg="cyan")
|
|
479
|
+
+ click.style(" Use option [3] to expand", fg="bright_black")
|
|
480
|
+
)
|
|
365
481
|
click.echo("ā" * width)
|
|
366
|
-
click.echo(
|
|
482
|
+
click.echo(
|
|
483
|
+
click.style(
|
|
484
|
+
f" {'Tool':<20} ā Enabled ā Disabled ā Total ā šÆ ā š¢ ā āļø ā Status",
|
|
485
|
+
bold=True,
|
|
486
|
+
)
|
|
487
|
+
)
|
|
367
488
|
click.echo("ā" * width)
|
|
368
489
|
|
|
369
490
|
for tool in sorted(rules_by_tool.keys()):
|
|
@@ -373,66 +494,107 @@ def _display_rules_dashboard(chaining: ToolChaining):
|
|
|
373
494
|
total = len(rules)
|
|
374
495
|
|
|
375
496
|
# Category counts
|
|
376
|
-
ctf_count = sum(1 for r in rules if r.category ==
|
|
377
|
-
enterprise_count = sum(1 for r in rules if r.category ==
|
|
378
|
-
general_count = sum(1 for r in rules if r.category ==
|
|
497
|
+
ctf_count = sum(1 for r in rules if r.category == "ctf")
|
|
498
|
+
enterprise_count = sum(1 for r in rules if r.category == "enterprise")
|
|
499
|
+
general_count = sum(1 for r in rules if r.category == "general")
|
|
379
500
|
|
|
380
501
|
# Icon based on status
|
|
381
502
|
if tool_disabled == 0:
|
|
382
|
-
icon = click.style("ā", fg=
|
|
503
|
+
icon = click.style("ā", fg="green", bold=True)
|
|
383
504
|
elif tool_enabled == 0:
|
|
384
|
-
icon = click.style("ā", fg=
|
|
505
|
+
icon = click.style("ā", fg="red")
|
|
385
506
|
else:
|
|
386
|
-
icon = click.style("ā", fg=
|
|
507
|
+
icon = click.style("ā", fg="yellow") # Partial
|
|
387
508
|
|
|
388
509
|
# Tool name
|
|
389
510
|
tool_display = f"{tool.upper():<20}"
|
|
390
|
-
on_display = click.style(f"{tool_enabled:>7}", fg=
|
|
391
|
-
off_display =
|
|
511
|
+
on_display = click.style(f"{tool_enabled:>7}", fg="green")
|
|
512
|
+
off_display = (
|
|
513
|
+
click.style(f"{tool_disabled:>8}", fg="yellow")
|
|
514
|
+
if tool_disabled > 0
|
|
515
|
+
else f"{tool_disabled:>8}"
|
|
516
|
+
)
|
|
392
517
|
|
|
393
518
|
# Category displays (dim if 0)
|
|
394
|
-
ctf_display =
|
|
395
|
-
|
|
396
|
-
|
|
519
|
+
ctf_display = (
|
|
520
|
+
click.style(f"{ctf_count:>4}", fg="bright_black")
|
|
521
|
+
if ctf_count == 0
|
|
522
|
+
else f"{ctf_count:>4}"
|
|
523
|
+
)
|
|
524
|
+
ent_display = (
|
|
525
|
+
click.style(f"{enterprise_count:>4}", fg="bright_black")
|
|
526
|
+
if enterprise_count == 0
|
|
527
|
+
else f"{enterprise_count:>4}"
|
|
528
|
+
)
|
|
529
|
+
gen_display = (
|
|
530
|
+
click.style(f"{general_count:>4}", fg="bright_black")
|
|
531
|
+
if general_count == 0
|
|
532
|
+
else f"{general_count:>4}"
|
|
533
|
+
)
|
|
397
534
|
|
|
398
|
-
click.echo(
|
|
535
|
+
click.echo(
|
|
536
|
+
f" {click.style(tool_display, fg='cyan')} ā {on_display} ā {off_display} ā {total:>5} ā {ctf_display} ā {ent_display} ā {gen_display} ā {icon}"
|
|
537
|
+
)
|
|
399
538
|
|
|
400
539
|
click.echo("ā" * width)
|
|
401
540
|
click.echo()
|
|
402
541
|
|
|
403
542
|
# Highlight disabled security-sensitive rules (AFTER Tool Groups)
|
|
404
543
|
if brute_force_disabled:
|
|
405
|
-
click.echo(
|
|
544
|
+
click.echo(
|
|
545
|
+
click.style(
|
|
546
|
+
"š BRUTE-FORCE RULES (Disabled by default for safety)",
|
|
547
|
+
bold=True,
|
|
548
|
+
fg="yellow",
|
|
549
|
+
)
|
|
550
|
+
)
|
|
406
551
|
click.echo("ā" * width)
|
|
407
552
|
for rule in brute_force_disabled:
|
|
408
|
-
click.echo(
|
|
409
|
-
|
|
410
|
-
|
|
553
|
+
click.echo(
|
|
554
|
+
f" ā {click.style(rule.trigger_tool.upper(), fg='cyan')} ā "
|
|
555
|
+
f"{click.style('Hydra', fg='magenta')} | {rule.trigger_condition}"
|
|
556
|
+
)
|
|
557
|
+
click.echo(click.style(f" {rule.description}", fg="bright_black"))
|
|
411
558
|
click.echo()
|
|
412
559
|
|
|
413
560
|
if brute_force_enabled > 0:
|
|
414
|
-
click.echo(
|
|
415
|
-
|
|
416
|
-
|
|
561
|
+
click.echo(
|
|
562
|
+
click.style(
|
|
563
|
+
f"ā ļø WARNING: {brute_force_enabled} brute-force rules are ACTIVE!",
|
|
564
|
+
fg="red",
|
|
565
|
+
bold=True,
|
|
566
|
+
)
|
|
567
|
+
)
|
|
568
|
+
click.echo(
|
|
569
|
+
click.style(
|
|
570
|
+
" These may trigger account lockouts. Review option [1] to disable.",
|
|
571
|
+
fg="red",
|
|
572
|
+
)
|
|
573
|
+
)
|
|
417
574
|
click.echo()
|
|
418
575
|
|
|
419
576
|
|
|
420
577
|
def _rules_bulk_action_menu(chaining, selected_ids: set) -> str:
|
|
421
578
|
"""Show bulk action menu for selected rules."""
|
|
422
579
|
from rich.console import Console
|
|
580
|
+
|
|
423
581
|
console = Console()
|
|
424
582
|
|
|
425
|
-
selected_rules = [
|
|
583
|
+
selected_rules = [
|
|
584
|
+
(idx, chaining.rules[idx]) for idx in selected_ids if idx < len(chaining.rules)
|
|
585
|
+
]
|
|
426
586
|
|
|
427
587
|
if not selected_rules:
|
|
428
|
-
return
|
|
588
|
+
return "continue"
|
|
429
589
|
|
|
430
590
|
# Check for brute-force rules in selection
|
|
431
|
-
has_hydra = any(rule.target_tool ==
|
|
591
|
+
has_hydra = any(rule.target_tool == "hydra" for _, rule in selected_rules)
|
|
432
592
|
hydra_warning = " ā ļø" if has_hydra else ""
|
|
433
593
|
|
|
434
594
|
console.print()
|
|
435
|
-
console.print(
|
|
595
|
+
console.print(
|
|
596
|
+
f" [bold]Selected: {len(selected_rules)} rule(s){hydra_warning}[/bold]"
|
|
597
|
+
)
|
|
436
598
|
console.print(" \\[v] View All Rules")
|
|
437
599
|
console.print(" \\[e] Enable All Rules")
|
|
438
600
|
console.print(" \\[d] Disable All Rules")
|
|
@@ -440,48 +602,79 @@ def _rules_bulk_action_menu(chaining, selected_ids: set) -> str:
|
|
|
440
602
|
console.print()
|
|
441
603
|
|
|
442
604
|
try:
|
|
443
|
-
action =
|
|
605
|
+
action = (
|
|
606
|
+
click.prompt(" Select option", default="0", show_default=False)
|
|
607
|
+
.strip()
|
|
608
|
+
.lower()
|
|
609
|
+
)
|
|
444
610
|
|
|
445
|
-
if action ==
|
|
446
|
-
return
|
|
447
|
-
elif action ==
|
|
611
|
+
if action == "q":
|
|
612
|
+
return "back"
|
|
613
|
+
elif action == "v":
|
|
448
614
|
# View all selected rules (show details)
|
|
449
615
|
DesignSystem.clear_screen()
|
|
450
616
|
width = DesignSystem.get_terminal_width()
|
|
451
617
|
|
|
452
618
|
click.echo("\nā" + "ā" * (width - 2) + "ā")
|
|
453
|
-
click.echo(
|
|
619
|
+
click.echo(
|
|
620
|
+
"ā"
|
|
621
|
+
+ click.style(
|
|
622
|
+
" SELECTED RULES ".center(width - 2), bold=True, fg="cyan"
|
|
623
|
+
)
|
|
624
|
+
+ "ā"
|
|
625
|
+
)
|
|
454
626
|
click.echo("ā" + "ā" * (width - 2) + "ā")
|
|
455
627
|
click.echo()
|
|
456
628
|
|
|
457
629
|
for idx, rule in selected_rules:
|
|
458
|
-
status =
|
|
459
|
-
|
|
630
|
+
status = (
|
|
631
|
+
click.style("ON", fg="green")
|
|
632
|
+
if rule.enabled
|
|
633
|
+
else click.style("OFF", fg="red")
|
|
634
|
+
)
|
|
635
|
+
warning = (
|
|
636
|
+
click.style(" ā ļø", fg="red") if rule.target_tool == "hydra" else ""
|
|
637
|
+
)
|
|
460
638
|
cat_icon = CATEGORY_ICONS.get(rule.category, "āļø")
|
|
461
639
|
|
|
462
|
-
click.echo(
|
|
640
|
+
click.echo(
|
|
641
|
+
f" [{idx + 1}] {rule.trigger_tool} ā {rule.target_tool}{warning}"
|
|
642
|
+
)
|
|
463
643
|
click.echo(f" Condition: {rule.trigger_condition}")
|
|
464
|
-
click.echo(
|
|
644
|
+
click.echo(
|
|
645
|
+
f" Priority: {rule.priority} Category: {cat_icon} Status: {status}"
|
|
646
|
+
)
|
|
465
647
|
if rule.description:
|
|
466
|
-
click.echo(
|
|
648
|
+
click.echo(
|
|
649
|
+
click.style(f" {rule.description}", fg="bright_black")
|
|
650
|
+
)
|
|
467
651
|
click.echo()
|
|
468
652
|
|
|
469
653
|
click.pause()
|
|
470
|
-
return
|
|
471
|
-
elif action ==
|
|
654
|
+
return "continue"
|
|
655
|
+
elif action == "e":
|
|
472
656
|
# Enable selected - warn about brute-force
|
|
473
657
|
enabling_hydra = any(
|
|
474
|
-
not chaining.rules[idx].enabled
|
|
658
|
+
not chaining.rules[idx].enabled
|
|
659
|
+
and chaining.rules[idx].target_tool == "hydra"
|
|
475
660
|
for idx in selected_ids
|
|
476
661
|
)
|
|
477
662
|
|
|
478
663
|
if enabling_hydra:
|
|
479
664
|
click.echo()
|
|
480
|
-
click.echo(
|
|
481
|
-
|
|
482
|
-
|
|
665
|
+
click.echo(
|
|
666
|
+
click.style(
|
|
667
|
+
"ā ļø WARNING: Selection includes BRUTE-FORCE rules!",
|
|
668
|
+
fg="red",
|
|
669
|
+
bold=True,
|
|
670
|
+
)
|
|
671
|
+
)
|
|
672
|
+
if not click.confirm(
|
|
673
|
+
click.style("Proceed with enabling?", fg="yellow"), default=False
|
|
674
|
+
):
|
|
675
|
+
click.echo(click.style("\nā Enable cancelled", fg="green"))
|
|
483
676
|
click.pause()
|
|
484
|
-
return
|
|
677
|
+
return "continue"
|
|
485
678
|
|
|
486
679
|
enabled = 0
|
|
487
680
|
for idx in selected_ids:
|
|
@@ -490,10 +683,10 @@ def _rules_bulk_action_menu(chaining, selected_ids: set) -> str:
|
|
|
490
683
|
enabled += 1
|
|
491
684
|
|
|
492
685
|
chaining.save_rules()
|
|
493
|
-
click.echo(click.style(f"\nā Enabled {enabled} rule(s)", fg=
|
|
686
|
+
click.echo(click.style(f"\nā Enabled {enabled} rule(s)", fg="green"))
|
|
494
687
|
click.pause()
|
|
495
|
-
return
|
|
496
|
-
elif action ==
|
|
688
|
+
return "clear"
|
|
689
|
+
elif action == "d":
|
|
497
690
|
# Disable selected
|
|
498
691
|
disabled = 0
|
|
499
692
|
for idx in selected_ids:
|
|
@@ -502,19 +695,23 @@ def _rules_bulk_action_menu(chaining, selected_ids: set) -> str:
|
|
|
502
695
|
disabled += 1
|
|
503
696
|
|
|
504
697
|
chaining.save_rules()
|
|
505
|
-
click.echo(click.style(f"\nā Disabled {disabled} rule(s)", fg=
|
|
698
|
+
click.echo(click.style(f"\nā Disabled {disabled} rule(s)", fg="yellow"))
|
|
506
699
|
click.pause()
|
|
507
|
-
return
|
|
700
|
+
return "clear"
|
|
508
701
|
|
|
509
702
|
except (KeyboardInterrupt, EOFError):
|
|
510
703
|
pass
|
|
511
704
|
|
|
512
|
-
return
|
|
705
|
+
return "continue"
|
|
513
706
|
|
|
514
707
|
|
|
515
708
|
def _toggle_rule_interactive(chaining: ToolChaining):
|
|
516
709
|
"""Interactive rule toggle with paginated table view."""
|
|
517
|
-
from souleyez.core.tool_chaining import
|
|
710
|
+
from souleyez.core.tool_chaining import (
|
|
711
|
+
CATEGORY_CTF,
|
|
712
|
+
CATEGORY_ENTERPRISE,
|
|
713
|
+
CATEGORY_GENERAL,
|
|
714
|
+
)
|
|
518
715
|
|
|
519
716
|
page_size = 20
|
|
520
717
|
current_page = 0
|
|
@@ -534,15 +731,23 @@ def _toggle_rule_interactive(chaining: ToolChaining):
|
|
|
534
731
|
else:
|
|
535
732
|
filtered_rules = [(i, r) for i, r in filtered_rules if not r.enabled]
|
|
536
733
|
if filter_category is not None:
|
|
537
|
-
filtered_rules = [
|
|
734
|
+
filtered_rules = [
|
|
735
|
+
(i, r) for i, r in filtered_rules if r.category == filter_category
|
|
736
|
+
]
|
|
538
737
|
|
|
539
738
|
total_filtered = len(filtered_rules)
|
|
540
739
|
total_pages = max(1, math.ceil(total_filtered / page_size))
|
|
541
|
-
current_page = min(
|
|
740
|
+
current_page = min(
|
|
741
|
+
current_page, total_pages - 1
|
|
742
|
+
) # Adjust if filter reduced pages
|
|
542
743
|
|
|
543
744
|
# Header box
|
|
544
745
|
click.echo("\nā" + "ā" * (width - 2) + "ā")
|
|
545
|
-
click.echo(
|
|
746
|
+
click.echo(
|
|
747
|
+
"ā"
|
|
748
|
+
+ click.style(" TOGGLE CHAIN RULE ".center(width - 2), bold=True, fg="cyan")
|
|
749
|
+
+ "ā"
|
|
750
|
+
)
|
|
546
751
|
click.echo("ā" + "ā" * (width - 2) + "ā")
|
|
547
752
|
click.echo()
|
|
548
753
|
|
|
@@ -550,33 +755,54 @@ def _toggle_rule_interactive(chaining: ToolChaining):
|
|
|
550
755
|
total_rules = len(chaining.rules)
|
|
551
756
|
enabled_rules = sum(1 for r in chaining.rules if r.enabled)
|
|
552
757
|
disabled_rules = total_rules - enabled_rules
|
|
553
|
-
brute_force_rules = sum(1 for r in chaining.rules if r.target_tool ==
|
|
758
|
+
brute_force_rules = sum(1 for r in chaining.rules if r.target_tool == "hydra")
|
|
554
759
|
ctf_rules = sum(1 for r in chaining.rules if r.category == CATEGORY_CTF)
|
|
555
|
-
enterprise_rules = sum(
|
|
760
|
+
enterprise_rules = sum(
|
|
761
|
+
1 for r in chaining.rules if r.category == CATEGORY_ENTERPRISE
|
|
762
|
+
)
|
|
556
763
|
general_rules = sum(1 for r in chaining.rules if r.category == CATEGORY_GENERAL)
|
|
557
764
|
|
|
558
|
-
click.echo(click.style("š OVERVIEW", bold=True, fg=
|
|
765
|
+
click.echo(click.style("š OVERVIEW", bold=True, fg="cyan"))
|
|
559
766
|
click.echo("ā" * width)
|
|
560
|
-
click.echo(
|
|
767
|
+
click.echo(
|
|
768
|
+
f" Total: {total_rules} ā {click.style(str(enabled_rules), fg='green')} enabled ā {click.style(str(disabled_rules), fg='yellow')} disabled ā {click.style(str(brute_force_rules), fg='red')} brute-force ā šÆ {ctf_rules} š¢ {enterprise_rules} āļø {general_rules}"
|
|
769
|
+
)
|
|
561
770
|
click.echo()
|
|
562
771
|
|
|
563
772
|
# Page info with filter indicator
|
|
564
773
|
filter_parts = []
|
|
565
774
|
if filter_status is True:
|
|
566
|
-
filter_parts.append(click.style("Enabled", fg=
|
|
775
|
+
filter_parts.append(click.style("Enabled", fg="green"))
|
|
567
776
|
elif filter_status is False:
|
|
568
|
-
filter_parts.append(click.style("Disabled", fg=
|
|
777
|
+
filter_parts.append(click.style("Disabled", fg="yellow"))
|
|
569
778
|
if filter_category is not None:
|
|
570
779
|
cat_icon = CATEGORY_ICONS.get(filter_category, "āļø")
|
|
571
|
-
cat_name = {
|
|
780
|
+
cat_name = {
|
|
781
|
+
"ctf": "CTF",
|
|
782
|
+
"enterprise": "Enterprise",
|
|
783
|
+
"general": "General",
|
|
784
|
+
}.get(filter_category, filter_category)
|
|
572
785
|
filter_parts.append(f"{cat_icon} {cat_name}")
|
|
573
|
-
filter_text =
|
|
786
|
+
filter_text = (
|
|
787
|
+
click.style(f" [Filter: {', '.join(filter_parts)}]", fg="bright_black")
|
|
788
|
+
if filter_parts
|
|
789
|
+
else ""
|
|
790
|
+
)
|
|
574
791
|
page_info = f"Page {current_page + 1}/{total_pages}"
|
|
575
|
-
click.echo(
|
|
792
|
+
click.echo(
|
|
793
|
+
click.style(f"š RULES ", bold=True, fg="cyan")
|
|
794
|
+
+ click.style(page_info, fg="bright_black")
|
|
795
|
+
+ filter_text
|
|
796
|
+
)
|
|
576
797
|
click.echo("ā" * width)
|
|
577
798
|
|
|
578
799
|
# Table header - with checkbox column and Fired column
|
|
579
|
-
click.echo(
|
|
800
|
+
click.echo(
|
|
801
|
+
click.style(
|
|
802
|
+
f" ā ā # ā {'Trigger':<22} ā {'Target':<22} ā {'Condition':<30} ā Priority ā Category ā Status ā Fired",
|
|
803
|
+
bold=True,
|
|
804
|
+
)
|
|
805
|
+
)
|
|
580
806
|
click.echo("ā" * width)
|
|
581
807
|
|
|
582
808
|
# Calculate slice for current page
|
|
@@ -589,31 +815,61 @@ def _toggle_rule_interactive(chaining: ToolChaining):
|
|
|
589
815
|
rule_num = original_idx + 1 # Show original rule number for toggling
|
|
590
816
|
|
|
591
817
|
# Checkbox for multi-select
|
|
592
|
-
checkbox =
|
|
818
|
+
checkbox = "ā" if original_idx in selected_rule_ids else "ā"
|
|
593
819
|
|
|
594
820
|
# Use full names (no truncation)
|
|
595
|
-
trigger =
|
|
596
|
-
|
|
597
|
-
|
|
821
|
+
trigger = (
|
|
822
|
+
rule.trigger_tool[:22]
|
|
823
|
+
if len(rule.trigger_tool) <= 22
|
|
824
|
+
else rule.trigger_tool[:19] + "..."
|
|
825
|
+
)
|
|
826
|
+
target = (
|
|
827
|
+
rule.target_tool[:22]
|
|
828
|
+
if len(rule.target_tool) <= 22
|
|
829
|
+
else rule.target_tool[:19] + "..."
|
|
830
|
+
)
|
|
831
|
+
condition = (
|
|
832
|
+
rule.trigger_condition[:30]
|
|
833
|
+
if len(rule.trigger_condition) <= 30
|
|
834
|
+
else rule.trigger_condition[:27] + "..."
|
|
835
|
+
)
|
|
598
836
|
|
|
599
837
|
# Status with color and brute-force warning
|
|
600
|
-
if rule.target_tool ==
|
|
601
|
-
status =
|
|
838
|
+
if rule.target_tool == "hydra":
|
|
839
|
+
status = (
|
|
840
|
+
click.style(" ON", fg="green") + click.style(" ā ļø", fg="red")
|
|
841
|
+
if rule.enabled
|
|
842
|
+
else click.style("OFF", fg="red") + click.style(" ā ļø", fg="red")
|
|
843
|
+
)
|
|
602
844
|
else:
|
|
603
|
-
status =
|
|
845
|
+
status = (
|
|
846
|
+
click.style(" ON", fg="green")
|
|
847
|
+
if rule.enabled
|
|
848
|
+
else click.style("OFF", fg="red")
|
|
849
|
+
)
|
|
604
850
|
|
|
605
851
|
# Priority with color (8-char width to match "Priority" header)
|
|
606
|
-
pri_color =
|
|
852
|
+
pri_color = (
|
|
853
|
+
"green"
|
|
854
|
+
if rule.priority >= 8
|
|
855
|
+
else "yellow" if rule.priority >= 5 else "white"
|
|
856
|
+
)
|
|
607
857
|
priority = click.style(f"{rule.priority:>8}", fg=pri_color)
|
|
608
858
|
|
|
609
859
|
# Category icon (8-char width to match "Category" header)
|
|
610
860
|
cat_icon = CATEGORY_ICONS.get(rule.category, "āļø")
|
|
611
861
|
|
|
612
862
|
# Trigger count with color
|
|
613
|
-
fired_count = rule.trigger_count if hasattr(rule,
|
|
614
|
-
fired_display =
|
|
863
|
+
fired_count = rule.trigger_count if hasattr(rule, "trigger_count") else 0
|
|
864
|
+
fired_display = (
|
|
865
|
+
click.style(f"{fired_count:>5}", fg="green")
|
|
866
|
+
if fired_count > 0
|
|
867
|
+
else click.style(f"{fired_count:>5}", fg="bright_black")
|
|
868
|
+
)
|
|
615
869
|
|
|
616
|
-
click.echo(
|
|
870
|
+
click.echo(
|
|
871
|
+
f" {checkbox} ā {rule_num:>4} ā {trigger:<22} ā {target:<22} ā {condition:<30} ā {priority} ā {cat_icon} ā {status} ā {fired_display}"
|
|
872
|
+
)
|
|
617
873
|
|
|
618
874
|
click.echo("ā" * width)
|
|
619
875
|
click.echo()
|
|
@@ -637,25 +893,29 @@ def _toggle_rule_interactive(chaining: ToolChaining):
|
|
|
637
893
|
click.echo()
|
|
638
894
|
|
|
639
895
|
try:
|
|
640
|
-
choice =
|
|
896
|
+
choice = (
|
|
897
|
+
click.prompt("Select option", default="0", show_default=False)
|
|
898
|
+
.strip()
|
|
899
|
+
.lower()
|
|
900
|
+
)
|
|
641
901
|
|
|
642
|
-
if choice ==
|
|
902
|
+
if choice == "q":
|
|
643
903
|
return
|
|
644
|
-
elif choice ==
|
|
904
|
+
elif choice == "n" and current_page < total_pages - 1:
|
|
645
905
|
current_page += 1
|
|
646
|
-
elif choice ==
|
|
906
|
+
elif choice == "p" and current_page > 0:
|
|
647
907
|
current_page -= 1
|
|
648
|
-
elif choice ==
|
|
908
|
+
elif choice == "e":
|
|
649
909
|
filter_status = True
|
|
650
910
|
current_page = 0
|
|
651
|
-
elif choice ==
|
|
911
|
+
elif choice == "d":
|
|
652
912
|
filter_status = False
|
|
653
913
|
current_page = 0
|
|
654
|
-
elif choice ==
|
|
914
|
+
elif choice == "a":
|
|
655
915
|
filter_status = None
|
|
656
916
|
filter_category = None
|
|
657
917
|
current_page = 0
|
|
658
|
-
elif choice ==
|
|
918
|
+
elif choice == "c":
|
|
659
919
|
# Category filter submenu
|
|
660
920
|
click.echo()
|
|
661
921
|
click.echo(click.style(" Select category:", bold=True))
|
|
@@ -663,14 +923,16 @@ def _toggle_rule_interactive(chaining: ToolChaining):
|
|
|
663
923
|
click.echo(f" [2] š¢ Enterprise - Real-world testing")
|
|
664
924
|
click.echo(f" [3] āļø General - Standard recon")
|
|
665
925
|
click.echo(f" [q] Cancel")
|
|
666
|
-
cat_choice = click.prompt(
|
|
667
|
-
|
|
926
|
+
cat_choice = click.prompt(
|
|
927
|
+
" Select option", default="0", show_default=False
|
|
928
|
+
).strip()
|
|
929
|
+
if cat_choice == "1":
|
|
668
930
|
filter_category = CATEGORY_CTF
|
|
669
931
|
current_page = 0
|
|
670
|
-
elif cat_choice ==
|
|
932
|
+
elif cat_choice == "2":
|
|
671
933
|
filter_category = CATEGORY_ENTERPRISE
|
|
672
934
|
current_page = 0
|
|
673
|
-
elif cat_choice ==
|
|
935
|
+
elif cat_choice == "3":
|
|
674
936
|
filter_category = CATEGORY_GENERAL
|
|
675
937
|
current_page = 0
|
|
676
938
|
elif choice.isdigit():
|
|
@@ -679,17 +941,37 @@ def _toggle_rule_interactive(chaining: ToolChaining):
|
|
|
679
941
|
rule = chaining.rules[rule_idx]
|
|
680
942
|
|
|
681
943
|
# Safety warning for enabling brute-force rules
|
|
682
|
-
if not rule.enabled and rule.target_tool ==
|
|
944
|
+
if not rule.enabled and rule.target_tool == "hydra":
|
|
683
945
|
click.echo()
|
|
684
|
-
click.echo(
|
|
685
|
-
|
|
946
|
+
click.echo(
|
|
947
|
+
click.style(
|
|
948
|
+
"ā ļø WARNING: You are about to enable a BRUTE-FORCE rule!",
|
|
949
|
+
fg="red",
|
|
950
|
+
bold=True,
|
|
951
|
+
)
|
|
952
|
+
)
|
|
953
|
+
click.echo(
|
|
954
|
+
click.style(
|
|
955
|
+
" This may cause account lockouts or trigger security alerts.",
|
|
956
|
+
fg="red",
|
|
957
|
+
)
|
|
958
|
+
)
|
|
686
959
|
click.echo()
|
|
687
960
|
click.echo(f" Rule: {rule.trigger_tool}ā{rule.target_tool}")
|
|
688
961
|
click.echo(f" Description: {rule.description}")
|
|
689
962
|
click.echo()
|
|
690
963
|
|
|
691
|
-
if not click.confirm(
|
|
692
|
-
click.
|
|
964
|
+
if not click.confirm(
|
|
965
|
+
click.style(
|
|
966
|
+
"Are you sure you want to enable this rule?",
|
|
967
|
+
fg="yellow",
|
|
968
|
+
bold=True,
|
|
969
|
+
),
|
|
970
|
+
default=False,
|
|
971
|
+
):
|
|
972
|
+
click.echo(
|
|
973
|
+
click.style("\nā Rule toggle cancelled", fg="green")
|
|
974
|
+
)
|
|
693
975
|
click.pause()
|
|
694
976
|
continue
|
|
695
977
|
|
|
@@ -698,19 +980,30 @@ def _toggle_rule_interactive(chaining: ToolChaining):
|
|
|
698
980
|
chaining.save_rules() # Persist the change
|
|
699
981
|
|
|
700
982
|
status = "ENABLED" if rule.enabled else "DISABLED"
|
|
701
|
-
status_color =
|
|
983
|
+
status_color = "green" if rule.enabled else "yellow"
|
|
702
984
|
|
|
703
985
|
click.echo()
|
|
704
|
-
click.echo(
|
|
986
|
+
click.echo(
|
|
987
|
+
click.style(
|
|
988
|
+
f"ā Rule {status}: {rule.trigger_tool}ā{rule.target_tool}",
|
|
989
|
+
fg=status_color,
|
|
990
|
+
bold=True,
|
|
991
|
+
)
|
|
992
|
+
)
|
|
705
993
|
|
|
706
994
|
if rule.enabled:
|
|
707
|
-
click.echo(
|
|
995
|
+
click.echo(
|
|
996
|
+
click.style(
|
|
997
|
+
" Note: Worker will apply changes on next job completion",
|
|
998
|
+
fg="bright_black",
|
|
999
|
+
)
|
|
1000
|
+
)
|
|
708
1001
|
|
|
709
1002
|
click.pause()
|
|
710
1003
|
else:
|
|
711
|
-
click.echo(click.style("\nā Invalid rule number", fg=
|
|
1004
|
+
click.echo(click.style("\nā Invalid rule number", fg="red"))
|
|
712
1005
|
click.pause()
|
|
713
|
-
elif choice ==
|
|
1006
|
+
elif choice == "i":
|
|
714
1007
|
# Interactive multi-select mode (respects current filters)
|
|
715
1008
|
from souleyez.ui.interactive_selector import interactive_select
|
|
716
1009
|
|
|
@@ -718,42 +1011,50 @@ def _toggle_rule_interactive(chaining: ToolChaining):
|
|
|
718
1011
|
# Convert FILTERED rules to dicts for selector
|
|
719
1012
|
rule_items = []
|
|
720
1013
|
for original_idx, rule in filtered_rules:
|
|
721
|
-
rule_items.append(
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
1014
|
+
rule_items.append(
|
|
1015
|
+
{
|
|
1016
|
+
"idx": original_idx,
|
|
1017
|
+
"trigger_tool": rule.trigger_tool,
|
|
1018
|
+
"target_tool": rule.target_tool,
|
|
1019
|
+
"condition": rule.trigger_condition,
|
|
1020
|
+
"enabled": rule.enabled,
|
|
1021
|
+
"priority": rule.priority,
|
|
1022
|
+
"category": rule.category,
|
|
1023
|
+
}
|
|
1024
|
+
)
|
|
730
1025
|
|
|
731
1026
|
columns = [
|
|
732
|
-
{
|
|
733
|
-
{
|
|
734
|
-
{
|
|
735
|
-
{
|
|
736
|
-
{
|
|
737
|
-
{
|
|
1027
|
+
{"name": "#", "width": 5, "key": "idx", "justify": "right"},
|
|
1028
|
+
{"name": "Trigger", "width": 18, "key": "trigger_tool"},
|
|
1029
|
+
{"name": "Target", "width": 18, "key": "target_tool"},
|
|
1030
|
+
{"name": "Condition", "width": 25, "key": "condition"},
|
|
1031
|
+
{"name": "Cat", "width": 5, "key": "category"},
|
|
1032
|
+
{"name": "Status", "width": 8, "key": "enabled"},
|
|
738
1033
|
]
|
|
739
1034
|
|
|
740
1035
|
def format_rule_cell(item: dict, key: str) -> str:
|
|
741
1036
|
value = item.get(key)
|
|
742
|
-
if key ==
|
|
743
|
-
return
|
|
744
|
-
if key ==
|
|
1037
|
+
if key == "enabled":
|
|
1038
|
+
return "[green]ON[/green]" if value else "[red]OFF[/red]"
|
|
1039
|
+
if key == "idx":
|
|
745
1040
|
return str(value + 1) # 1-indexed for display
|
|
746
|
-
if key ==
|
|
747
|
-
return CATEGORY_ICONS.get(value,
|
|
748
|
-
return str(value) if value else
|
|
1041
|
+
if key == "category":
|
|
1042
|
+
return CATEGORY_ICONS.get(value, "āļø")
|
|
1043
|
+
return str(value) if value else "-"
|
|
749
1044
|
|
|
750
1045
|
# Build title with filter info
|
|
751
|
-
title =
|
|
1046
|
+
title = "SELECT RULES"
|
|
752
1047
|
if filter_category or filter_status is not None:
|
|
753
1048
|
filter_info = []
|
|
754
1049
|
if filter_category:
|
|
755
|
-
cat_name = {
|
|
756
|
-
|
|
1050
|
+
cat_name = {
|
|
1051
|
+
"ctf": "CTF",
|
|
1052
|
+
"enterprise": "Enterprise",
|
|
1053
|
+
"general": "General",
|
|
1054
|
+
}.get(filter_category, "")
|
|
1055
|
+
filter_info.append(
|
|
1056
|
+
f"{CATEGORY_ICONS.get(filter_category, '')} {cat_name}"
|
|
1057
|
+
)
|
|
757
1058
|
if filter_status is True:
|
|
758
1059
|
filter_info.append("Enabled")
|
|
759
1060
|
elif filter_status is False:
|
|
@@ -764,18 +1065,18 @@ def _toggle_rule_interactive(chaining: ToolChaining):
|
|
|
764
1065
|
items=rule_items,
|
|
765
1066
|
columns=columns,
|
|
766
1067
|
selected_ids=selected_rule_ids,
|
|
767
|
-
get_id=lambda r: r.get(
|
|
1068
|
+
get_id=lambda r: r.get("idx"),
|
|
768
1069
|
title=title,
|
|
769
|
-
format_cell=format_rule_cell
|
|
1070
|
+
format_cell=format_rule_cell,
|
|
770
1071
|
)
|
|
771
1072
|
|
|
772
1073
|
if not selected_rule_ids:
|
|
773
1074
|
break
|
|
774
1075
|
|
|
775
1076
|
result = _rules_bulk_action_menu(chaining, selected_rule_ids)
|
|
776
|
-
if result ==
|
|
1077
|
+
if result == "back":
|
|
777
1078
|
break
|
|
778
|
-
elif result ==
|
|
1079
|
+
elif result == "clear":
|
|
779
1080
|
selected_rule_ids.clear()
|
|
780
1081
|
except (KeyboardInterrupt, EOFError):
|
|
781
1082
|
return
|
|
@@ -783,7 +1084,11 @@ def _toggle_rule_interactive(chaining: ToolChaining):
|
|
|
783
1084
|
|
|
784
1085
|
def _view_rule_details(chaining: ToolChaining):
|
|
785
1086
|
"""Display detailed information about a rule with paginated table view."""
|
|
786
|
-
from souleyez.core.tool_chaining import
|
|
1087
|
+
from souleyez.core.tool_chaining import (
|
|
1088
|
+
CATEGORY_CTF,
|
|
1089
|
+
CATEGORY_ENTERPRISE,
|
|
1090
|
+
CATEGORY_GENERAL,
|
|
1091
|
+
)
|
|
787
1092
|
|
|
788
1093
|
page_size = 20
|
|
789
1094
|
current_page = 0
|
|
@@ -803,15 +1108,23 @@ def _view_rule_details(chaining: ToolChaining):
|
|
|
803
1108
|
else:
|
|
804
1109
|
filtered_rules = [(i, r) for i, r in filtered_rules if not r.enabled]
|
|
805
1110
|
if filter_category is not None:
|
|
806
|
-
filtered_rules = [
|
|
1111
|
+
filtered_rules = [
|
|
1112
|
+
(i, r) for i, r in filtered_rules if r.category == filter_category
|
|
1113
|
+
]
|
|
807
1114
|
|
|
808
1115
|
total_filtered = len(filtered_rules)
|
|
809
1116
|
total_pages = max(1, math.ceil(total_filtered / page_size))
|
|
810
|
-
current_page = min(
|
|
1117
|
+
current_page = min(
|
|
1118
|
+
current_page, total_pages - 1
|
|
1119
|
+
) # Adjust if filter reduced pages
|
|
811
1120
|
|
|
812
1121
|
# Header box
|
|
813
1122
|
click.echo("\nā" + "ā" * (width - 2) + "ā")
|
|
814
|
-
click.echo(
|
|
1123
|
+
click.echo(
|
|
1124
|
+
"ā"
|
|
1125
|
+
+ click.style(" VIEW RULE DETAILS ".center(width - 2), bold=True, fg="cyan")
|
|
1126
|
+
+ "ā"
|
|
1127
|
+
)
|
|
815
1128
|
click.echo("ā" + "ā" * (width - 2) + "ā")
|
|
816
1129
|
click.echo()
|
|
817
1130
|
|
|
@@ -819,33 +1132,54 @@ def _view_rule_details(chaining: ToolChaining):
|
|
|
819
1132
|
total_rules = len(chaining.rules)
|
|
820
1133
|
enabled_rules = sum(1 for r in chaining.rules if r.enabled)
|
|
821
1134
|
disabled_rules = total_rules - enabled_rules
|
|
822
|
-
brute_force_rules = sum(1 for r in chaining.rules if r.target_tool ==
|
|
1135
|
+
brute_force_rules = sum(1 for r in chaining.rules if r.target_tool == "hydra")
|
|
823
1136
|
ctf_rules = sum(1 for r in chaining.rules if r.category == CATEGORY_CTF)
|
|
824
|
-
enterprise_rules = sum(
|
|
1137
|
+
enterprise_rules = sum(
|
|
1138
|
+
1 for r in chaining.rules if r.category == CATEGORY_ENTERPRISE
|
|
1139
|
+
)
|
|
825
1140
|
general_rules = sum(1 for r in chaining.rules if r.category == CATEGORY_GENERAL)
|
|
826
1141
|
|
|
827
|
-
click.echo(click.style("š OVERVIEW", bold=True, fg=
|
|
1142
|
+
click.echo(click.style("š OVERVIEW", bold=True, fg="cyan"))
|
|
828
1143
|
click.echo("ā" * width)
|
|
829
|
-
click.echo(
|
|
1144
|
+
click.echo(
|
|
1145
|
+
f" Total: {total_rules} ā {click.style(str(enabled_rules), fg='green')} enabled ā {click.style(str(disabled_rules), fg='yellow')} disabled ā {click.style(str(brute_force_rules), fg='red')} brute-force ā šÆ {ctf_rules} š¢ {enterprise_rules} āļø {general_rules}"
|
|
1146
|
+
)
|
|
830
1147
|
click.echo()
|
|
831
1148
|
|
|
832
1149
|
# Page info with filter indicator
|
|
833
1150
|
filter_parts = []
|
|
834
1151
|
if filter_status is True:
|
|
835
|
-
filter_parts.append(click.style("Enabled", fg=
|
|
1152
|
+
filter_parts.append(click.style("Enabled", fg="green"))
|
|
836
1153
|
elif filter_status is False:
|
|
837
|
-
filter_parts.append(click.style("Disabled", fg=
|
|
1154
|
+
filter_parts.append(click.style("Disabled", fg="yellow"))
|
|
838
1155
|
if filter_category is not None:
|
|
839
1156
|
cat_icon = CATEGORY_ICONS.get(filter_category, "āļø")
|
|
840
|
-
cat_name = {
|
|
1157
|
+
cat_name = {
|
|
1158
|
+
"ctf": "CTF",
|
|
1159
|
+
"enterprise": "Enterprise",
|
|
1160
|
+
"general": "General",
|
|
1161
|
+
}.get(filter_category, filter_category)
|
|
841
1162
|
filter_parts.append(f"{cat_icon} {cat_name}")
|
|
842
|
-
filter_text =
|
|
1163
|
+
filter_text = (
|
|
1164
|
+
click.style(f" [Filter: {', '.join(filter_parts)}]", fg="bright_black")
|
|
1165
|
+
if filter_parts
|
|
1166
|
+
else ""
|
|
1167
|
+
)
|
|
843
1168
|
page_info = f"Page {current_page + 1}/{total_pages}"
|
|
844
|
-
click.echo(
|
|
1169
|
+
click.echo(
|
|
1170
|
+
click.style(f"š RULES ", bold=True, fg="cyan")
|
|
1171
|
+
+ click.style(page_info, fg="bright_black")
|
|
1172
|
+
+ filter_text
|
|
1173
|
+
)
|
|
845
1174
|
click.echo("ā" * width)
|
|
846
1175
|
|
|
847
1176
|
# Table header - with checkbox column and Fired column
|
|
848
|
-
click.echo(
|
|
1177
|
+
click.echo(
|
|
1178
|
+
click.style(
|
|
1179
|
+
f" ā ā # ā {'Trigger':<22} ā {'Target':<22} ā {'Condition':<30} ā Priority ā Category ā Status ā Fired",
|
|
1180
|
+
bold=True,
|
|
1181
|
+
)
|
|
1182
|
+
)
|
|
849
1183
|
click.echo("ā" * width)
|
|
850
1184
|
|
|
851
1185
|
# Calculate slice for current page
|
|
@@ -858,28 +1192,54 @@ def _view_rule_details(chaining: ToolChaining):
|
|
|
858
1192
|
rule_num = original_idx + 1 # Show original rule number
|
|
859
1193
|
|
|
860
1194
|
# Checkbox for multi-select
|
|
861
|
-
checkbox =
|
|
1195
|
+
checkbox = "ā" if original_idx in selected_rule_ids else "ā"
|
|
862
1196
|
|
|
863
1197
|
# Use full names (no truncation)
|
|
864
|
-
trigger =
|
|
865
|
-
|
|
866
|
-
|
|
1198
|
+
trigger = (
|
|
1199
|
+
rule.trigger_tool[:22]
|
|
1200
|
+
if len(rule.trigger_tool) <= 22
|
|
1201
|
+
else rule.trigger_tool[:19] + "..."
|
|
1202
|
+
)
|
|
1203
|
+
target = (
|
|
1204
|
+
rule.target_tool[:22]
|
|
1205
|
+
if len(rule.target_tool) <= 22
|
|
1206
|
+
else rule.target_tool[:19] + "..."
|
|
1207
|
+
)
|
|
1208
|
+
condition = (
|
|
1209
|
+
rule.trigger_condition[:30]
|
|
1210
|
+
if len(rule.trigger_condition) <= 30
|
|
1211
|
+
else rule.trigger_condition[:27] + "..."
|
|
1212
|
+
)
|
|
867
1213
|
|
|
868
1214
|
# Status with color
|
|
869
|
-
status =
|
|
1215
|
+
status = (
|
|
1216
|
+
click.style(" ON", fg="green")
|
|
1217
|
+
if rule.enabled
|
|
1218
|
+
else click.style("OFF", fg="red")
|
|
1219
|
+
)
|
|
870
1220
|
|
|
871
1221
|
# Priority with color (8-char width to match "Priority" header)
|
|
872
|
-
pri_color =
|
|
1222
|
+
pri_color = (
|
|
1223
|
+
"green"
|
|
1224
|
+
if rule.priority >= 8
|
|
1225
|
+
else "yellow" if rule.priority >= 5 else "white"
|
|
1226
|
+
)
|
|
873
1227
|
priority = click.style(f"{rule.priority:>8}", fg=pri_color)
|
|
874
1228
|
|
|
875
1229
|
# Category icon (8-char width to match "Category" header)
|
|
876
1230
|
cat_icon = CATEGORY_ICONS.get(rule.category, "āļø")
|
|
877
1231
|
|
|
878
1232
|
# Trigger count with color
|
|
879
|
-
fired_count = rule.trigger_count if hasattr(rule,
|
|
880
|
-
fired_display =
|
|
1233
|
+
fired_count = rule.trigger_count if hasattr(rule, "trigger_count") else 0
|
|
1234
|
+
fired_display = (
|
|
1235
|
+
click.style(f"{fired_count:>5}", fg="green")
|
|
1236
|
+
if fired_count > 0
|
|
1237
|
+
else click.style(f"{fired_count:>5}", fg="bright_black")
|
|
1238
|
+
)
|
|
881
1239
|
|
|
882
|
-
click.echo(
|
|
1240
|
+
click.echo(
|
|
1241
|
+
f" {checkbox} ā {rule_num:>4} ā {trigger:<22} ā {target:<22} ā {condition:<30} ā {priority} ā {cat_icon} ā {status} ā {fired_display}"
|
|
1242
|
+
)
|
|
883
1243
|
|
|
884
1244
|
click.echo("ā" * width)
|
|
885
1245
|
click.echo()
|
|
@@ -903,25 +1263,29 @@ def _view_rule_details(chaining: ToolChaining):
|
|
|
903
1263
|
click.echo()
|
|
904
1264
|
|
|
905
1265
|
try:
|
|
906
|
-
choice =
|
|
1266
|
+
choice = (
|
|
1267
|
+
click.prompt("Select option", default="0", show_default=False)
|
|
1268
|
+
.strip()
|
|
1269
|
+
.lower()
|
|
1270
|
+
)
|
|
907
1271
|
|
|
908
|
-
if choice ==
|
|
1272
|
+
if choice == "q":
|
|
909
1273
|
return
|
|
910
|
-
elif choice ==
|
|
1274
|
+
elif choice == "n" and current_page < total_pages - 1:
|
|
911
1275
|
current_page += 1
|
|
912
|
-
elif choice ==
|
|
1276
|
+
elif choice == "p" and current_page > 0:
|
|
913
1277
|
current_page -= 1
|
|
914
|
-
elif choice ==
|
|
1278
|
+
elif choice == "e":
|
|
915
1279
|
filter_status = True
|
|
916
1280
|
current_page = 0
|
|
917
|
-
elif choice ==
|
|
1281
|
+
elif choice == "d":
|
|
918
1282
|
filter_status = False
|
|
919
1283
|
current_page = 0
|
|
920
|
-
elif choice ==
|
|
1284
|
+
elif choice == "a":
|
|
921
1285
|
filter_status = None
|
|
922
1286
|
filter_category = None
|
|
923
1287
|
current_page = 0
|
|
924
|
-
elif choice ==
|
|
1288
|
+
elif choice == "c":
|
|
925
1289
|
# Category filter submenu
|
|
926
1290
|
click.echo()
|
|
927
1291
|
click.echo(click.style(" Select category:", bold=True))
|
|
@@ -929,14 +1293,16 @@ def _view_rule_details(chaining: ToolChaining):
|
|
|
929
1293
|
click.echo(f" [2] š¢ Enterprise - Real-world testing")
|
|
930
1294
|
click.echo(f" [3] āļø General - Standard recon")
|
|
931
1295
|
click.echo(f" [q] Cancel")
|
|
932
|
-
cat_choice = click.prompt(
|
|
933
|
-
|
|
1296
|
+
cat_choice = click.prompt(
|
|
1297
|
+
" Select option", default="0", show_default=False
|
|
1298
|
+
).strip()
|
|
1299
|
+
if cat_choice == "1":
|
|
934
1300
|
filter_category = CATEGORY_CTF
|
|
935
1301
|
current_page = 0
|
|
936
|
-
elif cat_choice ==
|
|
1302
|
+
elif cat_choice == "2":
|
|
937
1303
|
filter_category = CATEGORY_ENTERPRISE
|
|
938
1304
|
current_page = 0
|
|
939
|
-
elif cat_choice ==
|
|
1305
|
+
elif cat_choice == "3":
|
|
940
1306
|
filter_category = CATEGORY_GENERAL
|
|
941
1307
|
current_page = 0
|
|
942
1308
|
elif choice.isdigit():
|
|
@@ -945,9 +1311,9 @@ def _view_rule_details(chaining: ToolChaining):
|
|
|
945
1311
|
if 0 <= rule_idx < len(chaining.rules):
|
|
946
1312
|
_show_single_rule_details(chaining.rules[rule_idx], width, chaining)
|
|
947
1313
|
else:
|
|
948
|
-
click.echo(click.style("\nā Invalid rule number", fg=
|
|
1314
|
+
click.echo(click.style("\nā Invalid rule number", fg="red"))
|
|
949
1315
|
click.pause()
|
|
950
|
-
elif choice ==
|
|
1316
|
+
elif choice == "i":
|
|
951
1317
|
# Interactive multi-select mode (respects current filters)
|
|
952
1318
|
from souleyez.ui.interactive_selector import interactive_select
|
|
953
1319
|
|
|
@@ -955,42 +1321,50 @@ def _view_rule_details(chaining: ToolChaining):
|
|
|
955
1321
|
# Convert FILTERED rules to dicts for selector
|
|
956
1322
|
rule_items = []
|
|
957
1323
|
for original_idx, rule in filtered_rules:
|
|
958
|
-
rule_items.append(
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
1324
|
+
rule_items.append(
|
|
1325
|
+
{
|
|
1326
|
+
"idx": original_idx,
|
|
1327
|
+
"trigger_tool": rule.trigger_tool,
|
|
1328
|
+
"target_tool": rule.target_tool,
|
|
1329
|
+
"condition": rule.trigger_condition,
|
|
1330
|
+
"enabled": rule.enabled,
|
|
1331
|
+
"priority": rule.priority,
|
|
1332
|
+
"category": rule.category,
|
|
1333
|
+
}
|
|
1334
|
+
)
|
|
967
1335
|
|
|
968
1336
|
columns = [
|
|
969
|
-
{
|
|
970
|
-
{
|
|
971
|
-
{
|
|
972
|
-
{
|
|
973
|
-
{
|
|
974
|
-
{
|
|
1337
|
+
{"name": "#", "width": 5, "key": "idx", "justify": "right"},
|
|
1338
|
+
{"name": "Trigger", "width": 18, "key": "trigger_tool"},
|
|
1339
|
+
{"name": "Target", "width": 18, "key": "target_tool"},
|
|
1340
|
+
{"name": "Condition", "width": 25, "key": "condition"},
|
|
1341
|
+
{"name": "Cat", "width": 5, "key": "category"},
|
|
1342
|
+
{"name": "Status", "width": 8, "key": "enabled"},
|
|
975
1343
|
]
|
|
976
1344
|
|
|
977
1345
|
def format_rule_cell(item: dict, key: str) -> str:
|
|
978
1346
|
value = item.get(key)
|
|
979
|
-
if key ==
|
|
980
|
-
return
|
|
981
|
-
if key ==
|
|
1347
|
+
if key == "enabled":
|
|
1348
|
+
return "[green]ON[/green]" if value else "[red]OFF[/red]"
|
|
1349
|
+
if key == "idx":
|
|
982
1350
|
return str(value + 1) # 1-indexed for display
|
|
983
|
-
if key ==
|
|
984
|
-
return CATEGORY_ICONS.get(value,
|
|
985
|
-
return str(value) if value else
|
|
1351
|
+
if key == "category":
|
|
1352
|
+
return CATEGORY_ICONS.get(value, "āļø")
|
|
1353
|
+
return str(value) if value else "-"
|
|
986
1354
|
|
|
987
1355
|
# Build title with filter info
|
|
988
|
-
title =
|
|
1356
|
+
title = "SELECT RULE TO VIEW"
|
|
989
1357
|
if filter_category or filter_status is not None:
|
|
990
1358
|
filter_info = []
|
|
991
1359
|
if filter_category:
|
|
992
|
-
cat_name = {
|
|
993
|
-
|
|
1360
|
+
cat_name = {
|
|
1361
|
+
"ctf": "CTF",
|
|
1362
|
+
"enterprise": "Enterprise",
|
|
1363
|
+
"general": "General",
|
|
1364
|
+
}.get(filter_category, "")
|
|
1365
|
+
filter_info.append(
|
|
1366
|
+
f"{CATEGORY_ICONS.get(filter_category, '')} {cat_name}"
|
|
1367
|
+
)
|
|
994
1368
|
if filter_status is True:
|
|
995
1369
|
filter_info.append("Enabled")
|
|
996
1370
|
elif filter_status is False:
|
|
@@ -1001,9 +1375,9 @@ def _view_rule_details(chaining: ToolChaining):
|
|
|
1001
1375
|
items=rule_items,
|
|
1002
1376
|
columns=columns,
|
|
1003
1377
|
selected_ids=selected_rule_ids,
|
|
1004
|
-
get_id=lambda r: r.get(
|
|
1378
|
+
get_id=lambda r: r.get("idx"),
|
|
1005
1379
|
title=title,
|
|
1006
|
-
format_cell=format_rule_cell
|
|
1380
|
+
format_cell=format_rule_cell,
|
|
1007
1381
|
)
|
|
1008
1382
|
|
|
1009
1383
|
if not selected_rule_ids:
|
|
@@ -1012,13 +1386,17 @@ def _view_rule_details(chaining: ToolChaining):
|
|
|
1012
1386
|
# View the first selected rule
|
|
1013
1387
|
rule_idx = next(iter(selected_rule_ids))
|
|
1014
1388
|
if 0 <= rule_idx < len(chaining.rules):
|
|
1015
|
-
_show_single_rule_details(
|
|
1389
|
+
_show_single_rule_details(
|
|
1390
|
+
chaining.rules[rule_idx], width, chaining
|
|
1391
|
+
)
|
|
1016
1392
|
selected_rule_ids.clear()
|
|
1017
1393
|
except (KeyboardInterrupt, EOFError):
|
|
1018
1394
|
return
|
|
1019
1395
|
|
|
1020
1396
|
|
|
1021
|
-
def _show_single_rule_details(
|
|
1397
|
+
def _show_single_rule_details(
|
|
1398
|
+
rule: ChainRule, width: int, chaining: ToolChaining = None
|
|
1399
|
+
):
|
|
1022
1400
|
"""Display detailed view for a single rule with toggle for raw definition."""
|
|
1023
1401
|
show_raw = False
|
|
1024
1402
|
|
|
@@ -1027,34 +1405,50 @@ def _show_single_rule_details(rule: ChainRule, width: int, chaining: ToolChainin
|
|
|
1027
1405
|
|
|
1028
1406
|
# Header
|
|
1029
1407
|
click.echo("\n" + "ā" * width)
|
|
1030
|
-
click.echo(
|
|
1408
|
+
click.echo(
|
|
1409
|
+
click.style(
|
|
1410
|
+
f" {rule.trigger_tool.upper()} ā {rule.target_tool.upper()}",
|
|
1411
|
+
bold=True,
|
|
1412
|
+
fg="cyan",
|
|
1413
|
+
)
|
|
1414
|
+
)
|
|
1031
1415
|
click.echo("ā" * width)
|
|
1032
1416
|
click.echo()
|
|
1033
1417
|
|
|
1034
1418
|
# Status
|
|
1035
1419
|
if rule.enabled:
|
|
1036
|
-
status_display = click.style("ā ENABLED", fg=
|
|
1420
|
+
status_display = click.style("ā ENABLED", fg="green", bold=True)
|
|
1037
1421
|
else:
|
|
1038
|
-
status_display = click.style("ā DISABLED", fg=
|
|
1422
|
+
status_display = click.style("ā DISABLED", fg="red", bold=True)
|
|
1039
1423
|
|
|
1040
1424
|
click.echo(f"Status: {status_display}")
|
|
1041
1425
|
|
|
1042
1426
|
# Priority
|
|
1043
|
-
priority_color =
|
|
1044
|
-
|
|
1045
|
-
|
|
1427
|
+
priority_color = (
|
|
1428
|
+
"green"
|
|
1429
|
+
if rule.priority >= 8
|
|
1430
|
+
else "yellow" if rule.priority >= 5 else "white"
|
|
1431
|
+
)
|
|
1432
|
+
click.echo(
|
|
1433
|
+
f"Priority: {click.style(str(rule.priority), fg=priority_color, bold=True)}/10"
|
|
1434
|
+
)
|
|
1435
|
+
|
|
1046
1436
|
# Trigger count
|
|
1047
|
-
fired_count = rule.trigger_count if hasattr(rule,
|
|
1437
|
+
fired_count = rule.trigger_count if hasattr(rule, "trigger_count") else 0
|
|
1048
1438
|
if fired_count > 0:
|
|
1049
|
-
click.echo(
|
|
1439
|
+
click.echo(
|
|
1440
|
+
f"Triggered: {click.style(str(fired_count), fg='green', bold=True)} times"
|
|
1441
|
+
)
|
|
1050
1442
|
else:
|
|
1051
1443
|
click.echo(f"Triggered: {click.style('Never', fg='bright_black')}")
|
|
1052
|
-
|
|
1444
|
+
|
|
1053
1445
|
click.echo()
|
|
1054
1446
|
|
|
1055
1447
|
# Rule Logic (human-readable)
|
|
1056
1448
|
click.echo(click.style("Rule Logic:", bold=True))
|
|
1057
|
-
click.echo(
|
|
1449
|
+
click.echo(
|
|
1450
|
+
f" IF {rule.trigger_tool} finds {rule.trigger_condition} ā THEN run {rule.target_tool}"
|
|
1451
|
+
)
|
|
1058
1452
|
click.echo()
|
|
1059
1453
|
|
|
1060
1454
|
# Description
|
|
@@ -1066,33 +1460,44 @@ def _show_single_rule_details(rule: ChainRule, width: int, chaining: ToolChainin
|
|
|
1066
1460
|
# Command Template
|
|
1067
1461
|
click.echo(click.style("Command Template:", bold=True))
|
|
1068
1462
|
if rule.args_template:
|
|
1069
|
-
click.echo(
|
|
1463
|
+
click.echo(
|
|
1464
|
+
f" {rule.target_tool} "
|
|
1465
|
+
+ " ".join(str(arg) for arg in rule.args_template)
|
|
1466
|
+
)
|
|
1070
1467
|
else:
|
|
1071
|
-
click.echo(
|
|
1468
|
+
click.echo(
|
|
1469
|
+
click.style(" (uses default tool arguments)", fg="bright_black")
|
|
1470
|
+
)
|
|
1072
1471
|
click.echo()
|
|
1073
1472
|
|
|
1074
1473
|
# Safety warnings for hydra
|
|
1075
|
-
if rule.target_tool ==
|
|
1076
|
-
click.echo(click.style("ā ļø SAFETY INFORMATION:", fg=
|
|
1077
|
-
click.echo(click.style(" ⢠This is a BRUTE-FORCE rule", fg=
|
|
1078
|
-
click.echo(click.style(" ⢠May trigger account lockouts", fg=
|
|
1079
|
-
click.echo(click.style(" ⢠May trigger security alerts", fg=
|
|
1080
|
-
click.echo(click.style(" ⢠Enabled by default: NO (safety)", fg=
|
|
1474
|
+
if rule.target_tool == "hydra":
|
|
1475
|
+
click.echo(click.style("ā ļø SAFETY INFORMATION:", fg="red", bold=True))
|
|
1476
|
+
click.echo(click.style(" ⢠This is a BRUTE-FORCE rule", fg="red"))
|
|
1477
|
+
click.echo(click.style(" ⢠May trigger account lockouts", fg="red"))
|
|
1478
|
+
click.echo(click.style(" ⢠May trigger security alerts", fg="red"))
|
|
1479
|
+
click.echo(click.style(" ⢠Enabled by default: NO (safety)", fg="green"))
|
|
1081
1480
|
click.echo()
|
|
1082
1481
|
|
|
1083
1482
|
# Raw rule definition (toggleable)
|
|
1084
1483
|
if show_raw:
|
|
1085
1484
|
click.echo("āāā Raw Rule Definition " + "ā" * (width - 24))
|
|
1086
|
-
click.echo(click.style("ChainRule(", fg=
|
|
1087
|
-
click.echo(
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
click.echo(
|
|
1485
|
+
click.echo(click.style("ChainRule(", fg="cyan"))
|
|
1486
|
+
click.echo(
|
|
1487
|
+
click.style(f" trigger_tool='{rule.trigger_tool}',", fg="cyan")
|
|
1488
|
+
)
|
|
1489
|
+
click.echo(
|
|
1490
|
+
click.style(
|
|
1491
|
+
f" trigger_condition='{rule.trigger_condition}',", fg="cyan"
|
|
1492
|
+
)
|
|
1493
|
+
)
|
|
1494
|
+
click.echo(click.style(f" target_tool='{rule.target_tool}',", fg="cyan"))
|
|
1495
|
+
click.echo(click.style(f" priority={rule.priority},", fg="cyan"))
|
|
1091
1496
|
if rule.args_template:
|
|
1092
1497
|
args_str = str(rule.args_template)
|
|
1093
|
-
click.echo(click.style(f" args_template={args_str},", fg=
|
|
1094
|
-
click.echo(click.style(f" enabled={rule.enabled}", fg=
|
|
1095
|
-
click.echo(click.style(")", fg=
|
|
1498
|
+
click.echo(click.style(f" args_template={args_str},", fg="cyan"))
|
|
1499
|
+
click.echo(click.style(f" enabled={rule.enabled}", fg="cyan"))
|
|
1500
|
+
click.echo(click.style(")", fg="cyan"))
|
|
1096
1501
|
click.echo("ā" * width)
|
|
1097
1502
|
click.echo()
|
|
1098
1503
|
|
|
@@ -1102,13 +1507,17 @@ def _show_single_rule_details(rule: ChainRule, width: int, chaining: ToolChainin
|
|
|
1102
1507
|
click.echo()
|
|
1103
1508
|
|
|
1104
1509
|
try:
|
|
1105
|
-
choice =
|
|
1510
|
+
choice = (
|
|
1511
|
+
click.prompt("Select option", default="0", show_default=False)
|
|
1512
|
+
.strip()
|
|
1513
|
+
.lower()
|
|
1514
|
+
)
|
|
1106
1515
|
|
|
1107
|
-
if choice ==
|
|
1516
|
+
if choice == "q":
|
|
1108
1517
|
return
|
|
1109
|
-
elif choice ==
|
|
1518
|
+
elif choice == "r":
|
|
1110
1519
|
show_raw = not show_raw
|
|
1111
|
-
elif choice ==
|
|
1520
|
+
elif choice == "e":
|
|
1112
1521
|
_edit_rule(rule, chaining)
|
|
1113
1522
|
except (KeyboardInterrupt, EOFError):
|
|
1114
1523
|
return
|
|
@@ -1123,88 +1532,147 @@ def _edit_rule(rule: ChainRule, chaining: ToolChaining):
|
|
|
1123
1532
|
|
|
1124
1533
|
# Header
|
|
1125
1534
|
click.echo("\n" + "ā" * width)
|
|
1126
|
-
click.echo(
|
|
1535
|
+
click.echo(
|
|
1536
|
+
click.style(
|
|
1537
|
+
f" EDIT RULE: {rule.trigger_tool.upper()} ā {rule.target_tool.upper()}",
|
|
1538
|
+
bold=True,
|
|
1539
|
+
fg="yellow",
|
|
1540
|
+
)
|
|
1541
|
+
)
|
|
1127
1542
|
click.echo("ā" * width)
|
|
1128
1543
|
click.echo()
|
|
1129
1544
|
|
|
1130
1545
|
# Show raw rule definition with editable fields highlighted
|
|
1131
1546
|
click.echo("āāā Rule Definition " + "ā" * (width - 20))
|
|
1132
|
-
click.echo(click.style("ChainRule(", fg=
|
|
1133
|
-
click.echo(
|
|
1134
|
-
|
|
1135
|
-
|
|
1547
|
+
click.echo(click.style("ChainRule(", fg="cyan"))
|
|
1548
|
+
click.echo(
|
|
1549
|
+
click.style(f" trigger_tool='{rule.trigger_tool}',", fg="bright_black")
|
|
1550
|
+
)
|
|
1551
|
+
click.echo(
|
|
1552
|
+
click.style(
|
|
1553
|
+
f" trigger_condition='{rule.trigger_condition}',", fg="bright_black"
|
|
1554
|
+
)
|
|
1555
|
+
)
|
|
1556
|
+
click.echo(
|
|
1557
|
+
click.style(f" target_tool='{rule.target_tool}',", fg="bright_black")
|
|
1558
|
+
)
|
|
1136
1559
|
|
|
1137
1560
|
# Editable: priority (highlighted)
|
|
1138
|
-
click.echo(
|
|
1561
|
+
click.echo(
|
|
1562
|
+
click.style(" priority=", fg="cyan")
|
|
1563
|
+
+ click.style(f"{rule.priority}", fg="yellow", bold=True)
|
|
1564
|
+
+ click.style(",", fg="cyan")
|
|
1565
|
+
+ click.style(" ā [p] edit", fg="green")
|
|
1566
|
+
)
|
|
1139
1567
|
|
|
1140
1568
|
# Editable: args_template (highlighted)
|
|
1141
1569
|
if rule.args_template:
|
|
1142
1570
|
args_str = str(rule.args_template)
|
|
1143
|
-
click.echo(
|
|
1571
|
+
click.echo(
|
|
1572
|
+
click.style(" args_template=", fg="cyan")
|
|
1573
|
+
+ click.style(f"{args_str}", fg="yellow", bold=True)
|
|
1574
|
+
+ click.style(",", fg="cyan")
|
|
1575
|
+
+ click.style(" ā [a] edit", fg="green")
|
|
1576
|
+
)
|
|
1144
1577
|
else:
|
|
1145
|
-
click.echo(
|
|
1578
|
+
click.echo(
|
|
1579
|
+
click.style(" args_template=", fg="cyan")
|
|
1580
|
+
+ click.style("[]", fg="yellow", bold=True)
|
|
1581
|
+
+ click.style(",", fg="cyan")
|
|
1582
|
+
+ click.style(" ā [a] edit", fg="green")
|
|
1583
|
+
)
|
|
1146
1584
|
|
|
1147
|
-
click.echo(click.style(f" enabled={rule.enabled},", fg=
|
|
1148
|
-
click.echo(
|
|
1149
|
-
|
|
1150
|
-
|
|
1585
|
+
click.echo(click.style(f" enabled={rule.enabled},", fg="bright_black"))
|
|
1586
|
+
click.echo(
|
|
1587
|
+
click.style(f" description='{rule.description}',", fg="bright_black")
|
|
1588
|
+
)
|
|
1589
|
+
click.echo(click.style(f" category='{rule.category}'", fg="bright_black"))
|
|
1590
|
+
click.echo(click.style(")", fg="cyan"))
|
|
1151
1591
|
click.echo("ā" * width)
|
|
1152
1592
|
click.echo()
|
|
1153
1593
|
|
|
1154
1594
|
# Menu
|
|
1155
|
-
click.echo(
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1595
|
+
click.echo(
|
|
1596
|
+
click.style(" [p]", fg="green", bold=True)
|
|
1597
|
+
+ " Edit priority "
|
|
1598
|
+
+ click.style("[a]", fg="green", bold=True)
|
|
1599
|
+
+ " Edit args "
|
|
1600
|
+
+ click.style("[c]", fg="yellow", bold=True)
|
|
1601
|
+
+ " Clear args "
|
|
1602
|
+
+ click.style("[s]", fg="cyan", bold=True)
|
|
1603
|
+
+ " Save "
|
|
1604
|
+
+ click.style("[q]", fg="red", bold=True)
|
|
1605
|
+
+ " Cancel"
|
|
1606
|
+
)
|
|
1160
1607
|
click.echo()
|
|
1161
1608
|
|
|
1162
1609
|
try:
|
|
1163
|
-
choice =
|
|
1610
|
+
choice = (
|
|
1611
|
+
click.prompt("Select option", default="s", show_default=False)
|
|
1612
|
+
.strip()
|
|
1613
|
+
.lower()
|
|
1614
|
+
)
|
|
1164
1615
|
|
|
1165
|
-
if choice ==
|
|
1616
|
+
if choice == "q":
|
|
1166
1617
|
return
|
|
1167
|
-
elif choice ==
|
|
1618
|
+
elif choice == "s":
|
|
1168
1619
|
# Save changes
|
|
1169
1620
|
chaining.save_rules()
|
|
1170
|
-
click.echo(click.style("\nā Rule saved!", fg=
|
|
1621
|
+
click.echo(click.style("\nā Rule saved!", fg="green"))
|
|
1171
1622
|
click.pause()
|
|
1172
1623
|
return
|
|
1173
|
-
elif choice ==
|
|
1624
|
+
elif choice == "p":
|
|
1174
1625
|
# Edit priority
|
|
1175
1626
|
click.echo()
|
|
1176
1627
|
try:
|
|
1177
|
-
new_priority = click.prompt(
|
|
1628
|
+
new_priority = click.prompt(
|
|
1629
|
+
" New priority (1-10)", type=int, default=rule.priority
|
|
1630
|
+
)
|
|
1178
1631
|
if 1 <= new_priority <= 10:
|
|
1179
1632
|
rule.priority = new_priority
|
|
1180
|
-
click.echo(
|
|
1633
|
+
click.echo(
|
|
1634
|
+
click.style(
|
|
1635
|
+
f" ā Priority set to {new_priority}", fg="green"
|
|
1636
|
+
)
|
|
1637
|
+
)
|
|
1181
1638
|
else:
|
|
1182
|
-
click.echo(
|
|
1639
|
+
click.echo(
|
|
1640
|
+
click.style(
|
|
1641
|
+
" ā Priority must be between 1 and 10", fg="red"
|
|
1642
|
+
)
|
|
1643
|
+
)
|
|
1183
1644
|
except (ValueError, click.Abort):
|
|
1184
1645
|
pass
|
|
1185
|
-
elif choice ==
|
|
1646
|
+
elif choice == "a":
|
|
1186
1647
|
# Edit args template
|
|
1187
1648
|
click.echo()
|
|
1188
|
-
current_args =
|
|
1649
|
+
current_args = (
|
|
1650
|
+
" ".join(rule.args_template) if rule.args_template else ""
|
|
1651
|
+
)
|
|
1189
1652
|
try:
|
|
1190
|
-
new_args = click.prompt(
|
|
1653
|
+
new_args = click.prompt(
|
|
1654
|
+
" Args (space-separated)", default=current_args
|
|
1655
|
+
).strip()
|
|
1191
1656
|
if new_args:
|
|
1192
1657
|
# Parse args - handle quoted strings
|
|
1193
1658
|
import shlex
|
|
1659
|
+
|
|
1194
1660
|
try:
|
|
1195
1661
|
rule.args_template = shlex.split(new_args)
|
|
1196
|
-
click.echo(click.style(f" ā Args updated", fg=
|
|
1662
|
+
click.echo(click.style(f" ā Args updated", fg="green"))
|
|
1197
1663
|
except ValueError as e:
|
|
1198
|
-
click.echo(
|
|
1664
|
+
click.echo(
|
|
1665
|
+
click.style(f" ā Invalid args format: {e}", fg="red")
|
|
1666
|
+
)
|
|
1199
1667
|
else:
|
|
1200
1668
|
rule.args_template = []
|
|
1201
|
-
click.echo(click.style(" ā Args cleared", fg=
|
|
1669
|
+
click.echo(click.style(" ā Args cleared", fg="green"))
|
|
1202
1670
|
except click.Abort:
|
|
1203
1671
|
pass
|
|
1204
|
-
elif choice ==
|
|
1672
|
+
elif choice == "c":
|
|
1205
1673
|
# Clear args
|
|
1206
1674
|
rule.args_template = []
|
|
1207
|
-
click.echo(click.style("\nā Args cleared", fg=
|
|
1675
|
+
click.echo(click.style("\nā Args cleared", fg="green"))
|
|
1208
1676
|
except (KeyboardInterrupt, EOFError):
|
|
1209
1677
|
return
|
|
1210
1678
|
|
|
@@ -1218,9 +1686,13 @@ def _filter_by_tool(chaining: ToolChaining):
|
|
|
1218
1686
|
# Get unique trigger tools with stats
|
|
1219
1687
|
tools = sorted(set(r.trigger_tool for r in chaining.rules))
|
|
1220
1688
|
|
|
1221
|
-
click.echo("\n" + click.style("EXPAND TOOL GROUP", bold=True, fg=
|
|
1689
|
+
click.echo("\n" + click.style("EXPAND TOOL GROUP", bold=True, fg="cyan"))
|
|
1222
1690
|
click.echo("ā" * width)
|
|
1223
|
-
click.echo(
|
|
1691
|
+
click.echo(
|
|
1692
|
+
click.style(
|
|
1693
|
+
" Select a tool to see all its chain rules in detail", fg="bright_black"
|
|
1694
|
+
)
|
|
1695
|
+
)
|
|
1224
1696
|
click.echo()
|
|
1225
1697
|
|
|
1226
1698
|
for idx, tool in enumerate(tools, 1):
|
|
@@ -1230,19 +1702,21 @@ def _filter_by_tool(chaining: ToolChaining):
|
|
|
1230
1702
|
|
|
1231
1703
|
# Status icon
|
|
1232
1704
|
if enabled_count == rule_count:
|
|
1233
|
-
icon = click.style("ā", fg=
|
|
1705
|
+
icon = click.style("ā", fg="green", bold=True)
|
|
1234
1706
|
elif enabled_count == 0:
|
|
1235
|
-
icon = click.style("ā", fg=
|
|
1707
|
+
icon = click.style("ā", fg="red")
|
|
1236
1708
|
else:
|
|
1237
|
-
icon = click.style("ā", fg=
|
|
1709
|
+
icon = click.style("ā", fg="yellow")
|
|
1238
1710
|
|
|
1239
1711
|
# Highlight brute-force
|
|
1240
|
-
has_hydra = any(r.target_tool ==
|
|
1241
|
-
warning = click.style(" ā ļø ", fg=
|
|
1712
|
+
has_hydra = any(r.target_tool == "hydra" for r in rules_for_tool)
|
|
1713
|
+
warning = click.style(" ā ļø ", fg="red") if has_hydra else " "
|
|
1242
1714
|
|
|
1243
1715
|
tool_padded = f"{tool.upper():<20}"
|
|
1244
|
-
click.echo(
|
|
1245
|
-
|
|
1716
|
+
click.echo(
|
|
1717
|
+
f"{warning}[{idx:2}] {icon} {click.style(tool_padded, fg='cyan')} "
|
|
1718
|
+
f"[{click.style(str(enabled_count), fg='green')}/{rule_count}]"
|
|
1719
|
+
)
|
|
1246
1720
|
|
|
1247
1721
|
click.echo()
|
|
1248
1722
|
click.echo(" [q] ā Back")
|
|
@@ -1261,50 +1735,81 @@ def _filter_by_tool(chaining: ToolChaining):
|
|
|
1261
1735
|
|
|
1262
1736
|
# Header
|
|
1263
1737
|
click.echo("\nā" + "ā" * (width - 2) + "ā")
|
|
1264
|
-
click.echo(
|
|
1738
|
+
click.echo(
|
|
1739
|
+
"ā"
|
|
1740
|
+
+ click.style(
|
|
1741
|
+
f" {selected_tool.upper()} CHAIN RULES ".center(width - 2),
|
|
1742
|
+
bold=True,
|
|
1743
|
+
fg="cyan",
|
|
1744
|
+
)
|
|
1745
|
+
+ "ā"
|
|
1746
|
+
)
|
|
1265
1747
|
click.echo("ā" + "ā" * (width - 2) + "ā")
|
|
1266
1748
|
click.echo()
|
|
1267
1749
|
|
|
1268
1750
|
# Summary
|
|
1269
1751
|
enabled_count = sum(1 for r in filtered_rules if r.enabled)
|
|
1270
|
-
click.echo(
|
|
1271
|
-
|
|
1272
|
-
|
|
1752
|
+
click.echo(
|
|
1753
|
+
f"Total Rules: {len(filtered_rules)} | "
|
|
1754
|
+
f"{click.style(str(enabled_count), fg='green')} enabled, "
|
|
1755
|
+
f"{click.style(str(len(filtered_rules) - enabled_count), fg='yellow')} disabled"
|
|
1756
|
+
)
|
|
1273
1757
|
click.echo()
|
|
1274
1758
|
|
|
1275
1759
|
# Display detailed rules
|
|
1276
|
-
for idx, rule in enumerate(
|
|
1760
|
+
for idx, rule in enumerate(
|
|
1761
|
+
sorted(filtered_rules, key=lambda r: (-r.priority, r.target_tool)), 1
|
|
1762
|
+
):
|
|
1277
1763
|
# Status
|
|
1278
|
-
status_icon =
|
|
1764
|
+
status_icon = (
|
|
1765
|
+
click.style("ā", fg="green", bold=True)
|
|
1766
|
+
if rule.enabled
|
|
1767
|
+
else click.style("ā", fg="red")
|
|
1768
|
+
)
|
|
1279
1769
|
|
|
1280
1770
|
# Priority color
|
|
1281
1771
|
if rule.priority >= 8:
|
|
1282
|
-
priority_color =
|
|
1772
|
+
priority_color = "green"
|
|
1283
1773
|
elif rule.priority >= 5:
|
|
1284
|
-
priority_color =
|
|
1774
|
+
priority_color = "yellow"
|
|
1285
1775
|
else:
|
|
1286
|
-
priority_color =
|
|
1776
|
+
priority_color = "white"
|
|
1287
1777
|
|
|
1288
1778
|
# Target tool with warning
|
|
1289
|
-
target = click.style(rule.target_tool, fg=
|
|
1290
|
-
warning =
|
|
1779
|
+
target = click.style(rule.target_tool, fg="magenta", bold=True)
|
|
1780
|
+
warning = (
|
|
1781
|
+
click.style(" ā ļø BRUTE-FORCE", fg="red")
|
|
1782
|
+
if rule.target_tool == "hydra"
|
|
1783
|
+
else ""
|
|
1784
|
+
)
|
|
1291
1785
|
|
|
1292
1786
|
# Rule header
|
|
1293
|
-
click.echo(
|
|
1294
|
-
|
|
1787
|
+
click.echo(
|
|
1788
|
+
f" {status_icon} [{idx}] {selected_tool.upper()} ā {target} "
|
|
1789
|
+
f"Priority: {click.style(str(rule.priority), fg=priority_color)}{warning}"
|
|
1790
|
+
)
|
|
1295
1791
|
|
|
1296
1792
|
# Condition
|
|
1297
|
-
click.echo(
|
|
1793
|
+
click.echo(
|
|
1794
|
+
click.style(
|
|
1795
|
+
f" Trigger: {rule.trigger_condition}", fg="bright_black"
|
|
1796
|
+
)
|
|
1797
|
+
)
|
|
1298
1798
|
|
|
1299
1799
|
# Description
|
|
1300
1800
|
if rule.description:
|
|
1301
|
-
desc_color =
|
|
1801
|
+
desc_color = "red" if rule.target_tool == "hydra" else "bright_black"
|
|
1302
1802
|
click.echo(click.style(f" {rule.description}", fg=desc_color))
|
|
1303
1803
|
|
|
1304
1804
|
click.echo()
|
|
1305
1805
|
|
|
1306
1806
|
click.echo("ā" * width)
|
|
1307
|
-
click.echo(
|
|
1807
|
+
click.echo(
|
|
1808
|
+
click.style(
|
|
1809
|
+
"š” Use option [1] from main menu to toggle individual rules",
|
|
1810
|
+
fg="bright_black",
|
|
1811
|
+
)
|
|
1812
|
+
)
|
|
1308
1813
|
click.pause()
|
|
1309
1814
|
|
|
1310
1815
|
except (ValueError, KeyboardInterrupt, EOFError):
|
|
@@ -1315,7 +1820,7 @@ def _filter_by_status(chaining: ToolChaining):
|
|
|
1315
1820
|
"""Filter rules by enabled/disabled status."""
|
|
1316
1821
|
DesignSystem.clear_screen()
|
|
1317
1822
|
|
|
1318
|
-
click.echo("\n" + click.style("FILTER BY STATUS", bold=True, fg=
|
|
1823
|
+
click.echo("\n" + click.style("FILTER BY STATUS", bold=True, fg="cyan"))
|
|
1319
1824
|
click.echo("ā" * 70)
|
|
1320
1825
|
click.echo()
|
|
1321
1826
|
click.echo(" [1] Show ENABLED rules only")
|
|
@@ -1329,20 +1834,24 @@ def _filter_by_status(chaining: ToolChaining):
|
|
|
1329
1834
|
if choice == 0:
|
|
1330
1835
|
return
|
|
1331
1836
|
|
|
1332
|
-
show_enabled =
|
|
1837
|
+
show_enabled = choice == 1
|
|
1333
1838
|
filtered_rules = [r for r in chaining.rules if r.enabled == show_enabled]
|
|
1334
1839
|
|
|
1335
1840
|
status_text = "ENABLED" if show_enabled else "DISABLED"
|
|
1336
1841
|
|
|
1337
1842
|
DesignSystem.clear_screen()
|
|
1338
|
-
click.echo("\n" + click.style(f"{status_text} RULES", bold=True, fg=
|
|
1843
|
+
click.echo("\n" + click.style(f"{status_text} RULES", bold=True, fg="cyan"))
|
|
1339
1844
|
click.echo("ā" * 70)
|
|
1340
1845
|
click.echo(f"\nShowing {len(filtered_rules)} rules\n")
|
|
1341
1846
|
|
|
1342
1847
|
for rule in sorted(filtered_rules, key=lambda r: (-r.priority, r.trigger_tool)):
|
|
1343
|
-
warning =
|
|
1848
|
+
warning = (
|
|
1849
|
+
click.style(" ā ļø BRUTE-FORCE", fg="red")
|
|
1850
|
+
if rule.target_tool == "hydra"
|
|
1851
|
+
else ""
|
|
1852
|
+
)
|
|
1344
1853
|
click.echo(f" {rule.trigger_tool}ā{rule.target_tool}{warning}")
|
|
1345
|
-
click.echo(click.style(f" {rule.description}", fg=
|
|
1854
|
+
click.echo(click.style(f" {rule.description}", fg="bright_black"))
|
|
1346
1855
|
click.echo()
|
|
1347
1856
|
|
|
1348
1857
|
click.pause()
|
|
@@ -1354,22 +1863,37 @@ def _filter_by_status(chaining: ToolChaining):
|
|
|
1354
1863
|
def _enable_all_rules(chaining: ToolChaining):
|
|
1355
1864
|
"""Enable all chain rules with safety confirmation."""
|
|
1356
1865
|
click.echo()
|
|
1357
|
-
click.echo(click.style("ā ļø WARNING: Enable ALL Chain Rules", fg=
|
|
1358
|
-
click.echo(click.style("ā" * 70, fg=
|
|
1866
|
+
click.echo(click.style("ā ļø WARNING: Enable ALL Chain Rules", fg="red", bold=True))
|
|
1867
|
+
click.echo(click.style("ā" * 70, fg="red"))
|
|
1359
1868
|
click.echo()
|
|
1360
1869
|
|
|
1361
|
-
brute_force_count = sum(1 for r in chaining.rules if r.target_tool ==
|
|
1870
|
+
brute_force_count = sum(1 for r in chaining.rules if r.target_tool == "hydra")
|
|
1362
1871
|
|
|
1363
1872
|
if brute_force_count > 0:
|
|
1364
|
-
click.echo(
|
|
1365
|
-
|
|
1873
|
+
click.echo(
|
|
1874
|
+
click.style(
|
|
1875
|
+
f" This will enable {brute_force_count} BRUTE-FORCE rules!",
|
|
1876
|
+
fg="red",
|
|
1877
|
+
bold=True,
|
|
1878
|
+
)
|
|
1879
|
+
)
|
|
1880
|
+
click.echo(
|
|
1881
|
+
click.style(
|
|
1882
|
+
" These rules may cause account lockouts and security alerts.",
|
|
1883
|
+
fg="red",
|
|
1884
|
+
)
|
|
1885
|
+
)
|
|
1366
1886
|
click.echo()
|
|
1367
1887
|
|
|
1368
|
-
click.echo(
|
|
1888
|
+
click.echo(
|
|
1889
|
+
f" Total rules to enable: {sum(1 for r in chaining.rules if not r.enabled)}"
|
|
1890
|
+
)
|
|
1369
1891
|
click.echo()
|
|
1370
1892
|
|
|
1371
|
-
if not click.confirm(
|
|
1372
|
-
click.
|
|
1893
|
+
if not click.confirm(
|
|
1894
|
+
click.style("Are you ABSOLUTELY SURE?", fg="yellow", bold=True), default=False
|
|
1895
|
+
):
|
|
1896
|
+
click.echo(click.style("\nā Operation cancelled", fg="green"))
|
|
1373
1897
|
click.pause()
|
|
1374
1898
|
return
|
|
1375
1899
|
|
|
@@ -1380,7 +1904,9 @@ def _enable_all_rules(chaining: ToolChaining):
|
|
|
1380
1904
|
chaining.save_rules()
|
|
1381
1905
|
|
|
1382
1906
|
click.echo()
|
|
1383
|
-
click.echo(
|
|
1907
|
+
click.echo(
|
|
1908
|
+
click.style(f"ā All {len(chaining.rules)} rules ENABLED", fg="green", bold=True)
|
|
1909
|
+
)
|
|
1384
1910
|
click.pause()
|
|
1385
1911
|
|
|
1386
1912
|
|
|
@@ -1397,7 +1923,11 @@ def _disable_all_rules(chaining: ToolChaining):
|
|
|
1397
1923
|
chaining.save_rules()
|
|
1398
1924
|
|
|
1399
1925
|
click.echo()
|
|
1400
|
-
click.echo(
|
|
1926
|
+
click.echo(
|
|
1927
|
+
click.style(
|
|
1928
|
+
f"ā All {len(chaining.rules)} rules DISABLED", fg="yellow", bold=True
|
|
1929
|
+
)
|
|
1930
|
+
)
|
|
1401
1931
|
click.pause()
|
|
1402
1932
|
|
|
1403
1933
|
|
|
@@ -1412,7 +1942,7 @@ def _reset_to_defaults(chaining: ToolChaining):
|
|
|
1412
1942
|
return
|
|
1413
1943
|
|
|
1414
1944
|
# Delete the saved state file
|
|
1415
|
-
rules_file = Path.home() /
|
|
1945
|
+
rules_file = Path.home() / ".souleyez" / "chain_rules_state.json"
|
|
1416
1946
|
if rules_file.exists():
|
|
1417
1947
|
os.remove(rules_file)
|
|
1418
1948
|
|
|
@@ -1421,6 +1951,8 @@ def _reset_to_defaults(chaining: ToolChaining):
|
|
|
1421
1951
|
chaining._init_default_rules()
|
|
1422
1952
|
|
|
1423
1953
|
click.echo()
|
|
1424
|
-
click.echo(click.style("ā Rules reset to defaults", fg=
|
|
1425
|
-
click.echo(
|
|
1954
|
+
click.echo(click.style("ā Rules reset to defaults", fg="green", bold=True))
|
|
1955
|
+
click.echo(
|
|
1956
|
+
click.style(" Note: Brute-force rules are DISABLED by default", fg="yellow")
|
|
1957
|
+
)
|
|
1426
1958
|
click.pause()
|