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,344 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import atexit
|
|
4
|
+
import contextlib
|
|
5
|
+
import signal
|
|
6
|
+
import sys
|
|
7
|
+
import threading
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from .browser_instance import BrowserInstance
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class BrowserTabManager:
|
|
14
|
+
def __init__(self) -> None:
|
|
15
|
+
self.browser_instance: BrowserInstance | None = None
|
|
16
|
+
self._lock = threading.Lock()
|
|
17
|
+
|
|
18
|
+
self._register_cleanup_handlers()
|
|
19
|
+
|
|
20
|
+
def launch_browser(self, url: str | None = None) -> dict[str, Any]:
|
|
21
|
+
with self._lock:
|
|
22
|
+
if self.browser_instance is not None:
|
|
23
|
+
raise ValueError("Browser is already launched")
|
|
24
|
+
|
|
25
|
+
try:
|
|
26
|
+
self.browser_instance = BrowserInstance()
|
|
27
|
+
result = self.browser_instance.launch(url)
|
|
28
|
+
result["message"] = "Browser launched successfully"
|
|
29
|
+
except (OSError, ValueError, RuntimeError) as e:
|
|
30
|
+
if self.browser_instance:
|
|
31
|
+
self.browser_instance = None
|
|
32
|
+
raise RuntimeError(f"Failed to launch browser: {e}") from e
|
|
33
|
+
else:
|
|
34
|
+
return result
|
|
35
|
+
|
|
36
|
+
def goto_url(self, url: str, tab_id: str | None = None) -> dict[str, Any]:
|
|
37
|
+
with self._lock:
|
|
38
|
+
if self.browser_instance is None:
|
|
39
|
+
raise ValueError("Browser not launched")
|
|
40
|
+
|
|
41
|
+
try:
|
|
42
|
+
result = self.browser_instance.goto(url, tab_id)
|
|
43
|
+
result["message"] = f"Navigated to {url}"
|
|
44
|
+
except (OSError, ValueError, RuntimeError) as e:
|
|
45
|
+
raise RuntimeError(f"Failed to navigate to URL: {e}") from e
|
|
46
|
+
else:
|
|
47
|
+
return result
|
|
48
|
+
|
|
49
|
+
def click(self, coordinate: str, tab_id: str | None = None) -> dict[str, Any]:
|
|
50
|
+
with self._lock:
|
|
51
|
+
if self.browser_instance is None:
|
|
52
|
+
raise ValueError("Browser not launched")
|
|
53
|
+
|
|
54
|
+
try:
|
|
55
|
+
result = self.browser_instance.click(coordinate, tab_id)
|
|
56
|
+
result["message"] = f"Clicked at {coordinate}"
|
|
57
|
+
except (OSError, ValueError, RuntimeError) as e:
|
|
58
|
+
raise RuntimeError(f"Failed to click: {e}") from e
|
|
59
|
+
else:
|
|
60
|
+
return result
|
|
61
|
+
|
|
62
|
+
def type_text(self, text: str, tab_id: str | None = None) -> dict[str, Any]:
|
|
63
|
+
with self._lock:
|
|
64
|
+
if self.browser_instance is None:
|
|
65
|
+
raise ValueError("Browser not launched")
|
|
66
|
+
|
|
67
|
+
try:
|
|
68
|
+
result = self.browser_instance.type_text(text, tab_id)
|
|
69
|
+
result["message"] = f"Typed text: {text[:50]}{'...' if len(text) > 50 else ''}"
|
|
70
|
+
except (OSError, ValueError, RuntimeError) as e:
|
|
71
|
+
raise RuntimeError(f"Failed to type text: {e}") from e
|
|
72
|
+
else:
|
|
73
|
+
return result
|
|
74
|
+
|
|
75
|
+
def scroll(self, direction: str, tab_id: str | None = None) -> dict[str, Any]:
|
|
76
|
+
with self._lock:
|
|
77
|
+
if self.browser_instance is None:
|
|
78
|
+
raise ValueError("Browser not launched")
|
|
79
|
+
|
|
80
|
+
try:
|
|
81
|
+
result = self.browser_instance.scroll(direction, tab_id)
|
|
82
|
+
result["message"] = f"Scrolled {direction}"
|
|
83
|
+
except (OSError, ValueError, RuntimeError) as e:
|
|
84
|
+
raise RuntimeError(f"Failed to scroll: {e}") from e
|
|
85
|
+
else:
|
|
86
|
+
return result
|
|
87
|
+
|
|
88
|
+
def back(self, tab_id: str | None = None) -> dict[str, Any]:
|
|
89
|
+
with self._lock:
|
|
90
|
+
if self.browser_instance is None:
|
|
91
|
+
raise ValueError("Browser not launched")
|
|
92
|
+
|
|
93
|
+
try:
|
|
94
|
+
result = self.browser_instance.back(tab_id)
|
|
95
|
+
result["message"] = "Navigated back"
|
|
96
|
+
except (OSError, ValueError, RuntimeError) as e:
|
|
97
|
+
raise RuntimeError(f"Failed to go back: {e}") from e
|
|
98
|
+
else:
|
|
99
|
+
return result
|
|
100
|
+
|
|
101
|
+
def forward(self, tab_id: str | None = None) -> dict[str, Any]:
|
|
102
|
+
with self._lock:
|
|
103
|
+
if self.browser_instance is None:
|
|
104
|
+
raise ValueError("Browser not launched")
|
|
105
|
+
|
|
106
|
+
try:
|
|
107
|
+
result = self.browser_instance.forward(tab_id)
|
|
108
|
+
result["message"] = "Navigated forward"
|
|
109
|
+
except (OSError, ValueError, RuntimeError) as e:
|
|
110
|
+
raise RuntimeError(f"Failed to go forward: {e}") from e
|
|
111
|
+
else:
|
|
112
|
+
return result
|
|
113
|
+
|
|
114
|
+
def new_tab(self, url: str | None = None) -> dict[str, Any]:
|
|
115
|
+
with self._lock:
|
|
116
|
+
if self.browser_instance is None:
|
|
117
|
+
raise ValueError("Browser not launched")
|
|
118
|
+
|
|
119
|
+
try:
|
|
120
|
+
result = self.browser_instance.new_tab(url)
|
|
121
|
+
result["message"] = f"Created new tab {result.get('tab_id', '')}"
|
|
122
|
+
except (OSError, ValueError, RuntimeError) as e:
|
|
123
|
+
raise RuntimeError(f"Failed to create new tab: {e}") from e
|
|
124
|
+
else:
|
|
125
|
+
return result
|
|
126
|
+
|
|
127
|
+
def switch_tab(self, tab_id: str) -> dict[str, Any]:
|
|
128
|
+
with self._lock:
|
|
129
|
+
if self.browser_instance is None:
|
|
130
|
+
raise ValueError("Browser not launched")
|
|
131
|
+
|
|
132
|
+
try:
|
|
133
|
+
result = self.browser_instance.switch_tab(tab_id)
|
|
134
|
+
result["message"] = f"Switched to tab {tab_id}"
|
|
135
|
+
except (OSError, ValueError, RuntimeError) as e:
|
|
136
|
+
raise RuntimeError(f"Failed to switch tab: {e}") from e
|
|
137
|
+
else:
|
|
138
|
+
return result
|
|
139
|
+
|
|
140
|
+
def close_tab(self, tab_id: str) -> dict[str, Any]:
|
|
141
|
+
with self._lock:
|
|
142
|
+
if self.browser_instance is None:
|
|
143
|
+
raise ValueError("Browser not launched")
|
|
144
|
+
|
|
145
|
+
try:
|
|
146
|
+
result = self.browser_instance.close_tab(tab_id)
|
|
147
|
+
result["message"] = f"Closed tab {tab_id}"
|
|
148
|
+
except (OSError, ValueError, RuntimeError) as e:
|
|
149
|
+
raise RuntimeError(f"Failed to close tab: {e}") from e
|
|
150
|
+
else:
|
|
151
|
+
return result
|
|
152
|
+
|
|
153
|
+
def wait_browser(self, duration: float, tab_id: str | None = None) -> dict[str, Any]:
|
|
154
|
+
with self._lock:
|
|
155
|
+
if self.browser_instance is None:
|
|
156
|
+
raise ValueError("Browser not launched")
|
|
157
|
+
|
|
158
|
+
try:
|
|
159
|
+
result = self.browser_instance.wait(duration, tab_id)
|
|
160
|
+
result["message"] = f"Waited {duration}s"
|
|
161
|
+
except (OSError, ValueError, RuntimeError) as e:
|
|
162
|
+
raise RuntimeError(f"Failed to wait: {e}") from e
|
|
163
|
+
else:
|
|
164
|
+
return result
|
|
165
|
+
|
|
166
|
+
def execute_js(self, js_code: str, tab_id: str | None = None) -> dict[str, Any]:
|
|
167
|
+
with self._lock:
|
|
168
|
+
if self.browser_instance is None:
|
|
169
|
+
raise ValueError("Browser not launched")
|
|
170
|
+
|
|
171
|
+
try:
|
|
172
|
+
result = self.browser_instance.execute_js(js_code, tab_id)
|
|
173
|
+
result["message"] = "JavaScript executed successfully"
|
|
174
|
+
except (OSError, ValueError, RuntimeError) as e:
|
|
175
|
+
raise RuntimeError(f"Failed to execute JavaScript: {e}") from e
|
|
176
|
+
else:
|
|
177
|
+
return result
|
|
178
|
+
|
|
179
|
+
def double_click(self, coordinate: str, tab_id: str | None = None) -> dict[str, Any]:
|
|
180
|
+
with self._lock:
|
|
181
|
+
if self.browser_instance is None:
|
|
182
|
+
raise ValueError("Browser not launched")
|
|
183
|
+
|
|
184
|
+
try:
|
|
185
|
+
result = self.browser_instance.double_click(coordinate, tab_id)
|
|
186
|
+
result["message"] = f"Double clicked at {coordinate}"
|
|
187
|
+
except (OSError, ValueError, RuntimeError) as e:
|
|
188
|
+
raise RuntimeError(f"Failed to double click: {e}") from e
|
|
189
|
+
else:
|
|
190
|
+
return result
|
|
191
|
+
|
|
192
|
+
def hover(self, coordinate: str, tab_id: str | None = None) -> dict[str, Any]:
|
|
193
|
+
with self._lock:
|
|
194
|
+
if self.browser_instance is None:
|
|
195
|
+
raise ValueError("Browser not launched")
|
|
196
|
+
|
|
197
|
+
try:
|
|
198
|
+
result = self.browser_instance.hover(coordinate, tab_id)
|
|
199
|
+
result["message"] = f"Hovered at {coordinate}"
|
|
200
|
+
except (OSError, ValueError, RuntimeError) as e:
|
|
201
|
+
raise RuntimeError(f"Failed to hover: {e}") from e
|
|
202
|
+
else:
|
|
203
|
+
return result
|
|
204
|
+
|
|
205
|
+
def press_key(self, key: str, tab_id: str | None = None) -> dict[str, Any]:
|
|
206
|
+
with self._lock:
|
|
207
|
+
if self.browser_instance is None:
|
|
208
|
+
raise ValueError("Browser not launched")
|
|
209
|
+
|
|
210
|
+
try:
|
|
211
|
+
result = self.browser_instance.press_key(key, tab_id)
|
|
212
|
+
result["message"] = f"Pressed key {key}"
|
|
213
|
+
except (OSError, ValueError, RuntimeError) as e:
|
|
214
|
+
raise RuntimeError(f"Failed to press key: {e}") from e
|
|
215
|
+
else:
|
|
216
|
+
return result
|
|
217
|
+
|
|
218
|
+
def save_pdf(self, file_path: str, tab_id: str | None = None) -> dict[str, Any]:
|
|
219
|
+
with self._lock:
|
|
220
|
+
if self.browser_instance is None:
|
|
221
|
+
raise ValueError("Browser not launched")
|
|
222
|
+
|
|
223
|
+
try:
|
|
224
|
+
result = self.browser_instance.save_pdf(file_path, tab_id)
|
|
225
|
+
result["message"] = f"Page saved as PDF: {file_path}"
|
|
226
|
+
except (OSError, ValueError, RuntimeError) as e:
|
|
227
|
+
raise RuntimeError(f"Failed to save PDF: {e}") from e
|
|
228
|
+
else:
|
|
229
|
+
return result
|
|
230
|
+
|
|
231
|
+
def get_console_logs(self, tab_id: str | None = None, clear: bool = False) -> dict[str, Any]:
|
|
232
|
+
with self._lock:
|
|
233
|
+
if self.browser_instance is None:
|
|
234
|
+
raise ValueError("Browser not launched")
|
|
235
|
+
|
|
236
|
+
try:
|
|
237
|
+
result = self.browser_instance.get_console_logs(tab_id, clear)
|
|
238
|
+
action_text = "cleared and retrieved" if clear else "retrieved"
|
|
239
|
+
|
|
240
|
+
logs = result.get("console_logs", [])
|
|
241
|
+
truncated = any(log.get("text", "").startswith("[TRUNCATED:") for log in logs)
|
|
242
|
+
truncated_text = " (truncated)" if truncated else ""
|
|
243
|
+
|
|
244
|
+
result["message"] = (
|
|
245
|
+
f"Console logs {action_text} for tab "
|
|
246
|
+
f"{result.get('tab_id', 'current')}{truncated_text}"
|
|
247
|
+
)
|
|
248
|
+
except (OSError, ValueError, RuntimeError) as e:
|
|
249
|
+
raise RuntimeError(f"Failed to get console logs: {e}") from e
|
|
250
|
+
else:
|
|
251
|
+
return result
|
|
252
|
+
|
|
253
|
+
def view_source(self, tab_id: str | None = None) -> dict[str, Any]:
|
|
254
|
+
with self._lock:
|
|
255
|
+
if self.browser_instance is None:
|
|
256
|
+
raise ValueError("Browser not launched")
|
|
257
|
+
|
|
258
|
+
try:
|
|
259
|
+
result = self.browser_instance.view_source(tab_id)
|
|
260
|
+
result["message"] = "Page source retrieved"
|
|
261
|
+
except (OSError, ValueError, RuntimeError) as e:
|
|
262
|
+
raise RuntimeError(f"Failed to get page source: {e}") from e
|
|
263
|
+
else:
|
|
264
|
+
return result
|
|
265
|
+
|
|
266
|
+
def list_tabs(self) -> dict[str, Any]:
|
|
267
|
+
with self._lock:
|
|
268
|
+
if self.browser_instance is None:
|
|
269
|
+
return {"tabs": {}, "total_count": 0, "current_tab": None}
|
|
270
|
+
|
|
271
|
+
try:
|
|
272
|
+
tab_info = {}
|
|
273
|
+
for tid, tab_page in self.browser_instance.pages.items():
|
|
274
|
+
try:
|
|
275
|
+
tab_info[tid] = {
|
|
276
|
+
"url": tab_page.url,
|
|
277
|
+
"title": "Unknown" if tab_page.is_closed() else "Active",
|
|
278
|
+
"is_current": tid == self.browser_instance.current_page_id,
|
|
279
|
+
}
|
|
280
|
+
except (AttributeError, RuntimeError):
|
|
281
|
+
tab_info[tid] = {
|
|
282
|
+
"url": "Unknown",
|
|
283
|
+
"title": "Closed",
|
|
284
|
+
"is_current": False,
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return {
|
|
288
|
+
"tabs": tab_info,
|
|
289
|
+
"total_count": len(tab_info),
|
|
290
|
+
"current_tab": self.browser_instance.current_page_id,
|
|
291
|
+
}
|
|
292
|
+
except (OSError, ValueError, RuntimeError) as e:
|
|
293
|
+
raise RuntimeError(f"Failed to list tabs: {e}") from e
|
|
294
|
+
|
|
295
|
+
def close_browser(self) -> dict[str, Any]:
|
|
296
|
+
with self._lock:
|
|
297
|
+
if self.browser_instance is None:
|
|
298
|
+
raise ValueError("Browser not launched")
|
|
299
|
+
|
|
300
|
+
try:
|
|
301
|
+
self.browser_instance.close()
|
|
302
|
+
self.browser_instance = None
|
|
303
|
+
except (OSError, ValueError, RuntimeError) as e:
|
|
304
|
+
raise RuntimeError(f"Failed to close browser: {e}") from e
|
|
305
|
+
else:
|
|
306
|
+
return {
|
|
307
|
+
"message": "Browser closed successfully",
|
|
308
|
+
"screenshot": "",
|
|
309
|
+
"is_running": False,
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
def cleanup_dead_browser(self) -> None:
|
|
313
|
+
with self._lock:
|
|
314
|
+
if self.browser_instance and not self.browser_instance.is_alive():
|
|
315
|
+
with contextlib.suppress(Exception):
|
|
316
|
+
self.browser_instance.close()
|
|
317
|
+
self.browser_instance = None
|
|
318
|
+
|
|
319
|
+
def close_all(self) -> None:
|
|
320
|
+
with self._lock:
|
|
321
|
+
if self.browser_instance:
|
|
322
|
+
with contextlib.suppress(Exception):
|
|
323
|
+
self.browser_instance.close()
|
|
324
|
+
self.browser_instance = None
|
|
325
|
+
|
|
326
|
+
def _register_cleanup_handlers(self) -> None:
|
|
327
|
+
atexit.register(self.close_all)
|
|
328
|
+
|
|
329
|
+
signal.signal(signal.SIGTERM, self._signal_handler)
|
|
330
|
+
signal.signal(signal.SIGINT, self._signal_handler)
|
|
331
|
+
|
|
332
|
+
if hasattr(signal, "SIGHUP"):
|
|
333
|
+
signal.signal(signal.SIGHUP, self._signal_handler)
|
|
334
|
+
|
|
335
|
+
def _signal_handler(self, _signum: int, _frame: Any) -> None:
|
|
336
|
+
self.close_all()
|
|
337
|
+
sys.exit(0)
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
_browser_tab_manager = BrowserTabManager()
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
def get_browser_tab_manager() -> BrowserTabManager:
|
|
344
|
+
return _browser_tab_manager
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AIPT Cloud Security Module - Multi-Cloud Vulnerability Scanning
|
|
3
|
+
|
|
4
|
+
Provides comprehensive cloud security assessment for:
|
|
5
|
+
- AWS (Amazon Web Services)
|
|
6
|
+
- Azure (Microsoft Azure)
|
|
7
|
+
- GCP (Google Cloud Platform)
|
|
8
|
+
|
|
9
|
+
Tools integrated:
|
|
10
|
+
- ScoutSuite: Multi-cloud security auditing
|
|
11
|
+
- Prowler: AWS security best practices
|
|
12
|
+
- Custom checks: IAM, S3, Security Groups, etc.
|
|
13
|
+
|
|
14
|
+
Usage:
|
|
15
|
+
from aipt_v2.tools.cloud import CloudScanner, get_cloud_scanner
|
|
16
|
+
|
|
17
|
+
scanner = get_cloud_scanner(provider="aws", profile="default")
|
|
18
|
+
findings = await scanner.scan()
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from aipt_v2.tools.cloud.cloud_config import (
|
|
22
|
+
CloudConfig,
|
|
23
|
+
AWSConfig,
|
|
24
|
+
AzureConfig,
|
|
25
|
+
GCPConfig,
|
|
26
|
+
get_cloud_config,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
from aipt_v2.tools.cloud.cloud_scanner import (
|
|
30
|
+
CloudScanner,
|
|
31
|
+
CloudFinding,
|
|
32
|
+
CloudSeverity,
|
|
33
|
+
get_cloud_scanner,
|
|
34
|
+
scan_cloud,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
from aipt_v2.tools.cloud.scoutsuite_tool import (
|
|
38
|
+
ScoutSuiteTool,
|
|
39
|
+
ScoutSuiteConfig,
|
|
40
|
+
run_scoutsuite,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
from aipt_v2.tools.cloud.prowler_tool import (
|
|
44
|
+
ProwlerTool,
|
|
45
|
+
ProwlerConfig,
|
|
46
|
+
run_prowler,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
__all__ = [
|
|
50
|
+
# Configuration
|
|
51
|
+
"CloudConfig",
|
|
52
|
+
"AWSConfig",
|
|
53
|
+
"AzureConfig",
|
|
54
|
+
"GCPConfig",
|
|
55
|
+
"get_cloud_config",
|
|
56
|
+
# Scanner
|
|
57
|
+
"CloudScanner",
|
|
58
|
+
"CloudFinding",
|
|
59
|
+
"CloudSeverity",
|
|
60
|
+
"get_cloud_scanner",
|
|
61
|
+
"scan_cloud",
|
|
62
|
+
# ScoutSuite
|
|
63
|
+
"ScoutSuiteTool",
|
|
64
|
+
"ScoutSuiteConfig",
|
|
65
|
+
"run_scoutsuite",
|
|
66
|
+
# Prowler
|
|
67
|
+
"ProwlerTool",
|
|
68
|
+
"ProwlerConfig",
|
|
69
|
+
"run_prowler",
|
|
70
|
+
]
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Cloud Configuration Management
|
|
3
|
+
|
|
4
|
+
Handles credentials and configuration for multi-cloud security scanning.
|
|
5
|
+
Supports AWS profiles, Azure subscriptions, and GCP projects.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
from dataclasses import dataclass, field
|
|
10
|
+
from typing import Optional, List, Dict, Any
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class AWSConfig:
|
|
16
|
+
"""AWS-specific configuration."""
|
|
17
|
+
profile: str = "default"
|
|
18
|
+
region: str = "us-east-1"
|
|
19
|
+
access_key_id: Optional[str] = None
|
|
20
|
+
secret_access_key: Optional[str] = None
|
|
21
|
+
session_token: Optional[str] = None
|
|
22
|
+
|
|
23
|
+
# Scanning options
|
|
24
|
+
services: List[str] = field(default_factory=lambda: [
|
|
25
|
+
"iam", "s3", "ec2", "rds", "lambda", "cloudtrail",
|
|
26
|
+
"cloudwatch", "kms", "sns", "sqs", "vpc", "elb"
|
|
27
|
+
])
|
|
28
|
+
skip_services: List[str] = field(default_factory=list)
|
|
29
|
+
|
|
30
|
+
def __post_init__(self):
|
|
31
|
+
# Load from environment if not provided
|
|
32
|
+
if not self.access_key_id:
|
|
33
|
+
self.access_key_id = os.getenv("AWS_ACCESS_KEY_ID")
|
|
34
|
+
if not self.secret_access_key:
|
|
35
|
+
self.secret_access_key = os.getenv("AWS_SECRET_ACCESS_KEY")
|
|
36
|
+
if not self.session_token:
|
|
37
|
+
self.session_token = os.getenv("AWS_SESSION_TOKEN")
|
|
38
|
+
if self.region == "us-east-1":
|
|
39
|
+
self.region = os.getenv("AWS_DEFAULT_REGION", "us-east-1")
|
|
40
|
+
|
|
41
|
+
def is_configured(self) -> bool:
|
|
42
|
+
"""Check if AWS credentials are available."""
|
|
43
|
+
# Either profile-based or key-based auth
|
|
44
|
+
if self.profile:
|
|
45
|
+
# Check if credentials file exists
|
|
46
|
+
creds_file = Path.home() / ".aws" / "credentials"
|
|
47
|
+
return creds_file.exists()
|
|
48
|
+
return bool(self.access_key_id and self.secret_access_key)
|
|
49
|
+
|
|
50
|
+
def to_env_dict(self) -> Dict[str, str]:
|
|
51
|
+
"""Convert to environment variables dict."""
|
|
52
|
+
env = {"AWS_DEFAULT_REGION": self.region}
|
|
53
|
+
if self.profile:
|
|
54
|
+
env["AWS_PROFILE"] = self.profile
|
|
55
|
+
if self.access_key_id:
|
|
56
|
+
env["AWS_ACCESS_KEY_ID"] = self.access_key_id
|
|
57
|
+
if self.secret_access_key:
|
|
58
|
+
env["AWS_SECRET_ACCESS_KEY"] = self.secret_access_key
|
|
59
|
+
if self.session_token:
|
|
60
|
+
env["AWS_SESSION_TOKEN"] = self.session_token
|
|
61
|
+
return env
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@dataclass
|
|
65
|
+
class AzureConfig:
|
|
66
|
+
"""Azure-specific configuration."""
|
|
67
|
+
subscription_id: Optional[str] = None
|
|
68
|
+
tenant_id: Optional[str] = None
|
|
69
|
+
client_id: Optional[str] = None
|
|
70
|
+
client_secret: Optional[str] = None
|
|
71
|
+
|
|
72
|
+
# Use Azure CLI auth by default
|
|
73
|
+
use_cli_auth: bool = True
|
|
74
|
+
|
|
75
|
+
# Scanning options
|
|
76
|
+
resource_groups: List[str] = field(default_factory=list) # Empty = all
|
|
77
|
+
|
|
78
|
+
def __post_init__(self):
|
|
79
|
+
# Load from environment if not provided
|
|
80
|
+
if not self.subscription_id:
|
|
81
|
+
self.subscription_id = os.getenv("AZURE_SUBSCRIPTION_ID")
|
|
82
|
+
if not self.tenant_id:
|
|
83
|
+
self.tenant_id = os.getenv("AZURE_TENANT_ID")
|
|
84
|
+
if not self.client_id:
|
|
85
|
+
self.client_id = os.getenv("AZURE_CLIENT_ID")
|
|
86
|
+
if not self.client_secret:
|
|
87
|
+
self.client_secret = os.getenv("AZURE_CLIENT_SECRET")
|
|
88
|
+
|
|
89
|
+
def is_configured(self) -> bool:
|
|
90
|
+
"""Check if Azure credentials are available."""
|
|
91
|
+
if self.use_cli_auth:
|
|
92
|
+
# Check for Azure CLI
|
|
93
|
+
return Path.home().joinpath(".azure").exists()
|
|
94
|
+
return bool(self.subscription_id and self.tenant_id and
|
|
95
|
+
self.client_id and self.client_secret)
|
|
96
|
+
|
|
97
|
+
def to_env_dict(self) -> Dict[str, str]:
|
|
98
|
+
"""Convert to environment variables dict."""
|
|
99
|
+
env = {}
|
|
100
|
+
if self.subscription_id:
|
|
101
|
+
env["AZURE_SUBSCRIPTION_ID"] = self.subscription_id
|
|
102
|
+
if self.tenant_id:
|
|
103
|
+
env["AZURE_TENANT_ID"] = self.tenant_id
|
|
104
|
+
if self.client_id:
|
|
105
|
+
env["AZURE_CLIENT_ID"] = self.client_id
|
|
106
|
+
if self.client_secret:
|
|
107
|
+
env["AZURE_CLIENT_SECRET"] = self.client_secret
|
|
108
|
+
return env
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
@dataclass
|
|
112
|
+
class GCPConfig:
|
|
113
|
+
"""GCP-specific configuration."""
|
|
114
|
+
project_id: Optional[str] = None
|
|
115
|
+
credentials_file: Optional[str] = None
|
|
116
|
+
|
|
117
|
+
# Use application default credentials
|
|
118
|
+
use_adc: bool = True
|
|
119
|
+
|
|
120
|
+
# Scanning options
|
|
121
|
+
regions: List[str] = field(default_factory=list) # Empty = all
|
|
122
|
+
|
|
123
|
+
def __post_init__(self):
|
|
124
|
+
# Load from environment if not provided
|
|
125
|
+
if not self.project_id:
|
|
126
|
+
self.project_id = os.getenv("GOOGLE_CLOUD_PROJECT") or os.getenv("GCP_PROJECT")
|
|
127
|
+
if not self.credentials_file:
|
|
128
|
+
self.credentials_file = os.getenv("GOOGLE_APPLICATION_CREDENTIALS")
|
|
129
|
+
|
|
130
|
+
def is_configured(self) -> bool:
|
|
131
|
+
"""Check if GCP credentials are available."""
|
|
132
|
+
if self.use_adc:
|
|
133
|
+
# Check for application default credentials
|
|
134
|
+
adc_path = Path.home() / ".config" / "gcloud" / "application_default_credentials.json"
|
|
135
|
+
return adc_path.exists() or bool(self.credentials_file)
|
|
136
|
+
return bool(self.project_id and self.credentials_file)
|
|
137
|
+
|
|
138
|
+
def to_env_dict(self) -> Dict[str, str]:
|
|
139
|
+
"""Convert to environment variables dict."""
|
|
140
|
+
env = {}
|
|
141
|
+
if self.project_id:
|
|
142
|
+
env["GOOGLE_CLOUD_PROJECT"] = self.project_id
|
|
143
|
+
if self.credentials_file:
|
|
144
|
+
env["GOOGLE_APPLICATION_CREDENTIALS"] = self.credentials_file
|
|
145
|
+
return env
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
@dataclass
|
|
149
|
+
class CloudConfig:
|
|
150
|
+
"""Unified cloud configuration."""
|
|
151
|
+
|
|
152
|
+
# Provider configs
|
|
153
|
+
aws: AWSConfig = field(default_factory=AWSConfig)
|
|
154
|
+
azure: AzureConfig = field(default_factory=AzureConfig)
|
|
155
|
+
gcp: GCPConfig = field(default_factory=GCPConfig)
|
|
156
|
+
|
|
157
|
+
# General settings
|
|
158
|
+
providers: List[str] = field(default_factory=lambda: ["aws"])
|
|
159
|
+
output_dir: str = "./cloud_scan_results"
|
|
160
|
+
severity_threshold: str = "low" # low, medium, high, critical
|
|
161
|
+
|
|
162
|
+
# Scanning options
|
|
163
|
+
parallel_scans: bool = True
|
|
164
|
+
max_workers: int = 5
|
|
165
|
+
timeout: int = 3600 # 1 hour default
|
|
166
|
+
|
|
167
|
+
def get_configured_providers(self) -> List[str]:
|
|
168
|
+
"""Get list of providers with valid credentials."""
|
|
169
|
+
configured = []
|
|
170
|
+
if "aws" in self.providers and self.aws.is_configured():
|
|
171
|
+
configured.append("aws")
|
|
172
|
+
if "azure" in self.providers and self.azure.is_configured():
|
|
173
|
+
configured.append("azure")
|
|
174
|
+
if "gcp" in self.providers and self.gcp.is_configured():
|
|
175
|
+
configured.append("gcp")
|
|
176
|
+
return configured
|
|
177
|
+
|
|
178
|
+
def get_provider_config(self, provider: str) -> Any:
|
|
179
|
+
"""Get configuration for a specific provider."""
|
|
180
|
+
provider_map = {
|
|
181
|
+
"aws": self.aws,
|
|
182
|
+
"azure": self.azure,
|
|
183
|
+
"gcp": self.gcp
|
|
184
|
+
}
|
|
185
|
+
return provider_map.get(provider.lower())
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def get_cloud_config(
|
|
189
|
+
providers: Optional[List[str]] = None,
|
|
190
|
+
aws_profile: Optional[str] = None,
|
|
191
|
+
azure_subscription: Optional[str] = None,
|
|
192
|
+
gcp_project: Optional[str] = None,
|
|
193
|
+
**kwargs
|
|
194
|
+
) -> CloudConfig:
|
|
195
|
+
"""
|
|
196
|
+
Create CloudConfig from parameters and environment.
|
|
197
|
+
|
|
198
|
+
Args:
|
|
199
|
+
providers: List of cloud providers to scan ("aws", "azure", "gcp")
|
|
200
|
+
aws_profile: AWS CLI profile name
|
|
201
|
+
azure_subscription: Azure subscription ID
|
|
202
|
+
gcp_project: GCP project ID
|
|
203
|
+
**kwargs: Additional configuration options
|
|
204
|
+
|
|
205
|
+
Returns:
|
|
206
|
+
CloudConfig instance
|
|
207
|
+
"""
|
|
208
|
+
# Build AWS config
|
|
209
|
+
aws_config = AWSConfig(
|
|
210
|
+
profile=aws_profile or os.getenv("AWS_PROFILE", "default"),
|
|
211
|
+
region=kwargs.get("aws_region", os.getenv("AWS_DEFAULT_REGION", "us-east-1"))
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
# Build Azure config
|
|
215
|
+
azure_config = AzureConfig(
|
|
216
|
+
subscription_id=azure_subscription or os.getenv("AZURE_SUBSCRIPTION_ID")
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
# Build GCP config
|
|
220
|
+
gcp_config = GCPConfig(
|
|
221
|
+
project_id=gcp_project or os.getenv("GOOGLE_CLOUD_PROJECT")
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
# Determine providers
|
|
225
|
+
if providers is None:
|
|
226
|
+
providers = ["aws"] # Default to AWS only
|
|
227
|
+
|
|
228
|
+
return CloudConfig(
|
|
229
|
+
aws=aws_config,
|
|
230
|
+
azure=azure_config,
|
|
231
|
+
gcp=gcp_config,
|
|
232
|
+
providers=providers,
|
|
233
|
+
output_dir=kwargs.get("output_dir", "./cloud_scan_results"),
|
|
234
|
+
severity_threshold=kwargs.get("severity_threshold", "low"),
|
|
235
|
+
timeout=kwargs.get("timeout", 3600)
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def validate_cloud_credentials() -> Dict[str, Dict[str, Any]]:
|
|
240
|
+
"""
|
|
241
|
+
Validate credentials for all cloud providers.
|
|
242
|
+
|
|
243
|
+
Returns:
|
|
244
|
+
Dict with validation results per provider
|
|
245
|
+
"""
|
|
246
|
+
results = {}
|
|
247
|
+
|
|
248
|
+
# Check AWS
|
|
249
|
+
aws_config = AWSConfig()
|
|
250
|
+
results["aws"] = {
|
|
251
|
+
"configured": aws_config.is_configured(),
|
|
252
|
+
"profile": aws_config.profile,
|
|
253
|
+
"region": aws_config.region,
|
|
254
|
+
"has_keys": bool(aws_config.access_key_id)
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
# Check Azure
|
|
258
|
+
azure_config = AzureConfig()
|
|
259
|
+
results["azure"] = {
|
|
260
|
+
"configured": azure_config.is_configured(),
|
|
261
|
+
"subscription": azure_config.subscription_id,
|
|
262
|
+
"use_cli": azure_config.use_cli_auth
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
# Check GCP
|
|
266
|
+
gcp_config = GCPConfig()
|
|
267
|
+
results["gcp"] = {
|
|
268
|
+
"configured": gcp_config.is_configured(),
|
|
269
|
+
"project": gcp_config.project_id,
|
|
270
|
+
"use_adc": gcp_config.use_adc
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return results
|