aiptx 2.0.7__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.
- aipt_v2/__init__.py +110 -0
- aipt_v2/__main__.py +24 -0
- aipt_v2/agents/AIPTxAgent/__init__.py +10 -0
- aipt_v2/agents/AIPTxAgent/aiptx_agent.py +211 -0
- aipt_v2/agents/__init__.py +46 -0
- aipt_v2/agents/base.py +520 -0
- aipt_v2/agents/exploit_agent.py +688 -0
- aipt_v2/agents/ptt.py +406 -0
- aipt_v2/agents/state.py +168 -0
- aipt_v2/app.py +957 -0
- aipt_v2/browser/__init__.py +31 -0
- aipt_v2/browser/automation.py +458 -0
- aipt_v2/browser/crawler.py +453 -0
- aipt_v2/cli.py +2933 -0
- aipt_v2/compliance/__init__.py +71 -0
- aipt_v2/compliance/compliance_report.py +449 -0
- aipt_v2/compliance/framework_mapper.py +424 -0
- aipt_v2/compliance/nist_mapping.py +345 -0
- aipt_v2/compliance/owasp_mapping.py +330 -0
- aipt_v2/compliance/pci_mapping.py +297 -0
- aipt_v2/config.py +341 -0
- aipt_v2/core/__init__.py +43 -0
- aipt_v2/core/agent.py +630 -0
- aipt_v2/core/llm.py +395 -0
- aipt_v2/core/memory.py +305 -0
- aipt_v2/core/ptt.py +329 -0
- aipt_v2/database/__init__.py +14 -0
- aipt_v2/database/models.py +232 -0
- aipt_v2/database/repository.py +384 -0
- aipt_v2/docker/__init__.py +23 -0
- aipt_v2/docker/builder.py +260 -0
- aipt_v2/docker/manager.py +222 -0
- aipt_v2/docker/sandbox.py +371 -0
- aipt_v2/evasion/__init__.py +58 -0
- aipt_v2/evasion/request_obfuscator.py +272 -0
- aipt_v2/evasion/tls_fingerprint.py +285 -0
- aipt_v2/evasion/ua_rotator.py +301 -0
- aipt_v2/evasion/waf_bypass.py +439 -0
- aipt_v2/execution/__init__.py +23 -0
- aipt_v2/execution/executor.py +302 -0
- aipt_v2/execution/parser.py +544 -0
- aipt_v2/execution/terminal.py +337 -0
- aipt_v2/health.py +437 -0
- aipt_v2/intelligence/__init__.py +194 -0
- aipt_v2/intelligence/adaptation.py +474 -0
- aipt_v2/intelligence/auth.py +520 -0
- aipt_v2/intelligence/chaining.py +775 -0
- aipt_v2/intelligence/correlation.py +536 -0
- aipt_v2/intelligence/cve_aipt.py +334 -0
- aipt_v2/intelligence/cve_info.py +1111 -0
- aipt_v2/intelligence/knowledge_graph.py +590 -0
- aipt_v2/intelligence/learning.py +626 -0
- aipt_v2/intelligence/llm_analyzer.py +502 -0
- aipt_v2/intelligence/llm_tool_selector.py +518 -0
- aipt_v2/intelligence/payload_generator.py +562 -0
- aipt_v2/intelligence/rag.py +239 -0
- aipt_v2/intelligence/scope.py +442 -0
- aipt_v2/intelligence/searchers/__init__.py +5 -0
- aipt_v2/intelligence/searchers/exploitdb_searcher.py +523 -0
- aipt_v2/intelligence/searchers/github_searcher.py +467 -0
- aipt_v2/intelligence/searchers/google_searcher.py +281 -0
- aipt_v2/intelligence/tools.json +443 -0
- aipt_v2/intelligence/triage.py +670 -0
- aipt_v2/interactive_shell.py +559 -0
- aipt_v2/interface/__init__.py +5 -0
- aipt_v2/interface/cli.py +230 -0
- aipt_v2/interface/main.py +501 -0
- aipt_v2/interface/tui.py +1276 -0
- aipt_v2/interface/utils.py +583 -0
- aipt_v2/llm/__init__.py +39 -0
- aipt_v2/llm/config.py +26 -0
- aipt_v2/llm/llm.py +514 -0
- aipt_v2/llm/memory.py +214 -0
- aipt_v2/llm/request_queue.py +89 -0
- aipt_v2/llm/utils.py +89 -0
- aipt_v2/local_tool_installer.py +1467 -0
- aipt_v2/models/__init__.py +15 -0
- aipt_v2/models/findings.py +295 -0
- aipt_v2/models/phase_result.py +224 -0
- aipt_v2/models/scan_config.py +207 -0
- aipt_v2/monitoring/grafana/dashboards/aipt-dashboard.json +355 -0
- aipt_v2/monitoring/grafana/dashboards/default.yml +17 -0
- aipt_v2/monitoring/grafana/datasources/prometheus.yml +17 -0
- aipt_v2/monitoring/prometheus.yml +60 -0
- aipt_v2/orchestration/__init__.py +52 -0
- aipt_v2/orchestration/pipeline.py +398 -0
- aipt_v2/orchestration/progress.py +300 -0
- aipt_v2/orchestration/scheduler.py +296 -0
- aipt_v2/orchestrator.py +2427 -0
- aipt_v2/payloads/__init__.py +27 -0
- aipt_v2/payloads/cmdi.py +150 -0
- aipt_v2/payloads/sqli.py +263 -0
- aipt_v2/payloads/ssrf.py +204 -0
- aipt_v2/payloads/templates.py +222 -0
- aipt_v2/payloads/traversal.py +166 -0
- aipt_v2/payloads/xss.py +204 -0
- aipt_v2/prompts/__init__.py +60 -0
- aipt_v2/proxy/__init__.py +29 -0
- aipt_v2/proxy/history.py +352 -0
- aipt_v2/proxy/interceptor.py +452 -0
- aipt_v2/recon/__init__.py +44 -0
- aipt_v2/recon/dns.py +241 -0
- aipt_v2/recon/osint.py +367 -0
- aipt_v2/recon/subdomain.py +372 -0
- aipt_v2/recon/tech_detect.py +311 -0
- aipt_v2/reports/__init__.py +17 -0
- aipt_v2/reports/generator.py +313 -0
- aipt_v2/reports/html_report.py +378 -0
- aipt_v2/runtime/__init__.py +53 -0
- aipt_v2/runtime/base.py +30 -0
- aipt_v2/runtime/docker.py +401 -0
- aipt_v2/runtime/local.py +346 -0
- aipt_v2/runtime/tool_server.py +205 -0
- aipt_v2/runtime/vps.py +830 -0
- aipt_v2/scanners/__init__.py +28 -0
- aipt_v2/scanners/base.py +273 -0
- aipt_v2/scanners/nikto.py +244 -0
- aipt_v2/scanners/nmap.py +402 -0
- aipt_v2/scanners/nuclei.py +273 -0
- aipt_v2/scanners/web.py +454 -0
- aipt_v2/scripts/security_audit.py +366 -0
- aipt_v2/setup_wizard.py +941 -0
- aipt_v2/skills/__init__.py +80 -0
- aipt_v2/skills/agents/__init__.py +14 -0
- aipt_v2/skills/agents/api_tester.py +706 -0
- aipt_v2/skills/agents/base.py +477 -0
- aipt_v2/skills/agents/code_review.py +459 -0
- aipt_v2/skills/agents/security_agent.py +336 -0
- aipt_v2/skills/agents/web_pentest.py +818 -0
- aipt_v2/skills/prompts/__init__.py +647 -0
- aipt_v2/system_detector.py +539 -0
- aipt_v2/telemetry/__init__.py +7 -0
- aipt_v2/telemetry/tracer.py +347 -0
- aipt_v2/terminal/__init__.py +28 -0
- aipt_v2/terminal/executor.py +400 -0
- aipt_v2/terminal/sandbox.py +350 -0
- aipt_v2/tools/__init__.py +44 -0
- aipt_v2/tools/active_directory/__init__.py +78 -0
- aipt_v2/tools/active_directory/ad_config.py +238 -0
- aipt_v2/tools/active_directory/bloodhound_wrapper.py +447 -0
- aipt_v2/tools/active_directory/kerberos_attacks.py +430 -0
- aipt_v2/tools/active_directory/ldap_enum.py +533 -0
- aipt_v2/tools/active_directory/smb_attacks.py +505 -0
- aipt_v2/tools/agents_graph/__init__.py +19 -0
- aipt_v2/tools/agents_graph/agents_graph_actions.py +69 -0
- aipt_v2/tools/api_security/__init__.py +76 -0
- aipt_v2/tools/api_security/api_discovery.py +608 -0
- aipt_v2/tools/api_security/graphql_scanner.py +622 -0
- aipt_v2/tools/api_security/jwt_analyzer.py +577 -0
- aipt_v2/tools/api_security/openapi_fuzzer.py +761 -0
- aipt_v2/tools/browser/__init__.py +5 -0
- aipt_v2/tools/browser/browser_actions.py +238 -0
- aipt_v2/tools/browser/browser_instance.py +535 -0
- aipt_v2/tools/browser/tab_manager.py +344 -0
- aipt_v2/tools/cloud/__init__.py +70 -0
- aipt_v2/tools/cloud/cloud_config.py +273 -0
- aipt_v2/tools/cloud/cloud_scanner.py +639 -0
- aipt_v2/tools/cloud/prowler_tool.py +571 -0
- aipt_v2/tools/cloud/scoutsuite_tool.py +359 -0
- aipt_v2/tools/executor.py +307 -0
- aipt_v2/tools/parser.py +408 -0
- aipt_v2/tools/proxy/__init__.py +5 -0
- aipt_v2/tools/proxy/proxy_actions.py +103 -0
- aipt_v2/tools/proxy/proxy_manager.py +789 -0
- aipt_v2/tools/registry.py +196 -0
- aipt_v2/tools/scanners/__init__.py +343 -0
- aipt_v2/tools/scanners/acunetix_tool.py +712 -0
- aipt_v2/tools/scanners/burp_tool.py +631 -0
- aipt_v2/tools/scanners/config.py +156 -0
- aipt_v2/tools/scanners/nessus_tool.py +588 -0
- aipt_v2/tools/scanners/zap_tool.py +612 -0
- aipt_v2/tools/terminal/__init__.py +5 -0
- aipt_v2/tools/terminal/terminal_actions.py +37 -0
- aipt_v2/tools/terminal/terminal_manager.py +153 -0
- aipt_v2/tools/terminal/terminal_session.py +449 -0
- aipt_v2/tools/tool_processing.py +108 -0
- aipt_v2/utils/__init__.py +17 -0
- aipt_v2/utils/logging.py +202 -0
- aipt_v2/utils/model_manager.py +187 -0
- aipt_v2/utils/searchers/__init__.py +269 -0
- aipt_v2/verify_install.py +793 -0
- aiptx-2.0.7.dist-info/METADATA +345 -0
- aiptx-2.0.7.dist-info/RECORD +187 -0
- aiptx-2.0.7.dist-info/WHEEL +5 -0
- aiptx-2.0.7.dist-info/entry_points.txt +7 -0
- aiptx-2.0.7.dist-info/licenses/LICENSE +21 -0
- aiptx-2.0.7.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AIPT Report Generator
|
|
3
|
+
|
|
4
|
+
Generates professional pentest reports from pipeline results.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
import logging
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
from datetime import datetime
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
from ..models.findings import Finding, Severity
|
|
16
|
+
from ..models.phase_result import PipelineResult
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
logger = logging.getLogger(__name__)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class ReportConfig:
|
|
24
|
+
"""Configuration for report generation"""
|
|
25
|
+
# Report metadata
|
|
26
|
+
client_name: str = "Client"
|
|
27
|
+
project_name: str = "Security Assessment"
|
|
28
|
+
assessor_name: str = "AIPT"
|
|
29
|
+
|
|
30
|
+
# Output settings
|
|
31
|
+
output_dir: Path = field(default_factory=lambda: Path("./reports"))
|
|
32
|
+
formats: list[str] = field(default_factory=lambda: ["html", "json", "markdown"])
|
|
33
|
+
|
|
34
|
+
# Content settings
|
|
35
|
+
include_evidence: bool = True
|
|
36
|
+
include_remediation: bool = True
|
|
37
|
+
include_ai_reasoning: bool = True
|
|
38
|
+
redact_sensitive: bool = False
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass
|
|
42
|
+
class ReportData:
|
|
43
|
+
"""Data structure for report generation"""
|
|
44
|
+
# Metadata
|
|
45
|
+
scan_id: str
|
|
46
|
+
target: str
|
|
47
|
+
generated_at: datetime
|
|
48
|
+
config: ReportConfig
|
|
49
|
+
|
|
50
|
+
# Findings by severity
|
|
51
|
+
critical_findings: list[Finding] = field(default_factory=list)
|
|
52
|
+
high_findings: list[Finding] = field(default_factory=list)
|
|
53
|
+
medium_findings: list[Finding] = field(default_factory=list)
|
|
54
|
+
low_findings: list[Finding] = field(default_factory=list)
|
|
55
|
+
info_findings: list[Finding] = field(default_factory=list)
|
|
56
|
+
|
|
57
|
+
# Statistics
|
|
58
|
+
total_findings: int = 0
|
|
59
|
+
unique_vuln_types: int = 0
|
|
60
|
+
sources: list[str] = field(default_factory=list)
|
|
61
|
+
|
|
62
|
+
# AI-specific
|
|
63
|
+
ai_findings_count: int = 0
|
|
64
|
+
ai_reasoning_samples: list[str] = field(default_factory=list)
|
|
65
|
+
|
|
66
|
+
@classmethod
|
|
67
|
+
def from_pipeline_result(
|
|
68
|
+
cls,
|
|
69
|
+
result: PipelineResult,
|
|
70
|
+
config: ReportConfig,
|
|
71
|
+
) -> "ReportData":
|
|
72
|
+
"""Create ReportData from pipeline result"""
|
|
73
|
+
findings = result.get_all_findings()
|
|
74
|
+
|
|
75
|
+
# Group by severity
|
|
76
|
+
critical = [f for f in findings if f.severity == Severity.CRITICAL]
|
|
77
|
+
high = [f for f in findings if f.severity == Severity.HIGH]
|
|
78
|
+
medium = [f for f in findings if f.severity == Severity.MEDIUM]
|
|
79
|
+
low = [f for f in findings if f.severity == Severity.LOW]
|
|
80
|
+
info = [f for f in findings if f.severity == Severity.INFO]
|
|
81
|
+
|
|
82
|
+
# Extract unique sources
|
|
83
|
+
sources = list(set(f.source for f in findings))
|
|
84
|
+
|
|
85
|
+
# Count AI findings
|
|
86
|
+
ai_findings = [f for f in findings if f.source == "aipt" or f.ai_reasoning]
|
|
87
|
+
ai_reasoning = [f.ai_reasoning for f in ai_findings if f.ai_reasoning][:5]
|
|
88
|
+
|
|
89
|
+
# Unique vulnerability types
|
|
90
|
+
unique_types = len(set(f.vuln_type for f in findings))
|
|
91
|
+
|
|
92
|
+
return cls(
|
|
93
|
+
scan_id=result.scan_id,
|
|
94
|
+
target=result.target,
|
|
95
|
+
generated_at=datetime.utcnow(),
|
|
96
|
+
config=config,
|
|
97
|
+
critical_findings=critical,
|
|
98
|
+
high_findings=high,
|
|
99
|
+
medium_findings=medium,
|
|
100
|
+
low_findings=low,
|
|
101
|
+
info_findings=info,
|
|
102
|
+
total_findings=len(findings),
|
|
103
|
+
unique_vuln_types=unique_types,
|
|
104
|
+
sources=sources,
|
|
105
|
+
ai_findings_count=len(ai_findings),
|
|
106
|
+
ai_reasoning_samples=ai_reasoning,
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
def get_severity_counts(self) -> dict[str, int]:
|
|
110
|
+
return {
|
|
111
|
+
"critical": len(self.critical_findings),
|
|
112
|
+
"high": len(self.high_findings),
|
|
113
|
+
"medium": len(self.medium_findings),
|
|
114
|
+
"low": len(self.low_findings),
|
|
115
|
+
"info": len(self.info_findings),
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
def get_risk_score(self) -> int:
|
|
119
|
+
"""Calculate overall risk score (0-100)"""
|
|
120
|
+
score = 0
|
|
121
|
+
score += len(self.critical_findings) * 25
|
|
122
|
+
score += len(self.high_findings) * 15
|
|
123
|
+
score += len(self.medium_findings) * 8
|
|
124
|
+
score += len(self.low_findings) * 2
|
|
125
|
+
return min(100, score)
|
|
126
|
+
|
|
127
|
+
def get_risk_rating(self) -> str:
|
|
128
|
+
"""Get risk rating based on score"""
|
|
129
|
+
score = self.get_risk_score()
|
|
130
|
+
if score >= 75:
|
|
131
|
+
return "Critical"
|
|
132
|
+
elif score >= 50:
|
|
133
|
+
return "High"
|
|
134
|
+
elif score >= 25:
|
|
135
|
+
return "Medium"
|
|
136
|
+
elif score > 0:
|
|
137
|
+
return "Low"
|
|
138
|
+
return "Informational"
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
class ReportGenerator:
|
|
142
|
+
"""
|
|
143
|
+
Generates professional pentest reports.
|
|
144
|
+
|
|
145
|
+
Example:
|
|
146
|
+
generator = ReportGenerator(config)
|
|
147
|
+
paths = await generator.generate(pipeline_result)
|
|
148
|
+
print(f"Reports saved to: {paths}")
|
|
149
|
+
"""
|
|
150
|
+
|
|
151
|
+
def __init__(self, config: ReportConfig | None = None):
|
|
152
|
+
self.config = config or ReportConfig()
|
|
153
|
+
|
|
154
|
+
async def generate(self, result: PipelineResult) -> dict[str, Path]:
|
|
155
|
+
"""
|
|
156
|
+
Generate reports in all configured formats.
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
result: Pipeline result with findings
|
|
160
|
+
|
|
161
|
+
Returns:
|
|
162
|
+
Dictionary of format -> file path
|
|
163
|
+
"""
|
|
164
|
+
self.config.output_dir.mkdir(parents=True, exist_ok=True)
|
|
165
|
+
|
|
166
|
+
# Prepare report data
|
|
167
|
+
data = ReportData.from_pipeline_result(result, self.config)
|
|
168
|
+
|
|
169
|
+
generated_files = {}
|
|
170
|
+
|
|
171
|
+
if "html" in self.config.formats:
|
|
172
|
+
path = await self._generate_html(data)
|
|
173
|
+
generated_files["html"] = path
|
|
174
|
+
|
|
175
|
+
if "json" in self.config.formats:
|
|
176
|
+
path = await self._generate_json(data, result)
|
|
177
|
+
generated_files["json"] = path
|
|
178
|
+
|
|
179
|
+
if "markdown" in self.config.formats:
|
|
180
|
+
path = await self._generate_markdown(data)
|
|
181
|
+
generated_files["markdown"] = path
|
|
182
|
+
|
|
183
|
+
logger.info(f"Generated {len(generated_files)} report(s)")
|
|
184
|
+
return generated_files
|
|
185
|
+
|
|
186
|
+
async def _generate_html(self, data: ReportData) -> Path:
|
|
187
|
+
"""Generate HTML report"""
|
|
188
|
+
from .html_report import generate_html_report
|
|
189
|
+
|
|
190
|
+
html_content = generate_html_report(data)
|
|
191
|
+
|
|
192
|
+
filename = f"aipt3_report_{data.scan_id}_{data.generated_at.strftime('%Y%m%d_%H%M%S')}.html"
|
|
193
|
+
path = self.config.output_dir / filename
|
|
194
|
+
|
|
195
|
+
path.write_text(html_content)
|
|
196
|
+
logger.info(f"Generated HTML report: {path}")
|
|
197
|
+
|
|
198
|
+
return path
|
|
199
|
+
|
|
200
|
+
async def _generate_json(self, data: ReportData, result: PipelineResult) -> Path:
|
|
201
|
+
"""Generate JSON report"""
|
|
202
|
+
json_data = {
|
|
203
|
+
"metadata": {
|
|
204
|
+
"scan_id": data.scan_id,
|
|
205
|
+
"target": data.target,
|
|
206
|
+
"generated_at": data.generated_at.isoformat(),
|
|
207
|
+
"client_name": data.config.client_name,
|
|
208
|
+
"project_name": data.config.project_name,
|
|
209
|
+
"assessor": data.config.assessor_name,
|
|
210
|
+
},
|
|
211
|
+
"summary": {
|
|
212
|
+
"total_findings": data.total_findings,
|
|
213
|
+
"severity_counts": data.get_severity_counts(),
|
|
214
|
+
"risk_score": data.get_risk_score(),
|
|
215
|
+
"risk_rating": data.get_risk_rating(),
|
|
216
|
+
"unique_vuln_types": data.unique_vuln_types,
|
|
217
|
+
"sources": data.sources,
|
|
218
|
+
"ai_findings_count": data.ai_findings_count,
|
|
219
|
+
},
|
|
220
|
+
"findings": [f.to_dict() for f in result.get_all_findings()],
|
|
221
|
+
"phases": {
|
|
222
|
+
phase.value: {
|
|
223
|
+
"status": pr.status.value,
|
|
224
|
+
"duration": pr.duration_seconds,
|
|
225
|
+
"findings_count": len(pr.findings),
|
|
226
|
+
"errors": pr.errors,
|
|
227
|
+
}
|
|
228
|
+
for phase, pr in result.phase_results.items()
|
|
229
|
+
},
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
filename = f"aipt3_report_{data.scan_id}_{data.generated_at.strftime('%Y%m%d_%H%M%S')}.json"
|
|
233
|
+
path = self.config.output_dir / filename
|
|
234
|
+
|
|
235
|
+
path.write_text(json.dumps(json_data, indent=2, default=str))
|
|
236
|
+
logger.info(f"Generated JSON report: {path}")
|
|
237
|
+
|
|
238
|
+
return path
|
|
239
|
+
|
|
240
|
+
async def _generate_markdown(self, data: ReportData) -> Path:
|
|
241
|
+
"""Generate Markdown report"""
|
|
242
|
+
lines = [
|
|
243
|
+
f"# Security Assessment Report",
|
|
244
|
+
f"",
|
|
245
|
+
f"**Target:** {data.target}",
|
|
246
|
+
f"**Scan ID:** {data.scan_id}",
|
|
247
|
+
f"**Date:** {data.generated_at.strftime('%Y-%m-%d %H:%M UTC')}",
|
|
248
|
+
f"**Risk Rating:** {data.get_risk_rating()} ({data.get_risk_score()}/100)",
|
|
249
|
+
f"",
|
|
250
|
+
f"## Executive Summary",
|
|
251
|
+
f"",
|
|
252
|
+
f"This automated security assessment identified **{data.total_findings}** vulnerabilities:",
|
|
253
|
+
f"",
|
|
254
|
+
f"| Severity | Count |",
|
|
255
|
+
f"|----------|-------|",
|
|
256
|
+
f"| Critical | {len(data.critical_findings)} |",
|
|
257
|
+
f"| High | {len(data.high_findings)} |",
|
|
258
|
+
f"| Medium | {len(data.medium_findings)} |",
|
|
259
|
+
f"| Low | {len(data.low_findings)} |",
|
|
260
|
+
f"| Info | {len(data.info_findings)} |",
|
|
261
|
+
f"",
|
|
262
|
+
]
|
|
263
|
+
|
|
264
|
+
# Add findings sections
|
|
265
|
+
for severity_name, findings in [
|
|
266
|
+
("Critical", data.critical_findings),
|
|
267
|
+
("High", data.high_findings),
|
|
268
|
+
("Medium", data.medium_findings),
|
|
269
|
+
("Low", data.low_findings),
|
|
270
|
+
]:
|
|
271
|
+
if findings:
|
|
272
|
+
lines.append(f"## {severity_name} Severity Findings")
|
|
273
|
+
lines.append("")
|
|
274
|
+
|
|
275
|
+
for i, finding in enumerate(findings, 1):
|
|
276
|
+
lines.append(f"### {i}. {finding.title}")
|
|
277
|
+
lines.append(f"")
|
|
278
|
+
lines.append(f"**URL:** `{finding.url}`")
|
|
279
|
+
if finding.parameter:
|
|
280
|
+
lines.append(f"**Parameter:** `{finding.parameter}`")
|
|
281
|
+
lines.append(f"**Source:** {finding.source}")
|
|
282
|
+
lines.append(f"")
|
|
283
|
+
if finding.description:
|
|
284
|
+
lines.append(f"**Description:**")
|
|
285
|
+
lines.append(f"{finding.description}")
|
|
286
|
+
lines.append(f"")
|
|
287
|
+
if finding.evidence and self.config.include_evidence:
|
|
288
|
+
lines.append(f"**Evidence:**")
|
|
289
|
+
lines.append(f"```")
|
|
290
|
+
lines.append(finding.evidence[:1000])
|
|
291
|
+
lines.append(f"```")
|
|
292
|
+
lines.append(f"")
|
|
293
|
+
if finding.remediation and self.config.include_remediation:
|
|
294
|
+
lines.append(f"**Remediation:**")
|
|
295
|
+
lines.append(f"{finding.remediation}")
|
|
296
|
+
lines.append(f"")
|
|
297
|
+
|
|
298
|
+
# Footer
|
|
299
|
+
lines.extend([
|
|
300
|
+
f"---",
|
|
301
|
+
f"",
|
|
302
|
+
f"*Generated by AIPT - AI-Powered Penetration Testing Framework*",
|
|
303
|
+
])
|
|
304
|
+
|
|
305
|
+
content = "\n".join(lines)
|
|
306
|
+
|
|
307
|
+
filename = f"aipt3_report_{data.scan_id}_{data.generated_at.strftime('%Y%m%d_%H%M%S')}.md"
|
|
308
|
+
path = self.config.output_dir / filename
|
|
309
|
+
|
|
310
|
+
path.write_text(content)
|
|
311
|
+
logger.info(f"Generated Markdown report: {path}")
|
|
312
|
+
|
|
313
|
+
return path
|
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AIPT HTML Report Template
|
|
3
|
+
|
|
4
|
+
Generates a standalone HTML report with embedded CSS.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import TYPE_CHECKING
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from .generator import ReportData
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def generate_html_report(data: "ReportData") -> str:
|
|
14
|
+
"""Generate standalone HTML report"""
|
|
15
|
+
|
|
16
|
+
severity_counts = data.get_severity_counts()
|
|
17
|
+
risk_score = data.get_risk_score()
|
|
18
|
+
risk_rating = data.get_risk_rating()
|
|
19
|
+
|
|
20
|
+
# Build findings HTML
|
|
21
|
+
findings_html = ""
|
|
22
|
+
|
|
23
|
+
for severity_name, findings, color in [
|
|
24
|
+
("Critical", data.critical_findings, "#dc2626"),
|
|
25
|
+
("High", data.high_findings, "#ea580c"),
|
|
26
|
+
("Medium", data.medium_findings, "#ca8a04"),
|
|
27
|
+
("Low", data.low_findings, "#2563eb"),
|
|
28
|
+
("Informational", data.info_findings, "#6b7280"),
|
|
29
|
+
]:
|
|
30
|
+
if findings:
|
|
31
|
+
findings_html += f"""
|
|
32
|
+
<div class="severity-section">
|
|
33
|
+
<h2 style="color: {color};">{severity_name} Severity ({len(findings)})</h2>
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
for i, finding in enumerate(findings, 1):
|
|
37
|
+
evidence_html = ""
|
|
38
|
+
if finding.evidence and data.config.include_evidence:
|
|
39
|
+
evidence_html = f"""
|
|
40
|
+
<div class="evidence">
|
|
41
|
+
<strong>Evidence:</strong>
|
|
42
|
+
<pre>{_escape_html(finding.evidence[:2000])}</pre>
|
|
43
|
+
</div>
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
remediation_html = ""
|
|
47
|
+
if finding.remediation and data.config.include_remediation:
|
|
48
|
+
remediation_html = f"""
|
|
49
|
+
<div class="remediation">
|
|
50
|
+
<strong>Remediation:</strong>
|
|
51
|
+
<p>{_escape_html(finding.remediation)}</p>
|
|
52
|
+
</div>
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
ai_html = ""
|
|
56
|
+
if finding.ai_reasoning and data.config.include_ai_reasoning:
|
|
57
|
+
ai_html = f"""
|
|
58
|
+
<div class="ai-reasoning">
|
|
59
|
+
<strong>AI Analysis:</strong>
|
|
60
|
+
<p>{_escape_html(finding.ai_reasoning[:500])}</p>
|
|
61
|
+
</div>
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
findings_html += f"""
|
|
65
|
+
<div class="finding" style="border-left-color: {color};">
|
|
66
|
+
<h3>{i}. {_escape_html(finding.title)}</h3>
|
|
67
|
+
<div class="finding-meta">
|
|
68
|
+
<span class="badge" style="background-color: {color};">{severity_name}</span>
|
|
69
|
+
<span class="badge source">{finding.source}</span>
|
|
70
|
+
<span class="vuln-type">{finding.vuln_type.value}</span>
|
|
71
|
+
</div>
|
|
72
|
+
<div class="finding-details">
|
|
73
|
+
<p><strong>URL:</strong> <code>{_escape_html(finding.url)}</code></p>
|
|
74
|
+
{f'<p><strong>Parameter:</strong> <code>{_escape_html(finding.parameter)}</code></p>' if finding.parameter else ''}
|
|
75
|
+
{f'<p><strong>Description:</strong> {_escape_html(finding.description)}</p>' if finding.description else ''}
|
|
76
|
+
{evidence_html}
|
|
77
|
+
{remediation_html}
|
|
78
|
+
{ai_html}
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
"""
|
|
82
|
+
|
|
83
|
+
findings_html += "</div>"
|
|
84
|
+
|
|
85
|
+
# Risk color
|
|
86
|
+
risk_colors = {
|
|
87
|
+
"Critical": "#dc2626",
|
|
88
|
+
"High": "#ea580c",
|
|
89
|
+
"Medium": "#ca8a04",
|
|
90
|
+
"Low": "#2563eb",
|
|
91
|
+
"Informational": "#6b7280",
|
|
92
|
+
}
|
|
93
|
+
risk_color = risk_colors.get(risk_rating, "#6b7280")
|
|
94
|
+
|
|
95
|
+
html = f"""<!DOCTYPE html>
|
|
96
|
+
<html lang="en">
|
|
97
|
+
<head>
|
|
98
|
+
<meta charset="UTF-8">
|
|
99
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
100
|
+
<title>Security Assessment Report - {_escape_html(data.target)}</title>
|
|
101
|
+
<style>
|
|
102
|
+
:root {{
|
|
103
|
+
--primary: #1e40af;
|
|
104
|
+
--critical: #dc2626;
|
|
105
|
+
--high: #ea580c;
|
|
106
|
+
--medium: #ca8a04;
|
|
107
|
+
--low: #2563eb;
|
|
108
|
+
--info: #6b7280;
|
|
109
|
+
--bg: #f8fafc;
|
|
110
|
+
--card-bg: #ffffff;
|
|
111
|
+
--text: #1e293b;
|
|
112
|
+
--text-muted: #64748b;
|
|
113
|
+
--border: #e2e8f0;
|
|
114
|
+
}}
|
|
115
|
+
|
|
116
|
+
* {{
|
|
117
|
+
margin: 0;
|
|
118
|
+
padding: 0;
|
|
119
|
+
box-sizing: border-box;
|
|
120
|
+
}}
|
|
121
|
+
|
|
122
|
+
body {{
|
|
123
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
124
|
+
background: var(--bg);
|
|
125
|
+
color: var(--text);
|
|
126
|
+
line-height: 1.6;
|
|
127
|
+
}}
|
|
128
|
+
|
|
129
|
+
.container {{
|
|
130
|
+
max-width: 1200px;
|
|
131
|
+
margin: 0 auto;
|
|
132
|
+
padding: 2rem;
|
|
133
|
+
}}
|
|
134
|
+
|
|
135
|
+
header {{
|
|
136
|
+
background: linear-gradient(135deg, var(--primary), #3b82f6);
|
|
137
|
+
color: white;
|
|
138
|
+
padding: 3rem 2rem;
|
|
139
|
+
margin-bottom: 2rem;
|
|
140
|
+
border-radius: 0 0 1rem 1rem;
|
|
141
|
+
}}
|
|
142
|
+
|
|
143
|
+
header h1 {{
|
|
144
|
+
font-size: 2rem;
|
|
145
|
+
margin-bottom: 0.5rem;
|
|
146
|
+
}}
|
|
147
|
+
|
|
148
|
+
header .meta {{
|
|
149
|
+
opacity: 0.9;
|
|
150
|
+
font-size: 0.95rem;
|
|
151
|
+
}}
|
|
152
|
+
|
|
153
|
+
.summary-grid {{
|
|
154
|
+
display: grid;
|
|
155
|
+
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
156
|
+
gap: 1rem;
|
|
157
|
+
margin-bottom: 2rem;
|
|
158
|
+
}}
|
|
159
|
+
|
|
160
|
+
.summary-card {{
|
|
161
|
+
background: var(--card-bg);
|
|
162
|
+
border-radius: 0.75rem;
|
|
163
|
+
padding: 1.5rem;
|
|
164
|
+
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
|
165
|
+
}}
|
|
166
|
+
|
|
167
|
+
.summary-card h3 {{
|
|
168
|
+
font-size: 0.875rem;
|
|
169
|
+
color: var(--text-muted);
|
|
170
|
+
text-transform: uppercase;
|
|
171
|
+
letter-spacing: 0.05em;
|
|
172
|
+
margin-bottom: 0.5rem;
|
|
173
|
+
}}
|
|
174
|
+
|
|
175
|
+
.summary-card .value {{
|
|
176
|
+
font-size: 2rem;
|
|
177
|
+
font-weight: 700;
|
|
178
|
+
}}
|
|
179
|
+
|
|
180
|
+
.risk-score {{
|
|
181
|
+
background: {risk_color};
|
|
182
|
+
color: white;
|
|
183
|
+
}}
|
|
184
|
+
|
|
185
|
+
.severity-breakdown {{
|
|
186
|
+
display: flex;
|
|
187
|
+
gap: 0.5rem;
|
|
188
|
+
margin-top: 1rem;
|
|
189
|
+
}}
|
|
190
|
+
|
|
191
|
+
.severity-dot {{
|
|
192
|
+
display: flex;
|
|
193
|
+
align-items: center;
|
|
194
|
+
gap: 0.25rem;
|
|
195
|
+
font-size: 0.875rem;
|
|
196
|
+
}}
|
|
197
|
+
|
|
198
|
+
.severity-dot::before {{
|
|
199
|
+
content: '';
|
|
200
|
+
width: 12px;
|
|
201
|
+
height: 12px;
|
|
202
|
+
border-radius: 50%;
|
|
203
|
+
}}
|
|
204
|
+
|
|
205
|
+
.severity-dot.critical::before {{ background: var(--critical); }}
|
|
206
|
+
.severity-dot.high::before {{ background: var(--high); }}
|
|
207
|
+
.severity-dot.medium::before {{ background: var(--medium); }}
|
|
208
|
+
.severity-dot.low::before {{ background: var(--low); }}
|
|
209
|
+
.severity-dot.info::before {{ background: var(--info); }}
|
|
210
|
+
|
|
211
|
+
.severity-section {{
|
|
212
|
+
margin-bottom: 2rem;
|
|
213
|
+
}}
|
|
214
|
+
|
|
215
|
+
.severity-section h2 {{
|
|
216
|
+
font-size: 1.5rem;
|
|
217
|
+
margin-bottom: 1rem;
|
|
218
|
+
padding-bottom: 0.5rem;
|
|
219
|
+
border-bottom: 2px solid var(--border);
|
|
220
|
+
}}
|
|
221
|
+
|
|
222
|
+
.finding {{
|
|
223
|
+
background: var(--card-bg);
|
|
224
|
+
border-radius: 0.75rem;
|
|
225
|
+
padding: 1.5rem;
|
|
226
|
+
margin-bottom: 1rem;
|
|
227
|
+
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
|
228
|
+
border-left: 4px solid;
|
|
229
|
+
}}
|
|
230
|
+
|
|
231
|
+
.finding h3 {{
|
|
232
|
+
font-size: 1.125rem;
|
|
233
|
+
margin-bottom: 0.75rem;
|
|
234
|
+
}}
|
|
235
|
+
|
|
236
|
+
.finding-meta {{
|
|
237
|
+
display: flex;
|
|
238
|
+
gap: 0.5rem;
|
|
239
|
+
flex-wrap: wrap;
|
|
240
|
+
margin-bottom: 1rem;
|
|
241
|
+
}}
|
|
242
|
+
|
|
243
|
+
.badge {{
|
|
244
|
+
display: inline-block;
|
|
245
|
+
padding: 0.25rem 0.75rem;
|
|
246
|
+
border-radius: 9999px;
|
|
247
|
+
font-size: 0.75rem;
|
|
248
|
+
font-weight: 600;
|
|
249
|
+
color: white;
|
|
250
|
+
}}
|
|
251
|
+
|
|
252
|
+
.badge.source {{
|
|
253
|
+
background: var(--primary);
|
|
254
|
+
}}
|
|
255
|
+
|
|
256
|
+
.vuln-type {{
|
|
257
|
+
font-size: 0.875rem;
|
|
258
|
+
color: var(--text-muted);
|
|
259
|
+
}}
|
|
260
|
+
|
|
261
|
+
.finding-details p {{
|
|
262
|
+
margin-bottom: 0.5rem;
|
|
263
|
+
}}
|
|
264
|
+
|
|
265
|
+
.finding-details code {{
|
|
266
|
+
background: var(--bg);
|
|
267
|
+
padding: 0.125rem 0.375rem;
|
|
268
|
+
border-radius: 0.25rem;
|
|
269
|
+
font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace;
|
|
270
|
+
font-size: 0.875rem;
|
|
271
|
+
}}
|
|
272
|
+
|
|
273
|
+
.evidence, .remediation, .ai-reasoning {{
|
|
274
|
+
margin-top: 1rem;
|
|
275
|
+
padding: 1rem;
|
|
276
|
+
background: var(--bg);
|
|
277
|
+
border-radius: 0.5rem;
|
|
278
|
+
}}
|
|
279
|
+
|
|
280
|
+
.evidence pre {{
|
|
281
|
+
white-space: pre-wrap;
|
|
282
|
+
word-wrap: break-word;
|
|
283
|
+
font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace;
|
|
284
|
+
font-size: 0.8rem;
|
|
285
|
+
max-height: 300px;
|
|
286
|
+
overflow-y: auto;
|
|
287
|
+
}}
|
|
288
|
+
|
|
289
|
+
.ai-reasoning {{
|
|
290
|
+
border-left: 3px solid var(--primary);
|
|
291
|
+
}}
|
|
292
|
+
|
|
293
|
+
footer {{
|
|
294
|
+
text-align: center;
|
|
295
|
+
padding: 2rem;
|
|
296
|
+
color: var(--text-muted);
|
|
297
|
+
font-size: 0.875rem;
|
|
298
|
+
}}
|
|
299
|
+
|
|
300
|
+
@media print {{
|
|
301
|
+
body {{ background: white; }}
|
|
302
|
+
.container {{ max-width: 100%; }}
|
|
303
|
+
.finding {{ break-inside: avoid; }}
|
|
304
|
+
}}
|
|
305
|
+
</style>
|
|
306
|
+
</head>
|
|
307
|
+
<body>
|
|
308
|
+
<header>
|
|
309
|
+
<div class="container">
|
|
310
|
+
<h1>Security Assessment Report</h1>
|
|
311
|
+
<div class="meta">
|
|
312
|
+
<p><strong>Target:</strong> {_escape_html(data.target)}</p>
|
|
313
|
+
<p><strong>Scan ID:</strong> {data.scan_id}</p>
|
|
314
|
+
<p><strong>Date:</strong> {data.generated_at.strftime('%B %d, %Y at %H:%M UTC')}</p>
|
|
315
|
+
<p><strong>Client:</strong> {_escape_html(data.config.client_name)} | <strong>Project:</strong> {_escape_html(data.config.project_name)}</p>
|
|
316
|
+
</div>
|
|
317
|
+
</div>
|
|
318
|
+
</header>
|
|
319
|
+
|
|
320
|
+
<div class="container">
|
|
321
|
+
<section class="summary-grid">
|
|
322
|
+
<div class="summary-card risk-score">
|
|
323
|
+
<h3>Risk Rating</h3>
|
|
324
|
+
<div class="value">{risk_rating}</div>
|
|
325
|
+
<div style="font-size: 0.875rem; opacity: 0.9;">Score: {risk_score}/100</div>
|
|
326
|
+
</div>
|
|
327
|
+
|
|
328
|
+
<div class="summary-card">
|
|
329
|
+
<h3>Total Findings</h3>
|
|
330
|
+
<div class="value">{data.total_findings}</div>
|
|
331
|
+
<div class="severity-breakdown">
|
|
332
|
+
<span class="severity-dot critical">{severity_counts['critical']}</span>
|
|
333
|
+
<span class="severity-dot high">{severity_counts['high']}</span>
|
|
334
|
+
<span class="severity-dot medium">{severity_counts['medium']}</span>
|
|
335
|
+
<span class="severity-dot low">{severity_counts['low']}</span>
|
|
336
|
+
</div>
|
|
337
|
+
</div>
|
|
338
|
+
|
|
339
|
+
<div class="summary-card">
|
|
340
|
+
<h3>AI Findings</h3>
|
|
341
|
+
<div class="value">{data.ai_findings_count}</div>
|
|
342
|
+
<div style="font-size: 0.875rem; color: var(--text-muted);">Strix AI-discovered vulnerabilities</div>
|
|
343
|
+
</div>
|
|
344
|
+
|
|
345
|
+
<div class="summary-card">
|
|
346
|
+
<h3>Scan Sources</h3>
|
|
347
|
+
<div class="value">{len(data.sources)}</div>
|
|
348
|
+
<div style="font-size: 0.875rem; color: var(--text-muted);">{', '.join(data.sources) if data.sources else 'N/A'}</div>
|
|
349
|
+
</div>
|
|
350
|
+
</section>
|
|
351
|
+
|
|
352
|
+
<section class="findings">
|
|
353
|
+
{findings_html if findings_html else '<p style="text-align: center; color: var(--text-muted); padding: 3rem;">No vulnerabilities found.</p>'}
|
|
354
|
+
</section>
|
|
355
|
+
</div>
|
|
356
|
+
|
|
357
|
+
<footer>
|
|
358
|
+
<p>Generated by <strong>AIPT</strong> - AI-Powered Penetration Testing Framework</p>
|
|
359
|
+
<p>Report generated on {data.generated_at.strftime('%Y-%m-%d %H:%M:%S UTC')}</p>
|
|
360
|
+
</footer>
|
|
361
|
+
</body>
|
|
362
|
+
</html>"""
|
|
363
|
+
|
|
364
|
+
return html
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
def _escape_html(text: str) -> str:
|
|
368
|
+
"""Escape HTML special characters"""
|
|
369
|
+
if not text:
|
|
370
|
+
return ""
|
|
371
|
+
return (
|
|
372
|
+
str(text)
|
|
373
|
+
.replace("&", "&")
|
|
374
|
+
.replace("<", "<")
|
|
375
|
+
.replace(">", ">")
|
|
376
|
+
.replace('"', """)
|
|
377
|
+
.replace("'", "'")
|
|
378
|
+
)
|