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
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
"""Evidence linking interface for deliverables."""
|
|
2
|
+
|
|
2
3
|
import click
|
|
3
4
|
from souleyez.storage.deliverable_evidence import EvidenceManager
|
|
4
5
|
from souleyez.storage.deliverables import DeliverableManager
|
|
@@ -8,7 +9,7 @@ from souleyez.ui.design_system import DesignSystem
|
|
|
8
9
|
def show_evidence_linking_view(deliverable_id: int):
|
|
9
10
|
"""
|
|
10
11
|
Show evidence linking interface for a deliverable.
|
|
11
|
-
|
|
12
|
+
|
|
12
13
|
Options:
|
|
13
14
|
- View linked evidence
|
|
14
15
|
- Add evidence (manual selection)
|
|
@@ -17,87 +18,141 @@ def show_evidence_linking_view(deliverable_id: int):
|
|
|
17
18
|
"""
|
|
18
19
|
em = EvidenceManager()
|
|
19
20
|
dm = DeliverableManager()
|
|
20
|
-
|
|
21
|
+
|
|
21
22
|
deliverable = dm.get_deliverable(deliverable_id)
|
|
22
23
|
if not deliverable:
|
|
23
|
-
click.echo(click.style(" Error: Deliverable not found", fg=
|
|
24
|
+
click.echo(click.style(" Error: Deliverable not found", fg="red"))
|
|
24
25
|
click.pause()
|
|
25
26
|
return
|
|
26
|
-
|
|
27
|
+
|
|
27
28
|
while True:
|
|
28
29
|
DesignSystem.clear_screen()
|
|
29
|
-
|
|
30
|
+
|
|
30
31
|
width = DesignSystem.get_terminal_width()
|
|
31
|
-
|
|
32
|
+
|
|
32
33
|
# Header
|
|
33
34
|
click.echo("\n┌" + "─" * (width - 2) + "┐")
|
|
34
|
-
click.echo(
|
|
35
|
+
click.echo(
|
|
36
|
+
"│"
|
|
37
|
+
+ click.style(
|
|
38
|
+
" 🔗 EVIDENCE LINKING ".center(width - 2), bold=True, fg="cyan"
|
|
39
|
+
)
|
|
40
|
+
+ "│"
|
|
41
|
+
)
|
|
35
42
|
click.echo("└" + "─" * (width - 2) + "┘")
|
|
36
43
|
click.echo()
|
|
37
|
-
|
|
44
|
+
|
|
38
45
|
# Deliverable info
|
|
39
|
-
click.echo(
|
|
40
|
-
|
|
46
|
+
click.echo(
|
|
47
|
+
f" Deliverable: {click.style(deliverable['title'], bold=True, fg='cyan')}"
|
|
48
|
+
)
|
|
49
|
+
if deliverable.get("description"):
|
|
41
50
|
click.echo(f" Description: {deliverable['description'][:80]}...")
|
|
42
51
|
click.echo()
|
|
43
|
-
|
|
52
|
+
|
|
44
53
|
# Get linked evidence
|
|
45
54
|
evidence = em.get_evidence(deliverable_id)
|
|
46
55
|
evidence_count = em.get_evidence_count(deliverable_id)
|
|
47
|
-
|
|
56
|
+
|
|
48
57
|
# Display linked evidence summary
|
|
49
|
-
click.echo(
|
|
58
|
+
click.echo(
|
|
59
|
+
click.style(
|
|
60
|
+
f" 📋 LINKED EVIDENCE ({evidence_count} items)", bold=True, fg="cyan"
|
|
61
|
+
)
|
|
62
|
+
)
|
|
50
63
|
click.echo(" " + "─" * (width - 4))
|
|
51
64
|
click.echo()
|
|
52
|
-
|
|
65
|
+
|
|
53
66
|
if evidence_count == 0:
|
|
54
|
-
click.echo(click.style(" No evidence linked yet", fg=
|
|
67
|
+
click.echo(click.style(" No evidence linked yet", fg="yellow"))
|
|
55
68
|
click.echo()
|
|
56
69
|
else:
|
|
57
70
|
# Show findings
|
|
58
|
-
if evidence[
|
|
59
|
-
click.echo(
|
|
60
|
-
|
|
71
|
+
if evidence["findings"]:
|
|
72
|
+
click.echo(
|
|
73
|
+
click.style(
|
|
74
|
+
f" 🔍 Findings ({len(evidence['findings'])})", bold=True
|
|
75
|
+
)
|
|
76
|
+
)
|
|
77
|
+
for f in evidence["findings"][:3]:
|
|
61
78
|
severity_color = {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
}.get(f.get(
|
|
67
|
-
click.echo(
|
|
68
|
-
|
|
69
|
-
|
|
79
|
+
"critical": "red",
|
|
80
|
+
"high": "yellow",
|
|
81
|
+
"medium": "white",
|
|
82
|
+
"low": "bright_black",
|
|
83
|
+
}.get(f.get("severity", "medium"), "white")
|
|
84
|
+
click.echo(
|
|
85
|
+
f" • [{click.style(f.get('severity', 'N/A').upper(), fg=severity_color)}] {f.get('title', 'Unknown')[:60]}"
|
|
86
|
+
)
|
|
87
|
+
if len(evidence["findings"]) > 3:
|
|
88
|
+
click.echo(
|
|
89
|
+
click.style(
|
|
90
|
+
f" ... and {len(evidence['findings']) - 3} more",
|
|
91
|
+
fg="bright_black",
|
|
92
|
+
)
|
|
93
|
+
)
|
|
70
94
|
click.echo()
|
|
71
|
-
|
|
95
|
+
|
|
72
96
|
# Show credentials
|
|
73
|
-
if evidence[
|
|
74
|
-
click.echo(
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
97
|
+
if evidence["credentials"]:
|
|
98
|
+
click.echo(
|
|
99
|
+
click.style(
|
|
100
|
+
f" 🔑 Credentials ({len(evidence['credentials'])})", bold=True
|
|
101
|
+
)
|
|
102
|
+
)
|
|
103
|
+
for c in evidence["credentials"][:3]:
|
|
104
|
+
click.echo(
|
|
105
|
+
f" • {c.get('username', 'N/A')}@{c.get('host', 'N/A')}"
|
|
106
|
+
)
|
|
107
|
+
if len(evidence["credentials"]) > 3:
|
|
108
|
+
click.echo(
|
|
109
|
+
click.style(
|
|
110
|
+
f" ... and {len(evidence['credentials']) - 3} more",
|
|
111
|
+
fg="bright_black",
|
|
112
|
+
)
|
|
113
|
+
)
|
|
79
114
|
click.echo()
|
|
80
|
-
|
|
115
|
+
|
|
81
116
|
# Show screenshots
|
|
82
|
-
if evidence[
|
|
83
|
-
click.echo(
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
117
|
+
if evidence["screenshots"]:
|
|
118
|
+
click.echo(
|
|
119
|
+
click.style(
|
|
120
|
+
f" 📸 Screenshots ({len(evidence['screenshots'])})", bold=True
|
|
121
|
+
)
|
|
122
|
+
)
|
|
123
|
+
for s in evidence["screenshots"][:3]:
|
|
124
|
+
click.echo(
|
|
125
|
+
f" • {s.get('description', s.get('filename', 'Unknown'))[:60]}"
|
|
126
|
+
)
|
|
127
|
+
if len(evidence["screenshots"]) > 3:
|
|
128
|
+
click.echo(
|
|
129
|
+
click.style(
|
|
130
|
+
f" ... and {len(evidence['screenshots']) - 3} more",
|
|
131
|
+
fg="bright_black",
|
|
132
|
+
)
|
|
133
|
+
)
|
|
88
134
|
click.echo()
|
|
89
|
-
|
|
135
|
+
|
|
90
136
|
# Show jobs
|
|
91
|
-
if evidence[
|
|
92
|
-
click.echo(
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
click.echo(
|
|
137
|
+
if evidence["jobs"]:
|
|
138
|
+
click.echo(
|
|
139
|
+
click.style(f" ⚡ Jobs ({len(evidence['jobs'])})", bold=True)
|
|
140
|
+
)
|
|
141
|
+
for j in evidence["jobs"][:3]:
|
|
142
|
+
click.echo(
|
|
143
|
+
f" • #{j.get('id')} - {j.get('tool', 'N/A')} @ {j.get('target', 'N/A')}"
|
|
144
|
+
)
|
|
145
|
+
if len(evidence["jobs"]) > 3:
|
|
146
|
+
click.echo(
|
|
147
|
+
click.style(
|
|
148
|
+
f" ... and {len(evidence['jobs']) - 3} more",
|
|
149
|
+
fg="bright_black",
|
|
150
|
+
)
|
|
151
|
+
)
|
|
97
152
|
click.echo()
|
|
98
|
-
|
|
153
|
+
|
|
99
154
|
# Menu
|
|
100
|
-
click.echo(click.style(" ⚙️ ACTIONS", bold=True, fg=
|
|
155
|
+
click.echo(click.style(" ⚙️ ACTIONS", bold=True, fg="cyan"))
|
|
101
156
|
click.echo(" " + "─" * (width - 4))
|
|
102
157
|
click.echo()
|
|
103
158
|
click.echo(" [A] Add Evidence (manual selection)")
|
|
@@ -107,18 +162,22 @@ def show_evidence_linking_view(deliverable_id: int):
|
|
|
107
162
|
click.echo()
|
|
108
163
|
click.echo(" [q] ← Back")
|
|
109
164
|
click.echo()
|
|
110
|
-
|
|
111
|
-
choice =
|
|
112
|
-
|
|
113
|
-
|
|
165
|
+
|
|
166
|
+
choice = (
|
|
167
|
+
click.prompt("Select option", type=str, default="q", show_default=False)
|
|
168
|
+
.strip()
|
|
169
|
+
.lower()
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
if choice == "q":
|
|
114
173
|
break
|
|
115
|
-
elif choice ==
|
|
116
|
-
_add_evidence_manual(deliverable_id, deliverable[
|
|
117
|
-
elif choice ==
|
|
118
|
-
_show_smart_suggestions(deliverable_id, deliverable[
|
|
119
|
-
elif choice ==
|
|
174
|
+
elif choice == "a":
|
|
175
|
+
_add_evidence_manual(deliverable_id, deliverable["engagement_id"])
|
|
176
|
+
elif choice == "s":
|
|
177
|
+
_show_smart_suggestions(deliverable_id, deliverable["engagement_id"])
|
|
178
|
+
elif choice == "v":
|
|
120
179
|
_view_evidence_detailed(deliverable_id)
|
|
121
|
-
elif choice ==
|
|
180
|
+
elif choice == "r":
|
|
122
181
|
_remove_evidence(deliverable_id)
|
|
123
182
|
|
|
124
183
|
|
|
@@ -126,14 +185,14 @@ def _add_evidence_manual(deliverable_id: int, engagement_id: int):
|
|
|
126
185
|
"""Manually select and link evidence."""
|
|
127
186
|
from souleyez.storage.findings import FindingsManager
|
|
128
187
|
from souleyez.storage.credentials import CredentialsManager
|
|
129
|
-
|
|
188
|
+
|
|
130
189
|
em = EvidenceManager()
|
|
131
190
|
fm = FindingsManager()
|
|
132
191
|
cm = CredentialsManager()
|
|
133
|
-
|
|
192
|
+
|
|
134
193
|
DesignSystem.clear_screen()
|
|
135
194
|
click.echo()
|
|
136
|
-
click.echo(click.style(" 📎 ADD EVIDENCE", bold=True, fg=
|
|
195
|
+
click.echo(click.style(" 📎 ADD EVIDENCE", bold=True, fg="cyan"))
|
|
137
196
|
click.echo()
|
|
138
197
|
click.echo(" Select evidence type:")
|
|
139
198
|
click.echo()
|
|
@@ -143,146 +202,172 @@ def _add_evidence_manual(deliverable_id: int, engagement_id: int):
|
|
|
143
202
|
click.echo(" [4] Job")
|
|
144
203
|
click.echo(" [q] Cancel")
|
|
145
204
|
click.echo()
|
|
146
|
-
|
|
147
|
-
etype_choice = click.prompt("Evidence type", type=str, default=
|
|
148
|
-
|
|
149
|
-
if etype_choice ==
|
|
205
|
+
|
|
206
|
+
etype_choice = click.prompt("Evidence type", type=str, default="q").strip()
|
|
207
|
+
|
|
208
|
+
if etype_choice == "q":
|
|
150
209
|
return
|
|
151
|
-
|
|
210
|
+
|
|
152
211
|
evidence_type_map = {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
212
|
+
"1": "finding",
|
|
213
|
+
"2": "credential",
|
|
214
|
+
"3": "screenshot",
|
|
215
|
+
"4": "job",
|
|
157
216
|
}
|
|
158
|
-
|
|
217
|
+
|
|
159
218
|
evidence_type = evidence_type_map.get(etype_choice)
|
|
160
219
|
if not evidence_type:
|
|
161
|
-
click.echo(click.style(" Invalid choice", fg=
|
|
220
|
+
click.echo(click.style(" Invalid choice", fg="yellow"))
|
|
162
221
|
click.pause()
|
|
163
222
|
return
|
|
164
|
-
|
|
223
|
+
|
|
165
224
|
# Fetch available evidence
|
|
166
|
-
if evidence_type ==
|
|
225
|
+
if evidence_type == "finding":
|
|
167
226
|
findings = fm.list_findings(engagement_id)
|
|
168
227
|
if not findings:
|
|
169
|
-
click.echo(click.style(" No findings available", fg=
|
|
228
|
+
click.echo(click.style(" No findings available", fg="yellow"))
|
|
170
229
|
click.pause()
|
|
171
230
|
return
|
|
172
|
-
|
|
231
|
+
|
|
173
232
|
click.echo()
|
|
174
233
|
click.echo(click.style(" Available Findings:", bold=True))
|
|
175
234
|
for idx, f in enumerate(findings[:20], 1):
|
|
176
235
|
severity_color = {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
}.get(f.get(
|
|
182
|
-
click.echo(
|
|
183
|
-
|
|
236
|
+
"critical": "red",
|
|
237
|
+
"high": "yellow",
|
|
238
|
+
"medium": "white",
|
|
239
|
+
"low": "bright_black",
|
|
240
|
+
}.get(f.get("severity", "medium"), "white")
|
|
241
|
+
click.echo(
|
|
242
|
+
f" [{idx}] [{click.style(f.get('severity', 'N/A').upper(), fg=severity_color)}] {f.get('title', 'Unknown')[:70]}"
|
|
243
|
+
)
|
|
244
|
+
|
|
184
245
|
click.echo()
|
|
185
246
|
choice = click.prompt("Select finding # (0 to cancel)", type=int, default=0)
|
|
186
247
|
if choice == 0 or choice > len(findings):
|
|
187
248
|
return
|
|
188
|
-
|
|
249
|
+
|
|
189
250
|
finding = findings[choice - 1]
|
|
190
|
-
notes = click.prompt("Optional notes", type=str, default=
|
|
191
|
-
|
|
192
|
-
em.link_evidence(deliverable_id,
|
|
193
|
-
click.echo(click.style(" ✅ Finding linked successfully", fg=
|
|
251
|
+
notes = click.prompt("Optional notes", type=str, default="")
|
|
252
|
+
|
|
253
|
+
em.link_evidence(deliverable_id, "finding", finding["id"], notes=notes or None)
|
|
254
|
+
click.echo(click.style(" ✅ Finding linked successfully", fg="green"))
|
|
194
255
|
click.pause()
|
|
195
|
-
|
|
196
|
-
elif evidence_type ==
|
|
256
|
+
|
|
257
|
+
elif evidence_type == "credential":
|
|
197
258
|
credentials = cm.list_credentials(engagement_id)
|
|
198
259
|
if not credentials:
|
|
199
|
-
click.echo(click.style(" No credentials available", fg=
|
|
260
|
+
click.echo(click.style(" No credentials available", fg="yellow"))
|
|
200
261
|
click.pause()
|
|
201
262
|
return
|
|
202
|
-
|
|
263
|
+
|
|
203
264
|
click.echo()
|
|
204
265
|
click.echo(click.style(" Available Credentials:", bold=True))
|
|
205
266
|
for idx, c in enumerate(credentials[:20], 1):
|
|
206
|
-
click.echo(
|
|
207
|
-
|
|
267
|
+
click.echo(
|
|
268
|
+
f" [{idx}] {c.get('username', 'N/A')}@{c.get('host', 'N/A')} ({c.get('credential_type', 'N/A')})"
|
|
269
|
+
)
|
|
270
|
+
|
|
208
271
|
click.echo()
|
|
209
272
|
choice = click.prompt("Select credential # (0 to cancel)", type=int, default=0)
|
|
210
273
|
if choice == 0 or choice > len(credentials):
|
|
211
274
|
return
|
|
212
|
-
|
|
275
|
+
|
|
213
276
|
credential = credentials[choice - 1]
|
|
214
|
-
notes = click.prompt("Optional notes", type=str, default=
|
|
215
|
-
|
|
216
|
-
em.link_evidence(
|
|
217
|
-
|
|
277
|
+
notes = click.prompt("Optional notes", type=str, default="")
|
|
278
|
+
|
|
279
|
+
em.link_evidence(
|
|
280
|
+
deliverable_id, "credential", credential["id"], notes=notes or None
|
|
281
|
+
)
|
|
282
|
+
click.echo(click.style(" ✅ Credential linked successfully", fg="green"))
|
|
218
283
|
click.pause()
|
|
219
|
-
|
|
284
|
+
|
|
220
285
|
else:
|
|
221
|
-
click.echo(click.style(" Feature coming soon", fg=
|
|
286
|
+
click.echo(click.style(" Feature coming soon", fg="yellow"))
|
|
222
287
|
click.pause()
|
|
223
288
|
|
|
224
289
|
|
|
225
290
|
def _show_smart_suggestions(deliverable_id: int, engagement_id: int):
|
|
226
291
|
"""Show AI-powered evidence suggestions."""
|
|
227
292
|
em = EvidenceManager()
|
|
228
|
-
|
|
293
|
+
|
|
229
294
|
DesignSystem.clear_screen()
|
|
230
295
|
click.echo()
|
|
231
|
-
click.echo(click.style(" 🤖 SMART EVIDENCE SUGGESTIONS", bold=True, fg=
|
|
296
|
+
click.echo(click.style(" 🤖 SMART EVIDENCE SUGGESTIONS", bold=True, fg="cyan"))
|
|
232
297
|
click.echo()
|
|
233
298
|
click.echo(" Analyzing deliverable and finding matches...")
|
|
234
299
|
click.echo()
|
|
235
|
-
|
|
300
|
+
|
|
236
301
|
suggestions = em.suggest_evidence(deliverable_id, engagement_id)
|
|
237
|
-
|
|
302
|
+
|
|
238
303
|
if not any(suggestions.values()):
|
|
239
|
-
click.echo(click.style(" No suggestions found", fg=
|
|
304
|
+
click.echo(click.style(" No suggestions found", fg="yellow"))
|
|
240
305
|
click.echo()
|
|
241
306
|
click.echo(" Try adding evidence manually or run more scans first.")
|
|
242
307
|
click.pause()
|
|
243
308
|
return
|
|
244
|
-
|
|
309
|
+
|
|
245
310
|
# Show findings suggestions
|
|
246
|
-
if suggestions[
|
|
247
|
-
click.echo(
|
|
311
|
+
if suggestions["findings"]:
|
|
312
|
+
click.echo(
|
|
313
|
+
click.style(
|
|
314
|
+
f" 🔍 SUGGESTED FINDINGS ({len(suggestions['findings'])} matches)",
|
|
315
|
+
bold=True,
|
|
316
|
+
)
|
|
317
|
+
)
|
|
248
318
|
click.echo()
|
|
249
|
-
for idx, f in enumerate(suggestions[
|
|
250
|
-
confidence = f.get(
|
|
251
|
-
keyword = f.get(
|
|
319
|
+
for idx, f in enumerate(suggestions["findings"][:10], 1):
|
|
320
|
+
confidence = f.get("_confidence", 0)
|
|
321
|
+
keyword = f.get("_match_keyword", "N/A")
|
|
252
322
|
severity_color = {
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
}.get(f.get(
|
|
258
|
-
|
|
259
|
-
confidence_color =
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
323
|
+
"critical": "red",
|
|
324
|
+
"high": "yellow",
|
|
325
|
+
"medium": "white",
|
|
326
|
+
"low": "bright_black",
|
|
327
|
+
}.get(f.get("severity", "medium"), "white")
|
|
328
|
+
|
|
329
|
+
confidence_color = (
|
|
330
|
+
"green"
|
|
331
|
+
if confidence >= 70
|
|
332
|
+
else ("yellow" if confidence >= 50 else "bright_black")
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
click.echo(
|
|
336
|
+
f" [{idx}] [{click.style(f'{confidence}%', fg=confidence_color)}] "
|
|
337
|
+
f"[{click.style(f.get('severity', 'N/A').upper(), fg=severity_color)}] "
|
|
338
|
+
f"{f.get('title', 'Unknown')[:60]}"
|
|
339
|
+
)
|
|
340
|
+
click.echo(click.style(f" Match: '{keyword}'", fg="bright_black"))
|
|
265
341
|
click.echo()
|
|
266
|
-
|
|
342
|
+
|
|
267
343
|
# Show credentials suggestions
|
|
268
|
-
if suggestions[
|
|
269
|
-
click.echo(
|
|
344
|
+
if suggestions["credentials"]:
|
|
345
|
+
click.echo(
|
|
346
|
+
click.style(
|
|
347
|
+
f" 🔑 SUGGESTED CREDENTIALS ({len(suggestions['credentials'])} matches)",
|
|
348
|
+
bold=True,
|
|
349
|
+
)
|
|
350
|
+
)
|
|
270
351
|
click.echo()
|
|
271
|
-
for idx, c in enumerate(suggestions[
|
|
352
|
+
for idx, c in enumerate(suggestions["credentials"][:5], 1):
|
|
272
353
|
click.echo(f" [{idx}] {c.get('username', 'N/A')}@{c.get('host', 'N/A')}")
|
|
273
354
|
click.echo()
|
|
274
|
-
|
|
355
|
+
|
|
275
356
|
# Offer to link
|
|
276
357
|
click.echo()
|
|
277
358
|
if click.confirm(" Link suggested findings to this deliverable?", default=True):
|
|
278
359
|
count = 0
|
|
279
|
-
for f in suggestions[
|
|
280
|
-
em.link_evidence(
|
|
281
|
-
|
|
360
|
+
for f in suggestions["findings"][:5]: # Link top 5
|
|
361
|
+
em.link_evidence(
|
|
362
|
+
deliverable_id,
|
|
363
|
+
"finding",
|
|
364
|
+
f["id"],
|
|
365
|
+
notes=f"Auto-linked (confidence: {f.get('_confidence', 0)}%)",
|
|
366
|
+
)
|
|
282
367
|
count += 1
|
|
283
|
-
|
|
284
|
-
click.echo(click.style(f" ✅ Linked {count} findings", fg=
|
|
285
|
-
|
|
368
|
+
|
|
369
|
+
click.echo(click.style(f" ✅ Linked {count} findings", fg="green"))
|
|
370
|
+
|
|
286
371
|
click.pause()
|
|
287
372
|
|
|
288
373
|
|
|
@@ -290,44 +375,50 @@ def _view_evidence_detailed(deliverable_id: int):
|
|
|
290
375
|
"""Show detailed view of all linked evidence."""
|
|
291
376
|
em = EvidenceManager()
|
|
292
377
|
evidence = em.get_evidence(deliverable_id)
|
|
293
|
-
|
|
378
|
+
|
|
294
379
|
DesignSystem.clear_screen()
|
|
295
380
|
click.echo()
|
|
296
|
-
click.echo(click.style(" 📋 EVIDENCE DETAILS", bold=True, fg=
|
|
381
|
+
click.echo(click.style(" 📋 EVIDENCE DETAILS", bold=True, fg="cyan"))
|
|
297
382
|
click.echo()
|
|
298
|
-
|
|
383
|
+
|
|
299
384
|
# Detailed findings
|
|
300
|
-
if evidence[
|
|
301
|
-
click.echo(
|
|
385
|
+
if evidence["findings"]:
|
|
386
|
+
click.echo(
|
|
387
|
+
click.style(f" 🔍 FINDINGS ({len(evidence['findings'])})", bold=True)
|
|
388
|
+
)
|
|
302
389
|
click.echo(" " + "─" * 80)
|
|
303
|
-
for f in evidence[
|
|
390
|
+
for f in evidence["findings"]:
|
|
304
391
|
severity_color = {
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
}.get(f.get(
|
|
310
|
-
|
|
311
|
-
click.echo(
|
|
392
|
+
"critical": "red",
|
|
393
|
+
"high": "yellow",
|
|
394
|
+
"medium": "white",
|
|
395
|
+
"low": "bright_black",
|
|
396
|
+
}.get(f.get("severity", "medium"), "white")
|
|
397
|
+
|
|
398
|
+
click.echo(
|
|
399
|
+
f" • [{click.style(f.get('severity', 'N/A').upper(), fg=severity_color)}] {f.get('title', 'Unknown')}"
|
|
400
|
+
)
|
|
312
401
|
click.echo(f" Host: {f.get('host', 'N/A')}")
|
|
313
|
-
if f.get(
|
|
402
|
+
if f.get("_link_notes"):
|
|
314
403
|
click.echo(f" Notes: {f['_link_notes']}")
|
|
315
404
|
click.echo(f" Linked: {f.get('_linked_at', 'N/A')}")
|
|
316
405
|
click.echo()
|
|
317
406
|
click.echo()
|
|
318
|
-
|
|
407
|
+
|
|
319
408
|
# Detailed credentials
|
|
320
|
-
if evidence[
|
|
321
|
-
click.echo(
|
|
409
|
+
if evidence["credentials"]:
|
|
410
|
+
click.echo(
|
|
411
|
+
click.style(f" 🔑 CREDENTIALS ({len(evidence['credentials'])})", bold=True)
|
|
412
|
+
)
|
|
322
413
|
click.echo(" " + "─" * 80)
|
|
323
|
-
for c in evidence[
|
|
414
|
+
for c in evidence["credentials"]:
|
|
324
415
|
click.echo(f" • {c.get('username', 'N/A')}@{c.get('host', 'N/A')}")
|
|
325
416
|
click.echo(f" Type: {c.get('credential_type', 'N/A')}")
|
|
326
|
-
if c.get(
|
|
417
|
+
if c.get("_link_notes"):
|
|
327
418
|
click.echo(f" Notes: {c['_link_notes']}")
|
|
328
419
|
click.echo()
|
|
329
420
|
click.echo()
|
|
330
|
-
|
|
421
|
+
|
|
331
422
|
click.pause()
|
|
332
423
|
|
|
333
424
|
|
|
@@ -335,32 +426,46 @@ def _remove_evidence(deliverable_id: int):
|
|
|
335
426
|
"""Remove evidence links."""
|
|
336
427
|
em = EvidenceManager()
|
|
337
428
|
evidence = em.get_evidence(deliverable_id)
|
|
338
|
-
|
|
429
|
+
|
|
339
430
|
if not any(evidence.values()):
|
|
340
|
-
click.echo(click.style(" No evidence to remove", fg=
|
|
431
|
+
click.echo(click.style(" No evidence to remove", fg="yellow"))
|
|
341
432
|
click.pause()
|
|
342
433
|
return
|
|
343
|
-
|
|
434
|
+
|
|
344
435
|
DesignSystem.clear_screen()
|
|
345
436
|
click.echo()
|
|
346
|
-
click.echo(click.style(" 🗑️ REMOVE EVIDENCE", bold=True, fg=
|
|
437
|
+
click.echo(click.style(" 🗑️ REMOVE EVIDENCE", bold=True, fg="red"))
|
|
347
438
|
click.echo()
|
|
348
|
-
|
|
439
|
+
|
|
349
440
|
# Build removal menu
|
|
350
441
|
items = []
|
|
351
|
-
|
|
352
|
-
for f in evidence[
|
|
353
|
-
items.append(
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
442
|
+
|
|
443
|
+
for f in evidence["findings"]:
|
|
444
|
+
items.append(
|
|
445
|
+
("finding", f["id"], f"[Finding] {f.get('title', 'Unknown')[:60]}")
|
|
446
|
+
)
|
|
447
|
+
|
|
448
|
+
for c in evidence["credentials"]:
|
|
449
|
+
items.append(
|
|
450
|
+
(
|
|
451
|
+
"credential",
|
|
452
|
+
c["id"],
|
|
453
|
+
f"[Credential] {c.get('username', 'N/A')}@{c.get('host', 'N/A')}",
|
|
454
|
+
)
|
|
455
|
+
)
|
|
456
|
+
|
|
457
|
+
for s in evidence["screenshots"]:
|
|
458
|
+
items.append(
|
|
459
|
+
(
|
|
460
|
+
"screenshot",
|
|
461
|
+
s["id"],
|
|
462
|
+
f"[Screenshot] {s.get('description', s.get('filename', 'Unknown'))[:60]}",
|
|
463
|
+
)
|
|
464
|
+
)
|
|
465
|
+
|
|
466
|
+
for j in evidence["jobs"]:
|
|
467
|
+
items.append(("job", j["id"], f"[Job] #{j.get('id')} - {j.get('tool', 'N/A')}"))
|
|
468
|
+
|
|
364
469
|
click.echo(" Select evidence to remove:")
|
|
365
470
|
click.echo()
|
|
366
471
|
for idx, item in enumerate(items, 1):
|
|
@@ -368,15 +473,15 @@ def _remove_evidence(deliverable_id: int):
|
|
|
368
473
|
click.echo()
|
|
369
474
|
click.echo(" [q] Cancel")
|
|
370
475
|
click.echo()
|
|
371
|
-
|
|
476
|
+
|
|
372
477
|
choice = click.prompt("Select option", type=int, default=0, show_default=False)
|
|
373
478
|
if choice == 0 or choice > len(items):
|
|
374
479
|
return
|
|
375
|
-
|
|
480
|
+
|
|
376
481
|
evidence_type, evidence_id, _ = items[choice - 1]
|
|
377
|
-
|
|
482
|
+
|
|
378
483
|
if click.confirm(" Are you sure?", default=False):
|
|
379
484
|
em.unlink_evidence(deliverable_id, evidence_type, evidence_id)
|
|
380
|
-
click.echo(click.style(" ✅ Evidence unlinked", fg=
|
|
381
|
-
|
|
485
|
+
click.echo(click.style(" ✅ Evidence unlinked", fg="green"))
|
|
486
|
+
|
|
382
487
|
click.pause()
|