aiptx 2.0.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of aiptx might be problematic. Click here for more details.

Files changed (165) hide show
  1. aipt_v2/__init__.py +110 -0
  2. aipt_v2/__main__.py +24 -0
  3. aipt_v2/agents/AIPTxAgent/__init__.py +10 -0
  4. aipt_v2/agents/AIPTxAgent/aiptx_agent.py +211 -0
  5. aipt_v2/agents/__init__.py +24 -0
  6. aipt_v2/agents/base.py +520 -0
  7. aipt_v2/agents/ptt.py +406 -0
  8. aipt_v2/agents/state.py +168 -0
  9. aipt_v2/app.py +960 -0
  10. aipt_v2/browser/__init__.py +31 -0
  11. aipt_v2/browser/automation.py +458 -0
  12. aipt_v2/browser/crawler.py +453 -0
  13. aipt_v2/cli.py +321 -0
  14. aipt_v2/compliance/__init__.py +71 -0
  15. aipt_v2/compliance/compliance_report.py +449 -0
  16. aipt_v2/compliance/framework_mapper.py +424 -0
  17. aipt_v2/compliance/nist_mapping.py +345 -0
  18. aipt_v2/compliance/owasp_mapping.py +330 -0
  19. aipt_v2/compliance/pci_mapping.py +297 -0
  20. aipt_v2/config.py +288 -0
  21. aipt_v2/core/__init__.py +43 -0
  22. aipt_v2/core/agent.py +630 -0
  23. aipt_v2/core/llm.py +395 -0
  24. aipt_v2/core/memory.py +305 -0
  25. aipt_v2/core/ptt.py +329 -0
  26. aipt_v2/database/__init__.py +14 -0
  27. aipt_v2/database/models.py +232 -0
  28. aipt_v2/database/repository.py +384 -0
  29. aipt_v2/docker/__init__.py +23 -0
  30. aipt_v2/docker/builder.py +260 -0
  31. aipt_v2/docker/manager.py +222 -0
  32. aipt_v2/docker/sandbox.py +371 -0
  33. aipt_v2/evasion/__init__.py +58 -0
  34. aipt_v2/evasion/request_obfuscator.py +272 -0
  35. aipt_v2/evasion/tls_fingerprint.py +285 -0
  36. aipt_v2/evasion/ua_rotator.py +301 -0
  37. aipt_v2/evasion/waf_bypass.py +439 -0
  38. aipt_v2/execution/__init__.py +23 -0
  39. aipt_v2/execution/executor.py +302 -0
  40. aipt_v2/execution/parser.py +544 -0
  41. aipt_v2/execution/terminal.py +337 -0
  42. aipt_v2/health.py +437 -0
  43. aipt_v2/intelligence/__init__.py +85 -0
  44. aipt_v2/intelligence/auth.py +520 -0
  45. aipt_v2/intelligence/chaining.py +775 -0
  46. aipt_v2/intelligence/cve_aipt.py +334 -0
  47. aipt_v2/intelligence/cve_info.py +1111 -0
  48. aipt_v2/intelligence/rag.py +239 -0
  49. aipt_v2/intelligence/scope.py +442 -0
  50. aipt_v2/intelligence/searchers/__init__.py +5 -0
  51. aipt_v2/intelligence/searchers/exploitdb_searcher.py +523 -0
  52. aipt_v2/intelligence/searchers/github_searcher.py +467 -0
  53. aipt_v2/intelligence/searchers/google_searcher.py +281 -0
  54. aipt_v2/intelligence/tools.json +443 -0
  55. aipt_v2/intelligence/triage.py +670 -0
  56. aipt_v2/interface/__init__.py +5 -0
  57. aipt_v2/interface/cli.py +230 -0
  58. aipt_v2/interface/main.py +501 -0
  59. aipt_v2/interface/tui.py +1276 -0
  60. aipt_v2/interface/utils.py +583 -0
  61. aipt_v2/llm/__init__.py +39 -0
  62. aipt_v2/llm/config.py +26 -0
  63. aipt_v2/llm/llm.py +514 -0
  64. aipt_v2/llm/memory.py +214 -0
  65. aipt_v2/llm/request_queue.py +89 -0
  66. aipt_v2/llm/utils.py +89 -0
  67. aipt_v2/models/__init__.py +15 -0
  68. aipt_v2/models/findings.py +295 -0
  69. aipt_v2/models/phase_result.py +224 -0
  70. aipt_v2/models/scan_config.py +207 -0
  71. aipt_v2/monitoring/grafana/dashboards/aipt-dashboard.json +355 -0
  72. aipt_v2/monitoring/grafana/dashboards/default.yml +17 -0
  73. aipt_v2/monitoring/grafana/datasources/prometheus.yml +17 -0
  74. aipt_v2/monitoring/prometheus.yml +60 -0
  75. aipt_v2/orchestration/__init__.py +52 -0
  76. aipt_v2/orchestration/pipeline.py +398 -0
  77. aipt_v2/orchestration/progress.py +300 -0
  78. aipt_v2/orchestration/scheduler.py +296 -0
  79. aipt_v2/orchestrator.py +2284 -0
  80. aipt_v2/payloads/__init__.py +27 -0
  81. aipt_v2/payloads/cmdi.py +150 -0
  82. aipt_v2/payloads/sqli.py +263 -0
  83. aipt_v2/payloads/ssrf.py +204 -0
  84. aipt_v2/payloads/templates.py +222 -0
  85. aipt_v2/payloads/traversal.py +166 -0
  86. aipt_v2/payloads/xss.py +204 -0
  87. aipt_v2/prompts/__init__.py +60 -0
  88. aipt_v2/proxy/__init__.py +29 -0
  89. aipt_v2/proxy/history.py +352 -0
  90. aipt_v2/proxy/interceptor.py +452 -0
  91. aipt_v2/recon/__init__.py +44 -0
  92. aipt_v2/recon/dns.py +241 -0
  93. aipt_v2/recon/osint.py +367 -0
  94. aipt_v2/recon/subdomain.py +372 -0
  95. aipt_v2/recon/tech_detect.py +311 -0
  96. aipt_v2/reports/__init__.py +17 -0
  97. aipt_v2/reports/generator.py +313 -0
  98. aipt_v2/reports/html_report.py +378 -0
  99. aipt_v2/runtime/__init__.py +44 -0
  100. aipt_v2/runtime/base.py +30 -0
  101. aipt_v2/runtime/docker.py +401 -0
  102. aipt_v2/runtime/local.py +346 -0
  103. aipt_v2/runtime/tool_server.py +205 -0
  104. aipt_v2/scanners/__init__.py +28 -0
  105. aipt_v2/scanners/base.py +273 -0
  106. aipt_v2/scanners/nikto.py +244 -0
  107. aipt_v2/scanners/nmap.py +402 -0
  108. aipt_v2/scanners/nuclei.py +273 -0
  109. aipt_v2/scanners/web.py +454 -0
  110. aipt_v2/scripts/security_audit.py +366 -0
  111. aipt_v2/telemetry/__init__.py +7 -0
  112. aipt_v2/telemetry/tracer.py +347 -0
  113. aipt_v2/terminal/__init__.py +28 -0
  114. aipt_v2/terminal/executor.py +400 -0
  115. aipt_v2/terminal/sandbox.py +350 -0
  116. aipt_v2/tools/__init__.py +44 -0
  117. aipt_v2/tools/active_directory/__init__.py +78 -0
  118. aipt_v2/tools/active_directory/ad_config.py +238 -0
  119. aipt_v2/tools/active_directory/bloodhound_wrapper.py +447 -0
  120. aipt_v2/tools/active_directory/kerberos_attacks.py +430 -0
  121. aipt_v2/tools/active_directory/ldap_enum.py +533 -0
  122. aipt_v2/tools/active_directory/smb_attacks.py +505 -0
  123. aipt_v2/tools/agents_graph/__init__.py +19 -0
  124. aipt_v2/tools/agents_graph/agents_graph_actions.py +69 -0
  125. aipt_v2/tools/api_security/__init__.py +76 -0
  126. aipt_v2/tools/api_security/api_discovery.py +608 -0
  127. aipt_v2/tools/api_security/graphql_scanner.py +622 -0
  128. aipt_v2/tools/api_security/jwt_analyzer.py +577 -0
  129. aipt_v2/tools/api_security/openapi_fuzzer.py +761 -0
  130. aipt_v2/tools/browser/__init__.py +5 -0
  131. aipt_v2/tools/browser/browser_actions.py +238 -0
  132. aipt_v2/tools/browser/browser_instance.py +535 -0
  133. aipt_v2/tools/browser/tab_manager.py +344 -0
  134. aipt_v2/tools/cloud/__init__.py +70 -0
  135. aipt_v2/tools/cloud/cloud_config.py +273 -0
  136. aipt_v2/tools/cloud/cloud_scanner.py +639 -0
  137. aipt_v2/tools/cloud/prowler_tool.py +571 -0
  138. aipt_v2/tools/cloud/scoutsuite_tool.py +359 -0
  139. aipt_v2/tools/executor.py +307 -0
  140. aipt_v2/tools/parser.py +408 -0
  141. aipt_v2/tools/proxy/__init__.py +5 -0
  142. aipt_v2/tools/proxy/proxy_actions.py +103 -0
  143. aipt_v2/tools/proxy/proxy_manager.py +789 -0
  144. aipt_v2/tools/registry.py +196 -0
  145. aipt_v2/tools/scanners/__init__.py +343 -0
  146. aipt_v2/tools/scanners/acunetix_tool.py +712 -0
  147. aipt_v2/tools/scanners/burp_tool.py +631 -0
  148. aipt_v2/tools/scanners/config.py +156 -0
  149. aipt_v2/tools/scanners/nessus_tool.py +588 -0
  150. aipt_v2/tools/scanners/zap_tool.py +612 -0
  151. aipt_v2/tools/terminal/__init__.py +5 -0
  152. aipt_v2/tools/terminal/terminal_actions.py +37 -0
  153. aipt_v2/tools/terminal/terminal_manager.py +153 -0
  154. aipt_v2/tools/terminal/terminal_session.py +449 -0
  155. aipt_v2/tools/tool_processing.py +108 -0
  156. aipt_v2/utils/__init__.py +17 -0
  157. aipt_v2/utils/logging.py +201 -0
  158. aipt_v2/utils/model_manager.py +187 -0
  159. aipt_v2/utils/searchers/__init__.py +269 -0
  160. aiptx-2.0.2.dist-info/METADATA +324 -0
  161. aiptx-2.0.2.dist-info/RECORD +165 -0
  162. aiptx-2.0.2.dist-info/WHEEL +5 -0
  163. aiptx-2.0.2.dist-info/entry_points.txt +7 -0
  164. aiptx-2.0.2.dist-info/licenses/LICENSE +21 -0
  165. aiptx-2.0.2.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("&", "&amp;")
374
+ .replace("<", "&lt;")
375
+ .replace(">", "&gt;")
376
+ .replace('"', "&quot;")
377
+ .replace("'", "&#39;")
378
+ )