devguard 0.2.0__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 (60) hide show
  1. devguard/INTEGRATION_SUMMARY.md +121 -0
  2. devguard/__init__.py +3 -0
  3. devguard/__main__.py +6 -0
  4. devguard/checkers/__init__.py +41 -0
  5. devguard/checkers/api_usage.py +523 -0
  6. devguard/checkers/aws_cost.py +331 -0
  7. devguard/checkers/aws_iam.py +284 -0
  8. devguard/checkers/base.py +25 -0
  9. devguard/checkers/container.py +137 -0
  10. devguard/checkers/domain.py +189 -0
  11. devguard/checkers/firecrawl.py +117 -0
  12. devguard/checkers/fly.py +225 -0
  13. devguard/checkers/github.py +210 -0
  14. devguard/checkers/npm.py +327 -0
  15. devguard/checkers/npm_security.py +244 -0
  16. devguard/checkers/redteam.py +290 -0
  17. devguard/checkers/secret.py +279 -0
  18. devguard/checkers/swarm.py +376 -0
  19. devguard/checkers/tailscale.py +143 -0
  20. devguard/checkers/tailsnitch.py +303 -0
  21. devguard/checkers/tavily.py +179 -0
  22. devguard/checkers/vercel.py +192 -0
  23. devguard/cli.py +1510 -0
  24. devguard/cli_helpers.py +189 -0
  25. devguard/config.py +249 -0
  26. devguard/core.py +293 -0
  27. devguard/dashboard.py +715 -0
  28. devguard/discovery.py +363 -0
  29. devguard/http_client.py +142 -0
  30. devguard/llm_service.py +481 -0
  31. devguard/mcp_server.py +259 -0
  32. devguard/metrics.py +144 -0
  33. devguard/models.py +208 -0
  34. devguard/reporting.py +1571 -0
  35. devguard/sarif.py +295 -0
  36. devguard/scripts/ANALYSIS_SUMMARY.md +141 -0
  37. devguard/scripts/README.md +221 -0
  38. devguard/scripts/auto_fix_recommendations.py +145 -0
  39. devguard/scripts/generate_npmignore.py +175 -0
  40. devguard/scripts/generate_security_report.py +324 -0
  41. devguard/scripts/prepublish_check.sh +29 -0
  42. devguard/scripts/redteam_npm_packages.py +1262 -0
  43. devguard/scripts/review_all_repos.py +300 -0
  44. devguard/spec.py +617 -0
  45. devguard/sweeps/__init__.py +23 -0
  46. devguard/sweeps/ai_editor_config_audit.py +697 -0
  47. devguard/sweeps/cargo_publish_audit.py +655 -0
  48. devguard/sweeps/dependency_audit.py +419 -0
  49. devguard/sweeps/gitignore_audit.py +336 -0
  50. devguard/sweeps/local_dev.py +260 -0
  51. devguard/sweeps/local_dirty_worktree_secrets.py +521 -0
  52. devguard/sweeps/project_flaudit.py +636 -0
  53. devguard/sweeps/public_github_secrets.py +680 -0
  54. devguard/sweeps/publish_audit.py +478 -0
  55. devguard/sweeps/ssh_key_audit.py +327 -0
  56. devguard/utils.py +174 -0
  57. devguard-0.2.0.dist-info/METADATA +225 -0
  58. devguard-0.2.0.dist-info/RECORD +60 -0
  59. devguard-0.2.0.dist-info/WHEEL +4 -0
  60. devguard-0.2.0.dist-info/entry_points.txt +2 -0
