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
aipt_v2/proxy/history.py
ADDED
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AIPT Proxy History
|
|
3
|
+
|
|
4
|
+
Traffic history management and analysis.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
import re
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
from datetime import datetime
|
|
12
|
+
from typing import Any, Optional
|
|
13
|
+
from urllib.parse import urlparse
|
|
14
|
+
|
|
15
|
+
from .interceptor import InterceptedRequest, InterceptedResponse
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class HistoryEntry:
|
|
20
|
+
"""A request/response pair"""
|
|
21
|
+
request: InterceptedRequest
|
|
22
|
+
response: Optional[InterceptedResponse] = None
|
|
23
|
+
|
|
24
|
+
# Analysis
|
|
25
|
+
has_parameters: bool = False
|
|
26
|
+
has_cookies: bool = False
|
|
27
|
+
has_auth: bool = False
|
|
28
|
+
interesting: bool = False
|
|
29
|
+
notes: str = ""
|
|
30
|
+
|
|
31
|
+
# Security indicators
|
|
32
|
+
security_flags: list[str] = field(default_factory=list)
|
|
33
|
+
|
|
34
|
+
def analyze(self) -> None:
|
|
35
|
+
"""Analyze entry for security-relevant features"""
|
|
36
|
+
self.has_parameters = bool(
|
|
37
|
+
self.request.query_params or
|
|
38
|
+
self.request.is_form or
|
|
39
|
+
self.request.is_json
|
|
40
|
+
)
|
|
41
|
+
self.has_cookies = bool(self.request.cookies)
|
|
42
|
+
self.has_auth = "authorization" in [h.lower() for h in self.request.headers.keys()]
|
|
43
|
+
|
|
44
|
+
# Check for interesting patterns
|
|
45
|
+
self._check_security_flags()
|
|
46
|
+
|
|
47
|
+
def _check_security_flags(self) -> None:
|
|
48
|
+
"""Check for security-relevant patterns"""
|
|
49
|
+
flags = []
|
|
50
|
+
|
|
51
|
+
# Request analysis
|
|
52
|
+
url_lower = self.request.url.lower()
|
|
53
|
+
|
|
54
|
+
# Potential admin endpoints
|
|
55
|
+
if any(p in url_lower for p in ["/admin", "/manage", "/dashboard", "/config"]):
|
|
56
|
+
flags.append("admin_endpoint")
|
|
57
|
+
|
|
58
|
+
# API endpoints
|
|
59
|
+
if "/api/" in url_lower or "/v1/" in url_lower or "/v2/" in url_lower:
|
|
60
|
+
flags.append("api_endpoint")
|
|
61
|
+
|
|
62
|
+
# Authentication endpoints
|
|
63
|
+
if any(p in url_lower for p in ["/login", "/auth", "/signin", "/token"]):
|
|
64
|
+
flags.append("auth_endpoint")
|
|
65
|
+
|
|
66
|
+
# File operations
|
|
67
|
+
if any(p in url_lower for p in ["/upload", "/download", "/file", "/export"]):
|
|
68
|
+
flags.append("file_operation")
|
|
69
|
+
|
|
70
|
+
# Potential SQL injection points (numeric IDs)
|
|
71
|
+
if re.search(r"/\d+(/|$|\?)", url_lower):
|
|
72
|
+
flags.append("numeric_id")
|
|
73
|
+
|
|
74
|
+
# Check request body for interesting patterns
|
|
75
|
+
body_text = self.request.get_body_text().lower()
|
|
76
|
+
if body_text:
|
|
77
|
+
if "password" in body_text or "passwd" in body_text:
|
|
78
|
+
flags.append("contains_password")
|
|
79
|
+
if "token" in body_text or "key" in body_text:
|
|
80
|
+
flags.append("contains_secret")
|
|
81
|
+
|
|
82
|
+
# Response analysis
|
|
83
|
+
if self.response:
|
|
84
|
+
resp_body = self.response.get_body_text().lower()
|
|
85
|
+
|
|
86
|
+
# Error messages that might leak info
|
|
87
|
+
if "error" in resp_body or "exception" in resp_body:
|
|
88
|
+
flags.append("error_response")
|
|
89
|
+
|
|
90
|
+
# Stack traces
|
|
91
|
+
if "traceback" in resp_body or "stack trace" in resp_body:
|
|
92
|
+
flags.append("stack_trace")
|
|
93
|
+
|
|
94
|
+
# SQL errors
|
|
95
|
+
if "sql" in resp_body and ("syntax" in resp_body or "error" in resp_body):
|
|
96
|
+
flags.append("sql_error")
|
|
97
|
+
|
|
98
|
+
# Interesting status codes
|
|
99
|
+
if self.response.status_code in [401, 403]:
|
|
100
|
+
flags.append("auth_required")
|
|
101
|
+
elif self.response.status_code >= 500:
|
|
102
|
+
flags.append("server_error")
|
|
103
|
+
|
|
104
|
+
self.security_flags = flags
|
|
105
|
+
self.interesting = len(flags) > 0
|
|
106
|
+
|
|
107
|
+
def to_dict(self) -> dict:
|
|
108
|
+
return {
|
|
109
|
+
"request": self.request.to_dict(),
|
|
110
|
+
"response": self.response.to_dict() if self.response else None,
|
|
111
|
+
"has_parameters": self.has_parameters,
|
|
112
|
+
"has_cookies": self.has_cookies,
|
|
113
|
+
"has_auth": self.has_auth,
|
|
114
|
+
"interesting": self.interesting,
|
|
115
|
+
"security_flags": self.security_flags,
|
|
116
|
+
"notes": self.notes,
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
class ProxyHistory:
|
|
121
|
+
"""
|
|
122
|
+
Proxy traffic history manager.
|
|
123
|
+
|
|
124
|
+
Features:
|
|
125
|
+
- Traffic storage and retrieval
|
|
126
|
+
- Filtering and search
|
|
127
|
+
- Security analysis
|
|
128
|
+
- Export capabilities
|
|
129
|
+
|
|
130
|
+
Example:
|
|
131
|
+
history = ProxyHistory()
|
|
132
|
+
history.add(request, response)
|
|
133
|
+
|
|
134
|
+
# Find interesting entries
|
|
135
|
+
interesting = history.get_interesting()
|
|
136
|
+
|
|
137
|
+
# Search
|
|
138
|
+
api_calls = history.search(path_contains="/api/")
|
|
139
|
+
"""
|
|
140
|
+
|
|
141
|
+
def __init__(self):
|
|
142
|
+
self._entries: list[HistoryEntry] = []
|
|
143
|
+
self._by_host: dict[str, list[HistoryEntry]] = {}
|
|
144
|
+
|
|
145
|
+
def add(
|
|
146
|
+
self,
|
|
147
|
+
request: InterceptedRequest,
|
|
148
|
+
response: Optional[InterceptedResponse] = None,
|
|
149
|
+
) -> HistoryEntry:
|
|
150
|
+
"""Add request/response to history"""
|
|
151
|
+
entry = HistoryEntry(request=request, response=response)
|
|
152
|
+
entry.analyze()
|
|
153
|
+
|
|
154
|
+
self._entries.append(entry)
|
|
155
|
+
|
|
156
|
+
# Index by host
|
|
157
|
+
host = request.host
|
|
158
|
+
if host not in self._by_host:
|
|
159
|
+
self._by_host[host] = []
|
|
160
|
+
self._by_host[host].append(entry)
|
|
161
|
+
|
|
162
|
+
return entry
|
|
163
|
+
|
|
164
|
+
def get_all(self) -> list[HistoryEntry]:
|
|
165
|
+
"""Get all entries"""
|
|
166
|
+
return self._entries.copy()
|
|
167
|
+
|
|
168
|
+
def get_by_host(self, host: str) -> list[HistoryEntry]:
|
|
169
|
+
"""Get entries for a specific host"""
|
|
170
|
+
return self._by_host.get(host, [])
|
|
171
|
+
|
|
172
|
+
def get_hosts(self) -> list[str]:
|
|
173
|
+
"""Get all unique hosts"""
|
|
174
|
+
return list(self._by_host.keys())
|
|
175
|
+
|
|
176
|
+
def get_interesting(self) -> list[HistoryEntry]:
|
|
177
|
+
"""Get entries flagged as interesting"""
|
|
178
|
+
return [e for e in self._entries if e.interesting]
|
|
179
|
+
|
|
180
|
+
def get_with_parameters(self) -> list[HistoryEntry]:
|
|
181
|
+
"""Get entries with parameters (potential injection points)"""
|
|
182
|
+
return [e for e in self._entries if e.has_parameters]
|
|
183
|
+
|
|
184
|
+
def get_authenticated(self) -> list[HistoryEntry]:
|
|
185
|
+
"""Get entries with authentication"""
|
|
186
|
+
return [e for e in self._entries if e.has_auth]
|
|
187
|
+
|
|
188
|
+
def get_by_flag(self, flag: str) -> list[HistoryEntry]:
|
|
189
|
+
"""Get entries with specific security flag"""
|
|
190
|
+
return [e for e in self._entries if flag in e.security_flags]
|
|
191
|
+
|
|
192
|
+
def search(
|
|
193
|
+
self,
|
|
194
|
+
method: Optional[str] = None,
|
|
195
|
+
host_contains: Optional[str] = None,
|
|
196
|
+
path_contains: Optional[str] = None,
|
|
197
|
+
status_code: Optional[int] = None,
|
|
198
|
+
content_type: Optional[str] = None,
|
|
199
|
+
body_contains: Optional[str] = None,
|
|
200
|
+
) -> list[HistoryEntry]:
|
|
201
|
+
"""Search history with filters"""
|
|
202
|
+
results = []
|
|
203
|
+
|
|
204
|
+
for entry in self._entries:
|
|
205
|
+
# Method filter
|
|
206
|
+
if method and entry.request.method != method.upper():
|
|
207
|
+
continue
|
|
208
|
+
|
|
209
|
+
# Host filter
|
|
210
|
+
if host_contains and host_contains.lower() not in entry.request.host.lower():
|
|
211
|
+
continue
|
|
212
|
+
|
|
213
|
+
# Path filter
|
|
214
|
+
if path_contains and path_contains.lower() not in entry.request.path.lower():
|
|
215
|
+
continue
|
|
216
|
+
|
|
217
|
+
# Status code filter
|
|
218
|
+
if status_code and entry.response and entry.response.status_code != status_code:
|
|
219
|
+
continue
|
|
220
|
+
|
|
221
|
+
# Content type filter
|
|
222
|
+
if content_type:
|
|
223
|
+
req_ct = entry.request.content_type.lower()
|
|
224
|
+
resp_ct = entry.response.content_type.lower() if entry.response else ""
|
|
225
|
+
if content_type.lower() not in req_ct and content_type.lower() not in resp_ct:
|
|
226
|
+
continue
|
|
227
|
+
|
|
228
|
+
# Body content filter
|
|
229
|
+
if body_contains:
|
|
230
|
+
body_lower = body_contains.lower()
|
|
231
|
+
req_body = entry.request.get_body_text().lower()
|
|
232
|
+
resp_body = entry.response.get_body_text().lower() if entry.response else ""
|
|
233
|
+
if body_lower not in req_body and body_lower not in resp_body:
|
|
234
|
+
continue
|
|
235
|
+
|
|
236
|
+
results.append(entry)
|
|
237
|
+
|
|
238
|
+
return results
|
|
239
|
+
|
|
240
|
+
def get_unique_endpoints(self) -> list[dict]:
|
|
241
|
+
"""Get unique endpoints (method + path, no query params)"""
|
|
242
|
+
seen = set()
|
|
243
|
+
endpoints = []
|
|
244
|
+
|
|
245
|
+
for entry in self._entries:
|
|
246
|
+
parsed = urlparse(entry.request.url)
|
|
247
|
+
key = (entry.request.method, parsed.netloc, parsed.path)
|
|
248
|
+
|
|
249
|
+
if key not in seen:
|
|
250
|
+
seen.add(key)
|
|
251
|
+
endpoints.append({
|
|
252
|
+
"method": entry.request.method,
|
|
253
|
+
"host": parsed.netloc,
|
|
254
|
+
"path": parsed.path,
|
|
255
|
+
"count": 1,
|
|
256
|
+
})
|
|
257
|
+
else:
|
|
258
|
+
# Increment count
|
|
259
|
+
for ep in endpoints:
|
|
260
|
+
if (ep["method"], ep["host"], ep["path"]) == key:
|
|
261
|
+
ep["count"] += 1
|
|
262
|
+
break
|
|
263
|
+
|
|
264
|
+
return sorted(endpoints, key=lambda x: x["count"], reverse=True)
|
|
265
|
+
|
|
266
|
+
def get_parameter_map(self) -> dict[str, list[str]]:
|
|
267
|
+
"""Get map of endpoints to their parameters"""
|
|
268
|
+
param_map = {}
|
|
269
|
+
|
|
270
|
+
for entry in self._entries:
|
|
271
|
+
if not entry.has_parameters:
|
|
272
|
+
continue
|
|
273
|
+
|
|
274
|
+
endpoint = f"{entry.request.method} {entry.request.path}"
|
|
275
|
+
if endpoint not in param_map:
|
|
276
|
+
param_map[endpoint] = []
|
|
277
|
+
|
|
278
|
+
# Extract parameter names
|
|
279
|
+
for key in entry.request.query_params.keys():
|
|
280
|
+
if key not in param_map[endpoint]:
|
|
281
|
+
param_map[endpoint].append(key)
|
|
282
|
+
|
|
283
|
+
# From form/JSON body
|
|
284
|
+
if entry.request.is_json:
|
|
285
|
+
body_json = entry.request.get_body_json()
|
|
286
|
+
if body_json and isinstance(body_json, dict):
|
|
287
|
+
for key in body_json.keys():
|
|
288
|
+
if key not in param_map[endpoint]:
|
|
289
|
+
param_map[endpoint].append(key)
|
|
290
|
+
|
|
291
|
+
return param_map
|
|
292
|
+
|
|
293
|
+
def get_statistics(self) -> dict:
|
|
294
|
+
"""Get history statistics"""
|
|
295
|
+
methods = {}
|
|
296
|
+
status_codes = {}
|
|
297
|
+
content_types = {}
|
|
298
|
+
|
|
299
|
+
for entry in self._entries:
|
|
300
|
+
# Methods
|
|
301
|
+
method = entry.request.method
|
|
302
|
+
methods[method] = methods.get(method, 0) + 1
|
|
303
|
+
|
|
304
|
+
# Status codes
|
|
305
|
+
if entry.response:
|
|
306
|
+
code = entry.response.status_code
|
|
307
|
+
status_codes[code] = status_codes.get(code, 0) + 1
|
|
308
|
+
|
|
309
|
+
# Content types
|
|
310
|
+
ct = entry.response.content_type.split(";")[0]
|
|
311
|
+
content_types[ct] = content_types.get(ct, 0) + 1
|
|
312
|
+
|
|
313
|
+
return {
|
|
314
|
+
"total_entries": len(self._entries),
|
|
315
|
+
"unique_hosts": len(self._by_host),
|
|
316
|
+
"interesting_count": len(self.get_interesting()),
|
|
317
|
+
"with_parameters": len(self.get_with_parameters()),
|
|
318
|
+
"methods": methods,
|
|
319
|
+
"status_codes": status_codes,
|
|
320
|
+
"content_types": content_types,
|
|
321
|
+
"security_flags": self._get_flag_counts(),
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
def _get_flag_counts(self) -> dict[str, int]:
|
|
325
|
+
"""Count security flags"""
|
|
326
|
+
counts = {}
|
|
327
|
+
for entry in self._entries:
|
|
328
|
+
for flag in entry.security_flags:
|
|
329
|
+
counts[flag] = counts.get(flag, 0) + 1
|
|
330
|
+
return counts
|
|
331
|
+
|
|
332
|
+
def export_json(self, filepath: str) -> bool:
|
|
333
|
+
"""Export history to JSON"""
|
|
334
|
+
try:
|
|
335
|
+
data = {
|
|
336
|
+
"exported_at": datetime.utcnow().isoformat(),
|
|
337
|
+
"statistics": self.get_statistics(),
|
|
338
|
+
"entries": [e.to_dict() for e in self._entries],
|
|
339
|
+
}
|
|
340
|
+
with open(filepath, "w") as f:
|
|
341
|
+
json.dump(data, f, indent=2, default=str)
|
|
342
|
+
return True
|
|
343
|
+
except Exception:
|
|
344
|
+
return False
|
|
345
|
+
|
|
346
|
+
def clear(self) -> None:
|
|
347
|
+
"""Clear all history"""
|
|
348
|
+
self._entries.clear()
|
|
349
|
+
self._by_host.clear()
|
|
350
|
+
|
|
351
|
+
def __len__(self) -> int:
|
|
352
|
+
return len(self._entries)
|