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.

Files changed (165) 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 +24 -0
  6. aipt_v2/agents/base.py +520 -0
  7. aipt_v2/agents/ptt.py +406 -0
  8. aipt_v2/agents/state.py +168 -0
  9. aipt_v2/app.py +960 -0
  10. aipt_v2/browser/__init__.py +31 -0
  11. aipt_v2/browser/automation.py +458 -0
  12. aipt_v2/browser/crawler.py +453 -0
  13. aipt_v2/cli.py +321 -0
  14. aipt_v2/compliance/__init__.py +71 -0
  15. aipt_v2/compliance/compliance_report.py +449 -0
  16. aipt_v2/compliance/framework_mapper.py +424 -0
  17. aipt_v2/compliance/nist_mapping.py +345 -0
  18. aipt_v2/compliance/owasp_mapping.py +330 -0
  19. aipt_v2/compliance/pci_mapping.py +297 -0
  20. aipt_v2/config.py +288 -0
  21. aipt_v2/core/__init__.py +43 -0
  22. aipt_v2/core/agent.py +630 -0
  23. aipt_v2/core/llm.py +395 -0
  24. aipt_v2/core/memory.py +305 -0
  25. aipt_v2/core/ptt.py +329 -0
  26. aipt_v2/database/__init__.py +14 -0
  27. aipt_v2/database/models.py +232 -0
  28. aipt_v2/database/repository.py +384 -0
  29. aipt_v2/docker/__init__.py +23 -0
  30. aipt_v2/docker/builder.py +260 -0
  31. aipt_v2/docker/manager.py +222 -0
  32. aipt_v2/docker/sandbox.py +371 -0
  33. aipt_v2/evasion/__init__.py +58 -0
  34. aipt_v2/evasion/request_obfuscator.py +272 -0
  35. aipt_v2/evasion/tls_fingerprint.py +285 -0
  36. aipt_v2/evasion/ua_rotator.py +301 -0
  37. aipt_v2/evasion/waf_bypass.py +439 -0
  38. aipt_v2/execution/__init__.py +23 -0
  39. aipt_v2/execution/executor.py +302 -0
  40. aipt_v2/execution/parser.py +544 -0
  41. aipt_v2/execution/terminal.py +337 -0
  42. aipt_v2/health.py +437 -0
  43. aipt_v2/intelligence/__init__.py +85 -0
  44. aipt_v2/intelligence/auth.py +520 -0
  45. aipt_v2/intelligence/chaining.py +775 -0
  46. aipt_v2/intelligence/cve_aipt.py +334 -0
  47. aipt_v2/intelligence/cve_info.py +1111 -0
  48. aipt_v2/intelligence/rag.py +239 -0
  49. aipt_v2/intelligence/scope.py +442 -0
  50. aipt_v2/intelligence/searchers/__init__.py +5 -0
  51. aipt_v2/intelligence/searchers/exploitdb_searcher.py +523 -0
  52. aipt_v2/intelligence/searchers/github_searcher.py +467 -0
  53. aipt_v2/intelligence/searchers/google_searcher.py +281 -0
  54. aipt_v2/intelligence/tools.json +443 -0
  55. aipt_v2/intelligence/triage.py +670 -0
  56. aipt_v2/interface/__init__.py +5 -0
  57. aipt_v2/interface/cli.py +230 -0
  58. aipt_v2/interface/main.py +501 -0
  59. aipt_v2/interface/tui.py +1276 -0
  60. aipt_v2/interface/utils.py +583 -0
  61. aipt_v2/llm/__init__.py +39 -0
  62. aipt_v2/llm/config.py +26 -0
  63. aipt_v2/llm/llm.py +514 -0
  64. aipt_v2/llm/memory.py +214 -0
  65. aipt_v2/llm/request_queue.py +89 -0
  66. aipt_v2/llm/utils.py +89 -0
  67. aipt_v2/models/__init__.py +15 -0
  68. aipt_v2/models/findings.py +295 -0
  69. aipt_v2/models/phase_result.py +224 -0
  70. aipt_v2/models/scan_config.py +207 -0
  71. aipt_v2/monitoring/grafana/dashboards/aipt-dashboard.json +355 -0
  72. aipt_v2/monitoring/grafana/dashboards/default.yml +17 -0
  73. aipt_v2/monitoring/grafana/datasources/prometheus.yml +17 -0
  74. aipt_v2/monitoring/prometheus.yml +60 -0
  75. aipt_v2/orchestration/__init__.py +52 -0
  76. aipt_v2/orchestration/pipeline.py +398 -0
  77. aipt_v2/orchestration/progress.py +300 -0
  78. aipt_v2/orchestration/scheduler.py +296 -0
  79. aipt_v2/orchestrator.py +2284 -0
  80. aipt_v2/payloads/__init__.py +27 -0
  81. aipt_v2/payloads/cmdi.py +150 -0
  82. aipt_v2/payloads/sqli.py +263 -0
  83. aipt_v2/payloads/ssrf.py +204 -0
  84. aipt_v2/payloads/templates.py +222 -0
  85. aipt_v2/payloads/traversal.py +166 -0
  86. aipt_v2/payloads/xss.py +204 -0
  87. aipt_v2/prompts/__init__.py +60 -0
  88. aipt_v2/proxy/__init__.py +29 -0
  89. aipt_v2/proxy/history.py +352 -0
  90. aipt_v2/proxy/interceptor.py +452 -0
  91. aipt_v2/recon/__init__.py +44 -0
  92. aipt_v2/recon/dns.py +241 -0
  93. aipt_v2/recon/osint.py +367 -0
  94. aipt_v2/recon/subdomain.py +372 -0
  95. aipt_v2/recon/tech_detect.py +311 -0
  96. aipt_v2/reports/__init__.py +17 -0
  97. aipt_v2/reports/generator.py +313 -0
  98. aipt_v2/reports/html_report.py +378 -0
  99. aipt_v2/runtime/__init__.py +44 -0
  100. aipt_v2/runtime/base.py +30 -0
  101. aipt_v2/runtime/docker.py +401 -0
  102. aipt_v2/runtime/local.py +346 -0
  103. aipt_v2/runtime/tool_server.py +205 -0
  104. aipt_v2/scanners/__init__.py +28 -0
  105. aipt_v2/scanners/base.py +273 -0
  106. aipt_v2/scanners/nikto.py +244 -0
  107. aipt_v2/scanners/nmap.py +402 -0
  108. aipt_v2/scanners/nuclei.py +273 -0
  109. aipt_v2/scanners/web.py +454 -0
  110. aipt_v2/scripts/security_audit.py +366 -0
  111. aipt_v2/telemetry/__init__.py +7 -0
  112. aipt_v2/telemetry/tracer.py +347 -0
  113. aipt_v2/terminal/__init__.py +28 -0
  114. aipt_v2/terminal/executor.py +400 -0
  115. aipt_v2/terminal/sandbox.py +350 -0
  116. aipt_v2/tools/__init__.py +44 -0
  117. aipt_v2/tools/active_directory/__init__.py +78 -0
  118. aipt_v2/tools/active_directory/ad_config.py +238 -0
  119. aipt_v2/tools/active_directory/bloodhound_wrapper.py +447 -0
  120. aipt_v2/tools/active_directory/kerberos_attacks.py +430 -0
  121. aipt_v2/tools/active_directory/ldap_enum.py +533 -0
  122. aipt_v2/tools/active_directory/smb_attacks.py +505 -0
  123. aipt_v2/tools/agents_graph/__init__.py +19 -0
  124. aipt_v2/tools/agents_graph/agents_graph_actions.py +69 -0
  125. aipt_v2/tools/api_security/__init__.py +76 -0
  126. aipt_v2/tools/api_security/api_discovery.py +608 -0
  127. aipt_v2/tools/api_security/graphql_scanner.py +622 -0
  128. aipt_v2/tools/api_security/jwt_analyzer.py +577 -0
  129. aipt_v2/tools/api_security/openapi_fuzzer.py +761 -0
  130. aipt_v2/tools/browser/__init__.py +5 -0
  131. aipt_v2/tools/browser/browser_actions.py +238 -0
  132. aipt_v2/tools/browser/browser_instance.py +535 -0
  133. aipt_v2/tools/browser/tab_manager.py +344 -0
  134. aipt_v2/tools/cloud/__init__.py +70 -0
  135. aipt_v2/tools/cloud/cloud_config.py +273 -0
  136. aipt_v2/tools/cloud/cloud_scanner.py +639 -0
  137. aipt_v2/tools/cloud/prowler_tool.py +571 -0
  138. aipt_v2/tools/cloud/scoutsuite_tool.py +359 -0
  139. aipt_v2/tools/executor.py +307 -0
  140. aipt_v2/tools/parser.py +408 -0
  141. aipt_v2/tools/proxy/__init__.py +5 -0
  142. aipt_v2/tools/proxy/proxy_actions.py +103 -0
  143. aipt_v2/tools/proxy/proxy_manager.py +789 -0
  144. aipt_v2/tools/registry.py +196 -0
  145. aipt_v2/tools/scanners/__init__.py +343 -0
  146. aipt_v2/tools/scanners/acunetix_tool.py +712 -0
  147. aipt_v2/tools/scanners/burp_tool.py +631 -0
  148. aipt_v2/tools/scanners/config.py +156 -0
  149. aipt_v2/tools/scanners/nessus_tool.py +588 -0
  150. aipt_v2/tools/scanners/zap_tool.py +612 -0
  151. aipt_v2/tools/terminal/__init__.py +5 -0
  152. aipt_v2/tools/terminal/terminal_actions.py +37 -0
  153. aipt_v2/tools/terminal/terminal_manager.py +153 -0
  154. aipt_v2/tools/terminal/terminal_session.py +449 -0
  155. aipt_v2/tools/tool_processing.py +108 -0
  156. aipt_v2/utils/__init__.py +17 -0
  157. aipt_v2/utils/logging.py +201 -0
  158. aipt_v2/utils/model_manager.py +187 -0
  159. aipt_v2/utils/searchers/__init__.py +269 -0
  160. aiptx-2.0.2.dist-info/METADATA +324 -0
  161. aiptx-2.0.2.dist-info/RECORD +165 -0
  162. aiptx-2.0.2.dist-info/WHEEL +5 -0
  163. aiptx-2.0.2.dist-info/entry_points.txt +7 -0
  164. aiptx-2.0.2.dist-info/licenses/LICENSE +21 -0
  165. aiptx-2.0.2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,639 @@
