aiptx 2.0.7__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (187) hide show
  1. aipt_v2/__init__.py +110 -0
  2. aipt_v2/__main__.py +24 -0
  3. aipt_v2/agents/AIPTxAgent/__init__.py +10 -0
  4. aipt_v2/agents/AIPTxAgent/aiptx_agent.py +211 -0
  5. aipt_v2/agents/__init__.py +46 -0
  6. aipt_v2/agents/base.py +520 -0
  7. aipt_v2/agents/exploit_agent.py +688 -0
  8. aipt_v2/agents/ptt.py +406 -0
  9. aipt_v2/agents/state.py +168 -0
  10. aipt_v2/app.py +957 -0
  11. aipt_v2/browser/__init__.py +31 -0
  12. aipt_v2/browser/automation.py +458 -0
  13. aipt_v2/browser/crawler.py +453 -0
  14. aipt_v2/cli.py +2933 -0
  15. aipt_v2/compliance/__init__.py +71 -0
  16. aipt_v2/compliance/compliance_report.py +449 -0
  17. aipt_v2/compliance/framework_mapper.py +424 -0
  18. aipt_v2/compliance/nist_mapping.py +345 -0
  19. aipt_v2/compliance/owasp_mapping.py +330 -0
  20. aipt_v2/compliance/pci_mapping.py +297 -0
  21. aipt_v2/config.py +341 -0
  22. aipt_v2/core/__init__.py +43 -0
  23. aipt_v2/core/agent.py +630 -0
  24. aipt_v2/core/llm.py +395 -0
  25. aipt_v2/core/memory.py +305 -0
  26. aipt_v2/core/ptt.py +329 -0
  27. aipt_v2/database/__init__.py +14 -0
  28. aipt_v2/database/models.py +232 -0
  29. aipt_v2/database/repository.py +384 -0
  30. aipt_v2/docker/__init__.py +23 -0
  31. aipt_v2/docker/builder.py +260 -0
  32. aipt_v2/docker/manager.py +222 -0
  33. aipt_v2/docker/sandbox.py +371 -0
  34. aipt_v2/evasion/__init__.py +58 -0
  35. aipt_v2/evasion/request_obfuscator.py +272 -0
  36. aipt_v2/evasion/tls_fingerprint.py +285 -0
  37. aipt_v2/evasion/ua_rotator.py +301 -0
  38. aipt_v2/evasion/waf_bypass.py +439 -0
  39. aipt_v2/execution/__init__.py +23 -0
  40. aipt_v2/execution/executor.py +302 -0
  41. aipt_v2/execution/parser.py +544 -0
  42. aipt_v2/execution/terminal.py +337 -0
  43. aipt_v2/health.py +437 -0
  44. aipt_v2/intelligence/__init__.py +194 -0
  45. aipt_v2/intelligence/adaptation.py +474 -0
  46. aipt_v2/intelligence/auth.py +520 -0
  47. aipt_v2/intelligence/chaining.py +775 -0
  48. aipt_v2/intelligence/correlation.py +536 -0
  49. aipt_v2/intelligence/cve_aipt.py +334 -0
  50. aipt_v2/intelligence/cve_info.py +1111 -0
  51. aipt_v2/intelligence/knowledge_graph.py +590 -0
  52. aipt_v2/intelligence/learning.py +626 -0
  53. aipt_v2/intelligence/llm_analyzer.py +502 -0
  54. aipt_v2/intelligence/llm_tool_selector.py +518 -0
  55. aipt_v2/intelligence/payload_generator.py +562 -0
  56. aipt_v2/intelligence/rag.py +239 -0
  57. aipt_v2/intelligence/scope.py +442 -0
  58. aipt_v2/intelligence/searchers/__init__.py +5 -0
  59. aipt_v2/intelligence/searchers/exploitdb_searcher.py +523 -0
  60. aipt_v2/intelligence/searchers/github_searcher.py +467 -0
  61. aipt_v2/intelligence/searchers/google_searcher.py +281 -0
  62. aipt_v2/intelligence/tools.json +443 -0
  63. aipt_v2/intelligence/triage.py +670 -0
  64. aipt_v2/interactive_shell.py +559 -0
  65. aipt_v2/interface/__init__.py +5 -0
  66. aipt_v2/interface/cli.py +230 -0
  67. aipt_v2/interface/main.py +501 -0
  68. aipt_v2/interface/tui.py +1276 -0
  69. aipt_v2/interface/utils.py +583 -0
  70. aipt_v2/llm/__init__.py +39 -0
  71. aipt_v2/llm/config.py +26 -0
  72. aipt_v2/llm/llm.py +514 -0
  73. aipt_v2/llm/memory.py +214 -0
  74. aipt_v2/llm/request_queue.py +89 -0
  75. aipt_v2/llm/utils.py +89 -0
  76. aipt_v2/local_tool_installer.py +1467 -0
  77. aipt_v2/models/__init__.py +15 -0
  78. aipt_v2/models/findings.py +295 -0
  79. aipt_v2/models/phase_result.py +224 -0
  80. aipt_v2/models/scan_config.py +207 -0
  81. aipt_v2/monitoring/grafana/dashboards/aipt-dashboard.json +355 -0
  82. aipt_v2/monitoring/grafana/dashboards/default.yml +17 -0
  83. aipt_v2/monitoring/grafana/datasources/prometheus.yml +17 -0
  84. aipt_v2/monitoring/prometheus.yml +60 -0
  85. aipt_v2/orchestration/__init__.py +52 -0
  86. aipt_v2/orchestration/pipeline.py +398 -0
  87. aipt_v2/orchestration/progress.py +300 -0
  88. aipt_v2/orchestration/scheduler.py +296 -0
  89. aipt_v2/orchestrator.py +2427 -0
  90. aipt_v2/payloads/__init__.py +27 -0
  91. aipt_v2/payloads/cmdi.py +150 -0
  92. aipt_v2/payloads/sqli.py +263 -0
  93. aipt_v2/payloads/ssrf.py +204 -0
  94. aipt_v2/payloads/templates.py +222 -0
  95. aipt_v2/payloads/traversal.py +166 -0
  96. aipt_v2/payloads/xss.py +204 -0
  97. aipt_v2/prompts/__init__.py +60 -0
  98. aipt_v2/proxy/__init__.py +29 -0
  99. aipt_v2/proxy/history.py +352 -0
  100. aipt_v2/proxy/interceptor.py +452 -0
  101. aipt_v2/recon/__init__.py +44 -0
  102. aipt_v2/recon/dns.py +241 -0
  103. aipt_v2/recon/osint.py +367 -0
  104. aipt_v2/recon/subdomain.py +372 -0
  105. aipt_v2/recon/tech_detect.py +311 -0
  106. aipt_v2/reports/__init__.py +17 -0
  107. aipt_v2/reports/generator.py +313 -0
  108. aipt_v2/reports/html_report.py +378 -0
  109. aipt_v2/runtime/__init__.py +53 -0
  110. aipt_v2/runtime/base.py +30 -0
  111. aipt_v2/runtime/docker.py +401 -0
  112. aipt_v2/runtime/local.py +346 -0
  113. aipt_v2/runtime/tool_server.py +205 -0
  114. aipt_v2/runtime/vps.py +830 -0
  115. aipt_v2/scanners/__init__.py +28 -0
  116. aipt_v2/scanners/base.py +273 -0
  117. aipt_v2/scanners/nikto.py +244 -0
  118. aipt_v2/scanners/nmap.py +402 -0
  119. aipt_v2/scanners/nuclei.py +273 -0
  120. aipt_v2/scanners/web.py +454 -0
  121. aipt_v2/scripts/security_audit.py +366 -0
  122. aipt_v2/setup_wizard.py +941 -0
  123. aipt_v2/skills/__init__.py +80 -0
  124. aipt_v2/skills/agents/__init__.py +14 -0
  125. aipt_v2/skills/agents/api_tester.py +706 -0
  126. aipt_v2/skills/agents/base.py +477 -0
  127. aipt_v2/skills/agents/code_review.py +459 -0
  128. aipt_v2/skills/agents/security_agent.py +336 -0
  129. aipt_v2/skills/agents/web_pentest.py +818 -0
  130. aipt_v2/skills/prompts/__init__.py +647 -0
  131. aipt_v2/system_detector.py +539 -0
  132. aipt_v2/telemetry/__init__.py +7 -0
  133. aipt_v2/telemetry/tracer.py +347 -0
  134. aipt_v2/terminal/__init__.py +28 -0
  135. aipt_v2/terminal/executor.py +400 -0
  136. aipt_v2/terminal/sandbox.py +350 -0
  137. aipt_v2/tools/__init__.py +44 -0
  138. aipt_v2/tools/active_directory/__init__.py +78 -0
  139. aipt_v2/tools/active_directory/ad_config.py +238 -0
  140. aipt_v2/tools/active_directory/bloodhound_wrapper.py +447 -0
  141. aipt_v2/tools/active_directory/kerberos_attacks.py +430 -0
  142. aipt_v2/tools/active_directory/ldap_enum.py +533 -0
  143. aipt_v2/tools/active_directory/smb_attacks.py +505 -0
  144. aipt_v2/tools/agents_graph/__init__.py +19 -0
  145. aipt_v2/tools/agents_graph/agents_graph_actions.py +69 -0
  146. aipt_v2/tools/api_security/__init__.py +76 -0
  147. aipt_v2/tools/api_security/api_discovery.py +608 -0
  148. aipt_v2/tools/api_security/graphql_scanner.py +622 -0
  149. aipt_v2/tools/api_security/jwt_analyzer.py +577 -0
  150. aipt_v2/tools/api_security/openapi_fuzzer.py +761 -0
  151. aipt_v2/tools/browser/__init__.py +5 -0
  152. aipt_v2/tools/browser/browser_actions.py +238 -0
  153. aipt_v2/tools/browser/browser_instance.py +535 -0
  154. aipt_v2/tools/browser/tab_manager.py +344 -0
  155. aipt_v2/tools/cloud/__init__.py +70 -0
  156. aipt_v2/tools/cloud/cloud_config.py +273 -0
  157. aipt_v2/tools/cloud/cloud_scanner.py +639 -0
  158. aipt_v2/tools/cloud/prowler_tool.py +571 -0
  159. aipt_v2/tools/cloud/scoutsuite_tool.py +359 -0
  160. aipt_v2/tools/executor.py +307 -0
  161. aipt_v2/tools/parser.py +408 -0
  162. aipt_v2/tools/proxy/__init__.py +5 -0
  163. aipt_v2/tools/proxy/proxy_actions.py +103 -0
  164. aipt_v2/tools/proxy/proxy_manager.py +789 -0
  165. aipt_v2/tools/registry.py +196 -0
  166. aipt_v2/tools/scanners/__init__.py +343 -0
  167. aipt_v2/tools/scanners/acunetix_tool.py +712 -0
  168. aipt_v2/tools/scanners/burp_tool.py +631 -0
  169. aipt_v2/tools/scanners/config.py +156 -0
  170. aipt_v2/tools/scanners/nessus_tool.py +588 -0
  171. aipt_v2/tools/scanners/zap_tool.py +612 -0
  172. aipt_v2/tools/terminal/__init__.py +5 -0
  173. aipt_v2/tools/terminal/terminal_actions.py +37 -0
  174. aipt_v2/tools/terminal/terminal_manager.py +153 -0
  175. aipt_v2/tools/terminal/terminal_session.py +449 -0
  176. aipt_v2/tools/tool_processing.py +108 -0
  177. aipt_v2/utils/__init__.py +17 -0
  178. aipt_v2/utils/logging.py +202 -0
  179. aipt_v2/utils/model_manager.py +187 -0
  180. aipt_v2/utils/searchers/__init__.py +269 -0
  181. aipt_v2/verify_install.py +793 -0
  182. aiptx-2.0.7.dist-info/METADATA +345 -0
  183. aiptx-2.0.7.dist-info/RECORD +187 -0
  184. aiptx-2.0.7.dist-info/WHEEL +5 -0
  185. aiptx-2.0.7.dist-info/entry_points.txt +7 -0
  186. aiptx-2.0.7.dist-info/licenses/LICENSE +21 -0
  187. aiptx-2.0.7.dist-info/top_level.txt +1 -0
