security-use 0.1.1__py3-none-any.whl → 0.2.9__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 (45) hide show
  1. security_use/__init__.py +9 -1
  2. security_use/auth/__init__.py +16 -0
  3. security_use/auth/client.py +223 -0
  4. security_use/auth/config.py +177 -0
  5. security_use/auth/oauth.py +317 -0
  6. security_use/cli.py +699 -34
  7. security_use/compliance/__init__.py +10 -0
  8. security_use/compliance/mapper.py +275 -0
  9. security_use/compliance/models.py +50 -0
  10. security_use/dependency_scanner.py +76 -30
  11. security_use/fixers/iac_fixer.py +173 -95
  12. security_use/iac/rules/azure.py +246 -0
  13. security_use/iac/rules/gcp.py +255 -0
  14. security_use/iac/rules/kubernetes.py +429 -0
  15. security_use/iac/rules/registry.py +56 -0
  16. security_use/parsers/__init__.py +18 -0
  17. security_use/parsers/base.py +2 -0
  18. security_use/parsers/composer.py +101 -0
  19. security_use/parsers/conda.py +97 -0
  20. security_use/parsers/dotnet.py +89 -0
  21. security_use/parsers/gradle.py +90 -0
  22. security_use/parsers/maven.py +108 -0
  23. security_use/parsers/npm.py +196 -0
  24. security_use/parsers/yarn.py +108 -0
  25. security_use/reporter.py +29 -1
  26. security_use/sbom/__init__.py +10 -0
  27. security_use/sbom/generator.py +340 -0
  28. security_use/sbom/models.py +40 -0
  29. security_use/scanner.py +15 -2
  30. security_use/sensor/__init__.py +125 -0
  31. security_use/sensor/alert_queue.py +207 -0
  32. security_use/sensor/config.py +217 -0
  33. security_use/sensor/dashboard_alerter.py +246 -0
  34. security_use/sensor/detector.py +415 -0
  35. security_use/sensor/endpoint_analyzer.py +339 -0
  36. security_use/sensor/middleware.py +521 -0
  37. security_use/sensor/models.py +140 -0
  38. security_use/sensor/webhook.py +227 -0
  39. security_use-0.2.9.dist-info/METADATA +531 -0
  40. security_use-0.2.9.dist-info/RECORD +60 -0
  41. security_use-0.2.9.dist-info/licenses/LICENSE +21 -0
  42. security_use-0.1.1.dist-info/METADATA +0 -92
  43. security_use-0.1.1.dist-info/RECORD +0 -30
  44. {security_use-0.1.1.dist-info → security_use-0.2.9.dist-info}/WHEEL +0 -0
  45. {security_use-0.1.1.dist-info → security_use-0.2.9.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,339 @@
1
+ """Analyze application endpoints to identify vulnerable paths.
2
+
3
+ This module combines dependency scanning with code analysis to identify
4
+ which API endpoints use vulnerable packages and should be monitored.
5
+ """
6
+
7
+ import ast
8
+ import logging
9
+ import re
10
+ from dataclasses import dataclass, field
11
+ from pathlib import Path
12
+ from typing import Optional
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ @dataclass
18
+ class EndpointInfo:
19
+ """Information about a discovered endpoint."""
20
+
21
+ path: str
22
+ method: str = "GET"
23
+ function_name: str = ""
24
+ file_path: str = ""
25
+ line_number: int = 0
26
+ imports: list[str] = field(default_factory=list)
27
+ vulnerable_packages: list[str] = field(default_factory=list)
28
+ risk_score: float = 0.0
29
+
30
+
31
+ @dataclass
32
+ class AnalysisResult:
33
+ """Result of endpoint vulnerability analysis."""
34
+
35
+ all_endpoints: list[EndpointInfo] = field(default_factory=list)
36
+ vulnerable_endpoints: list[EndpointInfo] = field(default_factory=list)
37
+ vulnerable_paths: list[str] = field(default_factory=list)
38
+ vulnerable_packages: dict[str, list[str]] = field(default_factory=dict)
39
+
40
+
41
+ class VulnerableEndpointDetector:
42
+ """Detect which endpoints use vulnerable packages.
43
+
44
+ Combines dependency scanning with static code analysis to identify
45
+ API routes that should be monitored more closely.
46
+
47
+ Usage:
48
+ from security_use.sensor import VulnerableEndpointDetector
49
+
50
+ detector = VulnerableEndpointDetector()
51
+ result = detector.analyze("./my-project")
52
+
53
+ # Get paths to monitor
54
+ vulnerable_paths = result.vulnerable_paths
55
+ """
56
+
57
+ # Patterns for detecting route decorators
58
+ FASTAPI_ROUTE_PATTERNS = [
59
+ r'@(?:app|router)\.(?:get|post|put|delete|patch|options|head)\s*\(\s*["\']([^"\']+)["\']',
60
+ r'@(?:app|router)\.api_route\s*\(\s*["\']([^"\']+)["\']',
61
+ ]
62
+
63
+ FLASK_ROUTE_PATTERNS = [
64
+ r'@(?:app|bp|blueprint)\.route\s*\(\s*["\']([^"\']+)["\']',
65
+ r'@(?:app|bp|blueprint)\.(?:get|post|put|delete|patch)\s*\(\s*["\']([^"\']+)["\']',
66
+ ]
67
+
68
+ # High-risk packages that handle user input
69
+ HIGH_RISK_PACKAGES = {
70
+ "flask": ["request", "g", "session"],
71
+ "fastapi": ["Request", "Form", "File", "Body", "Query", "Path"],
72
+ "django": ["request", "HttpRequest"],
73
+ "sqlalchemy": ["text", "execute", "raw"],
74
+ "pymysql": ["cursor", "execute"],
75
+ "psycopg2": ["cursor", "execute"],
76
+ "sqlite3": ["cursor", "execute"],
77
+ "subprocess": ["run", "call", "Popen", "check_output"],
78
+ "os": ["system", "popen", "exec"],
79
+ "pickle": ["loads", "load"],
80
+ "yaml": ["load", "unsafe_load"],
81
+ "eval": [],
82
+ "exec": [],
83
+ }
84
+
85
+ def __init__(self, project_path: Optional[str] = None):
86
+ """Initialize the endpoint detector.
87
+
88
+ Args:
89
+ project_path: Path to the project to analyze.
90
+ """
91
+ self.project_path = Path(project_path) if project_path else None
92
+ self._vulnerable_packages: set[str] = set()
93
+
94
+ def analyze(self, path: Optional[str] = None) -> AnalysisResult:
95
+ """Analyze a project for vulnerable endpoints.
96
+
97
+ Args:
98
+ path: Path to the project. Uses project_path if not provided.
99
+
100
+ Returns:
101
+ AnalysisResult with vulnerable endpoints and paths.
102
+ """
103
+ project_path = Path(path) if path else self.project_path
104
+ if not project_path:
105
+ raise ValueError("No project path provided")
106
+
107
+ result = AnalysisResult()
108
+
109
+ # Step 1: Run dependency scan to find vulnerable packages
110
+ self._scan_dependencies(project_path, result)
111
+
112
+ # Step 2: Find all Python files
113
+ python_files = self._find_python_files(project_path)
114
+
115
+ # Step 3: Analyze each file for endpoints
116
+ for file_path in python_files:
117
+ try:
118
+ self._analyze_file(file_path, result)
119
+ except Exception as e:
120
+ logger.debug(f"Error analyzing {file_path}: {e}")
121
+
122
+ # Step 4: Calculate risk scores and filter vulnerable endpoints
123
+ self._calculate_risk_scores(result)
124
+
125
+ # Step 5: Extract vulnerable paths
126
+ result.vulnerable_paths = list(set(
127
+ ep.path for ep in result.vulnerable_endpoints
128
+ ))
129
+
130
+ return result
131
+
132
+ def _scan_dependencies(self, project_path: Path, result: AnalysisResult) -> None:
133
+ """Scan dependencies for vulnerabilities."""
134
+ try:
135
+ from security_use import scan_dependencies
136
+
137
+ scan_result = scan_dependencies(str(project_path))
138
+
139
+ for vuln in scan_result.vulnerabilities:
140
+ package = vuln.package.lower()
141
+ self._vulnerable_packages.add(package)
142
+
143
+ if package not in result.vulnerable_packages:
144
+ result.vulnerable_packages[package] = []
145
+ result.vulnerable_packages[package].append(vuln.cve_id or vuln.title)
146
+
147
+ logger.info(f"Found {len(self._vulnerable_packages)} vulnerable packages")
148
+
149
+ except Exception as e:
150
+ logger.warning(f"Dependency scan failed: {e}")
151
+
152
+ def _find_python_files(self, project_path: Path) -> list[Path]:
153
+ """Find all Python files in the project."""
154
+ skip_dirs = {
155
+ "node_modules", ".git", ".venv", "venv", "__pycache__",
156
+ ".tox", ".pytest_cache", "dist", "build", ".eggs"
157
+ }
158
+
159
+ files = []
160
+ for file_path in project_path.rglob("*.py"):
161
+ if not any(skip in file_path.parts for skip in skip_dirs):
162
+ files.append(file_path)
163
+
164
+ return files
165
+
166
+ def _analyze_file(self, file_path: Path, result: AnalysisResult) -> None:
167
+ """Analyze a Python file for endpoints."""
168
+ try:
169
+ content = file_path.read_text(encoding="utf-8")
170
+ except Exception:
171
+ return
172
+
173
+ # Extract imports
174
+ imports = self._extract_imports(content)
175
+
176
+ # Find route decorators
177
+ endpoints = self._find_routes(content, str(file_path), imports)
178
+
179
+ for endpoint in endpoints:
180
+ endpoint.imports = imports
181
+ result.all_endpoints.append(endpoint)
182
+
183
+ def _extract_imports(self, content: str) -> list[str]:
184
+ """Extract imported packages from Python code."""
185
+ imports = []
186
+
187
+ try:
188
+ tree = ast.parse(content)
189
+
190
+ for node in ast.walk(tree):
191
+ if isinstance(node, ast.Import):
192
+ for alias in node.names:
193
+ imports.append(alias.name.split(".")[0])
194
+ elif isinstance(node, ast.ImportFrom):
195
+ if node.module:
196
+ imports.append(node.module.split(".")[0])
197
+
198
+ except SyntaxError:
199
+ # Fallback to regex
200
+ import_pattern = r'^(?:from\s+(\w+)|import\s+(\w+))'
201
+ for match in re.finditer(import_pattern, content, re.MULTILINE):
202
+ pkg = match.group(1) or match.group(2)
203
+ if pkg:
204
+ imports.append(pkg)
205
+
206
+ return list(set(imports))
207
+
208
+ def _find_routes(
209
+ self, content: str, file_path: str, imports: list[str]
210
+ ) -> list[EndpointInfo]:
211
+ """Find route definitions in Python code."""
212
+ endpoints = []
213
+ lines = content.split("\n")
214
+
215
+ # Combine patterns based on detected framework
216
+ patterns = []
217
+ if any(imp in ["fastapi", "starlette"] for imp in imports):
218
+ patterns.extend(self.FASTAPI_ROUTE_PATTERNS)
219
+ if any(imp in ["flask"] for imp in imports):
220
+ patterns.extend(self.FLASK_ROUTE_PATTERNS)
221
+
222
+ # If no framework detected, try all patterns
223
+ if not patterns:
224
+ patterns = self.FASTAPI_ROUTE_PATTERNS + self.FLASK_ROUTE_PATTERNS
225
+
226
+ for i, line in enumerate(lines):
227
+ for pattern in patterns:
228
+ match = re.search(pattern, line, re.IGNORECASE)
229
+ if match:
230
+ path = match.group(1)
231
+
232
+ # Detect HTTP method
233
+ method = "GET"
234
+ method_match = re.search(r'\.(get|post|put|delete|patch|options|head)\s*\(', line, re.I)
235
+ if method_match:
236
+ method = method_match.group(1).upper()
237
+
238
+ # Find function name (usually on next line or same line)
239
+ func_name = ""
240
+ for j in range(i, min(i + 3, len(lines))):
241
+ func_match = re.search(r'(?:async\s+)?def\s+(\w+)', lines[j])
242
+ if func_match:
243
+ func_name = func_match.group(1)
244
+ break
245
+
246
+ endpoints.append(EndpointInfo(
247
+ path=path,
248
+ method=method,
249
+ function_name=func_name,
250
+ file_path=file_path,
251
+ line_number=i + 1,
252
+ ))
253
+
254
+ return endpoints
255
+
256
+ def _calculate_risk_scores(self, result: AnalysisResult) -> None:
257
+ """Calculate risk scores for endpoints and identify vulnerable ones."""
258
+ for endpoint in result.all_endpoints:
259
+ score = 0.0
260
+ vulnerable_pkgs = []
261
+
262
+ # Check if endpoint uses vulnerable packages
263
+ for imp in endpoint.imports:
264
+ imp_lower = imp.lower()
265
+
266
+ # Direct vulnerable package
267
+ if imp_lower in self._vulnerable_packages:
268
+ score += 0.5
269
+ vulnerable_pkgs.append(imp_lower)
270
+
271
+ # High-risk package
272
+ if imp_lower in self.HIGH_RISK_PACKAGES:
273
+ score += 0.3
274
+
275
+ # Path-based risk factors
276
+ path_lower = endpoint.path.lower()
277
+ if any(term in path_lower for term in ["admin", "auth", "login", "password", "user"]):
278
+ score += 0.2
279
+ if any(term in path_lower for term in ["upload", "file", "download"]):
280
+ score += 0.2
281
+ if any(term in path_lower for term in ["search", "query", "filter"]):
282
+ score += 0.1
283
+ if any(term in path_lower for term in ["exec", "run", "eval", "shell"]):
284
+ score += 0.3
285
+
286
+ # Method-based risk
287
+ if endpoint.method in ["POST", "PUT", "PATCH", "DELETE"]:
288
+ score += 0.1
289
+
290
+ endpoint.risk_score = min(score, 1.0)
291
+ endpoint.vulnerable_packages = vulnerable_pkgs
292
+
293
+ # Mark as vulnerable if score is high enough or uses vulnerable packages
294
+ if score >= 0.3 or vulnerable_pkgs:
295
+ result.vulnerable_endpoints.append(endpoint)
296
+
297
+ def get_watch_paths(
298
+ self,
299
+ path: Optional[str] = None,
300
+ min_risk_score: float = 0.0,
301
+ include_high_risk: bool = True,
302
+ ) -> list[str]:
303
+ """Get list of paths that should be monitored.
304
+
305
+ Args:
306
+ path: Project path to analyze.
307
+ min_risk_score: Minimum risk score for inclusion.
308
+ include_high_risk: Include high-risk paths even without vulnerabilities.
309
+
310
+ Returns:
311
+ List of URL paths to monitor.
312
+ """
313
+ result = self.analyze(path)
314
+
315
+ paths = set()
316
+
317
+ for endpoint in result.vulnerable_endpoints:
318
+ if endpoint.risk_score >= min_risk_score:
319
+ paths.add(endpoint.path)
320
+
321
+ if include_high_risk:
322
+ for endpoint in result.all_endpoints:
323
+ if endpoint.risk_score >= 0.5:
324
+ paths.add(endpoint.path)
325
+
326
+ return list(paths)
327
+
328
+
329
+ def detect_vulnerable_endpoints(project_path: str) -> list[str]:
330
+ """Convenience function to detect vulnerable endpoints.
331
+
332
+ Args:
333
+ project_path: Path to the project to analyze.
334
+
335
+ Returns:
336
+ List of vulnerable endpoint paths.
337
+ """
338
+ detector = VulnerableEndpointDetector()
339
+ return detector.get_watch_paths(project_path)