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.
Files changed (187) 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 +46 -0
  6. aipt_v2/agents/base.py +520 -0
  7. aipt_v2/agents/exploit_agent.py +688 -0
  8. aipt_v2/agents/ptt.py +406 -0
  9. aipt_v2/agents/state.py +168 -0
  10. aipt_v2/app.py +957 -0
  11. aipt_v2/browser/__init__.py +31 -0
  12. aipt_v2/browser/automation.py +458 -0
  13. aipt_v2/browser/crawler.py +453 -0
  14. aipt_v2/cli.py +2933 -0
  15. aipt_v2/compliance/__init__.py +71 -0
  16. aipt_v2/compliance/compliance_report.py +449 -0
  17. aipt_v2/compliance/framework_mapper.py +424 -0
  18. aipt_v2/compliance/nist_mapping.py +345 -0
  19. aipt_v2/compliance/owasp_mapping.py +330 -0
  20. aipt_v2/compliance/pci_mapping.py +297 -0
  21. aipt_v2/config.py +341 -0
  22. aipt_v2/core/__init__.py +43 -0
  23. aipt_v2/core/agent.py +630 -0
  24. aipt_v2/core/llm.py +395 -0
  25. aipt_v2/core/memory.py +305 -0
  26. aipt_v2/core/ptt.py +329 -0
  27. aipt_v2/database/__init__.py +14 -0
  28. aipt_v2/database/models.py +232 -0
  29. aipt_v2/database/repository.py +384 -0
  30. aipt_v2/docker/__init__.py +23 -0
  31. aipt_v2/docker/builder.py +260 -0
  32. aipt_v2/docker/manager.py +222 -0
  33. aipt_v2/docker/sandbox.py +371 -0
  34. aipt_v2/evasion/__init__.py +58 -0
  35. aipt_v2/evasion/request_obfuscator.py +272 -0
  36. aipt_v2/evasion/tls_fingerprint.py +285 -0
  37. aipt_v2/evasion/ua_rotator.py +301 -0
  38. aipt_v2/evasion/waf_bypass.py +439 -0
  39. aipt_v2/execution/__init__.py +23 -0
  40. aipt_v2/execution/executor.py +302 -0
  41. aipt_v2/execution/parser.py +544 -0
  42. aipt_v2/execution/terminal.py +337 -0
  43. aipt_v2/health.py +437 -0
  44. aipt_v2/intelligence/__init__.py +194 -0
  45. aipt_v2/intelligence/adaptation.py +474 -0
  46. aipt_v2/intelligence/auth.py +520 -0
  47. aipt_v2/intelligence/chaining.py +775 -0
  48. aipt_v2/intelligence/correlation.py +536 -0
  49. aipt_v2/intelligence/cve_aipt.py +334 -0
  50. aipt_v2/intelligence/cve_info.py +1111 -0
  51. aipt_v2/intelligence/knowledge_graph.py +590 -0
  52. aipt_v2/intelligence/learning.py +626 -0
  53. aipt_v2/intelligence/llm_analyzer.py +502 -0
  54. aipt_v2/intelligence/llm_tool_selector.py +518 -0
  55. aipt_v2/intelligence/payload_generator.py +562 -0
  56. aipt_v2/intelligence/rag.py +239 -0
  57. aipt_v2/intelligence/scope.py +442 -0
  58. aipt_v2/intelligence/searchers/__init__.py +5 -0
  59. aipt_v2/intelligence/searchers/exploitdb_searcher.py +523 -0
  60. aipt_v2/intelligence/searchers/github_searcher.py +467 -0
  61. aipt_v2/intelligence/searchers/google_searcher.py +281 -0
  62. aipt_v2/intelligence/tools.json +443 -0
  63. aipt_v2/intelligence/triage.py +670 -0
  64. aipt_v2/interactive_shell.py +559 -0
  65. aipt_v2/interface/__init__.py +5 -0
  66. aipt_v2/interface/cli.py +230 -0
  67. aipt_v2/interface/main.py +501 -0
  68. aipt_v2/interface/tui.py +1276 -0
  69. aipt_v2/interface/utils.py +583 -0
  70. aipt_v2/llm/__init__.py +39 -0
  71. aipt_v2/llm/config.py +26 -0
  72. aipt_v2/llm/llm.py +514 -0
  73. aipt_v2/llm/memory.py +214 -0
  74. aipt_v2/llm/request_queue.py +89 -0
  75. aipt_v2/llm/utils.py +89 -0
  76. aipt_v2/local_tool_installer.py +1467 -0
  77. aipt_v2/models/__init__.py +15 -0
  78. aipt_v2/models/findings.py +295 -0
  79. aipt_v2/models/phase_result.py +224 -0
  80. aipt_v2/models/scan_config.py +207 -0
  81. aipt_v2/monitoring/grafana/dashboards/aipt-dashboard.json +355 -0
  82. aipt_v2/monitoring/grafana/dashboards/default.yml +17 -0
  83. aipt_v2/monitoring/grafana/datasources/prometheus.yml +17 -0
  84. aipt_v2/monitoring/prometheus.yml +60 -0
  85. aipt_v2/orchestration/__init__.py +52 -0
  86. aipt_v2/orchestration/pipeline.py +398 -0
  87. aipt_v2/orchestration/progress.py +300 -0
  88. aipt_v2/orchestration/scheduler.py +296 -0
  89. aipt_v2/orchestrator.py +2427 -0
  90. aipt_v2/payloads/__init__.py +27 -0
  91. aipt_v2/payloads/cmdi.py +150 -0
  92. aipt_v2/payloads/sqli.py +263 -0
  93. aipt_v2/payloads/ssrf.py +204 -0
  94. aipt_v2/payloads/templates.py +222 -0
  95. aipt_v2/payloads/traversal.py +166 -0
  96. aipt_v2/payloads/xss.py +204 -0
  97. aipt_v2/prompts/__init__.py +60 -0
  98. aipt_v2/proxy/__init__.py +29 -0
  99. aipt_v2/proxy/history.py +352 -0
  100. aipt_v2/proxy/interceptor.py +452 -0
  101. aipt_v2/recon/__init__.py +44 -0
  102. aipt_v2/recon/dns.py +241 -0
  103. aipt_v2/recon/osint.py +367 -0
  104. aipt_v2/recon/subdomain.py +372 -0
  105. aipt_v2/recon/tech_detect.py +311 -0
  106. aipt_v2/reports/__init__.py +17 -0
  107. aipt_v2/reports/generator.py +313 -0
  108. aipt_v2/reports/html_report.py +378 -0
  109. aipt_v2/runtime/__init__.py +53 -0
  110. aipt_v2/runtime/base.py +30 -0
  111. aipt_v2/runtime/docker.py +401 -0
  112. aipt_v2/runtime/local.py +346 -0
  113. aipt_v2/runtime/tool_server.py +205 -0
  114. aipt_v2/runtime/vps.py +830 -0
  115. aipt_v2/scanners/__init__.py +28 -0
  116. aipt_v2/scanners/base.py +273 -0
  117. aipt_v2/scanners/nikto.py +244 -0
  118. aipt_v2/scanners/nmap.py +402 -0
  119. aipt_v2/scanners/nuclei.py +273 -0
  120. aipt_v2/scanners/web.py +454 -0
  121. aipt_v2/scripts/security_audit.py +366 -0
  122. aipt_v2/setup_wizard.py +941 -0
  123. aipt_v2/skills/__init__.py +80 -0
  124. aipt_v2/skills/agents/__init__.py +14 -0
  125. aipt_v2/skills/agents/api_tester.py +706 -0
  126. aipt_v2/skills/agents/base.py +477 -0
  127. aipt_v2/skills/agents/code_review.py +459 -0
  128. aipt_v2/skills/agents/security_agent.py +336 -0
  129. aipt_v2/skills/agents/web_pentest.py +818 -0
  130. aipt_v2/skills/prompts/__init__.py +647 -0
  131. aipt_v2/system_detector.py +539 -0
  132. aipt_v2/telemetry/__init__.py +7 -0
  133. aipt_v2/telemetry/tracer.py +347 -0
  134. aipt_v2/terminal/__init__.py +28 -0
  135. aipt_v2/terminal/executor.py +400 -0
  136. aipt_v2/terminal/sandbox.py +350 -0
  137. aipt_v2/tools/__init__.py +44 -0
  138. aipt_v2/tools/active_directory/__init__.py +78 -0
  139. aipt_v2/tools/active_directory/ad_config.py +238 -0
  140. aipt_v2/tools/active_directory/bloodhound_wrapper.py +447 -0
  141. aipt_v2/tools/active_directory/kerberos_attacks.py +430 -0
  142. aipt_v2/tools/active_directory/ldap_enum.py +533 -0
  143. aipt_v2/tools/active_directory/smb_attacks.py +505 -0
  144. aipt_v2/tools/agents_graph/__init__.py +19 -0
  145. aipt_v2/tools/agents_graph/agents_graph_actions.py +69 -0
  146. aipt_v2/tools/api_security/__init__.py +76 -0
  147. aipt_v2/tools/api_security/api_discovery.py +608 -0
  148. aipt_v2/tools/api_security/graphql_scanner.py +622 -0
  149. aipt_v2/tools/api_security/jwt_analyzer.py +577 -0
  150. aipt_v2/tools/api_security/openapi_fuzzer.py +761 -0
  151. aipt_v2/tools/browser/__init__.py +5 -0
  152. aipt_v2/tools/browser/browser_actions.py +238 -0
  153. aipt_v2/tools/browser/browser_instance.py +535 -0
  154. aipt_v2/tools/browser/tab_manager.py +344 -0
  155. aipt_v2/tools/cloud/__init__.py +70 -0
  156. aipt_v2/tools/cloud/cloud_config.py +273 -0
  157. aipt_v2/tools/cloud/cloud_scanner.py +639 -0
  158. aipt_v2/tools/cloud/prowler_tool.py +571 -0
  159. aipt_v2/tools/cloud/scoutsuite_tool.py +359 -0
  160. aipt_v2/tools/executor.py +307 -0
  161. aipt_v2/tools/parser.py +408 -0
  162. aipt_v2/tools/proxy/__init__.py +5 -0
  163. aipt_v2/tools/proxy/proxy_actions.py +103 -0
  164. aipt_v2/tools/proxy/proxy_manager.py +789 -0
  165. aipt_v2/tools/registry.py +196 -0
  166. aipt_v2/tools/scanners/__init__.py +343 -0
  167. aipt_v2/tools/scanners/acunetix_tool.py +712 -0
  168. aipt_v2/tools/scanners/burp_tool.py +631 -0
  169. aipt_v2/tools/scanners/config.py +156 -0
  170. aipt_v2/tools/scanners/nessus_tool.py +588 -0
  171. aipt_v2/tools/scanners/zap_tool.py +612 -0
  172. aipt_v2/tools/terminal/__init__.py +5 -0
  173. aipt_v2/tools/terminal/terminal_actions.py +37 -0
  174. aipt_v2/tools/terminal/terminal_manager.py +153 -0
  175. aipt_v2/tools/terminal/terminal_session.py +449 -0
  176. aipt_v2/tools/tool_processing.py +108 -0
  177. aipt_v2/utils/__init__.py +17 -0
  178. aipt_v2/utils/logging.py +202 -0
  179. aipt_v2/utils/model_manager.py +187 -0
  180. aipt_v2/utils/searchers/__init__.py +269 -0
  181. aipt_v2/verify_install.py +793 -0
  182. aiptx-2.0.7.dist-info/METADATA +345 -0
  183. aiptx-2.0.7.dist-info/RECORD +187 -0
  184. aiptx-2.0.7.dist-info/WHEEL +5 -0
  185. aiptx-2.0.7.dist-info/entry_points.txt +7 -0
  186. aiptx-2.0.7.dist-info/licenses/LICENSE +21 -0
  187. aiptx-2.0.7.dist-info/top_level.txt +1 -0
