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.
- 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 +24 -0
- aipt_v2/agents/base.py +520 -0
- aipt_v2/agents/ptt.py +406 -0
- aipt_v2/agents/state.py +168 -0
- aipt_v2/app.py +960 -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 +321 -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 +288 -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 +85 -0
- aipt_v2/intelligence/auth.py +520 -0
- aipt_v2/intelligence/chaining.py +775 -0
- aipt_v2/intelligence/cve_aipt.py +334 -0
- aipt_v2/intelligence/cve_info.py +1111 -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/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/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 +2284 -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 +44 -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/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/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 +201 -0
- aipt_v2/utils/model_manager.py +187 -0
- aipt_v2/utils/searchers/__init__.py +269 -0
- aiptx-2.0.2.dist-info/METADATA +324 -0
- aiptx-2.0.2.dist-info/RECORD +165 -0
- aiptx-2.0.2.dist-info/WHEEL +5 -0
- aiptx-2.0.2.dist-info/entry_points.txt +7 -0
- aiptx-2.0.2.dist-info/licenses/LICENSE +21 -0
- aiptx-2.0.2.dist-info/top_level.txt +1 -0
aipt_v2/runtime/local.py
ADDED
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Local Runtime for AIPT v2
|
|
3
|
+
=========================
|
|
4
|
+
|
|
5
|
+
Non-sandboxed local execution runtime.
|
|
6
|
+
Implements AbstractRuntime interface for local command execution.
|
|
7
|
+
|
|
8
|
+
WARNING: This runtime provides NO isolation. Only use for trusted commands.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import asyncio
|
|
12
|
+
import uuid
|
|
13
|
+
import os
|
|
14
|
+
from dataclasses import dataclass, field
|
|
15
|
+
from typing import Optional, Dict, Tuple, List
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from datetime import datetime
|
|
18
|
+
|
|
19
|
+
from aipt_v2.utils.logging import logger
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class LocalSandboxInfo:
|
|
24
|
+
"""Information about a local 'sandbox' (execution context)."""
|
|
25
|
+
|
|
26
|
+
sandbox_id: str
|
|
27
|
+
created_at: datetime = field(default_factory=datetime.now)
|
|
28
|
+
working_dir: str = field(default_factory=lambda: os.getcwd())
|
|
29
|
+
is_local: bool = True
|
|
30
|
+
url: str = "local"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class LocalRuntime:
|
|
34
|
+
"""
|
|
35
|
+
Local runtime for command execution without sandboxing.
|
|
36
|
+
|
|
37
|
+
This runtime executes commands directly on the local system.
|
|
38
|
+
It provides a consistent interface with sandboxed runtimes
|
|
39
|
+
but does NOT provide any isolation.
|
|
40
|
+
|
|
41
|
+
Usage:
|
|
42
|
+
runtime = LocalRuntime()
|
|
43
|
+
sandbox = await runtime.create_sandbox()
|
|
44
|
+
stdout, stderr, code = await runtime.execute(sandbox.sandbox_id, "ls -la")
|
|
45
|
+
await runtime.destroy_sandbox(sandbox.sandbox_id)
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
def __init__(self, working_dir: Optional[str] = None):
|
|
49
|
+
"""
|
|
50
|
+
Initialize local runtime.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
working_dir: Default working directory for commands
|
|
54
|
+
"""
|
|
55
|
+
self.working_dir = working_dir or os.getcwd()
|
|
56
|
+
self._sandboxes: Dict[str, LocalSandboxInfo] = {}
|
|
57
|
+
self._processes: Dict[str, asyncio.subprocess.Process] = {}
|
|
58
|
+
|
|
59
|
+
async def create_sandbox(
|
|
60
|
+
self,
|
|
61
|
+
image: Optional[str] = None,
|
|
62
|
+
working_dir: Optional[str] = None,
|
|
63
|
+
**kwargs
|
|
64
|
+
) -> LocalSandboxInfo:
|
|
65
|
+
"""
|
|
66
|
+
Create a local execution context.
|
|
67
|
+
|
|
68
|
+
Note: For local runtime, this just creates a tracking ID.
|
|
69
|
+
No actual isolation is provided.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
image: Ignored for local runtime
|
|
73
|
+
working_dir: Working directory for commands
|
|
74
|
+
**kwargs: Additional options (ignored)
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
LocalSandboxInfo with sandbox details
|
|
78
|
+
"""
|
|
79
|
+
sandbox_id = f"local-{uuid.uuid4().hex[:8]}"
|
|
80
|
+
work_dir = working_dir or self.working_dir
|
|
81
|
+
|
|
82
|
+
# Ensure working directory exists
|
|
83
|
+
Path(work_dir).mkdir(parents=True, exist_ok=True)
|
|
84
|
+
|
|
85
|
+
info = LocalSandboxInfo(
|
|
86
|
+
sandbox_id=sandbox_id,
|
|
87
|
+
working_dir=work_dir,
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
self._sandboxes[sandbox_id] = info
|
|
91
|
+
logger.info("Created local execution context", sandbox_id=sandbox_id, working_dir=work_dir)
|
|
92
|
+
|
|
93
|
+
return info
|
|
94
|
+
|
|
95
|
+
async def get_sandbox_url(self, sandbox_id: str) -> str:
|
|
96
|
+
"""
|
|
97
|
+
Get sandbox URL.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
sandbox_id: Sandbox identifier
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
URL string ('local' for local runtime)
|
|
104
|
+
|
|
105
|
+
Raises:
|
|
106
|
+
ValueError: If sandbox not found
|
|
107
|
+
"""
|
|
108
|
+
if sandbox_id not in self._sandboxes:
|
|
109
|
+
raise ValueError(f"Unknown sandbox: {sandbox_id}")
|
|
110
|
+
return "local"
|
|
111
|
+
|
|
112
|
+
async def destroy_sandbox(self, sandbox_id: str) -> None:
|
|
113
|
+
"""
|
|
114
|
+
Destroy sandbox and cleanup resources.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
sandbox_id: Sandbox identifier
|
|
118
|
+
"""
|
|
119
|
+
if sandbox_id in self._sandboxes:
|
|
120
|
+
# Kill any running processes
|
|
121
|
+
if sandbox_id in self._processes:
|
|
122
|
+
try:
|
|
123
|
+
self._processes[sandbox_id].kill()
|
|
124
|
+
await self._processes[sandbox_id].wait()
|
|
125
|
+
except ProcessLookupError:
|
|
126
|
+
pass
|
|
127
|
+
del self._processes[sandbox_id]
|
|
128
|
+
|
|
129
|
+
del self._sandboxes[sandbox_id]
|
|
130
|
+
logger.info("Destroyed local sandbox", sandbox_id=sandbox_id)
|
|
131
|
+
|
|
132
|
+
async def execute(
|
|
133
|
+
self,
|
|
134
|
+
sandbox_id: str,
|
|
135
|
+
command: str,
|
|
136
|
+
timeout: int = 300,
|
|
137
|
+
env: Optional[Dict[str, str]] = None,
|
|
138
|
+
) -> Tuple[str, str, int]:
|
|
139
|
+
"""
|
|
140
|
+
Execute command in sandbox.
|
|
141
|
+
|
|
142
|
+
Args:
|
|
143
|
+
sandbox_id: Sandbox identifier
|
|
144
|
+
command: Shell command to execute
|
|
145
|
+
timeout: Command timeout in seconds
|
|
146
|
+
env: Additional environment variables
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
Tuple of (stdout, stderr, exit_code)
|
|
150
|
+
|
|
151
|
+
Raises:
|
|
152
|
+
ValueError: If sandbox not found
|
|
153
|
+
"""
|
|
154
|
+
if sandbox_id not in self._sandboxes:
|
|
155
|
+
raise ValueError(f"Unknown sandbox: {sandbox_id}")
|
|
156
|
+
|
|
157
|
+
sandbox = self._sandboxes[sandbox_id]
|
|
158
|
+
|
|
159
|
+
# Prepare environment
|
|
160
|
+
cmd_env = os.environ.copy()
|
|
161
|
+
if env:
|
|
162
|
+
cmd_env.update(env)
|
|
163
|
+
|
|
164
|
+
logger.debug(
|
|
165
|
+
"Executing command",
|
|
166
|
+
sandbox_id=sandbox_id,
|
|
167
|
+
command=command[:100] + "..." if len(command) > 100 else command,
|
|
168
|
+
timeout=timeout,
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
try:
|
|
172
|
+
proc = await asyncio.create_subprocess_shell(
|
|
173
|
+
command,
|
|
174
|
+
stdout=asyncio.subprocess.PIPE,
|
|
175
|
+
stderr=asyncio.subprocess.PIPE,
|
|
176
|
+
cwd=sandbox.working_dir,
|
|
177
|
+
env=cmd_env,
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
self._processes[sandbox_id] = proc
|
|
181
|
+
|
|
182
|
+
try:
|
|
183
|
+
stdout_bytes, stderr_bytes = await asyncio.wait_for(
|
|
184
|
+
proc.communicate(),
|
|
185
|
+
timeout=timeout
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
stdout = stdout_bytes.decode("utf-8", errors="replace")
|
|
189
|
+
stderr = stderr_bytes.decode("utf-8", errors="replace")
|
|
190
|
+
exit_code = proc.returncode or 0
|
|
191
|
+
|
|
192
|
+
logger.debug(
|
|
193
|
+
"Command completed",
|
|
194
|
+
sandbox_id=sandbox_id,
|
|
195
|
+
exit_code=exit_code,
|
|
196
|
+
stdout_len=len(stdout),
|
|
197
|
+
stderr_len=len(stderr),
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
return stdout, stderr, exit_code
|
|
201
|
+
|
|
202
|
+
except asyncio.TimeoutError:
|
|
203
|
+
logger.warning(
|
|
204
|
+
"Command timed out",
|
|
205
|
+
sandbox_id=sandbox_id,
|
|
206
|
+
command=command[:50],
|
|
207
|
+
timeout=timeout,
|
|
208
|
+
)
|
|
209
|
+
proc.kill()
|
|
210
|
+
await proc.wait()
|
|
211
|
+
return "", f"Command timed out after {timeout} seconds", 124
|
|
212
|
+
|
|
213
|
+
except Exception as e:
|
|
214
|
+
logger.error(
|
|
215
|
+
"Command execution failed",
|
|
216
|
+
sandbox_id=sandbox_id,
|
|
217
|
+
command=command[:50],
|
|
218
|
+
error=str(e),
|
|
219
|
+
exc_info=True,
|
|
220
|
+
)
|
|
221
|
+
return "", str(e), 1
|
|
222
|
+
|
|
223
|
+
finally:
|
|
224
|
+
if sandbox_id in self._processes:
|
|
225
|
+
del self._processes[sandbox_id]
|
|
226
|
+
|
|
227
|
+
async def execute_background(
|
|
228
|
+
self,
|
|
229
|
+
sandbox_id: str,
|
|
230
|
+
command: str,
|
|
231
|
+
env: Optional[Dict[str, str]] = None,
|
|
232
|
+
) -> str:
|
|
233
|
+
"""
|
|
234
|
+
Execute command in background.
|
|
235
|
+
|
|
236
|
+
Args:
|
|
237
|
+
sandbox_id: Sandbox identifier
|
|
238
|
+
command: Shell command to execute
|
|
239
|
+
env: Additional environment variables
|
|
240
|
+
|
|
241
|
+
Returns:
|
|
242
|
+
Process ID as string
|
|
243
|
+
"""
|
|
244
|
+
if sandbox_id not in self._sandboxes:
|
|
245
|
+
raise ValueError(f"Unknown sandbox: {sandbox_id}")
|
|
246
|
+
|
|
247
|
+
sandbox = self._sandboxes[sandbox_id]
|
|
248
|
+
|
|
249
|
+
cmd_env = os.environ.copy()
|
|
250
|
+
if env:
|
|
251
|
+
cmd_env.update(env)
|
|
252
|
+
|
|
253
|
+
proc = await asyncio.create_subprocess_shell(
|
|
254
|
+
command,
|
|
255
|
+
stdout=asyncio.subprocess.PIPE,
|
|
256
|
+
stderr=asyncio.subprocess.PIPE,
|
|
257
|
+
cwd=sandbox.working_dir,
|
|
258
|
+
env=cmd_env,
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
proc_id = f"{sandbox_id}-{proc.pid}"
|
|
262
|
+
self._processes[proc_id] = proc
|
|
263
|
+
|
|
264
|
+
logger.info(
|
|
265
|
+
"Started background process",
|
|
266
|
+
sandbox_id=sandbox_id,
|
|
267
|
+
proc_id=proc_id,
|
|
268
|
+
command=command[:50],
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
return proc_id
|
|
272
|
+
|
|
273
|
+
async def check_tool_available(self, tool_name: str) -> bool:
|
|
274
|
+
"""
|
|
275
|
+
Check if a tool is available locally.
|
|
276
|
+
|
|
277
|
+
Args:
|
|
278
|
+
tool_name: Name of the tool to check
|
|
279
|
+
|
|
280
|
+
Returns:
|
|
281
|
+
True if tool is available
|
|
282
|
+
"""
|
|
283
|
+
try:
|
|
284
|
+
proc = await asyncio.create_subprocess_shell(
|
|
285
|
+
f"which {tool_name}",
|
|
286
|
+
stdout=asyncio.subprocess.PIPE,
|
|
287
|
+
stderr=asyncio.subprocess.PIPE,
|
|
288
|
+
)
|
|
289
|
+
stdout, _ = await proc.communicate()
|
|
290
|
+
return proc.returncode == 0 and len(stdout.strip()) > 0
|
|
291
|
+
except Exception:
|
|
292
|
+
return False
|
|
293
|
+
|
|
294
|
+
async def get_available_tools(self, tool_list: List[str]) -> Dict[str, bool]:
|
|
295
|
+
"""
|
|
296
|
+
Check availability of multiple tools.
|
|
297
|
+
|
|
298
|
+
Args:
|
|
299
|
+
tool_list: List of tool names to check
|
|
300
|
+
|
|
301
|
+
Returns:
|
|
302
|
+
Dict mapping tool names to availability
|
|
303
|
+
"""
|
|
304
|
+
results = {}
|
|
305
|
+
for tool in tool_list:
|
|
306
|
+
results[tool] = await self.check_tool_available(tool)
|
|
307
|
+
return results
|
|
308
|
+
|
|
309
|
+
def get_sandbox_info(self, sandbox_id: str) -> Optional[LocalSandboxInfo]:
|
|
310
|
+
"""
|
|
311
|
+
Get information about a sandbox.
|
|
312
|
+
|
|
313
|
+
Args:
|
|
314
|
+
sandbox_id: Sandbox identifier
|
|
315
|
+
|
|
316
|
+
Returns:
|
|
317
|
+
LocalSandboxInfo or None if not found
|
|
318
|
+
"""
|
|
319
|
+
return self._sandboxes.get(sandbox_id)
|
|
320
|
+
|
|
321
|
+
async def cleanup_all(self) -> int:
|
|
322
|
+
"""
|
|
323
|
+
Cleanup all sandboxes and processes.
|
|
324
|
+
|
|
325
|
+
Returns:
|
|
326
|
+
Number of sandboxes cleaned up
|
|
327
|
+
"""
|
|
328
|
+
count = len(self._sandboxes)
|
|
329
|
+
|
|
330
|
+
for sandbox_id in list(self._sandboxes.keys()):
|
|
331
|
+
await self.destroy_sandbox(sandbox_id)
|
|
332
|
+
|
|
333
|
+
logger.info("Cleaned up all local sandboxes", count=count)
|
|
334
|
+
return count
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
# Singleton instance for convenience
|
|
338
|
+
_default_runtime: Optional[LocalRuntime] = None
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
def get_local_runtime() -> LocalRuntime:
|
|
342
|
+
"""Get or create the default local runtime instance."""
|
|
343
|
+
global _default_runtime
|
|
344
|
+
if _default_runtime is None:
|
|
345
|
+
_default_runtime = LocalRuntime()
|
|
346
|
+
return _default_runtime
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import asyncio
|
|
5
|
+
import logging
|
|
6
|
+
import os
|
|
7
|
+
import signal
|
|
8
|
+
import sys
|
|
9
|
+
from multiprocessing import Process, Queue
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
import uvicorn
|
|
13
|
+
from fastapi import Depends, FastAPI, HTTPException, status
|
|
14
|
+
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
|
15
|
+
from pydantic import BaseModel, ValidationError
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
SANDBOX_MODE = os.getenv("AIPT_SANDBOX_MODE", "false").lower() == "true"
|
|
19
|
+
if not SANDBOX_MODE:
|
|
20
|
+
raise RuntimeError("Tool server should only run in sandbox mode (AIPT_SANDBOX_MODE=true)")
|
|
21
|
+
|
|
22
|
+
parser = argparse.ArgumentParser(description="Start AIPTx tool server")
|
|
23
|
+
parser.add_argument("--token", required=True, help="Authentication token")
|
|
24
|
+
parser.add_argument("--host", default="0.0.0.0", help="Host to bind to") # nosec
|
|
25
|
+
parser.add_argument("--port", type=int, required=True, help="Port to bind to")
|
|
26
|
+
|
|
27
|
+
args = parser.parse_args()
|
|
28
|
+
EXPECTED_TOKEN = args.token
|
|
29
|
+
|
|
30
|
+
app = FastAPI()
|
|
31
|
+
security = HTTPBearer()
|
|
32
|
+
|
|
33
|
+
security_dependency = Depends(security)
|
|
34
|
+
|
|
35
|
+
agent_processes: dict[str, dict[str, Any]] = {}
|
|
36
|
+
agent_queues: dict[str, dict[str, Queue[Any]]] = {}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def verify_token(credentials: HTTPAuthorizationCredentials) -> str:
|
|
40
|
+
if not credentials or credentials.scheme != "Bearer":
|
|
41
|
+
raise HTTPException(
|
|
42
|
+
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
43
|
+
detail="Invalid authentication scheme. Bearer token required.",
|
|
44
|
+
headers={"WWW-Authenticate": "Bearer"},
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
if credentials.credentials != EXPECTED_TOKEN:
|
|
48
|
+
raise HTTPException(
|
|
49
|
+
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
50
|
+
detail="Invalid authentication token",
|
|
51
|
+
headers={"WWW-Authenticate": "Bearer"},
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
return credentials.credentials
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class ToolExecutionRequest(BaseModel):
|
|
58
|
+
agent_id: str
|
|
59
|
+
tool_name: str
|
|
60
|
+
kwargs: dict[str, Any]
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class ToolExecutionResponse(BaseModel):
|
|
64
|
+
result: Any | None = None
|
|
65
|
+
error: str | None = None
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def agent_worker(_agent_id: str, request_queue: Queue[Any], response_queue: Queue[Any]) -> None:
|
|
69
|
+
null_handler = logging.NullHandler()
|
|
70
|
+
|
|
71
|
+
root_logger = logging.getLogger()
|
|
72
|
+
root_logger.handlers = [null_handler]
|
|
73
|
+
root_logger.setLevel(logging.CRITICAL)
|
|
74
|
+
|
|
75
|
+
from aipt_v2.tools.argument_parser import ArgumentConversionError, convert_arguments
|
|
76
|
+
from aipt_v2.tools.registry import get_tool_by_name
|
|
77
|
+
|
|
78
|
+
while True:
|
|
79
|
+
try:
|
|
80
|
+
request = request_queue.get()
|
|
81
|
+
|
|
82
|
+
if request is None:
|
|
83
|
+
break
|
|
84
|
+
|
|
85
|
+
tool_name = request["tool_name"]
|
|
86
|
+
kwargs = request["kwargs"]
|
|
87
|
+
|
|
88
|
+
try:
|
|
89
|
+
tool_func = get_tool_by_name(tool_name)
|
|
90
|
+
if not tool_func:
|
|
91
|
+
response_queue.put({"error": f"Tool '{tool_name}' not found"})
|
|
92
|
+
continue
|
|
93
|
+
|
|
94
|
+
converted_kwargs = convert_arguments(tool_func, kwargs)
|
|
95
|
+
result = tool_func(**converted_kwargs)
|
|
96
|
+
|
|
97
|
+
response_queue.put({"result": result})
|
|
98
|
+
|
|
99
|
+
except (ArgumentConversionError, ValidationError) as e:
|
|
100
|
+
response_queue.put({"error": f"Invalid arguments: {e}"})
|
|
101
|
+
except (RuntimeError, ValueError, ImportError) as e:
|
|
102
|
+
response_queue.put({"error": f"Tool execution error: {e}"})
|
|
103
|
+
|
|
104
|
+
except (RuntimeError, ValueError, ImportError) as e:
|
|
105
|
+
response_queue.put({"error": f"Worker error: {e}"})
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def ensure_agent_process(agent_id: str) -> tuple[Queue[Any], Queue[Any]]:
|
|
109
|
+
if agent_id not in agent_processes:
|
|
110
|
+
request_queue: Queue[Any] = Queue()
|
|
111
|
+
response_queue: Queue[Any] = Queue()
|
|
112
|
+
|
|
113
|
+
process = Process(
|
|
114
|
+
target=agent_worker, args=(agent_id, request_queue, response_queue), daemon=True
|
|
115
|
+
)
|
|
116
|
+
process.start()
|
|
117
|
+
|
|
118
|
+
agent_processes[agent_id] = {"process": process, "pid": process.pid}
|
|
119
|
+
agent_queues[agent_id] = {"request": request_queue, "response": response_queue}
|
|
120
|
+
|
|
121
|
+
return agent_queues[agent_id]["request"], agent_queues[agent_id]["response"]
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
@app.post("/execute", response_model=ToolExecutionResponse)
|
|
125
|
+
async def execute_tool(
|
|
126
|
+
request: ToolExecutionRequest, credentials: HTTPAuthorizationCredentials = security_dependency
|
|
127
|
+
) -> ToolExecutionResponse:
|
|
128
|
+
verify_token(credentials)
|
|
129
|
+
|
|
130
|
+
request_queue, response_queue = ensure_agent_process(request.agent_id)
|
|
131
|
+
|
|
132
|
+
request_queue.put({"tool_name": request.tool_name, "kwargs": request.kwargs})
|
|
133
|
+
|
|
134
|
+
try:
|
|
135
|
+
loop = asyncio.get_event_loop()
|
|
136
|
+
response = await loop.run_in_executor(None, response_queue.get)
|
|
137
|
+
|
|
138
|
+
if "error" in response:
|
|
139
|
+
return ToolExecutionResponse(error=response["error"])
|
|
140
|
+
return ToolExecutionResponse(result=response.get("result"))
|
|
141
|
+
|
|
142
|
+
except (RuntimeError, ValueError, OSError) as e:
|
|
143
|
+
return ToolExecutionResponse(error=f"Worker error: {e}")
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
@app.post("/register_agent")
|
|
147
|
+
async def register_agent(
|
|
148
|
+
agent_id: str, credentials: HTTPAuthorizationCredentials = security_dependency
|
|
149
|
+
) -> dict[str, str]:
|
|
150
|
+
verify_token(credentials)
|
|
151
|
+
|
|
152
|
+
ensure_agent_process(agent_id)
|
|
153
|
+
return {"status": "registered", "agent_id": agent_id}
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
@app.get("/health")
|
|
157
|
+
async def health_check() -> dict[str, Any]:
|
|
158
|
+
return {
|
|
159
|
+
"status": "healthy",
|
|
160
|
+
"sandbox_mode": str(SANDBOX_MODE),
|
|
161
|
+
"environment": "sandbox" if SANDBOX_MODE else "main",
|
|
162
|
+
"auth_configured": "true" if EXPECTED_TOKEN else "false",
|
|
163
|
+
"active_agents": len(agent_processes),
|
|
164
|
+
"agents": list(agent_processes.keys()),
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def cleanup_all_agents() -> None:
|
|
169
|
+
for agent_id in list(agent_processes.keys()):
|
|
170
|
+
try:
|
|
171
|
+
agent_queues[agent_id]["request"].put(None)
|
|
172
|
+
process = agent_processes[agent_id]["process"]
|
|
173
|
+
|
|
174
|
+
process.join(timeout=1)
|
|
175
|
+
|
|
176
|
+
if process.is_alive():
|
|
177
|
+
process.terminate()
|
|
178
|
+
process.join(timeout=1)
|
|
179
|
+
|
|
180
|
+
if process.is_alive():
|
|
181
|
+
process.kill()
|
|
182
|
+
|
|
183
|
+
except (BrokenPipeError, EOFError, OSError) as e:
|
|
184
|
+
logging.getLogger(__name__).debug(f"Expected error during process cleanup: {e}")
|
|
185
|
+
except (RuntimeError, ValueError) as e:
|
|
186
|
+
logging.getLogger(__name__).debug(f"Error during agent cleanup: {e}")
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def signal_handler(_signum: int, _frame: Any) -> None:
|
|
190
|
+
signal.signal(signal.SIGPIPE, signal.SIG_IGN) if hasattr(signal, "SIGPIPE") else None
|
|
191
|
+
cleanup_all_agents()
|
|
192
|
+
sys.exit(0)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
if hasattr(signal, "SIGPIPE"):
|
|
196
|
+
signal.signal(signal.SIGPIPE, signal.SIG_IGN)
|
|
197
|
+
|
|
198
|
+
signal.signal(signal.SIGTERM, signal_handler)
|
|
199
|
+
signal.signal(signal.SIGINT, signal_handler)
|
|
200
|
+
|
|
201
|
+
if __name__ == "__main__":
|
|
202
|
+
try:
|
|
203
|
+
uvicorn.run(app, host=args.host, port=args.port, log_level="info")
|
|
204
|
+
finally:
|
|
205
|
+
cleanup_all_agents()
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AIPT Scanners Module
|
|
3
|
+
|
|
4
|
+
Integrations with popular security scanning tools:
|
|
5
|
+
- Nuclei - Template-based vulnerability scanner
|
|
6
|
+
- Nmap - Network scanner
|
|
7
|
+
- Nikto - Web server scanner
|
|
8
|
+
- SQLMap - SQL injection scanner
|
|
9
|
+
- Dirb/Gobuster - Directory enumeration
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from .base import BaseScanner, ScanResult
|
|
13
|
+
from .nuclei import NucleiScanner, NucleiConfig
|
|
14
|
+
from .nmap import NmapScanner, NmapConfig
|
|
15
|
+
from .nikto import NiktoScanner
|
|
16
|
+
from .web import WebScanner, WebScanConfig
|
|
17
|
+
|
|
18
|
+
__all__ = [
|
|
19
|
+
"BaseScanner",
|
|
20
|
+
"ScanResult",
|
|
21
|
+
"NucleiScanner",
|
|
22
|
+
"NucleiConfig",
|
|
23
|
+
"NmapScanner",
|
|
24
|
+
"NmapConfig",
|
|
25
|
+
"NiktoScanner",
|
|
26
|
+
"WebScanner",
|
|
27
|
+
"WebScanConfig",
|
|
28
|
+
]
|