@@ -0,0 +1,544 @@
1
+ """
2
+ AIPT Output Parser - Extract structured data from tool outputs
3
+
4
+ Uses regex patterns + LLM fallback for complex parsing.
5
+
6
+ Supports:
7
+ - nmap, masscan (port scanning)
8
+ - gobuster, ffuf (directory enumeration)
9
+ - nuclei (vulnerability scanning)
10
+ - hydra (credential brute-forcing)
11
+ - Custom patterns
12
+ """
13
+ from __future__ import annotations
14
+
15
+ import re
16
+ from typing import Optional, List, Dict, Any, Callable
17
+ from dataclasses import dataclass, field
18
+
19
+
20
+ @dataclass
21
+ class Finding:
22
+ """A structured finding from tool output"""
23
+ type: str # port, service, vuln, credential, host, path, info
24
+ value: str
25
+ description: str
26
+ severity: str = "info" # info, low, medium, high, critical
27
+ metadata: Dict[str, Any] = field(default_factory=dict)
28
+ source_tool: str = ""
29
+ raw_line: str = ""
30
+
31
+ def to_dict(self) -> Dict[str, Any]:
32
+ return {
33
+ "type": self.type,
34
+ "value": self.value,
35
+ "description": self.description,
36
+ "severity": self.severity,
37
+ "metadata": self.metadata,
38
+ "source_tool": self.source_tool,
39
+ }
40
+
41
+
42
+ class OutputParser:
43
+ """
44
+ Parse tool outputs into structured findings.
45
+
46
+ Uses regex patterns for known tools, with LLM fallback
47
+ for unstructured or unknown outputs.
48
+ """
49
+
50
+ # Regex patterns for common tools
51
+ PATTERNS = {
52
+ # nmap patterns
53
+ "nmap_port": re.compile(
54
+ r"(\d+)/(tcp|udp)\s+(\w+)\s+(\S+)(?:\s+(.*))?",
55
+ re.MULTILINE
56
+ ),
57
+ "nmap_host": re.compile(
58
+ r"Nmap scan report for\s+(\S+)(?:\s+\((\d+\.\d+\.\d+\.\d+)\))?",
59
+ re.MULTILINE
60
+ ),
61
+ "nmap_os": re.compile(
62
+ r"OS details?:\s*(.+)",
63
+ re.MULTILINE
64
+ ),
65
+
66
+ # masscan patterns
67
+ "masscan_port": re.compile(
68
+ r"Discovered open port\s+(\d+)/(tcp|udp)\s+on\s+(\S+)",
69
+ re.MULTILINE
70
+ ),
71
+
72
+ # gobuster/ffuf patterns
73
+ "directory": re.compile(
74
+ r"(/\S+)\s+\(Status:\s*(\d+)\)",
75
+ re.MULTILINE
76
+ ),
77
+ "ffuf_result": re.compile(
78
+ r'"url":\s*"([^"]+)".*?"status":\s*(\d+)',
79
+ re.MULTILINE
80
+ ),
81
+
82
+ # nuclei patterns
83
+ "nuclei_vuln": re.compile(
84
+ r"\[([^\]]+)\]\s+\[([^\]]+)\]\s+\[([^\]]+)\]\s+(.+)",
85
+ re.MULTILINE
86
+ ),
87
+
88
+ # hydra patterns
89
+ "hydra_cred": re.compile(
90
+ r"\[(\d+)\]\[(\w+)\]\s+host:\s+(\S+)\s+login:\s+(\S+)\s+password:\s+(\S+)",
91
+ re.MULTILINE
92
+ ),
93
+
94
+ # sqlmap patterns
95
+ "sqlmap_injectable": re.compile(
96
+ r"Parameter:\s+(\S+)\s+\(([^)]+)\)",
97
+ re.MULTILINE
98
+ ),
99
+ "sqlmap_dbms": re.compile(
100
+ r"back-end DBMS:\s+(.+)",
101
+ re.MULTILINE
102
+ ),
103
+
104
+ # generic patterns
105
+ "ip_address": re.compile(
106
+ r"\b(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\b"
107
+ ),
108
+ "domain": re.compile(
109
+ r"\b([a-zA-Z0-9][-a-zA-Z0-9]*\.)+[a-zA-Z]{2,}\b"
110
+ ),
111
+ "email": re.compile(
112
+ r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b"
113
+ ),
114
+ "hash_md5": re.compile(
115
+ r"\b[a-fA-F0-9]{32}\b"
116
+ ),
117
+ "hash_sha1": re.compile(
118
+ r"\b[a-fA-F0-9]{40}\b"
119
+ ),
120
+ "hash_sha256": re.compile(
121
+ r"\b[a-fA-F0-9]{64}\b"
122
+ ),
123
+ "cve": re.compile(
124
+ r"CVE-\d{4}-\d{4,}",
125
+ re.IGNORECASE
126
+ ),
127
+ "url": re.compile(
128
+ r"https?://[^\s<>\"']+",
129
+ re.IGNORECASE
130
+ ),
131
+ }
132
+
133
+ # Custom parsers for specific tools
134
+ _custom_parsers: Dict[str, Callable] = {}
135
+
136
+ def __init__(self, llm: Any = None):
137
+ self.llm = llm
138
+
139
+ def register_parser(self, tool_name: str, parser_func: Callable) -> None:
140
+ """Register a custom parser for a tool"""
141
+ self._custom_parsers[tool_name.lower()] = parser_func
142
+
143
+ def parse(
144
+ self,
145
+ output: str,
146
+ tool_name: Optional[str] = None,
147
+ include_generic: bool = True,
148
+ ) -> List[Finding]:
149
+ """
150
+ Parse tool output into structured findings.
151
+
152
+ Args:
153
+ output: Raw tool output
154
+ tool_name: Name of the tool (for tool-specific parsing)
155
+ include_generic: Include generic pattern matching
156
+
157
+ Returns:
158
+ List of Finding objects
159
+ """
160
+ findings = []
161
+
162
+ if not output:
163
+ return findings
164
+
165
+ # Check for custom parser
166
+ if tool_name and tool_name.lower() in self._custom_parsers:
167
+ custom_findings = self._custom_parsers[tool_name.lower()](output)
168
+ findings.extend(custom_findings)
169
+ elif tool_name:
170
+ # Tool-specific parsing
171
+ tool_findings = self._parse_tool_specific(output, tool_name.lower())
172
+ findings.extend(tool_findings)
173
+
174
+ # Generic pattern matching
175
+ if include_generic:
176
+ generic_findings = self._parse_generic(output)
177
+ findings.extend(generic_findings)
178
+
179
+ # Set source tool
180
+ for finding in findings:
181
+ if not finding.source_tool and tool_name:
182
+ finding.source_tool = tool_name
183
+
184
+ # Deduplicate
185
+ findings = self._deduplicate(findings)
186
+
187
+ return findings
188
+
189
+ def _parse_tool_specific(self, output: str, tool_name: str) -> List[Finding]:
190
+ """Parse output based on known tool"""
191
+ findings = []
192
+
193
+ if tool_name in ["nmap", "nmap-scan"]:
194
+ findings.extend(self._parse_nmap(output))
195
+
196
+ elif tool_name == "masscan":
197
+ findings.extend(self._parse_masscan(output))
198
+
199
+ elif tool_name in ["gobuster", "ffuf", "dirb", "dirbuster", "feroxbuster"]:
200
+ findings.extend(self._parse_directory(output))
201
+
202
+ elif tool_name == "nuclei":
203
+ findings.extend(self._parse_nuclei(output))
204
+
205
+ elif tool_name == "hydra":
206
+ findings.extend(self._parse_hydra(output))
207
+
208
+ elif tool_name == "sqlmap":
209
+ findings.extend(self._parse_sqlmap(output))
210
+
211
+ elif tool_name in ["nikto", "wpscan", "whatweb"]:
212
+ findings.extend(self._parse_vuln_scanner(output))
213
+
214
+ return findings
215
+
216
+ def _parse_nmap(self, output: str) -> List[Finding]:
217
+ """Parse nmap output"""
218
+ findings = []
219
+
220
+ # Parse hosts
221
+ for match in self.PATTERNS["nmap_host"].finditer(output):
222
+ hostname = match.group(1)
223
+ ip = match.group(2) or hostname
224
+ findings.append(Finding(
225
+ type="host",
226
+ value=ip,
227
+ description=f"Host discovered: {hostname} ({ip})",
228
+ metadata={"hostname": hostname, "ip": ip},
229
+ raw_line=match.group(0),
230
+ ))
231
+
232
+ # Parse ports
233
+ for match in self.PATTERNS["nmap_port"].finditer(output):
234
+ port = match.group(1)
235
+ protocol = match.group(2)
236
+ state = match.group(3)
237
+ service = match.group(4)
238
+ version = match.group(5) or ""
239
+
240
+ if state == "open":
241
+ findings.append(Finding(
242
+ type="port",
243
+ value=f"{port}/{protocol}",
244
+ description=f"Open port {port}/{protocol}: {service} {version}".strip(),
245
+ severity="low" if service in ["http", "https", "ftp", "ssh"] else "info",
246
+ metadata={
247
+ "port": int(port),
248
+ "protocol": protocol,
249
+ "service": service,
250
+ "version": version,
251
+ },
252
+ raw_line=match.group(0),
253
+ ))
254
+
255
+ # Parse OS detection
256
+ for match in self.PATTERNS["nmap_os"].finditer(output):
257
+ os_info = match.group(1)
258
+ findings.append(Finding(
259
+ type="info",
260
+ value=os_info,
261
+ description=f"OS detected: {os_info}",
262
+ metadata={"os": os_info},
263
+ ))
264
+
265
+ return findings
266
+
267
+ def _parse_masscan(self, output: str) -> List[Finding]:
268
+ """Parse masscan output"""
269
+ findings = []
270
+
271
+ for match in self.PATTERNS["masscan_port"].finditer(output):
272
+ port = match.group(1)
273
+ protocol = match.group(2)
274
+ ip = match.group(3)
275
+
276
+ findings.append(Finding(
277
+ type="port",
278
+ value=f"{ip}:{port}/{protocol}",
279
+ description=f"Open port {port}/{protocol} on {ip}",
280
+ metadata={"ip": ip, "port": int(port), "protocol": protocol},
281
+ ))
282
+
283
+ return findings
284
+
285
+ def _parse_directory(self, output: str) -> List[Finding]:
286
+ """Parse directory brute-force output"""
287
+ findings = []
288
+
289
+ # Standard format
290
+ for match in self.PATTERNS["directory"].finditer(output):
291
+ path = match.group(1)
292
+ status = match.group(2)
293
+
294
+ severity = "info"
295
+ if status in ["200", "301", "302"]:
296
+ severity = "low"
297
+ if any(kw in path.lower() for kw in ["admin", "backup", "config", "upload", "api", "debug"]):
298
+ severity = "medium"
299
+
300
+ findings.append(Finding(
301
+ type="path",
302
+ value=path,
303
+ description=f"Directory found: {path} (Status: {status})",
304
+ severity=severity,
305
+ metadata={"status_code": int(status)},
306
+ ))
307
+
308
+ # ffuf JSON format
309
+ for match in self.PATTERNS["ffuf_result"].finditer(output):
310
+ url = match.group(1)
311
+ status = match.group(2)
312
+
313
+ findings.append(Finding(
314
+ type="path",
315
+ value=url,
316
+ description=f"Endpoint found: {url} (Status: {status})",
317
+ severity="low",
318
+ metadata={"status_code": int(status)},
319
+ ))
320
+
321
+ return findings
322
+
323
+ def _parse_nuclei(self, output: str) -> List[Finding]:
324
+ """Parse nuclei vulnerability scanner output"""
325
+ findings = []
326
+
327
+ for match in self.PATTERNS["nuclei_vuln"].finditer(output):
328
+ template_id = match.group(1)
329
+ severity = match.group(2).lower()
330
+ protocol = match.group(3)
331
+ target = match.group(4)
332
+
333
+ if severity not in ["info", "low", "medium", "high", "critical"]:
334
+ severity = "info"
335
+
336
+ findings.append(Finding(
337
+ type="vuln",
338
+ value=template_id,
339
+ description=f"Vulnerability: {template_id} on {target}",
340
+ severity=severity,
341
+ metadata={
342
+ "template": template_id,
343
+ "protocol": protocol,
344
+ "target": target,
345
+ },
346
+ ))
347
+
348
+ return findings
349
+
350
+ def _parse_hydra(self, output: str) -> List[Finding]:
351
+ """Parse hydra brute-force output"""
352
+ findings = []
353
+
354
+ for match in self.PATTERNS["hydra_cred"].finditer(output):
355
+ port = match.group(1)
356
+ service = match.group(2)
357
+ host = match.group(3)
358
+ username = match.group(4)
359
+ password = match.group(5)
360
+
361
+ findings.append(Finding(
362
+ type="credential",
363
+ value=f"{username}:{password}",
364
+ description=f"Valid credentials found for {service} on {host}:{port}",
365
+ severity="critical",
366
+ metadata={
367
+ "host": host,
368
+ "port": int(port),
369
+ "service": service,
370
+ "username": username,
371
+ "password": password,
372
+ },
373
+ ))
374
+
375
+ return findings
376
+
377
+ def _parse_sqlmap(self, output: str) -> List[Finding]:
378
+ """Parse sqlmap output"""
379
+ findings = []
380
+
381
+ # Injectable parameters
382
+ for match in self.PATTERNS["sqlmap_injectable"].finditer(output):
383
+ param = match.group(1)
384
+ injection_type = match.group(2)
385
+
386
+ findings.append(Finding(
387
+ type="vuln",
388
+ value=f"SQLi: {param}",
389
+ description=f"SQL Injection in parameter '{param}' ({injection_type})",
390
+ severity="high",
391
+ metadata={"parameter": param, "injection_type": injection_type},
392
+ ))
393
+
394
+ # DBMS detection
395
+ for match in self.PATTERNS["sqlmap_dbms"].finditer(output):
396
+ dbms = match.group(1)
397
+ findings.append(Finding(
398
+ type="info",
399
+ value=dbms,
400
+ description=f"Backend DBMS: {dbms}",
401
+ metadata={"dbms": dbms},
402
+ ))
403
+
404
+ return findings
405
+
406
+ def _parse_vuln_scanner(self, output: str) -> List[Finding]:
407
+ """Parse generic vulnerability scanner output (nikto, wpscan)"""
408
+ findings = []
409
+
410
+ # Look for CVEs
411
+ for match in self.PATTERNS["cve"].finditer(output):
412
+ cve = match.group(0).upper()
413
+ findings.append(Finding(
414
+ type="vuln",
415
+ value=cve,
416
+ description=f"CVE detected: {cve}",
417
+ severity="high",
418
+ metadata={"cve": cve},
419
+ ))
420
+
421
+ # Look for common vulnerability keywords
422
+ vuln_keywords = [
423
+ ("SQL injection", "high", "sqli"),
424
+ ("XSS", "medium", "xss"),
425
+ ("Cross-Site Scripting", "medium", "xss"),
426
+ ("CSRF", "medium", "csrf"),
427
+ ("directory listing", "low", "info_disclosure"),
428
+ ("information disclosure", "medium", "info_disclosure"),
429
+ ("remote code execution", "critical", "rce"),
430
+ ("RCE", "critical", "rce"),
431
+ ("LFI", "high", "lfi"),
432
+ ("Local File Inclusion", "high", "lfi"),
433
+ ("RFI", "high", "rfi"),
434
+ ("Remote File Inclusion", "high", "rfi"),
435
+ ("SSRF", "high", "ssrf"),
436
+ ("XXE", "high", "xxe"),
437
+ ]
438
+
439
+ output_lower = output.lower()
440
+ for keyword, severity, vuln_type in vuln_keywords:
441
+ if keyword.lower() in output_lower:
442
+ findings.append(Finding(
443
+ type="vuln",
444
+ value=keyword,
445
+ description=f"Potential vulnerability: {keyword}",
446
+ severity=severity,
447
+ metadata={"vuln_type": vuln_type},
448
+ ))
449
+
450
+ return findings
451
+
452
+ def _parse_generic(self, output: str) -> List[Finding]:
453
+ """Extract generic patterns from any output"""
454
+ findings = []
455
+
456
+ # Extract CVEs
457
+ cves_found = set()
458
+ for match in self.PATTERNS["cve"].finditer(output):
459
+ cve = match.group(0).upper()
460
+ if cve not in cves_found:
461
+ cves_found.add(cve)
462
+ findings.append(Finding(
463
+ type="vuln",
464
+ value=cve,
465
+ description=f"CVE reference: {cve}",
466
+ severity="medium",
467
+ ))
468
+
469
+ # Extract emails
470
+ emails_found = set()
471
+ for match in self.PATTERNS["email"].finditer(output):
472
+ email = match.group(0)
473
+ if email not in emails_found:
474
+ emails_found.add(email)
475
+ findings.append(Finding(
476
+ type="info",
477
+ value=email,
478
+ description=f"Email discovered: {email}",
479
+ ))
480
+
481
+ return findings
482
+
483
+ def _deduplicate(self, findings: List[Finding]) -> List[Finding]:
484
+ """Remove duplicate findings"""
485
+ seen = set()
486
+ unique = []
487
+
488
+ for finding in findings:
489
+ key = (finding.type, finding.value)
490
+ if key not in seen:
491
+ seen.add(key)
492
+ unique.append(finding)
493
+
494
+ return unique
495
+
496
+ def parse_with_llm(self, output: str, tool_name: str = "unknown") -> List[Finding]:
497
+ """
498
+ Parse output using LLM for complex/unknown formats.
499
+
500
+ Falls back to regex if LLM not available.
501
+ """
502
+ if not self.llm:
503
+ return self.parse(output, tool_name)
504
+
505
+ prompt = f"""Analyze this security tool output and extract findings.
506
+
507
+ Tool: {tool_name}
508
+
509
+ Output:
510
+ {output[:5000]}
511
+
512
+ Extract findings in this JSON format:
513
+ [
514
+ {{"type": "port|service|vuln|credential|host|path|info", "value": "identifier", "description": "brief description", "severity": "info|low|medium|high|critical"}}
515
+ ]
516
+
517
+ Only return valid JSON array. If no findings, return []."""
518
+
519
+ try:
520
+ response = self.llm.invoke([
521
+ {"role": "system", "content": "You are a security findings parser. Return only valid JSON."},
522
+ {"role": "user", "content": prompt},
523
+ ], max_tokens=1000)
524
+
525
+ import json
526
+ content = response.content.strip()
527
+ if content.startswith("```"):
528
+ content = content.split("```")[1]
529
+ if content.startswith("json"):
530
+ content = content[4:]
531
+
532
+ raw_findings = json.loads(content)
533
+ return [
534
+ Finding(
535
+ type=f.get("type", "info"),
536
+ value=f.get("value", ""),
537
+ description=f.get("description", ""),
538
+ severity=f.get("severity", "info"),
539
+ source_tool=tool_name,
540
+ )
541
+ for f in raw_findings
542
+ ]
543
+ except Exception:
544
+ return self.parse(output, tool_name)