@@ -0,0 +1,712 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Acunetix Scanner Tool - Plug & Play Integration for AIPT Orchestration
4
+ Provides comprehensive API integration with Acunetix Web Vulnerability Scanner.
5
+ """
6
+
7
+ import json
8
+ import time
9
+ import logging
10
+ import requests
11
+ from typing import Optional, Dict, List, Any
12
+ from dataclasses import dataclass, field
13
+ from enum import Enum
14
+ from datetime import datetime
15
+ from pathlib import Path
16
+ import urllib3
17
+
18
+ # Disable SSL warnings for self-signed certificates
19
+ urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
20
+
21
+ logger = logging.getLogger(__name__)
22
+
23
+
24
+ class ScanProfile(Enum):
25
+ """Acunetix scan profile types."""
26
+ FULL_SCAN = "11111111-1111-1111-1111-111111111111"
27
+ HIGH_RISK = "11111111-1111-1111-1111-111111111112"
28
+ WEAK_PASSWORDS = "11111111-1111-1111-1111-111111111115"
29
+ CRAWL_ONLY = "11111111-1111-1111-1111-111111111117"
30
+ XSS_SCAN = "11111111-1111-1111-1111-111111111116"
31
+ SQL_INJECTION = "11111111-1111-1111-1111-111111111113"
32
+ MALWARE_SCAN = "11111111-1111-1111-1111-111111111120"
33
+
34
+
35
+ class ScanStatus(Enum):
36
+ """Acunetix scan status types."""
37
+ PENDING = "pending"
38
+ QUEUED = "queued"
39
+ STARTING = "starting"
40
+ PROCESSING = "processing"
41
+ COMPLETED = "completed"
42
+ FAILED = "failed"
43
+ ABORTED = "aborted"
44
+ PAUSED = "paused"
45
+
46
+
47
+ class Severity(Enum):
48
+ """Vulnerability severity levels."""
49
+ CRITICAL = "critical"
50
+ HIGH = "high"
51
+ MEDIUM = "medium"
52
+ LOW = "low"
53
+ INFO = "info"
54
+
55
+
56
+ @dataclass
57
+ class AcunetixConfig:
58
+ """Configuration for Acunetix connection."""
59
+ base_url: str = "https://13.127.28.41:3443"
60
+ api_key: str = "1986ad8c0a5b3df4d7028d5f3c06e936c83ef0a486ef74537812989cff1a41a7c"
61
+ verify_ssl: bool = False
62
+ timeout: int = 30
63
+
64
+
65
+ @dataclass
66
+ class ScanResult:
67
+ """Result of an Acunetix scan."""
68
+ scan_id: str
69
+ target_id: str
70
+ target_url: str
71
+ status: str
72
+ progress: int
73
+ start_time: Optional[str] = None
74
+ end_time: Optional[str] = None
75
+ vulnerabilities: Dict[str, int] = field(default_factory=dict)
76
+ threat_level: int = 0
77
+
78
+
79
+ @dataclass
80
+ class Vulnerability:
81
+ """Vulnerability finding from Acunetix."""
82
+ vuln_id: str
83
+ severity: str
84
+ name: str
85
+ description: str
86
+ target_url: str
87
+ affected_url: str
88
+ recommendation: Optional[str] = None
89
+ cvss_score: Optional[float] = None
90
+ cwe_id: Optional[str] = None
91
+ references: List[str] = field(default_factory=list)
92
+
93
+
94
+ class AcunetixTool:
95
+ """
96
+ Acunetix Scanner Tool for AIPT Orchestration.
97
+
98
+ Provides plug-and-play integration with Acunetix Web Vulnerability Scanner.
99
+ Supports target management, scan execution, vulnerability retrieval, and reporting.
100
+ """
101
+
102
+ def __init__(self, config: Optional[AcunetixConfig] = None):
103
+ """Initialize Acunetix tool with configuration."""
104
+ self.config = config or AcunetixConfig()
105
+ self.session = requests.Session()
106
+ self.session.headers.update({
107
+ "X-Auth": self.config.api_key,
108
+ "Content-Type": "application/json"
109
+ })
110
+ self.session.verify = self.config.verify_ssl
111
+ self._connected = False
112
+
113
+ @property
114
+ def api_url(self) -> str:
115
+ """Get the API base URL."""
116
+ return f"{self.config.base_url}/api/v1"
117
+
118
+ def _request(self, method: str, endpoint: str, data: Optional[Dict] = None,
119
+ params: Optional[Dict] = None) -> Dict[str, Any]:
120
+ """Make an API request to Acunetix."""
121
+ url = f"{self.api_url}/{endpoint}"
122
+ try:
123
+ response = self.session.request(
124
+ method=method,
125
+ url=url,
126
+ json=data,
127
+ params=params,
128
+ timeout=self.config.timeout
129
+ )
130
+ response.raise_for_status()
131
+ if response.content:
132
+ return response.json()
133
+ return {"success": True}
134
+ except requests.exceptions.RequestException as e:
135
+ logger.error(f"Acunetix API error: {e}")
136
+ raise
137
+
138
+ # ==================== Connection ====================
139
+
140
+ def connect(self) -> bool:
141
+ """Test connection to Acunetix and verify API key."""
142
+ try:
143
+ result = self._request("GET", "me")
144
+ self._connected = result.get("enabled", False)
145
+ logger.info(f"Connected to Acunetix as: {result.get('email')}")
146
+ return self._connected
147
+ except Exception as e:
148
+ logger.error(f"Failed to connect to Acunetix: {e}")
149
+ return False
150
+
151
+ def get_info(self) -> Dict[str, Any]:
152
+ """Get Acunetix instance information."""
153
+ return self._request("GET", "me")
154
+
155
+ def is_connected(self) -> bool:
156
+ """Check if connected to Acunetix."""
157
+ return self._connected
158
+
159
+ # ==================== Target Management ====================
160
+
161
+ def add_target(self, url: str, description: str = "",
162
+ criticality: int = 10) -> str:
163
+ """
164
+ Add a new target to Acunetix.
165
+
166
+ Args:
167
+ url: Target URL to scan
168
+ description: Target description
169
+ criticality: Target criticality (0-30, 10=normal, 30=critical)
170
+
171
+ Returns:
172
+ Target ID
173
+ """
174
+ data = {
175
+ "address": url,
176
+ "description": description or f"AIPT Target - {datetime.now().isoformat()}",
177
+ "criticality": criticality
178
+ }
179
+ result = self._request("POST", "targets", data=data)
180
+ target_id = result.get("target_id")
181
+ logger.info(f"Added target: {url} (ID: {target_id})")
182
+ return target_id
183
+
184
+ def get_target(self, target_id: str) -> Dict[str, Any]:
185
+ """Get target information by ID."""
186
+ return self._request("GET", f"targets/{target_id}")
187
+
188
+ def get_target_by_url(self, url: str) -> Optional[str]:
189
+ """Find target ID by URL."""
190
+ targets = self.list_targets()
191
+ for target in targets:
192
+ if target.get("address") == url:
193
+ return target.get("target_id")
194
+ return None
195
+
196
+ def list_targets(self, limit: int = 100) -> List[Dict[str, Any]]:
197
+ """List all targets."""
198
+ result = self._request("GET", "targets", params={"l": limit})
199
+ return result.get("targets", [])
200
+
201
+ def delete_target(self, target_id: str) -> bool:
202
+ """Delete a target."""
203
+ try:
204
+ self._request("DELETE", f"targets/{target_id}")
205
+ logger.info(f"Deleted target: {target_id}")
206
+ return True
207
+ except Exception:
208
+ return False
209
+
210
+ def configure_target(self, target_id: str, login_url: str = None,
211
+ username: str = None, password: str = None,
212
+ custom_headers: Dict[str, str] = None) -> bool:
213
+ """
214
+ Configure target with authentication and custom settings.
215
+
216
+ Args:
217
+ target_id: Target ID
218
+ login_url: Login page URL for authenticated scanning
219
+ username: Login username
220
+ password: Login password
221
+ custom_headers: Custom HTTP headers to include
222
+ """
223
+ config = {}
224
+
225
+ if login_url and username and password:
226
+ config["login"] = {
227
+ "kind": "automatic",
228
+ "credentials": {
229
+ "enabled": True,
230
+ "username": username,
231
+ "password": password
232
+ }
233
+ }
234
+
235
+ if custom_headers:
236
+ config["custom_headers"] = [
237
+ {"name": k, "value": v} for k, v in custom_headers.items()
238
+ ]
239
+
240
+ if config:
241
+ self._request("PATCH", f"targets/{target_id}/configuration", data=config)
242
+ logger.info(f"Configured target: {target_id}")
243
+ return True
244
+ return False
245
+
246
+ # ==================== Scan Management ====================
247
+
248
+ def start_scan(self, target_id: str,
249
+ profile: ScanProfile = ScanProfile.FULL_SCAN,
250
+ schedule: bool = False) -> str:
251
+ """
252
+ Start a scan on a target.
253
+
254
+ Args:
255
+ target_id: Target ID to scan
256
+ profile: Scan profile to use
257
+ schedule: If True, schedule for later (not immediate)
258
+
259
+ Returns:
260
+ Scan ID
261
+ """
262
+ data = {
263
+ "target_id": target_id,
264
+ "profile_id": profile.value,
265
+ "schedule": {
266
+ "disable": False,
267
+ "start_date": None,
268
+ "time_sensitive": False
269
+ }
270
+ }
271
+ result = self._request("POST", "scans", data=data)
272
+ scan_id = result.get("scan_id") or self._get_latest_scan_id(target_id)
273
+ logger.info(f"Started scan: {scan_id} with profile: {profile.name}")
274
+ return scan_id
275
+
276
+ def _get_latest_scan_id(self, target_id: str) -> Optional[str]:
277
+ """Get the latest scan ID for a target."""
278
+ scans = self.list_scans(limit=10)
279
+ for scan in scans:
280
+ if scan.get("target_id") == target_id:
281
+ return scan.get("scan_id")
282
+ return None
283
+
284
+ def scan_url(self, url: str, profile: ScanProfile = ScanProfile.FULL_SCAN,
285
+ description: str = "") -> str:
286
+ """
287
+ Convenience method to add target and start scan in one call.
288
+
289
+ Args:
290
+ url: URL to scan
291
+ profile: Scan profile to use
292
+ description: Target description
293
+
294
+ Returns:
295
+ Scan ID
296
+ """
297
+ # Check if target already exists
298
+ target_id = self.get_target_by_url(url)
299
+ if not target_id:
300
+ target_id = self.add_target(url, description)
301
+
302
+ return self.start_scan(target_id, profile)
303
+
304
+ def get_scan_status(self, scan_id: str) -> ScanResult:
305
+ """Get current status of a scan."""
306
+ result = self._request("GET", f"scans/{scan_id}")
307
+
308
+ current = result.get("current_session", {})
309
+ target = result.get("target", {})
310
+
311
+ return ScanResult(
312
+ scan_id=scan_id,
313
+ target_id=result.get("target_id", ""),
314
+ target_url=target.get("address", ""),
315
+ status=current.get("status", "unknown"),
316
+ progress=current.get("progress", 0),
317
+ start_time=current.get("start_date"),
318
+ vulnerabilities=current.get("severity_counts", {}),
319
+ threat_level=current.get("threat", 0)
320
+ )
321
+
322
+ def list_scans(self, limit: int = 20) -> List[Dict[str, Any]]:
323
+ """List all scans."""
324
+ result = self._request("GET", "scans", params={"l": limit})
325
+ return result.get("scans", [])
326
+
327
+ def stop_scan(self, scan_id: str) -> bool:
328
+ """Stop a running scan."""
329
+ try:
330
+ self._request("POST", f"scans/{scan_id}/abort")
331
+ logger.info(f"Stopped scan: {scan_id}")
332
+ return True
333
+ except Exception:
334
+ return False
335
+
336
+ def pause_scan(self, scan_id: str) -> bool:
337
+ """Pause a running scan."""
338
+ try:
339
+ self._request("POST", f"scans/{scan_id}/pause")
340
+ return True
341
+ except Exception:
342
+ return False
343
+
344
+ def resume_scan(self, scan_id: str) -> bool:
345
+ """Resume a paused scan."""
346
+ try:
347
+ self._request("POST", f"scans/{scan_id}/resume")
348
+ return True
349
+ except Exception:
350
+ return False
351
+
352
+ def delete_scan(self, scan_id: str) -> bool:
353
+ """Delete a scan."""
354
+ try:
355
+ self._request("DELETE", f"scans/{scan_id}")
356
+ return True
357
+ except Exception:
358
+ return False
359
+
360
+ def wait_for_scan(self, scan_id: str, timeout: int = 3600,
361
+ poll_interval: int = 30,
362
+ callback: callable = None) -> ScanResult:
363
+ """
364
+ Wait for a scan to complete.
365
+
366
+ Args:
367
+ scan_id: Scan ID to wait for
368
+ timeout: Maximum time to wait in seconds
369
+ poll_interval: Time between status checks
370
+ callback: Optional callback function called with ScanResult on each poll
371
+
372
+ Returns:
373
+ Final ScanResult
374
+ """
375
+ start_time = time.time()
376
+ while time.time() - start_time < timeout:
377
+ result = self.get_scan_status(scan_id)
378
+
379
+ if callback:
380
+ callback(result)
381
+
382
+ if result.status in [ScanStatus.COMPLETED.value,
383
+ ScanStatus.FAILED.value,
384
+ ScanStatus.ABORTED.value]:
385
+ return result
386
+
387
+ logger.info(f"Scan {scan_id}: {result.status} ({result.progress}%)")
388
+ time.sleep(poll_interval)
389
+
390
+ raise TimeoutError(f"Scan {scan_id} did not complete within {timeout}s")
391
+
392
+ # ==================== Vulnerability Management ====================
393
+
394
+ def get_vulnerabilities(self, scan_id: str = None,
395
+ severity: Severity = None,
396
+ limit: int = 100) -> List[Vulnerability]:
397
+ """
398
+ Get vulnerabilities from scans.
399
+
400
+ Args:
401
+ scan_id: Optional scan ID to filter by
402
+ severity: Optional severity filter
403
+ limit: Maximum results to return
404
+ """
405
+ params = {"l": limit}
406
+ if severity:
407
+ params["q"] = f"severity:{severity.value}"
408
+
409
+ result = self._request("GET", "vulnerabilities", params=params)
410
+ vulns = []
411
+
412
+ for v in result.get("vulnerabilities", []):
413
+ vulns.append(Vulnerability(
414
+ vuln_id=v.get("vuln_id", ""),
415
+ severity=v.get("severity", "info"),
416
+ name=v.get("vt_name", "Unknown"),
417
+ description=v.get("vt_description", ""),
418
+ target_url=v.get("target", {}).get("address", ""),
419
+ affected_url=v.get("affects_url", ""),
420
+ recommendation=v.get("recommendation", ""),
421
+ cvss_score=v.get("cvss_score"),
422
+ cwe_id=v.get("cwe_id")
423
+ ))
424
+
425
+ return vulns
426
+
427
+ def get_vulnerability_details(self, vuln_id: str) -> Dict[str, Any]:
428
+ """Get detailed vulnerability information."""
429
+ return self._request("GET", f"vulnerabilities/{vuln_id}")
430
+
431
+ def get_scan_vulnerabilities(self, scan_id: str) -> List[Vulnerability]:
432
+ """Get all vulnerabilities for a specific scan."""
433
+ # Get scan to find target
434
+ scan = self._request("GET", f"scans/{scan_id}")
435
+ target_id = scan.get("target_id")
436
+
437
+ # Get vulnerabilities for target
438
+ result = self._request("GET", f"targets/{target_id}/vulnerabilities")
439
+ vulns = []
440
+
441
+ for v in result.get("vulnerabilities", []):
442
+ vulns.append(Vulnerability(
443
+ vuln_id=v.get("vuln_id", ""),
444
+ severity=v.get("severity", "info"),
445
+ name=v.get("vt_name", "Unknown"),
446
+ description="", # Need to fetch details separately
447
+ target_url=scan.get("target", {}).get("address", ""),
448
+ affected_url=v.get("affects_url", "")
449
+ ))
450
+
451
+ return vulns
452
+
453
+ # ==================== Reporting ====================
454
+
455
+ def generate_report(self, scan_id: str,
456
+ template: str = "11111111-1111-1111-1111-111111111111",
457
+ format_type: str = "pdf") -> str:
458
+ """
459
+ Generate a report for a scan.
460
+
461
+ Args:
462
+ scan_id: Scan ID to report on
463
+ template: Report template ID
464
+ format_type: Report format (pdf, html)
465
+
466
+ Returns:
467
+ Report ID
468
+ """
469
+ # Get scan to find source
470
+ scan = self._request("GET", f"scans/{scan_id}")
471
+
472
+ data = {
473
+ "template_id": template,
474
+ "source": {
475
+ "list_type": "scans",
476
+ "id_list": [scan_id]
477
+ }
478
+ }
479
+ result = self._request("POST", "reports", data=data)
480
+ return result.get("report_id", "")
481
+
482
+ def get_report_status(self, report_id: str) -> Dict[str, Any]:
483
+ """Get report generation status."""
484
+ return self._request("GET", f"reports/{report_id}")
485
+
486
+ def download_report(self, report_id: str, output_path: str) -> str:
487
+ """
488
+ Download a generated report.
489
+
490
+ Args:
491
+ report_id: Report ID
492
+ output_path: Path to save the report
493
+
494
+ Returns:
495
+ Path to saved report
496
+ """
497
+ url = f"{self.api_url}/reports/{report_id}/download"
498
+ response = self.session.get(url, stream=True)
499
+ response.raise_for_status()
500
+
501
+ with open(output_path, 'wb') as f:
502
+ for chunk in response.iter_content(chunk_size=8192):
503
+ f.write(chunk)
504
+
505
+ logger.info(f"Report saved to: {output_path}")
506
+ return output_path
507
+
508
+ # ==================== Statistics & Dashboard ====================
509
+
510
+ def get_dashboard_stats(self) -> Dict[str, Any]:
511
+ """Get dashboard statistics."""
512
+ try:
513
+ return self._request("GET", "dashboard/stats")
514
+ except Exception:
515
+ # Fallback: calculate from scans
516
+ scans = self.list_scans(limit=100)
517
+ stats = {
518
+ "total_scans": len(scans),
519
+ "completed": sum(1 for s in scans if s.get("current_session", {}).get("status") == "completed"),
520
+ "running": sum(1 for s in scans if s.get("current_session", {}).get("status") == "processing"),
521
+ "vulnerabilities": {
522
+ "critical": 0, "high": 0, "medium": 0, "low": 0, "info": 0
523
+ }
524
+ }
525
+ for scan in scans:
526
+ counts = scan.get("current_session", {}).get("severity_counts", {})
527
+ for sev in stats["vulnerabilities"]:
528
+ stats["vulnerabilities"][sev] += counts.get(sev, 0)
529
+ return stats
530
+
531
+ def get_scan_summary(self) -> Dict[str, Any]:
532
+ """Get summary of all scans with vulnerability counts."""
533
+ scans = self.list_scans(limit=50)
534
+ summary = {
535
+ "total_scans": len(scans),
536
+ "by_status": {},
537
+ "total_vulnerabilities": {"critical": 0, "high": 0, "medium": 0, "low": 0, "info": 0},
538
+ "recent_scans": []
539
+ }
540
+
541
+ for scan in scans:
542
+ status = scan.get("current_session", {}).get("status", "unknown")
543
+ summary["by_status"][status] = summary["by_status"].get(status, 0) + 1
544
+
545
+ counts = scan.get("current_session", {}).get("severity_counts", {})
546
+ for sev in summary["total_vulnerabilities"]:
547
+ summary["total_vulnerabilities"][sev] += counts.get(sev, 0)
548
+
549
+ if len(summary["recent_scans"]) < 10:
550
+ summary["recent_scans"].append({
551
+ "scan_id": scan.get("scan_id"),
552
+ "target": scan.get("target", {}).get("address"),
553
+ "status": status,
554
+ "progress": scan.get("current_session", {}).get("progress", 0),
555
+ "vulnerabilities": counts
556
+ })
557
+
558
+ return summary
559
+
560
+ # ==================== Orchestration Helpers ====================
561
+
562
+ def to_finding(self, vuln: Vulnerability) -> Dict[str, Any]:
563
+ """Convert Acunetix vulnerability to AIPT finding format."""
564
+ return {
565
+ "type": "vulnerability",
566
+ "value": vuln.name,
567
+ "description": vuln.description or f"{vuln.name} at {vuln.affected_url}",
568
+ "severity": vuln.severity,
569
+ "phase": "scanning",
570
+ "tool": "acunetix",
571
+ "metadata": {
572
+ "vuln_id": vuln.vuln_id,
573
+ "target_url": vuln.target_url,
574
+ "affected_url": vuln.affected_url,
575
+ "cvss_score": vuln.cvss_score,
576
+ "cwe_id": vuln.cwe_id
577
+ }
578
+ }
579
+
580
+ def export_findings(self, scan_id: str, output_path: str = None) -> List[Dict[str, Any]]:
581
+ """
582
+ Export scan findings in AIPT format.
583
+
584
+ Args:
585
+ scan_id: Scan ID to export
586
+ output_path: Optional path to save JSON
587
+
588
+ Returns:
589
+ List of findings in AIPT format
590
+ """
591
+ vulns = self.get_scan_vulnerabilities(scan_id)
592
+ findings = [self.to_finding(v) for v in vulns]
593
+
594
+ if output_path:
595
+ with open(output_path, 'w') as f:
596
+ json.dump(findings, f, indent=2)
597
+ logger.info(f"Exported {len(findings)} findings to {output_path}")
598
+
599
+ return findings
600
+
601
+
602
+ # ==================== Standalone Functions for Orchestration ====================
603
+
604
+ # Global instance for quick access
605
+ _acunetix: Optional[AcunetixTool] = None
606
+
607
+
608
+ def get_acunetix(config: Optional[AcunetixConfig] = None) -> AcunetixTool:
609
+ """Get or create Acunetix tool instance."""
610
+ global _acunetix
611
+ if _acunetix is None or config is not None:
612
+ _acunetix = AcunetixTool(config)
613
+ _acunetix.connect()
614
+ return _acunetix
615
+
616
+
617
+ def acunetix_scan(url: str, profile: str = "full") -> ScanResult:
618
+ """
619
+ Quick scan function for orchestration.
620
+
621
+ Args:
622
+ url: URL to scan
623
+ profile: Scan profile (full, high_risk, xss, sqli, crawl)
624
+
625
+ Returns:
626
+ ScanResult
627
+ """
628
+ profiles = {
629
+ "full": ScanProfile.FULL_SCAN,
630
+ "high_risk": ScanProfile.HIGH_RISK,
631
+ "xss": ScanProfile.XSS_SCAN,
632
+ "sqli": ScanProfile.SQL_INJECTION,
633
+ "crawl": ScanProfile.CRAWL_ONLY,
634
+ "malware": ScanProfile.MALWARE_SCAN
635
+ }
636
+
637
+ acunetix = get_acunetix()
638
+ scan_id = acunetix.scan_url(url, profiles.get(profile, ScanProfile.FULL_SCAN))
639
+ return acunetix.get_scan_status(scan_id)
640
+
641
+
642
+ def acunetix_status(scan_id: str) -> ScanResult:
643
+ """Get scan status."""
644
+ return get_acunetix().get_scan_status(scan_id)
645
+
646
+
647
+ def acunetix_vulns(scan_id: str = None, severity: str = None) -> List[Dict[str, Any]]:
648
+ """Get vulnerabilities as AIPT findings."""
649
+ acunetix = get_acunetix()
650
+ sev = Severity(severity) if severity else None
651
+ vulns = acunetix.get_vulnerabilities(scan_id, sev)
652
+ return [acunetix.to_finding(v) for v in vulns]
653
+
654
+
655
+ def acunetix_summary() -> Dict[str, Any]:
656
+ """Get scan summary."""
657
+ return get_acunetix().get_scan_summary()
658
+
659
+
660
+ # ==================== CLI for Testing ====================
661
+
662
+ if __name__ == "__main__":
663
+ import argparse
664
+
665
+ parser = argparse.ArgumentParser(description="Acunetix Scanner Tool")
666
+ parser.add_argument("command", choices=["test", "scan", "status", "vulns", "summary"])
667
+ parser.add_argument("--url", help="Target URL for scanning")
668
+ parser.add_argument("--scan-id", help="Scan ID for status/vulns")
669
+ parser.add_argument("--profile", default="full", help="Scan profile")
670
+
671
+ args = parser.parse_args()
672
+
673
+ acunetix = get_acunetix()
674
+
675
+ if args.command == "test":
676
+ print("Testing Acunetix connection...")
677
+ if acunetix.connect():
678
+ info = acunetix.get_info()
679
+ print(f"✓ Connected as: {info.get('email')}")
680
+ print(f"✓ Access: {info.get('access_rights')}")
681
+ else:
682
+ print("✗ Connection failed")
683
+
684
+ elif args.command == "scan":
685
+ if not args.url:
686
+ print("Error: --url required for scan")
687
+ else:
688
+ result = acunetix_scan(args.url, args.profile)
689
+ print(f"Scan started: {result.scan_id}")
690
+ print(f"Status: {result.status}")
691
+
692
+ elif args.command == "status":
693
+ if not args.scan_id:
694
+ print("Error: --scan-id required")
695
+ else:
696
+ result = acunetix_status(args.scan_id)
697
+ print(f"Scan: {result.scan_id}")
698
+ print(f"Target: {result.target_url}")
699
+ print(f"Status: {result.status} ({result.progress}%)")
700
+ print(f"Vulnerabilities: {result.vulnerabilities}")
701
+
702
+ elif args.command == "vulns":
703
+ findings = acunetix_vulns(args.scan_id)
704
+ print(f"Found {len(findings)} vulnerabilities:")
705
+ for f in findings[:10]:
706
+ print(f" [{f['severity'].upper()}] {f['value']}")
707
+
708
+ elif args.command == "summary":
709
+ summary = acunetix_summary()
710
+ print(f"Total Scans: {summary['total_scans']}")
711
+ print(f"By Status: {summary['by_status']}")
712
+ print(f"Total Vulnerabilities: {summary['total_vulnerabilities']}")