souleyez 2.43.29__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 +22827 -10678
- souleyez/ui/interactive_selector.py +75 -68
- souleyez/ui/log_formatter.py +47 -39
- souleyez/ui/menu_components.py +22 -13
- souleyez/ui/msf_auxiliary_menu.py +184 -133
- souleyez/ui/pending_chains_view.py +336 -172
- souleyez/ui/progress_indicators.py +5 -3
- souleyez/ui/recommendations_view.py +195 -137
- souleyez/ui/rule_builder.py +343 -225
- souleyez/ui/setup_wizard.py +678 -284
- souleyez/ui/shortcuts.py +217 -165
- souleyez/ui/splunk_gap_analysis_view.py +452 -270
- souleyez/ui/splunk_vulns_view.py +139 -86
- souleyez/ui/team_dashboard.py +498 -335
- souleyez/ui/template_selector.py +196 -105
- souleyez/ui/terminal.py +6 -6
- souleyez/ui/timeline_view.py +198 -127
- souleyez/ui/tool_setup.py +264 -164
- souleyez/ui/tutorial.py +202 -72
- souleyez/ui/tutorial_state.py +40 -40
- souleyez/ui/wazuh_vulns_view.py +235 -141
- souleyez/ui/wordlist_browser.py +260 -107
- souleyez/ui.py +464 -312
- souleyez/utils/tool_checker.py +427 -367
- souleyez/utils.py +33 -29
- souleyez/wordlists.py +134 -167
- {souleyez-2.43.29.dist-info → souleyez-2.43.34.dist-info}/METADATA +1 -1
- souleyez-2.43.34.dist-info/RECORD +443 -0
- {souleyez-2.43.29.dist-info → souleyez-2.43.34.dist-info}/WHEEL +1 -1
- souleyez-2.43.29.dist-info/RECORD +0 -379
- {souleyez-2.43.29.dist-info → souleyez-2.43.34.dist-info}/entry_points.txt +0 -0
- {souleyez-2.43.29.dist-info → souleyez-2.43.34.dist-info}/licenses/LICENSE +0 -0
- {souleyez-2.43.29.dist-info → souleyez-2.43.34.dist-info}/top_level.txt +0 -0
souleyez/ai/report_context.py
CHANGED
|
@@ -3,6 +3,7 @@ souleyez.ai.report_context - Build context for AI report generation
|
|
|
3
3
|
|
|
4
4
|
Prepares engagement data in formats suitable for LLM prompt templates.
|
|
5
5
|
"""
|
|
6
|
+
|
|
6
7
|
import logging
|
|
7
8
|
from typing import Dict, Any, List, Optional
|
|
8
9
|
|
|
@@ -48,44 +49,44 @@ class ReportContextBuilder:
|
|
|
48
49
|
creds = self.cm.list_credentials(engagement_id)
|
|
49
50
|
|
|
50
51
|
# Count findings by severity
|
|
51
|
-
severity_counts = {
|
|
52
|
+
severity_counts = {"critical": 0, "high": 0, "medium": 0, "low": 0, "info": 0}
|
|
52
53
|
for f in findings:
|
|
53
|
-
sev = f.get(
|
|
54
|
+
sev = f.get("severity", "info").lower()
|
|
54
55
|
if sev in severity_counts:
|
|
55
56
|
severity_counts[sev] += 1
|
|
56
57
|
else:
|
|
57
|
-
severity_counts[
|
|
58
|
+
severity_counts["info"] += 1
|
|
58
59
|
|
|
59
60
|
# Count hosts with critical/high findings
|
|
60
61
|
hosts_with_issues = set()
|
|
61
62
|
for f in findings:
|
|
62
|
-
if f.get(
|
|
63
|
-
if f.get(
|
|
64
|
-
hosts_with_issues.add(f[
|
|
63
|
+
if f.get("severity", "").lower() in ["critical", "high"]:
|
|
64
|
+
if f.get("host_id"):
|
|
65
|
+
hosts_with_issues.add(f["host_id"])
|
|
65
66
|
|
|
66
67
|
# Build top findings summary
|
|
67
68
|
top_findings = self._format_top_findings(findings, limit=5)
|
|
68
69
|
|
|
69
70
|
# Engagement type and duration
|
|
70
|
-
eng_type = engagement.get(
|
|
71
|
+
eng_type = engagement.get("type", "Penetration Test")
|
|
71
72
|
duration = self._calculate_duration(engagement)
|
|
72
|
-
scope = engagement.get(
|
|
73
|
+
scope = engagement.get("description", "Network and application assessment")
|
|
73
74
|
|
|
74
75
|
return {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
76
|
+
"engagement_name": engagement.get("name", "Unknown"),
|
|
77
|
+
"engagement_type": eng_type,
|
|
78
|
+
"duration": duration,
|
|
79
|
+
"scope_summary": scope[:200] if scope else "Full scope assessment",
|
|
80
|
+
"total_findings": len(findings),
|
|
81
|
+
"critical_count": severity_counts["critical"],
|
|
82
|
+
"high_count": severity_counts["high"],
|
|
83
|
+
"medium_count": severity_counts["medium"],
|
|
84
|
+
"low_count": severity_counts["low"],
|
|
85
|
+
"info_count": severity_counts["info"],
|
|
86
|
+
"total_hosts": len(hosts),
|
|
87
|
+
"compromised_hosts": len(hosts_with_issues),
|
|
88
|
+
"credentials_count": len(creds),
|
|
89
|
+
"top_findings": top_findings,
|
|
89
90
|
}
|
|
90
91
|
|
|
91
92
|
def build_finding_context(self, finding: Dict[str, Any]) -> Dict[str, Any]:
|
|
@@ -99,35 +100,35 @@ class ReportContextBuilder:
|
|
|
99
100
|
dict: Context variables for FINDING_ENHANCEMENT_PROMPT
|
|
100
101
|
"""
|
|
101
102
|
# Get host info if available
|
|
102
|
-
host_ip =
|
|
103
|
-
hostname =
|
|
104
|
-
if finding.get(
|
|
105
|
-
host = self.hm.get_host(finding[
|
|
103
|
+
host_ip = "N/A"
|
|
104
|
+
hostname = ""
|
|
105
|
+
if finding.get("host_id"):
|
|
106
|
+
host = self.hm.get_host(finding["host_id"])
|
|
106
107
|
if host:
|
|
107
|
-
host_ip = host.get(
|
|
108
|
-
hostname = host.get(
|
|
108
|
+
host_ip = host.get("ip_address", "N/A")
|
|
109
|
+
hostname = host.get("hostname", "")
|
|
109
110
|
|
|
110
111
|
# Extract CVE/CWE
|
|
111
|
-
cve = finding.get(
|
|
112
|
+
cve = finding.get("refs", "") or "N/A"
|
|
112
113
|
if isinstance(cve, list):
|
|
113
|
-
cve =
|
|
114
|
+
cve = ", ".join(cve[:3]) # Limit to first 3
|
|
114
115
|
|
|
115
116
|
# Clean evidence (truncate if too long)
|
|
116
|
-
evidence = finding.get(
|
|
117
|
+
evidence = finding.get("evidence", "")
|
|
117
118
|
if len(evidence) > 1000:
|
|
118
|
-
evidence = evidence[:1000] +
|
|
119
|
+
evidence = evidence[:1000] + "\n... [truncated]"
|
|
119
120
|
|
|
120
121
|
return {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
122
|
+
"title": finding.get("title", "Unknown Finding"),
|
|
123
|
+
"severity": finding.get("severity", "Unknown").upper(),
|
|
124
|
+
"host": host_ip,
|
|
125
|
+
"hostname": hostname,
|
|
126
|
+
"port": finding.get("port", "N/A"),
|
|
127
|
+
"service": finding.get("service", "Unknown"),
|
|
128
|
+
"tool": finding.get("tool", "Manual"),
|
|
129
|
+
"description": finding.get("description", "No description provided"),
|
|
130
|
+
"cve": cve,
|
|
131
|
+
"evidence": evidence or "No evidence recorded",
|
|
131
132
|
}
|
|
132
133
|
|
|
133
134
|
def build_remediation_context(self, engagement_id: int) -> Dict[str, Any]:
|
|
@@ -145,9 +146,9 @@ class ReportContextBuilder:
|
|
|
145
146
|
creds = self.cm.list_credentials(engagement_id)
|
|
146
147
|
|
|
147
148
|
# Count by severity
|
|
148
|
-
severity_counts = {
|
|
149
|
+
severity_counts = {"critical": 0, "high": 0, "medium": 0, "low": 0, "info": 0}
|
|
149
150
|
for f in findings:
|
|
150
|
-
sev = f.get(
|
|
151
|
+
sev = f.get("severity", "info").lower()
|
|
151
152
|
if sev in severity_counts:
|
|
152
153
|
severity_counts[sev] += 1
|
|
153
154
|
|
|
@@ -158,61 +159,59 @@ class ReportContextBuilder:
|
|
|
158
159
|
top_vulns = self._format_top_findings(findings, limit=10, include_medium=True)
|
|
159
160
|
|
|
160
161
|
return {
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
162
|
+
"findings_summary": findings_summary,
|
|
163
|
+
"total_hosts": len(hosts),
|
|
164
|
+
"critical_count": severity_counts["critical"],
|
|
165
|
+
"high_count": severity_counts["high"],
|
|
166
|
+
"medium_count": severity_counts["medium"],
|
|
167
|
+
"creds_count": len(creds),
|
|
168
|
+
"top_vulnerabilities": top_vulns,
|
|
168
169
|
}
|
|
169
170
|
|
|
170
171
|
def _format_top_findings(
|
|
171
|
-
self,
|
|
172
|
-
findings: List[Dict],
|
|
173
|
-
limit: int = 5,
|
|
174
|
-
include_medium: bool = False
|
|
172
|
+
self, findings: List[Dict], limit: int = 5, include_medium: bool = False
|
|
175
173
|
) -> str:
|
|
176
174
|
"""Format top critical/high findings as text."""
|
|
177
|
-
severity_order = {
|
|
175
|
+
severity_order = {"critical": 0, "high": 1, "medium": 2, "low": 3, "info": 4}
|
|
178
176
|
|
|
179
177
|
# Filter to critical/high (and optionally medium)
|
|
180
178
|
max_severity = 2 if include_medium else 1
|
|
181
179
|
priority_findings = [
|
|
182
|
-
f
|
|
183
|
-
|
|
180
|
+
f
|
|
181
|
+
for f in findings
|
|
182
|
+
if severity_order.get(f.get("severity", "info").lower(), 4) <= max_severity
|
|
184
183
|
]
|
|
185
184
|
|
|
186
185
|
# Sort by severity
|
|
187
186
|
priority_findings.sort(
|
|
188
|
-
key=lambda f: severity_order.get(f.get(
|
|
187
|
+
key=lambda f: severity_order.get(f.get("severity", "info").lower(), 4)
|
|
189
188
|
)
|
|
190
189
|
|
|
191
190
|
# Format
|
|
192
191
|
lines = []
|
|
193
192
|
for f in priority_findings[:limit]:
|
|
194
|
-
sev = f.get(
|
|
195
|
-
title = f.get(
|
|
196
|
-
desc = f.get(
|
|
193
|
+
sev = f.get("severity", "unknown").upper()
|
|
194
|
+
title = f.get("title", "Unknown")
|
|
195
|
+
desc = f.get("description", "")[:100]
|
|
197
196
|
lines.append(f"- [{sev}] {title}")
|
|
198
197
|
if desc:
|
|
199
198
|
lines.append(f" {desc}...")
|
|
200
199
|
|
|
201
|
-
return
|
|
200
|
+
return "\n".join(lines) if lines else "No critical or high severity findings."
|
|
202
201
|
|
|
203
202
|
def _format_findings_by_severity(self, findings: List[Dict]) -> str:
|
|
204
203
|
"""Format all findings grouped by severity."""
|
|
205
|
-
by_severity = {
|
|
204
|
+
by_severity = {"critical": [], "high": [], "medium": [], "low": [], "info": []}
|
|
206
205
|
|
|
207
206
|
for f in findings:
|
|
208
|
-
sev = f.get(
|
|
207
|
+
sev = f.get("severity", "info").lower()
|
|
209
208
|
if sev in by_severity:
|
|
210
209
|
by_severity[sev].append(f)
|
|
211
210
|
else:
|
|
212
|
-
by_severity[
|
|
211
|
+
by_severity["info"].append(f)
|
|
213
212
|
|
|
214
213
|
lines = []
|
|
215
|
-
for sev in [
|
|
214
|
+
for sev in ["critical", "high", "medium", "low"]:
|
|
216
215
|
items = by_severity[sev]
|
|
217
216
|
if items:
|
|
218
217
|
lines.append(f"\n{sev.upper()} ({len(items)}):")
|
|
@@ -221,20 +220,22 @@ class ReportContextBuilder:
|
|
|
221
220
|
if len(items) > 5:
|
|
222
221
|
lines.append(f" ... and {len(items) - 5} more")
|
|
223
222
|
|
|
224
|
-
return
|
|
223
|
+
return "\n".join(lines) if lines else "No findings recorded."
|
|
225
224
|
|
|
226
225
|
def _calculate_duration(self, engagement: Dict) -> str:
|
|
227
226
|
"""Calculate engagement duration from dates."""
|
|
228
227
|
from datetime import datetime
|
|
229
228
|
|
|
230
|
-
start = engagement.get(
|
|
231
|
-
end = engagement.get(
|
|
229
|
+
start = engagement.get("start_date")
|
|
230
|
+
end = engagement.get("end_date")
|
|
232
231
|
|
|
233
232
|
if not start:
|
|
234
|
-
return
|
|
233
|
+
return "Duration not specified"
|
|
235
234
|
|
|
236
235
|
try:
|
|
237
|
-
start_dt =
|
|
236
|
+
start_dt = (
|
|
237
|
+
datetime.fromisoformat(start) if isinstance(start, str) else start
|
|
238
|
+
)
|
|
238
239
|
if end:
|
|
239
240
|
end_dt = datetime.fromisoformat(end) if isinstance(end, str) else end
|
|
240
241
|
days = (end_dt - start_dt).days
|
|
@@ -242,4 +243,4 @@ class ReportContextBuilder:
|
|
|
242
243
|
else:
|
|
243
244
|
return "Ongoing"
|
|
244
245
|
except Exception:
|
|
245
|
-
return
|
|
246
|
+
return "Duration not specified"
|
souleyez/ai/report_service.py
CHANGED
|
@@ -4,6 +4,7 @@ souleyez.ai.report_service - AI-powered report generation service
|
|
|
4
4
|
Provides methods for generating AI-enhanced report sections using
|
|
5
5
|
configured LLM providers (Claude or Ollama).
|
|
6
6
|
"""
|
|
7
|
+
|
|
7
8
|
import logging
|
|
8
9
|
import re
|
|
9
10
|
import concurrent.futures
|
|
@@ -83,13 +84,11 @@ class AIReportService:
|
|
|
83
84
|
def get_provider_info(self) -> Dict[str, Any]:
|
|
84
85
|
"""Get information about the current provider."""
|
|
85
86
|
if not self.provider:
|
|
86
|
-
return {
|
|
87
|
+
return {"available": False, "error": "No provider configured"}
|
|
87
88
|
return self.provider.get_status()
|
|
88
89
|
|
|
89
90
|
def generate_executive_summary(
|
|
90
|
-
self,
|
|
91
|
-
engagement_id: int,
|
|
92
|
-
max_tokens: int = 1500
|
|
91
|
+
self, engagement_id: int, max_tokens: int = 1500
|
|
93
92
|
) -> Optional[str]:
|
|
94
93
|
"""
|
|
95
94
|
Generate AI-powered executive summary.
|
|
@@ -118,13 +117,15 @@ class AIReportService:
|
|
|
118
117
|
prompt=prompt,
|
|
119
118
|
system_prompt=REPORT_SYSTEM_PROMPT,
|
|
120
119
|
max_tokens=max_tokens,
|
|
121
|
-
temperature=0.3
|
|
120
|
+
temperature=0.3,
|
|
122
121
|
)
|
|
123
122
|
|
|
124
123
|
result = _run_with_timeout(_generate)
|
|
125
124
|
|
|
126
125
|
if result:
|
|
127
|
-
logger.info(
|
|
126
|
+
logger.info(
|
|
127
|
+
f"Generated executive summary for engagement {engagement_id}"
|
|
128
|
+
)
|
|
128
129
|
return result
|
|
129
130
|
|
|
130
131
|
except Exception as e:
|
|
@@ -132,9 +133,7 @@ class AIReportService:
|
|
|
132
133
|
return None
|
|
133
134
|
|
|
134
135
|
def enhance_finding(
|
|
135
|
-
self,
|
|
136
|
-
finding: Dict[str, Any],
|
|
137
|
-
max_tokens: int = 800
|
|
136
|
+
self, finding: Dict[str, Any], max_tokens: int = 800
|
|
138
137
|
) -> Optional[Dict[str, str]]:
|
|
139
138
|
"""
|
|
140
139
|
Enhance a single finding with business context.
|
|
@@ -160,7 +159,7 @@ class AIReportService:
|
|
|
160
159
|
prompt=prompt,
|
|
161
160
|
system_prompt=REPORT_SYSTEM_PROMPT,
|
|
162
161
|
max_tokens=max_tokens,
|
|
163
|
-
temperature=0.3
|
|
162
|
+
temperature=0.3,
|
|
164
163
|
)
|
|
165
164
|
|
|
166
165
|
result = _run_with_timeout(_generate)
|
|
@@ -174,9 +173,7 @@ class AIReportService:
|
|
|
174
173
|
return None
|
|
175
174
|
|
|
176
175
|
def generate_remediation_plan(
|
|
177
|
-
self,
|
|
178
|
-
engagement_id: int,
|
|
179
|
-
max_tokens: int = 2500
|
|
176
|
+
self, engagement_id: int, max_tokens: int = 2500
|
|
180
177
|
) -> Optional[str]:
|
|
181
178
|
"""
|
|
182
179
|
Generate prioritized remediation plan.
|
|
@@ -205,23 +202,22 @@ class AIReportService:
|
|
|
205
202
|
prompt=prompt,
|
|
206
203
|
system_prompt=REPORT_SYSTEM_PROMPT,
|
|
207
204
|
max_tokens=max_tokens,
|
|
208
|
-
temperature=0.3
|
|
205
|
+
temperature=0.3,
|
|
209
206
|
)
|
|
210
207
|
|
|
211
208
|
result = _run_with_timeout(_generate)
|
|
212
209
|
|
|
213
210
|
if result:
|
|
214
|
-
logger.info(
|
|
211
|
+
logger.info(
|
|
212
|
+
f"Generated remediation plan for engagement {engagement_id}"
|
|
213
|
+
)
|
|
215
214
|
return result
|
|
216
215
|
|
|
217
216
|
except Exception as e:
|
|
218
217
|
logger.error(f"Failed to generate remediation plan: {e}")
|
|
219
218
|
return None
|
|
220
219
|
|
|
221
|
-
def generate_risk_rating(
|
|
222
|
-
self,
|
|
223
|
-
engagement_id: int
|
|
224
|
-
) -> Optional[Dict[str, str]]:
|
|
220
|
+
def generate_risk_rating(self, engagement_id: int) -> Optional[Dict[str, str]]:
|
|
225
221
|
"""
|
|
226
222
|
Generate overall risk rating.
|
|
227
223
|
|
|
@@ -251,7 +247,7 @@ Credentials Compromised: {context.get('credentials_count', 0)}
|
|
|
251
247
|
prompt=prompt,
|
|
252
248
|
system_prompt=REPORT_SYSTEM_PROMPT,
|
|
253
249
|
max_tokens=200,
|
|
254
|
-
temperature=0.2
|
|
250
|
+
temperature=0.2,
|
|
255
251
|
)
|
|
256
252
|
|
|
257
253
|
result = _run_with_timeout(_generate)
|
|
@@ -265,10 +261,7 @@ Credentials Compromised: {context.get('credentials_count', 0)}
|
|
|
265
261
|
return None
|
|
266
262
|
|
|
267
263
|
def generate_all_content(
|
|
268
|
-
self,
|
|
269
|
-
engagement_id: int,
|
|
270
|
-
enhance_findings: bool = True,
|
|
271
|
-
max_findings: int = 10
|
|
264
|
+
self, engagement_id: int, enhance_findings: bool = True, max_findings: int = 10
|
|
272
265
|
) -> Dict[str, Any]:
|
|
273
266
|
"""
|
|
274
267
|
Generate all AI content for a report.
|
|
@@ -284,37 +277,39 @@ Credentials Compromised: {context.get('credentials_count', 0)}
|
|
|
284
277
|
from souleyez.storage.findings import FindingsManager
|
|
285
278
|
|
|
286
279
|
content = {
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
280
|
+
"executive_summary": None,
|
|
281
|
+
"remediation_plan": None,
|
|
282
|
+
"risk_rating": None,
|
|
283
|
+
"enhanced_findings": {},
|
|
284
|
+
"provider": None,
|
|
285
|
+
"errors": [],
|
|
293
286
|
}
|
|
294
287
|
|
|
295
288
|
if not self.is_available():
|
|
296
|
-
content[
|
|
289
|
+
content["errors"].append("AI provider not available")
|
|
297
290
|
return content
|
|
298
291
|
|
|
299
|
-
content[
|
|
292
|
+
content["provider"] = self.provider.provider_type.value
|
|
300
293
|
|
|
301
294
|
# Generate executive summary
|
|
302
295
|
try:
|
|
303
|
-
content[
|
|
296
|
+
content["executive_summary"] = self.generate_executive_summary(
|
|
297
|
+
engagement_id
|
|
298
|
+
)
|
|
304
299
|
except Exception as e:
|
|
305
|
-
content[
|
|
300
|
+
content["errors"].append(f"Executive summary: {e}")
|
|
306
301
|
|
|
307
302
|
# Generate remediation plan
|
|
308
303
|
try:
|
|
309
|
-
content[
|
|
304
|
+
content["remediation_plan"] = self.generate_remediation_plan(engagement_id)
|
|
310
305
|
except Exception as e:
|
|
311
|
-
content[
|
|
306
|
+
content["errors"].append(f"Remediation plan: {e}")
|
|
312
307
|
|
|
313
308
|
# Generate risk rating
|
|
314
309
|
try:
|
|
315
|
-
content[
|
|
310
|
+
content["risk_rating"] = self.generate_risk_rating(engagement_id)
|
|
316
311
|
except Exception as e:
|
|
317
|
-
content[
|
|
312
|
+
content["errors"].append(f"Risk rating: {e}")
|
|
318
313
|
|
|
319
314
|
# Enhance individual findings (top critical/high only)
|
|
320
315
|
if enhance_findings:
|
|
@@ -323,38 +318,48 @@ Credentials Compromised: {context.get('credentials_count', 0)}
|
|
|
323
318
|
findings = fm.list_findings(engagement_id)
|
|
324
319
|
|
|
325
320
|
# Sort by severity and take top N
|
|
326
|
-
severity_order = {
|
|
321
|
+
severity_order = {
|
|
322
|
+
"critical": 0,
|
|
323
|
+
"high": 1,
|
|
324
|
+
"medium": 2,
|
|
325
|
+
"low": 3,
|
|
326
|
+
"info": 4,
|
|
327
|
+
}
|
|
327
328
|
priority_findings = sorted(
|
|
328
|
-
[
|
|
329
|
-
|
|
329
|
+
[
|
|
330
|
+
f
|
|
331
|
+
for f in findings
|
|
332
|
+
if f.get("severity", "").lower() in ["critical", "high"]
|
|
333
|
+
],
|
|
334
|
+
key=lambda f: severity_order.get(
|
|
335
|
+
f.get("severity", "info").lower(), 4
|
|
336
|
+
),
|
|
330
337
|
)[:max_findings]
|
|
331
338
|
|
|
332
339
|
for finding in priority_findings:
|
|
333
340
|
try:
|
|
334
341
|
enhanced = self.enhance_finding(finding)
|
|
335
342
|
if enhanced:
|
|
336
|
-
content[
|
|
343
|
+
content["enhanced_findings"][finding["id"]] = enhanced
|
|
337
344
|
except Exception as e:
|
|
338
|
-
logger.warning(
|
|
345
|
+
logger.warning(
|
|
346
|
+
f"Failed to enhance finding {finding.get('id')}: {e}"
|
|
347
|
+
)
|
|
339
348
|
|
|
340
349
|
except Exception as e:
|
|
341
|
-
content[
|
|
350
|
+
content["errors"].append(f"Finding enhancement: {e}")
|
|
342
351
|
|
|
343
352
|
return content
|
|
344
353
|
|
|
345
354
|
def _parse_finding_enhancement(self, response: str) -> Dict[str, str]:
|
|
346
355
|
"""Parse LLM response into structured finding enhancement."""
|
|
347
|
-
result = {
|
|
348
|
-
'business_impact': '',
|
|
349
|
-
'attack_scenario': '',
|
|
350
|
-
'risk_context': ''
|
|
351
|
-
}
|
|
356
|
+
result = {"business_impact": "", "attack_scenario": "", "risk_context": ""}
|
|
352
357
|
|
|
353
358
|
# Try to extract sections
|
|
354
359
|
sections = {
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
360
|
+
"business_impact": r"BUSINESS IMPACT[:\s]*(.+?)(?=ATTACK SCENARIO|RISK CONTEXT|$)",
|
|
361
|
+
"attack_scenario": r"ATTACK SCENARIO[:\s]*(.+?)(?=RISK CONTEXT|BUSINESS IMPACT|$)",
|
|
362
|
+
"risk_context": r"RISK CONTEXT[:\s]*(.+?)(?=BUSINESS IMPACT|ATTACK SCENARIO|$)",
|
|
358
363
|
}
|
|
359
364
|
|
|
360
365
|
for key, pattern in sections.items():
|
|
@@ -364,19 +369,23 @@ Credentials Compromised: {context.get('credentials_count', 0)}
|
|
|
364
369
|
|
|
365
370
|
# If parsing failed, use the whole response as business impact
|
|
366
371
|
if not any(result.values()):
|
|
367
|
-
result[
|
|
372
|
+
result["business_impact"] = response.strip()
|
|
368
373
|
|
|
369
374
|
return result
|
|
370
375
|
|
|
371
376
|
def _parse_risk_rating(self, response: str) -> Dict[str, str]:
|
|
372
377
|
"""Parse risk rating response."""
|
|
373
|
-
result = {
|
|
378
|
+
result = {"rating": "UNKNOWN", "justification": response}
|
|
374
379
|
|
|
375
380
|
# Look for rating pattern
|
|
376
|
-
match = re.search(
|
|
381
|
+
match = re.search(
|
|
382
|
+
r"RATING:\s*(CRITICAL|HIGH|MODERATE|LOW)\s*[-–]\s*(.+)",
|
|
383
|
+
response,
|
|
384
|
+
re.IGNORECASE,
|
|
385
|
+
)
|
|
377
386
|
if match:
|
|
378
|
-
result[
|
|
379
|
-
result[
|
|
387
|
+
result["rating"] = match.group(1).upper()
|
|
388
|
+
result["justification"] = match.group(2).strip()
|
|
380
389
|
|
|
381
390
|
return result
|
|
382
391
|
|
|
@@ -404,18 +413,27 @@ Credentials Compromised: {context.get('credentials_count', 0)}
|
|
|
404
413
|
remediation_input = len(REMEDIATION_PLAN_PROMPT.format(**remediation_ctx)) // 4
|
|
405
414
|
remediation_output = 800
|
|
406
415
|
|
|
407
|
-
critical_high = len(
|
|
416
|
+
critical_high = len(
|
|
417
|
+
[
|
|
418
|
+
f
|
|
419
|
+
for f in findings
|
|
420
|
+
if f.get("severity", "").lower() in ["critical", "high"]
|
|
421
|
+
]
|
|
422
|
+
)
|
|
408
423
|
findings_input = critical_high * 300
|
|
409
424
|
findings_output = critical_high * 200
|
|
410
425
|
|
|
411
426
|
return {
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
executive_input
|
|
417
|
-
|
|
418
|
-
|
|
427
|
+
"executive_summary": executive_input + executive_output,
|
|
428
|
+
"remediation_plan": remediation_input + remediation_output,
|
|
429
|
+
"findings_enhancement": findings_input + findings_output,
|
|
430
|
+
"total_estimated": (
|
|
431
|
+
executive_input
|
|
432
|
+
+ executive_output
|
|
433
|
+
+ remediation_input
|
|
434
|
+
+ remediation_output
|
|
435
|
+
+ findings_input
|
|
436
|
+
+ findings_output
|
|
419
437
|
),
|
|
420
|
-
|
|
438
|
+
"findings_to_enhance": min(critical_high, 10),
|
|
421
439
|
}
|