1
+ """
2
+ Unified Cloud Security Scanner
3
+
4
+ Provides a unified interface for scanning AWS, Azure, and GCP
5
+ cloud infrastructure for security misconfigurations.
6
+ """
7
+
8
+ import asyncio
9
+ import json
10
+ import logging
11
+ import os
12
+ import subprocess
13
+ from dataclasses import dataclass, field
14
+ from datetime import datetime, timezone
15
+ from enum import Enum
16
+ from pathlib import Path
17
+ from typing import List, Dict, Any, Optional, Callable
18
+
19
+ from aipt_v2.tools.cloud.cloud_config import CloudConfig, get_cloud_config
20
+
21
+ logger = logging.getLogger(__name__)
22
+
23
+
24
+ class CloudSeverity(Enum):
25
+ """Cloud finding severity levels."""
26
+ CRITICAL = "critical"
27
+ HIGH = "high"
28
+ MEDIUM = "medium"
29
+ LOW = "low"
30
+ INFO = "info"
31
+
32
+
33
+ class CloudProvider(Enum):
34
+ """Supported cloud providers."""
35
+ AWS = "aws"
36
+ AZURE = "azure"
37
+ GCP = "gcp"
38
+
39
+
40
+ @dataclass
41
+ class CloudFinding:
42
+ """A security finding from cloud scanning."""
43
+ provider: str
44
+ service: str
45
+ resource_id: str
46
+ resource_name: str
47
+ title: str
48
+ description: str
49
+ severity: str
50
+ recommendation: str
51
+ region: str = ""
52
+ account_id: str = ""
53
+ compliance: List[str] = field(default_factory=list) # e.g., ["CIS 1.1", "PCI 2.1"]
54
+ metadata: Dict[str, Any] = field(default_factory=dict)
55
+ timestamp: str = field(default_factory=lambda: datetime.now(timezone.utc).isoformat())
56
+
57
+ def to_aipt_finding(self) -> Dict[str, Any]:
58
+ """Convert to AIPT Finding format."""
59
+ return {
60
+ "type": f"cloud_{self.service}",
61
+ "value": self.resource_id,
62
+ "description": f"[{self.provider.upper()}] {self.title}: {self.description}",
63
+ "severity": self.severity,
64
+ "phase": "scan",
65
+ "tool": f"cloud_scanner_{self.provider}",
66
+ "target": self.resource_name,
67
+ "evidence": json.dumps(self.metadata),
68
+ "remediation": self.recommendation,
69
+ "metadata": {
70
+ "provider": self.provider,
71
+ "service": self.service,
72
+ "region": self.region,
73
+ "account_id": self.account_id,
74
+ "compliance": self.compliance,
75
+ "resource_id": self.resource_id
76
+ },
77
+ "timestamp": self.timestamp
78
+ }
79
+
80
+
81
+ @dataclass
82
+ class CloudScanResult:
83
+ """Result of a cloud security scan."""
84
+ provider: str
85
+ status: str # completed, failed, partial
86
+ started_at: str
87
+ finished_at: str
88
+ duration: float
89
+ findings: List[CloudFinding]
90
+ summary: Dict[str, int] # Severity counts
91
+ errors: List[str] = field(default_factory=list)
92
+ metadata: Dict[str, Any] = field(default_factory=dict)
93
+
94
+
95
+ class CloudScanner:
96
+ """
97
+ Unified cloud security scanner.
98
+
99
+ Orchestrates scanning across multiple cloud providers using
100
+ ScoutSuite, Prowler, and custom checks.
101
+ """
102
+
103
+ def __init__(self, config: Optional[CloudConfig] = None):
104
+ """
105
+ Initialize cloud scanner.
106
+
107
+ Args:
108
+ config: Cloud configuration (auto-detected if not provided)
109
+ """
110
+ self.config = config or get_cloud_config()
111
+ self.output_dir = Path(self.config.output_dir)
112
+ self.output_dir.mkdir(parents=True, exist_ok=True)
113
+ self.findings: List[CloudFinding] = []
114
+ self.on_finding: Optional[Callable[[CloudFinding], None]] = None
115
+
116
+ def _log(self, message: str, level: str = "info"):
117
+ """Log with timestamp."""
118
+ timestamp = datetime.now().strftime("%H:%M:%S")
119
+ prefix = {"info": "[*]", "success": "[+]", "error": "[-]", "warning": "[!]"}
120
+ print(f"{timestamp} {prefix.get(level, '[*]')} {message}")
121
+ if level == "error":
122
+ logger.error(message)
123
+ else:
124
+ logger.info(message)
125
+
126
+ async def scan(self, providers: Optional[List[str]] = None) -> List[CloudScanResult]:
127
+ """
128
+ Run cloud security scan across specified providers.
129
+
130
+ Args:
131
+ providers: List of providers to scan (uses config if not specified)
132
+
133
+ Returns:
134
+ List of CloudScanResult for each provider
135
+ """
136
+ providers = providers or self.config.get_configured_providers()
137
+
138
+ if not providers:
139
+ self._log("No cloud providers configured. Set credentials first.", "error")
140
+ return []
141
+
142
+ self._log(f"Starting cloud security scan for: {', '.join(providers)}")
143
+ results = []
144
+
145
+ for provider in providers:
146
+ try:
147
+ result = await self._scan_provider(provider)
148
+ results.append(result)
149
+ self.findings.extend(result.findings)
150
+ except Exception as e:
151
+ self._log(f"Error scanning {provider}: {str(e)}", "error")
152
+ results.append(CloudScanResult(
153
+ provider=provider,
154
+ status="failed",
155
+ started_at=datetime.now(timezone.utc).isoformat(),
156
+ finished_at=datetime.now(timezone.utc).isoformat(),
157
+ duration=0,
158
+ findings=[],
159
+ summary={},
160
+ errors=[str(e)]
161
+ ))
162
+
163
+ return results
164
+
165
+ async def _scan_provider(self, provider: str) -> CloudScanResult:
166
+ """Scan a specific cloud provider."""
167
+ started_at = datetime.now(timezone.utc).isoformat()
168
+ start_time = asyncio.get_event_loop().time()
169
+
170
+ self._log(f"Scanning {provider.upper()}...")
171
+
172
+ findings = []
173
+ errors = []
174
+
175
+ if provider == "aws":
176
+ findings, errors = await self._scan_aws()
177
+ elif provider == "azure":
178
+ findings, errors = await self._scan_azure()
179
+ elif provider == "gcp":
180
+ findings, errors = await self._scan_gcp()
181
+ else:
182
+ errors.append(f"Unknown provider: {provider}")
183
+
184
+ finished_at = datetime.now(timezone.utc).isoformat()
185
+ duration = asyncio.get_event_loop().time() - start_time
186
+
187
+ # Calculate summary
188
+ summary = {"critical": 0, "high": 0, "medium": 0, "low": 0, "info": 0}
189
+ for finding in findings:
190
+ sev = finding.severity.lower()
191
+ if sev in summary:
192
+ summary[sev] += 1
193
+
194
+ self._log(f"{provider.upper()} scan complete: {len(findings)} findings", "success")
195
+
196
+ return CloudScanResult(
197
+ provider=provider,
198
+ status="completed" if not errors else "partial",
199
+ started_at=started_at,
200
+ finished_at=finished_at,
201
+ duration=duration,
202
+ findings=findings,
203
+ summary=summary,
204
+ errors=errors
205
+ )
206
+
207
+ async def _scan_aws(self) -> tuple:
208
+ """Scan AWS infrastructure."""
209
+ findings = []
210
+ errors = []
211
+
212
+ aws_config = self.config.aws
213
+ env = {**os.environ, **aws_config.to_env_dict()}
214
+
215
+ # Run ScoutSuite for AWS
216
+ try:
217
+ scoutsuite_findings = await self._run_scoutsuite("aws", env)
218
+ findings.extend(scoutsuite_findings)
219
+ except Exception as e:
220
+ errors.append(f"ScoutSuite AWS: {str(e)}")
221
+ self._log(f"ScoutSuite failed: {e}", "warning")
222
+
223
+ # Run Prowler for AWS
224
+ try:
225
+ prowler_findings = await self._run_prowler(env)
226
+ findings.extend(prowler_findings)
227
+ except Exception as e:
228
+ errors.append(f"Prowler: {str(e)}")
229
+ self._log(f"Prowler failed: {e}", "warning")
230
+
231
+ # Run custom AWS checks
232
+ try:
233
+ custom_findings = await self._run_aws_custom_checks(env)
234
+ findings.extend(custom_findings)
235
+ except Exception as e:
236
+ errors.append(f"Custom AWS checks: {str(e)}")
237
+
238
+ return findings, errors
239
+
240
+ async def _scan_azure(self) -> tuple:
241
+ """Scan Azure infrastructure."""
242
+ findings = []
243
+ errors = []
244
+
245
+ azure_config = self.config.azure
246
+ env = {**os.environ, **azure_config.to_env_dict()}
247
+
248
+ # Run ScoutSuite for Azure
249
+ try:
250
+ scoutsuite_findings = await self._run_scoutsuite("azure", env)
251
+ findings.extend(scoutsuite_findings)
252
+ except Exception as e:
253
+ errors.append(f"ScoutSuite Azure: {str(e)}")
254
+
255
+ return findings, errors
256
+
257
+ async def _scan_gcp(self) -> tuple:
258
+ """Scan GCP infrastructure."""
259
+ findings = []
260
+ errors = []
261
+
262
+ gcp_config = self.config.gcp
263
+ env = {**os.environ, **gcp_config.to_env_dict()}
264
+
265
+ # Run ScoutSuite for GCP
266
+ try:
267
+ scoutsuite_findings = await self._run_scoutsuite("gcp", env)
268
+ findings.extend(scoutsuite_findings)
269
+ except Exception as e:
270
+ errors.append(f"ScoutSuite GCP: {str(e)}")
271
+
272
+ return findings, errors
273
+
274
+ async def _run_scoutsuite(self, provider: str, env: Dict[str, str]) -> List[CloudFinding]:
275
+ """Run ScoutSuite for a provider."""
276
+ findings = []
277
+ output_dir = self.output_dir / f"scoutsuite_{provider}"
278
+ output_dir.mkdir(exist_ok=True)
279
+
280
+ cmd = f"scout {provider} --report-dir {output_dir} --no-browser"
281
+
282
+ self._log(f"Running ScoutSuite for {provider}...")
283
+
284
+ process = await asyncio.create_subprocess_shell(
285
+ cmd,
286
+ stdout=asyncio.subprocess.PIPE,
287
+ stderr=asyncio.subprocess.PIPE,
288
+ env=env
289
+ )
290
+ stdout, stderr = await asyncio.wait_for(
291
+ process.communicate(),
292
+ timeout=self.config.timeout
293
+ )
294
+
295
+ if process.returncode == 0:
296
+ # Parse ScoutSuite results
297
+ results_file = output_dir / "scoutsuite-results" / "scoutsuite_results.js"
298
+ if results_file.exists():
299
+ findings = self._parse_scoutsuite_results(results_file, provider)
300
+ else:
301
+ raise Exception(f"ScoutSuite failed: {stderr.decode()}")
302
+
303
+ return findings
304
+
305
+ async def _run_prowler(self, env: Dict[str, str]) -> List[CloudFinding]:
306
+ """Run Prowler for AWS."""
307
+ findings = []
308
+ output_file = self.output_dir / "prowler_results.json"
309
+
310
+ cmd = f"prowler aws --output-formats json --output-filename {output_file}"
311
+
312
+ self._log("Running Prowler for AWS...")
313
+
314
+ process = await asyncio.create_subprocess_shell(
315
+ cmd,
316
+ stdout=asyncio.subprocess.PIPE,
317
+ stderr=asyncio.subprocess.PIPE,
318
+ env=env
319
+ )
320
+ stdout, stderr = await asyncio.wait_for(
321
+ process.communicate(),
322
+ timeout=self.config.timeout
323
+ )
324
+
325
+ if process.returncode == 0 and output_file.exists():
326
+ findings = self._parse_prowler_results(output_file)
327
+ else:
328
+ # Prowler might not be installed
329
+ self._log("Prowler not available or failed", "warning")
330
+
331
+ return findings
332
+
333
+ async def _run_aws_custom_checks(self, env: Dict[str, str]) -> List[CloudFinding]:
334
+ """Run custom AWS security checks using boto3."""
335
+ findings = []
336
+
337
+ try:
338
+ import boto3
339
+ from botocore.exceptions import ClientError
340
+
341
+ # Create session
342
+ session = boto3.Session(
343
+ profile_name=self.config.aws.profile,
344
+ region_name=self.config.aws.region
345
+ )
346
+
347
+ # Check S3 public buckets
348
+ s3_findings = await self._check_s3_public_access(session)
349
+ findings.extend(s3_findings)
350
+
351
+ # Check security groups
352
+ sg_findings = await self._check_security_groups(session)
353
+ findings.extend(sg_findings)
354
+
355
+ # Check IAM
356
+ iam_findings = await self._check_iam_issues(session)
357
+ findings.extend(iam_findings)
358
+
359
+ except ImportError:
360
+ self._log("boto3 not installed, skipping custom AWS checks", "warning")
361
+ except Exception as e:
362
+ self._log(f"Custom AWS check error: {e}", "warning")
363
+
364
+ return findings
365
+
366
+ async def _check_s3_public_access(self, session) -> List[CloudFinding]:
367
+ """Check for public S3 buckets."""
368
+ findings = []
369
+ s3 = session.client('s3')
370
+
371
+ try:
372
+ buckets = s3.list_buckets().get('Buckets', [])
373
+ for bucket in buckets:
374
+ bucket_name = bucket['Name']
375
+ try:
376
+ # Check bucket ACL
377
+ acl = s3.get_bucket_acl(Bucket=bucket_name)
378
+ for grant in acl.get('Grants', []):
379
+ grantee = grant.get('Grantee', {})
380
+ if grantee.get('URI', '').endswith('AllUsers'):
381
+ findings.append(CloudFinding(
382
+ provider="aws",
383
+ service="s3",
384
+ resource_id=bucket_name,
385
+ resource_name=bucket_name,
386
+ title="Public S3 Bucket",
387
+ description=f"S3 bucket {bucket_name} is publicly accessible",
388
+ severity="critical",
389
+ recommendation="Remove public access from the bucket ACL",
390
+ compliance=["CIS 2.1.1", "PCI 2.1"],
391
+ metadata={"acl_grant": str(grant)}
392
+ ))
393
+ except Exception:
394
+ continue
395
+ except Exception as e:
396
+ self._log(f"S3 check error: {e}", "warning")
397
+
398
+ return findings
399
+
400
+ async def _check_security_groups(self, session) -> List[CloudFinding]:
401
+ """Check for overly permissive security groups."""
402
+ findings = []
403
+ ec2 = session.client('ec2')
404
+
405
+ try:
406
+ sgs = ec2.describe_security_groups().get('SecurityGroups', [])
407
+ for sg in sgs:
408
+ sg_id = sg['GroupId']
409
+ sg_name = sg['GroupName']
410
+
411
+ for rule in sg.get('IpPermissions', []):
412
+ for ip_range in rule.get('IpRanges', []):
413
+ cidr = ip_range.get('CidrIp', '')
414
+ if cidr == '0.0.0.0/0':
415
+ port = rule.get('FromPort', 'All')
416
+ if port in [22, 3389, 'All']:
417
+ findings.append(CloudFinding(
418
+ provider="aws",
419
+ service="ec2",
420
+ resource_id=sg_id,
421
+ resource_name=sg_name,
422
+ title="Security Group Open to Internet",
423
+ description=f"Security group {sg_name} allows inbound traffic from 0.0.0.0/0 on port {port}",
424
+ severity="high" if port in [22, 3389] else "medium",
425
+ recommendation="Restrict inbound access to specific IP ranges",
426
+ compliance=["CIS 4.1", "CIS 4.2"],
427
+ metadata={"port": port, "cidr": cidr}
428
+ ))
429
+ except Exception as e:
430
+ self._log(f"Security groups check error: {e}", "warning")
431
+
432
+ return findings
433
+
434
+ async def _check_iam_issues(self, session) -> List[CloudFinding]:
435
+ """Check for IAM security issues."""
436
+ findings = []
437
+ iam = session.client('iam')
438
+
439
+ try:
440
+ # Check for root account access keys
441
+ try:
442
+ summary = iam.get_account_summary()
443
+ if summary.get('SummaryMap', {}).get('AccountAccessKeysPresent', 0) > 0:
444
+ findings.append(CloudFinding(
445
+ provider="aws",
446
+ service="iam",
447
+ resource_id="root",
448
+ resource_name="Root Account",
449
+ title="Root Account Has Access Keys",
450
+ description="The root account has active access keys which is a security risk",
451
+ severity="critical",
452
+ recommendation="Delete root account access keys and use IAM users instead",
453
+ compliance=["CIS 1.4"]
454
+ ))
455
+ except Exception:
456
+ pass
457
+
458
+ # Check for users without MFA
459
+ users = iam.list_users().get('Users', [])
460
+ for user in users:
461
+ username = user['UserName']
462
+ try:
463
+ mfa = iam.list_mfa_devices(UserName=username)
464
+ if not mfa.get('MFADevices', []):
465
+ findings.append(CloudFinding(
466
+ provider="aws",
467
+ service="iam",
468
+ resource_id=username,
469
+ resource_name=username,
470
+ title="IAM User Without MFA",
471
+ description=f"IAM user {username} does not have MFA enabled",
472
+ severity="medium",
473
+ recommendation="Enable MFA for all IAM users",
474
+ compliance=["CIS 1.2"]
475
+ ))
476
+ except Exception:
477
+ continue
478
+
479
+ except Exception as e:
480
+ self._log(f"IAM check error: {e}", "warning")
481
+
482
+ return findings
483
+
484
+ def _parse_scoutsuite_results(self, results_file: Path, provider: str) -> List[CloudFinding]:
485
+ """Parse ScoutSuite results file."""
486
+ findings = []
487
+
488
+ try:
489
+ content = results_file.read_text()
490
+ # Remove JS wrapper
491
+ if content.startswith("scoutsuite_results ="):
492
+ content = content.replace("scoutsuite_results =", "").strip()
493
+
494
+ data = json.loads(content)
495
+ services = data.get('services', {})
496
+
497
+ for service_name, service_data in services.items():
498
+ service_findings = service_data.get('findings', {})
499
+ for finding_id, finding_data in service_findings.items():
500
+ if finding_data.get('flagged_items', 0) > 0:
501
+ severity = self._map_scoutsuite_severity(finding_data.get('level', 'warning'))
502
+ for item in finding_data.get('items', []):
503
+ findings.append(CloudFinding(
504
+ provider=provider,
505
+ service=service_name,
506
+ resource_id=item,
507
+ resource_name=item,
508
+ title=finding_data.get('description', finding_id),
509
+ description=finding_data.get('rationale', ''),
510
+ severity=severity,
511
+ recommendation=finding_data.get('remediation', ''),
512
+ compliance=finding_data.get('compliance', [])
513
+ ))
514
+ except Exception as e:
515
+ self._log(f"Error parsing ScoutSuite results: {e}", "warning")
516
+
517
+ return findings
518
+
519
+ def _parse_prowler_results(self, results_file: Path) -> List[CloudFinding]:
520
+ """Parse Prowler JSON results."""
521
+ findings = []
522
+
523
+ try:
524
+ with open(results_file) as f:
525
+ for line in f:
526
+ if line.strip():
527
+ check = json.loads(line)
528
+ if check.get('StatusExtended', '').upper() == 'FAIL':
529
+ findings.append(CloudFinding(
530
+ provider="aws",
531
+ service=check.get('ServiceName', 'unknown'),
532
+ resource_id=check.get('ResourceId', ''),
533
+ resource_name=check.get('ResourceName', ''),
534
+ title=check.get('CheckTitle', ''),
535
+ description=check.get('StatusExtended', ''),
536
+ severity=check.get('Severity', 'medium').lower(),
537
+ recommendation=check.get('Remediation', {}).get('Recommendation', {}).get('Text', ''),
538
+ region=check.get('Region', ''),
539
+ account_id=check.get('AccountId', ''),
540
+ compliance=check.get('Compliance', [])
541
+ ))
542
+ except Exception as e:
543
+ self._log(f"Error parsing Prowler results: {e}", "warning")
544
+
545
+ return findings
546
+
547
+ def _map_scoutsuite_severity(self, level: str) -> str:
548
+ """Map ScoutSuite severity to standard levels."""
549
+ mapping = {
550
+ "danger": "critical",
551
+ "warning": "high",
552
+ "info": "medium"
553
+ }
554
+ return mapping.get(level.lower(), "medium")
555
+
556
+ def get_findings_by_severity(self, severity: str) -> List[CloudFinding]:
557
+ """Get findings filtered by severity."""
558
+ return [f for f in self.findings if f.severity.lower() == severity.lower()]
559
+
560
+ def get_findings_by_provider(self, provider: str) -> List[CloudFinding]:
561
+ """Get findings filtered by provider."""
562
+ return [f for f in self.findings if f.provider.lower() == provider.lower()]
563
+
564
+ def get_summary(self) -> Dict[str, Any]:
565
+ """Get scan summary."""
566
+ summary = {
567
+ "total_findings": len(self.findings),
568
+ "by_severity": {"critical": 0, "high": 0, "medium": 0, "low": 0, "info": 0},
569
+ "by_provider": {},
570
+ "by_service": {}
571
+ }
572
+
573
+ for finding in self.findings:
574
+ # Count by severity
575
+ sev = finding.severity.lower()
576
+ if sev in summary["by_severity"]:
577
+ summary["by_severity"][sev] += 1
578
+
579
+ # Count by provider
580
+ provider = finding.provider
581
+ if provider not in summary["by_provider"]:
582
+ summary["by_provider"][provider] = 0
583
+ summary["by_provider"][provider] += 1
584
+
585
+ # Count by service
586
+ service = f"{finding.provider}:{finding.service}"
587
+ if service not in summary["by_service"]:
588
+ summary["by_service"][service] = 0
589
+ summary["by_service"][service] += 1
590
+
591
+ return summary
592
+
593
+
594
+ # Convenience functions
595
+ def get_cloud_scanner(
596
+ providers: Optional[List[str]] = None,
597
+ aws_profile: Optional[str] = None,
598
+ azure_subscription: Optional[str] = None,
599
+ gcp_project: Optional[str] = None,
600
+ **kwargs
601
+ ) -> CloudScanner:
602
+ """
603
+ Get a configured cloud scanner instance.
604
+
605
+ Args:
606
+ providers: Cloud providers to scan
607
+ aws_profile: AWS CLI profile name
608
+ azure_subscription: Azure subscription ID
609
+ gcp_project: GCP project ID
610
+
611
+ Returns:
612
+ CloudScanner instance
613
+ """
614
+ config = get_cloud_config(
615
+ providers=providers,
616
+ aws_profile=aws_profile,
617
+ azure_subscription=azure_subscription,
618
+ gcp_project=gcp_project,
619
+ **kwargs
620
+ )
621
+ return CloudScanner(config)
622
+
623
+
624
+ async def scan_cloud(
625
+ providers: Optional[List[str]] = None,
626
+ **kwargs
627
+ ) -> List[CloudScanResult]:
628
+ """
629
+ Run cloud security scan.
630
+
631
+ Args:
632
+ providers: Cloud providers to scan
633
+ **kwargs: Additional configuration
634
+
635
+ Returns:
636
+ List of CloudScanResult
637
+ """
638
+ scanner = get_cloud_scanner(providers=providers, **kwargs)
639
+ return await scanner.scan()