@@ -0,0 +1,192 @@
1
+ """Vercel deployment status checker."""
2
+
3
+ import logging
4
+ from datetime import datetime
5
+
6
+ import httpx
7
+
8
+ from devguard.checkers.base import BaseChecker
9
+ from devguard.http_client import create_client, retry_with_backoff
10
+ from devguard.models import CheckResult, CheckStatus, DeploymentStatus
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+ class VercelChecker(BaseChecker):
16
+ """Check Vercel deployments for health status."""
17
+
18
+ check_type = "vercel"
19
+
20
+ async def check(self) -> CheckResult:
21
+ """Check Vercel deployments."""
22
+ deployments: list[DeploymentStatus] = []
23
+ errors: list[str] = []
24
+
25
+ if not self.settings.vercel_token:
26
+ return CheckResult(
27
+ check_type=self.check_type,
28
+ success=False,
29
+ deployments=[],
30
+ errors=["Vercel token not configured"],
31
+ )
32
+
33
+ # Handle SecretStr
34
+ vercel_token = self.settings.vercel_token
35
+ if hasattr(vercel_token, "get_secret_value"):
36
+ vercel_token = vercel_token.get_secret_value()
37
+
38
+ headers = {
39
+ "Authorization": f"Bearer {vercel_token}",
40
+ }
41
+
42
+ if self.settings.vercel_team_id:
43
+ headers["x-vercel-team-id"] = self.settings.vercel_team_id
44
+
45
+ try:
46
+ async with create_client() as client:
47
+ # Get list of projects
48
+ projects = await self._get_projects(client, headers)
49
+
50
+ # Check deployments for each project
51
+ for project in projects:
52
+ try:
53
+ project_deployments = await self._get_project_deployments(
54
+ client, headers, project
55
+ )
56
+ deployments.extend(project_deployments)
57
+ except httpx.HTTPStatusError as e:
58
+ errors.append(
59
+ f"Error checking project {project}: "
60
+ f"HTTP {e.response.status_code} - {e.response.text[:100]}"
61
+ )
62
+ except httpx.RequestError as e:
63
+ errors.append(f"Error checking project {project}: Network error - {str(e)}")
64
+ except Exception as e:
65
+ errors.append(f"Error checking project {project}: {str(e)}")
66
+
67
+ except httpx.RequestError as e:
68
+ errors.append(f"Error connecting to Vercel API: {str(e)}")
69
+ except Exception as e:
70
+ errors.append(f"Error checking Vercel: {str(e)}")
71
+
72
+ # Vercel doesn't have a public billing API, so we don't track costs
73
+ return CheckResult(
74
+ check_type=self.check_type,
75
+ success=len(errors) == 0,
76
+ deployments=deployments,
77
+ errors=errors,
78
+ cost_metrics=[],
79
+ )
80
+
81
+ async def _get_projects(self, client: httpx.AsyncClient, headers: dict) -> list[str]:
82
+ """Get list of projects to monitor."""
83
+ if self.settings.vercel_projects_to_monitor:
84
+ return self.settings.vercel_projects_to_monitor
85
+
86
+ # Get all projects
87
+ try:
88
+
89
+ async def fetch_projects():
90
+ response = await client.get(
91
+ "https://api.vercel.com/v9/projects",
92
+ headers=headers,
93
+ )
94
+ response.raise_for_status()
95
+ return response
96
+
97
+ response = await retry_with_backoff(fetch_projects, max_retries=3)
98
+ data = response.json()
99
+ return [
100
+ project.get("name", "")
101
+ for project in data.get("projects", [])
102
+ if project.get("name")
103
+ ]
104
+ except httpx.HTTPStatusError as e:
105
+ logger.warning(f"Failed to fetch Vercel projects: HTTP {e.response.status_code}")
106
+ except httpx.RequestError as e:
107
+ logger.warning(f"Failed to fetch Vercel projects: {str(e)}")
108
+ except Exception as e:
109
+ logger.warning(f"Unexpected error fetching Vercel projects: {str(e)}")
110
+
111
+ return []
112
+
113
+ async def _get_project_deployments(
114
+ self, client: httpx.AsyncClient, headers: dict, project_name: str
115
+ ) -> list[DeploymentStatus]:
116
+ """Get deployments for a project."""
117
+ deployments: list[DeploymentStatus] = []
118
+
119
+ try:
120
+
121
+ async def fetch_deployments():
122
+ response = await client.get(
123
+ "https://api.vercel.com/v6/deployments",
124
+ headers=headers,
125
+ params={"projectId": project_name, "limit": 10},
126
+ )
127
+ response.raise_for_status()
128
+ return response
129
+
130
+ response = await retry_with_backoff(fetch_deployments, max_retries=3)
131
+ data = response.json()
132
+ for deployment_data in data.get("deployments", []):
133
+ deployment = self._parse_deployment(deployment_data, project_name)
134
+ if deployment:
135
+ deployments.append(deployment)
136
+ except httpx.HTTPStatusError as e:
137
+ logger.warning(
138
+ f"Failed to fetch deployments for {project_name}: HTTP {e.response.status_code}"
139
+ )
140
+ except httpx.RequestError as e:
141
+ logger.warning(f"Network error fetching deployments for {project_name}: {str(e)}")
142
+ except Exception as e:
143
+ logger.warning(f"Unexpected error fetching deployments for {project_name}: {str(e)}")
144
+
145
+ return deployments
146
+
147
+ def _parse_deployment(
148
+ self, deployment_data: dict, project_name: str
149
+ ) -> DeploymentStatus | None:
150
+ """Parse a deployment from API response."""
151
+ try:
152
+ # Map status
153
+ state = deployment_data.get("state", "UNKNOWN").lower()
154
+ status_map = {
155
+ "ready": CheckStatus.HEALTHY,
156
+ "building": CheckStatus.UNKNOWN,
157
+ "error": CheckStatus.UNHEALTHY,
158
+ "queued": CheckStatus.UNKNOWN,
159
+ "canceled": CheckStatus.UNHEALTHY,
160
+ }
161
+ status = status_map.get(state, CheckStatus.UNKNOWN)
162
+
163
+ # Parse dates
164
+ created_at = None
165
+ if deployment_data.get("createdAt"):
166
+ created_at = datetime.fromtimestamp(deployment_data.get("createdAt") / 1000)
167
+
168
+ updated_at = None
169
+ if deployment_data.get("updatedAt"):
170
+ updated_at = datetime.fromtimestamp(deployment_data.get("updatedAt") / 1000)
171
+
172
+ url = deployment_data.get("url")
173
+ if url and not url.startswith("http"):
174
+ url = f"https://{url}"
175
+
176
+ return DeploymentStatus(
177
+ platform="vercel",
178
+ project_name=project_name,
179
+ deployment_id=deployment_data.get("uid", ""),
180
+ status=status,
181
+ url=url,
182
+ created_at=created_at,
183
+ updated_at=updated_at,
184
+ error_message=deployment_data.get("errorMessage"),
185
+ metadata={
186
+ "state": state,
187
+ "target": deployment_data.get("target"),
188
+ "type": deployment_data.get("type"),
189
+ },
190
+ )
191
+ except Exception:
192
+ return None