aiptx 2.0.7__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- aipt_v2/__init__.py +110 -0
- aipt_v2/__main__.py +24 -0
- aipt_v2/agents/AIPTxAgent/__init__.py +10 -0
- aipt_v2/agents/AIPTxAgent/aiptx_agent.py +211 -0
- aipt_v2/agents/__init__.py +46 -0
- aipt_v2/agents/base.py +520 -0
- aipt_v2/agents/exploit_agent.py +688 -0
- aipt_v2/agents/ptt.py +406 -0
- aipt_v2/agents/state.py +168 -0
- aipt_v2/app.py +957 -0
- aipt_v2/browser/__init__.py +31 -0
- aipt_v2/browser/automation.py +458 -0
- aipt_v2/browser/crawler.py +453 -0
- aipt_v2/cli.py +2933 -0
- aipt_v2/compliance/__init__.py +71 -0
- aipt_v2/compliance/compliance_report.py +449 -0
- aipt_v2/compliance/framework_mapper.py +424 -0
- aipt_v2/compliance/nist_mapping.py +345 -0
- aipt_v2/compliance/owasp_mapping.py +330 -0
- aipt_v2/compliance/pci_mapping.py +297 -0
- aipt_v2/config.py +341 -0
- aipt_v2/core/__init__.py +43 -0
- aipt_v2/core/agent.py +630 -0
- aipt_v2/core/llm.py +395 -0
- aipt_v2/core/memory.py +305 -0
- aipt_v2/core/ptt.py +329 -0
- aipt_v2/database/__init__.py +14 -0
- aipt_v2/database/models.py +232 -0
- aipt_v2/database/repository.py +384 -0
- aipt_v2/docker/__init__.py +23 -0
- aipt_v2/docker/builder.py +260 -0
- aipt_v2/docker/manager.py +222 -0
- aipt_v2/docker/sandbox.py +371 -0
- aipt_v2/evasion/__init__.py +58 -0
- aipt_v2/evasion/request_obfuscator.py +272 -0
- aipt_v2/evasion/tls_fingerprint.py +285 -0
- aipt_v2/evasion/ua_rotator.py +301 -0
- aipt_v2/evasion/waf_bypass.py +439 -0
- aipt_v2/execution/__init__.py +23 -0
- aipt_v2/execution/executor.py +302 -0
- aipt_v2/execution/parser.py +544 -0
- aipt_v2/execution/terminal.py +337 -0
- aipt_v2/health.py +437 -0
- aipt_v2/intelligence/__init__.py +194 -0
- aipt_v2/intelligence/adaptation.py +474 -0
- aipt_v2/intelligence/auth.py +520 -0
- aipt_v2/intelligence/chaining.py +775 -0
- aipt_v2/intelligence/correlation.py +536 -0
- aipt_v2/intelligence/cve_aipt.py +334 -0
- aipt_v2/intelligence/cve_info.py +1111 -0
- aipt_v2/intelligence/knowledge_graph.py +590 -0
- aipt_v2/intelligence/learning.py +626 -0
- aipt_v2/intelligence/llm_analyzer.py +502 -0
- aipt_v2/intelligence/llm_tool_selector.py +518 -0
- aipt_v2/intelligence/payload_generator.py +562 -0
- aipt_v2/intelligence/rag.py +239 -0
- aipt_v2/intelligence/scope.py +442 -0
- aipt_v2/intelligence/searchers/__init__.py +5 -0
- aipt_v2/intelligence/searchers/exploitdb_searcher.py +523 -0
- aipt_v2/intelligence/searchers/github_searcher.py +467 -0
- aipt_v2/intelligence/searchers/google_searcher.py +281 -0
- aipt_v2/intelligence/tools.json +443 -0
- aipt_v2/intelligence/triage.py +670 -0
- aipt_v2/interactive_shell.py +559 -0
- aipt_v2/interface/__init__.py +5 -0
- aipt_v2/interface/cli.py +230 -0
- aipt_v2/interface/main.py +501 -0
- aipt_v2/interface/tui.py +1276 -0
- aipt_v2/interface/utils.py +583 -0
- aipt_v2/llm/__init__.py +39 -0
- aipt_v2/llm/config.py +26 -0
- aipt_v2/llm/llm.py +514 -0
- aipt_v2/llm/memory.py +214 -0
- aipt_v2/llm/request_queue.py +89 -0
- aipt_v2/llm/utils.py +89 -0
- aipt_v2/local_tool_installer.py +1467 -0
- aipt_v2/models/__init__.py +15 -0
- aipt_v2/models/findings.py +295 -0
- aipt_v2/models/phase_result.py +224 -0
- aipt_v2/models/scan_config.py +207 -0
- aipt_v2/monitoring/grafana/dashboards/aipt-dashboard.json +355 -0
- aipt_v2/monitoring/grafana/dashboards/default.yml +17 -0
- aipt_v2/monitoring/grafana/datasources/prometheus.yml +17 -0
- aipt_v2/monitoring/prometheus.yml +60 -0
- aipt_v2/orchestration/__init__.py +52 -0
- aipt_v2/orchestration/pipeline.py +398 -0
- aipt_v2/orchestration/progress.py +300 -0
- aipt_v2/orchestration/scheduler.py +296 -0
- aipt_v2/orchestrator.py +2427 -0
- aipt_v2/payloads/__init__.py +27 -0
- aipt_v2/payloads/cmdi.py +150 -0
- aipt_v2/payloads/sqli.py +263 -0
- aipt_v2/payloads/ssrf.py +204 -0
- aipt_v2/payloads/templates.py +222 -0
- aipt_v2/payloads/traversal.py +166 -0
- aipt_v2/payloads/xss.py +204 -0
- aipt_v2/prompts/__init__.py +60 -0
- aipt_v2/proxy/__init__.py +29 -0
- aipt_v2/proxy/history.py +352 -0
- aipt_v2/proxy/interceptor.py +452 -0
- aipt_v2/recon/__init__.py +44 -0
- aipt_v2/recon/dns.py +241 -0
- aipt_v2/recon/osint.py +367 -0
- aipt_v2/recon/subdomain.py +372 -0
- aipt_v2/recon/tech_detect.py +311 -0
- aipt_v2/reports/__init__.py +17 -0
- aipt_v2/reports/generator.py +313 -0
- aipt_v2/reports/html_report.py +378 -0
- aipt_v2/runtime/__init__.py +53 -0
- aipt_v2/runtime/base.py +30 -0
- aipt_v2/runtime/docker.py +401 -0
- aipt_v2/runtime/local.py +346 -0
- aipt_v2/runtime/tool_server.py +205 -0
- aipt_v2/runtime/vps.py +830 -0
- aipt_v2/scanners/__init__.py +28 -0
- aipt_v2/scanners/base.py +273 -0
- aipt_v2/scanners/nikto.py +244 -0
- aipt_v2/scanners/nmap.py +402 -0
- aipt_v2/scanners/nuclei.py +273 -0
- aipt_v2/scanners/web.py +454 -0
- aipt_v2/scripts/security_audit.py +366 -0
- aipt_v2/setup_wizard.py +941 -0
- aipt_v2/skills/__init__.py +80 -0
- aipt_v2/skills/agents/__init__.py +14 -0
- aipt_v2/skills/agents/api_tester.py +706 -0
- aipt_v2/skills/agents/base.py +477 -0
- aipt_v2/skills/agents/code_review.py +459 -0
- aipt_v2/skills/agents/security_agent.py +336 -0
- aipt_v2/skills/agents/web_pentest.py +818 -0
- aipt_v2/skills/prompts/__init__.py +647 -0
- aipt_v2/system_detector.py +539 -0
- aipt_v2/telemetry/__init__.py +7 -0
- aipt_v2/telemetry/tracer.py +347 -0
- aipt_v2/terminal/__init__.py +28 -0
- aipt_v2/terminal/executor.py +400 -0
- aipt_v2/terminal/sandbox.py +350 -0
- aipt_v2/tools/__init__.py +44 -0
- aipt_v2/tools/active_directory/__init__.py +78 -0
- aipt_v2/tools/active_directory/ad_config.py +238 -0
- aipt_v2/tools/active_directory/bloodhound_wrapper.py +447 -0
- aipt_v2/tools/active_directory/kerberos_attacks.py +430 -0
- aipt_v2/tools/active_directory/ldap_enum.py +533 -0
- aipt_v2/tools/active_directory/smb_attacks.py +505 -0
- aipt_v2/tools/agents_graph/__init__.py +19 -0
- aipt_v2/tools/agents_graph/agents_graph_actions.py +69 -0
- aipt_v2/tools/api_security/__init__.py +76 -0
- aipt_v2/tools/api_security/api_discovery.py +608 -0
- aipt_v2/tools/api_security/graphql_scanner.py +622 -0
- aipt_v2/tools/api_security/jwt_analyzer.py +577 -0
- aipt_v2/tools/api_security/openapi_fuzzer.py +761 -0
- aipt_v2/tools/browser/__init__.py +5 -0
- aipt_v2/tools/browser/browser_actions.py +238 -0
- aipt_v2/tools/browser/browser_instance.py +535 -0
- aipt_v2/tools/browser/tab_manager.py +344 -0
- aipt_v2/tools/cloud/__init__.py +70 -0
- aipt_v2/tools/cloud/cloud_config.py +273 -0
- aipt_v2/tools/cloud/cloud_scanner.py +639 -0
- aipt_v2/tools/cloud/prowler_tool.py +571 -0
- aipt_v2/tools/cloud/scoutsuite_tool.py +359 -0
- aipt_v2/tools/executor.py +307 -0
- aipt_v2/tools/parser.py +408 -0
- aipt_v2/tools/proxy/__init__.py +5 -0
- aipt_v2/tools/proxy/proxy_actions.py +103 -0
- aipt_v2/tools/proxy/proxy_manager.py +789 -0
- aipt_v2/tools/registry.py +196 -0
- aipt_v2/tools/scanners/__init__.py +343 -0
- aipt_v2/tools/scanners/acunetix_tool.py +712 -0
- aipt_v2/tools/scanners/burp_tool.py +631 -0
- aipt_v2/tools/scanners/config.py +156 -0
- aipt_v2/tools/scanners/nessus_tool.py +588 -0
- aipt_v2/tools/scanners/zap_tool.py +612 -0
- aipt_v2/tools/terminal/__init__.py +5 -0
- aipt_v2/tools/terminal/terminal_actions.py +37 -0
- aipt_v2/tools/terminal/terminal_manager.py +153 -0
- aipt_v2/tools/terminal/terminal_session.py +449 -0
- aipt_v2/tools/tool_processing.py +108 -0
- aipt_v2/utils/__init__.py +17 -0
- aipt_v2/utils/logging.py +202 -0
- aipt_v2/utils/model_manager.py +187 -0
- aipt_v2/utils/searchers/__init__.py +269 -0
- aipt_v2/verify_install.py +793 -0
- aiptx-2.0.7.dist-info/METADATA +345 -0
- aiptx-2.0.7.dist-info/RECORD +187 -0
- aiptx-2.0.7.dist-info/WHEEL +5 -0
- aiptx-2.0.7.dist-info/entry_points.txt +7 -0
- aiptx-2.0.7.dist-info/licenses/LICENSE +21 -0
- aiptx-2.0.7.dist-info/top_level.txt +1 -0
aipt_v2/core/ptt.py
ADDED
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AIPT PTT (Penetration Testing Tree) Tracker
|
|
3
|
+
|
|
4
|
+
Hierarchical task tracking for pentest sessions.
|
|
5
|
+
Inspired by PentestGPT's PTT concept.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import Optional, List, Dict, Any
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
from enum import Enum
|
|
12
|
+
from datetime import datetime
|
|
13
|
+
import json
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class TaskStatus(str, Enum):
|
|
17
|
+
"""Task status"""
|
|
18
|
+
PENDING = "pending"
|
|
19
|
+
IN_PROGRESS = "in_progress"
|
|
20
|
+
COMPLETED = "completed"
|
|
21
|
+
FAILED = "failed"
|
|
22
|
+
BLOCKED = "blocked"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass
|
|
26
|
+
class PTTNode:
|
|
27
|
+
"""A node in the Penetration Testing Tree"""
|
|
28
|
+
id: str
|
|
29
|
+
name: str
|
|
30
|
+
description: str = ""
|
|
31
|
+
status: TaskStatus = TaskStatus.PENDING
|
|
32
|
+
phase: str = "recon"
|
|
33
|
+
parent_id: Optional[str] = None
|
|
34
|
+
children: List[str] = field(default_factory=list)
|
|
35
|
+
findings: List[dict] = field(default_factory=list)
|
|
36
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
37
|
+
created_at: str = field(default_factory=lambda: datetime.now().isoformat())
|
|
38
|
+
updated_at: str = field(default_factory=lambda: datetime.now().isoformat())
|
|
39
|
+
|
|
40
|
+
def to_dict(self) -> dict:
|
|
41
|
+
return {
|
|
42
|
+
"id": self.id,
|
|
43
|
+
"name": self.name,
|
|
44
|
+
"description": self.description,
|
|
45
|
+
"status": self.status.value,
|
|
46
|
+
"phase": self.phase,
|
|
47
|
+
"parent_id": self.parent_id,
|
|
48
|
+
"children": self.children,
|
|
49
|
+
"findings": self.findings,
|
|
50
|
+
"metadata": self.metadata,
|
|
51
|
+
"created_at": self.created_at,
|
|
52
|
+
"updated_at": self.updated_at,
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
@classmethod
|
|
56
|
+
def from_dict(cls, data: dict) -> "PTTNode":
|
|
57
|
+
return cls(
|
|
58
|
+
id=data["id"],
|
|
59
|
+
name=data["name"],
|
|
60
|
+
description=data.get("description", ""),
|
|
61
|
+
status=TaskStatus(data.get("status", "pending")),
|
|
62
|
+
phase=data.get("phase", "recon"),
|
|
63
|
+
parent_id=data.get("parent_id"),
|
|
64
|
+
children=data.get("children", []),
|
|
65
|
+
findings=data.get("findings", []),
|
|
66
|
+
metadata=data.get("metadata", {}),
|
|
67
|
+
created_at=data.get("created_at", datetime.now().isoformat()),
|
|
68
|
+
updated_at=data.get("updated_at", datetime.now().isoformat()),
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class PTTTracker:
|
|
73
|
+
"""
|
|
74
|
+
Penetration Testing Tree Tracker.
|
|
75
|
+
|
|
76
|
+
Maintains a hierarchical view of pentest tasks:
|
|
77
|
+
- Target (root)
|
|
78
|
+
- Phase (recon, enum, exploit, post)
|
|
79
|
+
- Task (specific action)
|
|
80
|
+
- Finding (discovered information)
|
|
81
|
+
|
|
82
|
+
Example:
|
|
83
|
+
ptt = PTTTracker()
|
|
84
|
+
ptt.initialize("192.168.1.0/24")
|
|
85
|
+
ptt.add_task("recon", "Port scan with nmap")
|
|
86
|
+
ptt.add_finding("recon", task_id, {"type": "port", "value": "80/tcp"})
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
def __init__(self):
|
|
90
|
+
self.nodes: Dict[str, PTTNode] = {}
|
|
91
|
+
self.root_id: Optional[str] = None
|
|
92
|
+
self._id_counter: int = 0
|
|
93
|
+
|
|
94
|
+
def _generate_id(self) -> str:
|
|
95
|
+
"""Generate unique node ID"""
|
|
96
|
+
self._id_counter += 1
|
|
97
|
+
return f"node_{self._id_counter}"
|
|
98
|
+
|
|
99
|
+
def initialize(self, target: str) -> dict:
|
|
100
|
+
"""
|
|
101
|
+
Initialize PTT for a new target.
|
|
102
|
+
|
|
103
|
+
Creates root node and phase nodes.
|
|
104
|
+
"""
|
|
105
|
+
self.nodes = {}
|
|
106
|
+
self._id_counter = 0
|
|
107
|
+
|
|
108
|
+
# Create root node
|
|
109
|
+
root_id = self._generate_id()
|
|
110
|
+
self.root_id = root_id
|
|
111
|
+
root = PTTNode(
|
|
112
|
+
id=root_id,
|
|
113
|
+
name=target,
|
|
114
|
+
description=f"Pentest target: {target}",
|
|
115
|
+
phase="root",
|
|
116
|
+
)
|
|
117
|
+
self.nodes[root_id] = root
|
|
118
|
+
|
|
119
|
+
# Create phase nodes
|
|
120
|
+
phases = ["recon", "enum", "exploit", "post", "report"]
|
|
121
|
+
for phase in phases:
|
|
122
|
+
phase_id = self._generate_id()
|
|
123
|
+
phase_node = PTTNode(
|
|
124
|
+
id=phase_id,
|
|
125
|
+
name=phase.upper(),
|
|
126
|
+
description=f"{phase.title()} phase",
|
|
127
|
+
phase=phase,
|
|
128
|
+
parent_id=root_id,
|
|
129
|
+
)
|
|
130
|
+
self.nodes[phase_id] = phase_node
|
|
131
|
+
root.children.append(phase_id)
|
|
132
|
+
|
|
133
|
+
return self.to_dict()
|
|
134
|
+
|
|
135
|
+
def add_task(
|
|
136
|
+
self,
|
|
137
|
+
phase: str,
|
|
138
|
+
name: str,
|
|
139
|
+
description: str = "",
|
|
140
|
+
parent_id: Optional[str] = None,
|
|
141
|
+
) -> str:
|
|
142
|
+
"""
|
|
143
|
+
Add a task to a phase.
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
phase: Phase name (recon, enum, exploit, post)
|
|
147
|
+
name: Task name
|
|
148
|
+
description: Task description
|
|
149
|
+
parent_id: Optional parent task ID
|
|
150
|
+
|
|
151
|
+
Returns:
|
|
152
|
+
Task node ID
|
|
153
|
+
"""
|
|
154
|
+
# Find phase node
|
|
155
|
+
phase_node = self._get_phase_node(phase)
|
|
156
|
+
if not phase_node:
|
|
157
|
+
raise ValueError(f"Phase not found: {phase}")
|
|
158
|
+
|
|
159
|
+
# Create task node
|
|
160
|
+
task_id = self._generate_id()
|
|
161
|
+
task = PTTNode(
|
|
162
|
+
id=task_id,
|
|
163
|
+
name=name,
|
|
164
|
+
description=description,
|
|
165
|
+
phase=phase,
|
|
166
|
+
parent_id=parent_id or phase_node.id,
|
|
167
|
+
status=TaskStatus.PENDING,
|
|
168
|
+
)
|
|
169
|
+
self.nodes[task_id] = task
|
|
170
|
+
|
|
171
|
+
# Add to parent's children
|
|
172
|
+
parent_node = self.nodes.get(parent_id or phase_node.id)
|
|
173
|
+
if parent_node:
|
|
174
|
+
parent_node.children.append(task_id)
|
|
175
|
+
|
|
176
|
+
return task_id
|
|
177
|
+
|
|
178
|
+
def update_task_status(self, task_id: str, status: TaskStatus) -> None:
|
|
179
|
+
"""Update task status"""
|
|
180
|
+
if task_id in self.nodes:
|
|
181
|
+
self.nodes[task_id].status = status
|
|
182
|
+
self.nodes[task_id].updated_at = datetime.now().isoformat()
|
|
183
|
+
|
|
184
|
+
def add_finding(self, task_id: str, finding: dict) -> None:
|
|
185
|
+
"""Add finding to a task"""
|
|
186
|
+
if task_id in self.nodes:
|
|
187
|
+
self.nodes[task_id].findings.append(finding)
|
|
188
|
+
self.nodes[task_id].updated_at = datetime.now().isoformat()
|
|
189
|
+
|
|
190
|
+
def add_findings(self, phase: str, findings: List[dict]) -> None:
|
|
191
|
+
"""Add multiple findings to a phase"""
|
|
192
|
+
phase_node = self._get_phase_node(phase)
|
|
193
|
+
if phase_node:
|
|
194
|
+
for finding in findings:
|
|
195
|
+
phase_node.findings.append(finding)
|
|
196
|
+
phase_node.updated_at = datetime.now().isoformat()
|
|
197
|
+
|
|
198
|
+
def _get_phase_node(self, phase: str) -> Optional[PTTNode]:
|
|
199
|
+
"""Get phase node by name"""
|
|
200
|
+
for node in self.nodes.values():
|
|
201
|
+
if node.phase == phase and node.parent_id == self.root_id:
|
|
202
|
+
return node
|
|
203
|
+
return None
|
|
204
|
+
|
|
205
|
+
def get_tasks_by_phase(self, phase: str) -> List[PTTNode]:
|
|
206
|
+
"""Get all tasks in a phase"""
|
|
207
|
+
phase_node = self._get_phase_node(phase)
|
|
208
|
+
if not phase_node:
|
|
209
|
+
return []
|
|
210
|
+
|
|
211
|
+
return [
|
|
212
|
+
self.nodes[child_id]
|
|
213
|
+
for child_id in phase_node.children
|
|
214
|
+
if child_id in self.nodes
|
|
215
|
+
]
|
|
216
|
+
|
|
217
|
+
def get_pending_tasks(self, phase: Optional[str] = None) -> List[PTTNode]:
|
|
218
|
+
"""Get all pending tasks, optionally filtered by phase"""
|
|
219
|
+
pending = []
|
|
220
|
+
for node in self.nodes.values():
|
|
221
|
+
if node.status == TaskStatus.PENDING:
|
|
222
|
+
if phase is None or node.phase == phase:
|
|
223
|
+
pending.append(node)
|
|
224
|
+
return pending
|
|
225
|
+
|
|
226
|
+
def get_all_findings(self) -> List[dict]:
|
|
227
|
+
"""Get all findings across all nodes"""
|
|
228
|
+
findings = []
|
|
229
|
+
for node in self.nodes.values():
|
|
230
|
+
for finding in node.findings:
|
|
231
|
+
finding["task_id"] = node.id
|
|
232
|
+
finding["task_name"] = node.name
|
|
233
|
+
finding["phase"] = node.phase
|
|
234
|
+
findings.append(finding)
|
|
235
|
+
return findings
|
|
236
|
+
|
|
237
|
+
def get_phase_summary(self, phase: str) -> dict:
|
|
238
|
+
"""Get summary for a phase"""
|
|
239
|
+
phase_node = self._get_phase_node(phase)
|
|
240
|
+
if not phase_node:
|
|
241
|
+
return {}
|
|
242
|
+
|
|
243
|
+
tasks = self.get_tasks_by_phase(phase)
|
|
244
|
+
return {
|
|
245
|
+
"phase": phase,
|
|
246
|
+
"status": phase_node.status.value,
|
|
247
|
+
"total_tasks": len(tasks),
|
|
248
|
+
"completed_tasks": len([t for t in tasks if t.status == TaskStatus.COMPLETED]),
|
|
249
|
+
"pending_tasks": len([t for t in tasks if t.status == TaskStatus.PENDING]),
|
|
250
|
+
"findings_count": len(phase_node.findings) + sum(len(t.findings) for t in tasks),
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
def to_prompt(self) -> str:
|
|
254
|
+
"""Format PTT for LLM prompt"""
|
|
255
|
+
if not self.root_id:
|
|
256
|
+
return "No PTT initialized."
|
|
257
|
+
|
|
258
|
+
lines = []
|
|
259
|
+
root = self.nodes[self.root_id]
|
|
260
|
+
lines.append(f"Target: {root.name}")
|
|
261
|
+
lines.append("")
|
|
262
|
+
|
|
263
|
+
for phase in ["recon", "enum", "exploit", "post"]:
|
|
264
|
+
phase_node = self._get_phase_node(phase)
|
|
265
|
+
if not phase_node:
|
|
266
|
+
continue
|
|
267
|
+
|
|
268
|
+
status_emoji = {
|
|
269
|
+
TaskStatus.PENDING: "⏳",
|
|
270
|
+
TaskStatus.IN_PROGRESS: "🔄",
|
|
271
|
+
TaskStatus.COMPLETED: "✅",
|
|
272
|
+
TaskStatus.FAILED: "❌",
|
|
273
|
+
TaskStatus.BLOCKED: "🚫",
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
lines.append(f"## {phase.upper()} {status_emoji.get(phase_node.status, '⏳')}")
|
|
277
|
+
|
|
278
|
+
tasks = self.get_tasks_by_phase(phase)
|
|
279
|
+
if tasks:
|
|
280
|
+
for task in tasks:
|
|
281
|
+
emoji = status_emoji.get(task.status, "⏳")
|
|
282
|
+
lines.append(f" - {emoji} {task.name}")
|
|
283
|
+
if task.findings:
|
|
284
|
+
for finding in task.findings[:3]: # Limit findings shown
|
|
285
|
+
lines.append(f" • {finding.get('type', 'info')}: {finding.get('description', 'N/A')[:50]}")
|
|
286
|
+
|
|
287
|
+
if phase_node.findings:
|
|
288
|
+
lines.append(f" Findings: {len(phase_node.findings)}")
|
|
289
|
+
|
|
290
|
+
lines.append("")
|
|
291
|
+
|
|
292
|
+
return "\n".join(lines)
|
|
293
|
+
|
|
294
|
+
def to_dict(self) -> dict:
|
|
295
|
+
"""Export PTT to dictionary"""
|
|
296
|
+
return {
|
|
297
|
+
"root_id": self.root_id,
|
|
298
|
+
"nodes": {k: v.to_dict() for k, v in self.nodes.items()},
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
@classmethod
|
|
302
|
+
def from_dict(cls, data: dict) -> "PTTTracker":
|
|
303
|
+
"""Create PTT from dictionary"""
|
|
304
|
+
tracker = cls()
|
|
305
|
+
tracker.root_id = data.get("root_id")
|
|
306
|
+
tracker.nodes = {
|
|
307
|
+
k: PTTNode.from_dict(v) for k, v in data.get("nodes", {}).items()
|
|
308
|
+
}
|
|
309
|
+
return tracker
|
|
310
|
+
|
|
311
|
+
def to_json(self) -> str:
|
|
312
|
+
"""Export to JSON"""
|
|
313
|
+
return json.dumps(self.to_dict(), indent=2, default=str)
|
|
314
|
+
|
|
315
|
+
@classmethod
|
|
316
|
+
def from_json(cls, json_str: str) -> "PTTTracker":
|
|
317
|
+
"""Create from JSON"""
|
|
318
|
+
return cls.from_dict(json.loads(json_str))
|
|
319
|
+
|
|
320
|
+
def save(self, filepath: str) -> None:
|
|
321
|
+
"""Save PTT to file"""
|
|
322
|
+
with open(filepath, "w") as f:
|
|
323
|
+
f.write(self.to_json())
|
|
324
|
+
|
|
325
|
+
@classmethod
|
|
326
|
+
def load(cls, filepath: str) -> "PTTTracker":
|
|
327
|
+
"""Load PTT from file"""
|
|
328
|
+
with open(filepath, "r") as f:
|
|
329
|
+
return cls.from_json(f.read())
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AIPT Database Module - SQLAlchemy persistence layer
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from aipt_v2.database.models import Base, Project, Session, Finding
|
|
6
|
+
from aipt_v2.database.repository import Repository
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"Base",
|
|
10
|
+
"Project",
|
|
11
|
+
"Session",
|
|
12
|
+
"Finding",
|
|
13
|
+
"Repository",
|
|
14
|
+
]
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AIPT Database Models - SQLAlchemy ORM models for persistence
|
|
3
|
+
Supports SQLite (development) and PostgreSQL (production)
|
|
4
|
+
|
|
5
|
+
Models:
|
|
6
|
+
- Project: Top-level container for pentests
|
|
7
|
+
- Session: Individual scan/attack session
|
|
8
|
+
- Finding: Discovered vulnerability/info
|
|
9
|
+
- Task: PTT task tracking
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from datetime import datetime
|
|
13
|
+
from typing import Optional
|
|
14
|
+
from enum import Enum
|
|
15
|
+
|
|
16
|
+
from sqlalchemy import (
|
|
17
|
+
Column, Integer, String, Text, DateTime,
|
|
18
|
+
ForeignKey, JSON, Boolean, Float, Enum as SQLEnum,
|
|
19
|
+
create_engine, Index
|
|
20
|
+
)
|
|
21
|
+
from sqlalchemy.orm import relationship, declarative_base
|
|
22
|
+
from sqlalchemy.sql import func
|
|
23
|
+
|
|
24
|
+
Base = declarative_base()
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class SeverityLevel(str, Enum):
|
|
28
|
+
"""Finding severity levels"""
|
|
29
|
+
INFO = "info"
|
|
30
|
+
LOW = "low"
|
|
31
|
+
MEDIUM = "medium"
|
|
32
|
+
HIGH = "high"
|
|
33
|
+
CRITICAL = "critical"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class TaskStatus(str, Enum):
|
|
37
|
+
"""Task status values"""
|
|
38
|
+
TODO = "to-do"
|
|
39
|
+
IN_PROGRESS = "in-progress"
|
|
40
|
+
COMPLETED = "completed"
|
|
41
|
+
BLOCKED = "blocked"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class PhaseType(str, Enum):
|
|
45
|
+
"""Pentest phases"""
|
|
46
|
+
RECON = "recon"
|
|
47
|
+
ENUM = "enum"
|
|
48
|
+
EXPLOIT = "exploit"
|
|
49
|
+
POST = "post"
|
|
50
|
+
REPORT = "report"
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class Project(Base):
|
|
54
|
+
"""
|
|
55
|
+
Top-level project container.
|
|
56
|
+
A project represents a complete pentest engagement.
|
|
57
|
+
"""
|
|
58
|
+
__tablename__ = "projects"
|
|
59
|
+
|
|
60
|
+
id = Column(Integer, primary_key=True, autoincrement=True)
|
|
61
|
+
name = Column(String(255), nullable=False)
|
|
62
|
+
description = Column(Text, nullable=True)
|
|
63
|
+
target = Column(String(255), nullable=False) # Primary target
|
|
64
|
+
scope = Column(JSON, default=list) # List of in-scope targets
|
|
65
|
+
|
|
66
|
+
# Metadata
|
|
67
|
+
created_at = Column(DateTime, server_default=func.now())
|
|
68
|
+
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now())
|
|
69
|
+
status = Column(String(50), default="active") # active, completed, archived
|
|
70
|
+
|
|
71
|
+
# Configuration
|
|
72
|
+
config = Column(JSON, default=dict) # LLM settings, timeouts, etc.
|
|
73
|
+
|
|
74
|
+
# Relationships
|
|
75
|
+
sessions = relationship("Session", back_populates="project", cascade="all, delete-orphan")
|
|
76
|
+
findings = relationship("Finding", back_populates="project", cascade="all, delete-orphan")
|
|
77
|
+
|
|
78
|
+
# Indexes
|
|
79
|
+
__table_args__ = (
|
|
80
|
+
Index("idx_project_target", "target"),
|
|
81
|
+
Index("idx_project_status", "status"),
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
def __repr__(self):
|
|
85
|
+
return f"<Project(id={self.id}, name='{self.name}', target='{self.target}')>"
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class Session(Base):
|
|
89
|
+
"""
|
|
90
|
+
Individual scan/attack session within a project.
|
|
91
|
+
Tracks a single run of the agent.
|
|
92
|
+
"""
|
|
93
|
+
__tablename__ = "sessions"
|
|
94
|
+
|
|
95
|
+
id = Column(Integer, primary_key=True, autoincrement=True)
|
|
96
|
+
project_id = Column(Integer, ForeignKey("projects.id"), nullable=False)
|
|
97
|
+
|
|
98
|
+
# Session info
|
|
99
|
+
name = Column(String(255), nullable=True)
|
|
100
|
+
phase = Column(SQLEnum(PhaseType), default=PhaseType.RECON)
|
|
101
|
+
|
|
102
|
+
# Timing
|
|
103
|
+
started_at = Column(DateTime, server_default=func.now())
|
|
104
|
+
ended_at = Column(DateTime, nullable=True)
|
|
105
|
+
|
|
106
|
+
# Progress
|
|
107
|
+
iteration = Column(Integer, default=0)
|
|
108
|
+
max_iterations = Column(Integer, default=100)
|
|
109
|
+
status = Column(String(50), default="running") # running, paused, completed, error
|
|
110
|
+
|
|
111
|
+
# State
|
|
112
|
+
state = Column(JSON, default=dict) # Full agent state for resume
|
|
113
|
+
memory_summary = Column(Text, nullable=True) # Compressed memory
|
|
114
|
+
|
|
115
|
+
# Relationships
|
|
116
|
+
project = relationship("Project", back_populates="sessions")
|
|
117
|
+
tasks = relationship("Task", back_populates="session", cascade="all, delete-orphan")
|
|
118
|
+
|
|
119
|
+
# Indexes
|
|
120
|
+
__table_args__ = (
|
|
121
|
+
Index("idx_session_project", "project_id"),
|
|
122
|
+
Index("idx_session_status", "status"),
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
def __repr__(self):
|
|
126
|
+
return f"<Session(id={self.id}, project_id={self.project_id}, phase='{self.phase}')>"
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class Finding(Base):
|
|
130
|
+
"""
|
|
131
|
+
Discovered finding (vulnerability, service, credential, etc.)
|
|
132
|
+
"""
|
|
133
|
+
__tablename__ = "findings"
|
|
134
|
+
|
|
135
|
+
id = Column(Integer, primary_key=True, autoincrement=True)
|
|
136
|
+
project_id = Column(Integer, ForeignKey("projects.id"), nullable=False)
|
|
137
|
+
session_id = Column(Integer, ForeignKey("sessions.id"), nullable=True)
|
|
138
|
+
|
|
139
|
+
# Finding details
|
|
140
|
+
type = Column(String(50), nullable=False) # port, service, vuln, credential, host, path
|
|
141
|
+
value = Column(String(500), nullable=False) # Primary identifier
|
|
142
|
+
description = Column(Text, nullable=True)
|
|
143
|
+
severity = Column(SQLEnum(SeverityLevel), default=SeverityLevel.INFO)
|
|
144
|
+
|
|
145
|
+
# Metadata
|
|
146
|
+
phase = Column(SQLEnum(PhaseType), nullable=True)
|
|
147
|
+
tool = Column(String(100), nullable=True) # Tool that found this
|
|
148
|
+
raw_output = Column(Text, nullable=True) # Original tool output
|
|
149
|
+
extra_data = Column(JSON, default=dict) # Additional structured data (renamed from metadata - reserved in SQLAlchemy)
|
|
150
|
+
|
|
151
|
+
# Verification
|
|
152
|
+
verified = Column(Boolean, default=False)
|
|
153
|
+
false_positive = Column(Boolean, default=False)
|
|
154
|
+
notes = Column(Text, nullable=True)
|
|
155
|
+
|
|
156
|
+
# Timing
|
|
157
|
+
discovered_at = Column(DateTime, server_default=func.now())
|
|
158
|
+
|
|
159
|
+
# Relationships
|
|
160
|
+
project = relationship("Project", back_populates="findings")
|
|
161
|
+
|
|
162
|
+
# Indexes
|
|
163
|
+
__table_args__ = (
|
|
164
|
+
Index("idx_finding_project", "project_id"),
|
|
165
|
+
Index("idx_finding_type", "type"),
|
|
166
|
+
Index("idx_finding_severity", "severity"),
|
|
167
|
+
Index("idx_finding_value", "value"),
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
def __repr__(self):
|
|
171
|
+
return f"<Finding(id={self.id}, type='{self.type}', value='{self.value[:30]}')>"
|
|
172
|
+
|
|
173
|
+
def to_dict(self) -> dict:
|
|
174
|
+
"""Convert to dictionary"""
|
|
175
|
+
return {
|
|
176
|
+
"id": self.id,
|
|
177
|
+
"type": self.type,
|
|
178
|
+
"value": self.value,
|
|
179
|
+
"description": self.description,
|
|
180
|
+
"severity": self.severity.value if self.severity else "info",
|
|
181
|
+
"phase": self.phase.value if self.phase else None,
|
|
182
|
+
"tool": self.tool,
|
|
183
|
+
"extra_data": self.extra_data,
|
|
184
|
+
"verified": self.verified,
|
|
185
|
+
"false_positive": self.false_positive,
|
|
186
|
+
"discovered_at": self.discovered_at.isoformat() if self.discovered_at else None,
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
class Task(Base):
|
|
191
|
+
"""
|
|
192
|
+
PTT task for tracking pentest progress
|
|
193
|
+
"""
|
|
194
|
+
__tablename__ = "tasks"
|
|
195
|
+
|
|
196
|
+
id = Column(Integer, primary_key=True, autoincrement=True)
|
|
197
|
+
session_id = Column(Integer, ForeignKey("sessions.id"), nullable=False)
|
|
198
|
+
|
|
199
|
+
# Task details
|
|
200
|
+
task_id = Column(String(10), nullable=False) # e.g., "R1", "E2"
|
|
201
|
+
description = Column(Text, nullable=False)
|
|
202
|
+
phase = Column(SQLEnum(PhaseType), nullable=False)
|
|
203
|
+
status = Column(SQLEnum(TaskStatus), default=TaskStatus.TODO)
|
|
204
|
+
|
|
205
|
+
# Timing
|
|
206
|
+
created_at = Column(DateTime, server_default=func.now())
|
|
207
|
+
started_at = Column(DateTime, nullable=True)
|
|
208
|
+
completed_at = Column(DateTime, nullable=True)
|
|
209
|
+
|
|
210
|
+
# Results
|
|
211
|
+
findings_count = Column(Integer, default=0)
|
|
212
|
+
notes = Column(Text, nullable=True)
|
|
213
|
+
|
|
214
|
+
# Relationships
|
|
215
|
+
session = relationship("Session", back_populates="tasks")
|
|
216
|
+
|
|
217
|
+
# Indexes
|
|
218
|
+
__table_args__ = (
|
|
219
|
+
Index("idx_task_session", "session_id"),
|
|
220
|
+
Index("idx_task_phase", "phase"),
|
|
221
|
+
Index("idx_task_status", "status"),
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
def __repr__(self):
|
|
225
|
+
return f"<Task(id={self.id}, task_id='{self.task_id}', status='{self.status}')>"
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def create_database(db_url: str = "sqlite:///aipt.db") -> None:
|
|
229
|
+
"""Create database tables"""
|
|
230
|
+
engine = create_engine(db_url)
|
|
231
|
+
Base.metadata.create_all(engine)
|
|
232
|
+
return engine
|