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
devguard/core.py ADDED
@@ -0,0 +1,293 @@
1
+ """Core devguard orchestration."""
2
+
3
+ import asyncio
4
+
5
+ import httpx
6
+
7
+ from devguard.checkers import (
8
+ APIUsageChecker,
9
+ AWSCostChecker,
10
+ AWSIAMChecker,
11
+ ContainerChecker,
12
+ DomainChecker,
13
+ FirecrawlChecker,
14
+ FlyChecker,
15
+ GitHubChecker,
16
+ NpmChecker,
17
+ NpmSecurityChecker,
18
+ RedTeamChecker,
19
+ SecretChecker,
20
+ SwarmChecker,
21
+ TailscaleChecker,
22
+ TailsnitchChecker,
23
+ TavilyChecker,
24
+ VercelChecker,
25
+ )
26
+ from devguard.checkers.base import BaseChecker
27
+ from devguard.config import Settings
28
+ from devguard.models import GuardianReport
29
+
30
+
31
+ class Guardian:
32
+ """Main devguard orchestrator."""
33
+
34
+ def __init__(self, settings: Settings):
35
+ """Initialize devguard with settings."""
36
+ self.settings = settings
37
+ self.checkers: list[BaseChecker] = []
38
+
39
+ # For settings objects used in tests (MagicMock), treat feature flags as enabled
40
+ # only when they are explicitly `True`, not merely truthy.
41
+ def enabled(attr: str) -> bool:
42
+ return getattr(settings, attr, False) is True
43
+
44
+ # Initialize checkers based on configuration
45
+ if settings.npm_packages_to_monitor or settings.snyk_token:
46
+ self.checkers.append(NpmChecker(settings))
47
+
48
+ # Deep npm security analysis (separate from basic vulnerability checking)
49
+ if enabled("npm_security_enabled") and settings.npm_packages_to_monitor:
50
+ self.checkers.append(NpmSecurityChecker(settings))
51
+
52
+ if settings.github_token:
53
+ self.checkers.append(GitHubChecker(settings))
54
+
55
+ if settings.vercel_token:
56
+ self.checkers.append(VercelChecker(settings))
57
+
58
+ if settings.fly_api_token:
59
+ self.checkers.append(FlyChecker(settings))
60
+
61
+ if settings.firecrawl_api_key:
62
+ self.checkers.append(FirecrawlChecker(settings))
63
+
64
+ if settings.tavily_api_key:
65
+ self.checkers.append(TavilyChecker(settings))
66
+
67
+ # Secret scanning (uses trufflehog - runs locally, no API needed)
68
+ if enabled("secret_scan_enabled"):
69
+ self.checkers.append(SecretChecker(settings))
70
+
71
+ # Container/Dockerfile security checks
72
+ if enabled("container_check_enabled"):
73
+ self.checkers.append(ContainerChecker(settings))
74
+
75
+ # AWS IAM security checks for satellite nodes
76
+ if enabled("aws_iam_check_enabled"):
77
+ self.checkers.append(AWSIAMChecker(settings))
78
+
79
+ # AWS Cost monitoring
80
+ if enabled("aws_cost_check_enabled"):
81
+ self.checkers.append(AWSCostChecker(settings))
82
+
83
+ # Tailscale network health
84
+ if enabled("tailscale_check_enabled"):
85
+ self.checkers.append(TailscaleChecker(settings))
86
+
87
+ # Tailsnitch ACL security audit
88
+ if enabled("tailsnitch_check_enabled"):
89
+ self.checkers.append(TailsnitchChecker(settings))
90
+
91
+ # Domain and SSL monitoring
92
+ if enabled("domain_check_enabled"):
93
+ self.checkers.append(DomainChecker(settings))
94
+
95
+ # Docker Swarm health
96
+ if enabled("swarm_check_enabled"):
97
+ self.checkers.append(SwarmChecker(settings))
98
+
99
+ # API usage/credits monitoring
100
+ if enabled("api_usage_check_enabled"):
101
+ self.checkers.append(APIUsageChecker(settings))
102
+
103
+ # Red team testing (runs after deployment checks to test endpoints)
104
+ if enabled("redteam_enabled") and (settings.vercel_token or settings.fly_api_token):
105
+ self.checkers.append(RedTeamChecker(settings))
106
+
107
+ def validate_configuration(self) -> list[str]:
108
+ """
109
+ Validate configuration and return list of warnings/errors.
110
+
111
+ Returns:
112
+ List of validation messages (warnings or errors)
113
+ """
114
+ warnings: list[str] = []
115
+
116
+ # Check if any checkers are configured
117
+ if not self.checkers:
118
+ warnings.append(
119
+ "No checkers configured. Set at least one of: "
120
+ "npm_packages_to_monitor, github_token, vercel_token, fly_api_token"
121
+ )
122
+
123
+ # Check npm checker configuration
124
+ if any(isinstance(c, NpmChecker) for c in self.checkers):
125
+ if not self.settings.npm_packages_to_monitor and not self.settings.snyk_token:
126
+ warnings.append(
127
+ "NpmChecker is enabled but no packages or Snyk token configured. "
128
+ "Set npm_packages_to_monitor or snyk_token."
129
+ )
130
+
131
+ # Check GitHub checker configuration
132
+ if any(isinstance(c, GitHubChecker) for c in self.checkers):
133
+ if not self.settings.github_repos_to_monitor and not self.settings.github_org:
134
+ warnings.append(
135
+ "GitHubChecker is enabled but no repos or org configured. "
136
+ "Set github_repos_to_monitor or github_org."
137
+ )
138
+
139
+ # Check Vercel checker configuration
140
+ if any(isinstance(c, VercelChecker) for c in self.checkers):
141
+ if not self.settings.vercel_projects_to_monitor:
142
+ warnings.append(
143
+ "VercelChecker is enabled but no projects configured. "
144
+ "Set vercel_projects_to_monitor or it will fetch all projects."
145
+ )
146
+
147
+ # Check Fly checker configuration
148
+ if any(isinstance(c, FlyChecker) for c in self.checkers):
149
+ if not self.settings.fly_apps_to_monitor:
150
+ warnings.append(
151
+ "FlyChecker is enabled but no apps configured. "
152
+ "Set fly_apps_to_monitor or it will fetch all apps."
153
+ )
154
+
155
+ return warnings
156
+
157
+ async def run_checks(self, checker_types: list[str] | None = None) -> GuardianReport:
158
+ """Run all configured checks and generate a report.
159
+
160
+ Args:
161
+ checker_types: Optional list of checker types to run. If None, runs all checkers.
162
+ Example: ["npm", "github"] to run only npm and github checkers.
163
+ """
164
+ from devguard.models import CheckResult
165
+
166
+ checks: list[CheckResult] = []
167
+ redteam_checker = None
168
+
169
+ # Filter checkers if specific types requested
170
+ checkers_to_run = self.checkers
171
+ if checker_types:
172
+ checkers_to_run = [c for c in self.checkers if c.check_type in checker_types]
173
+
174
+ # First pass: run all checkers except red team in parallel
175
+ async def run_checker(checker: BaseChecker) -> CheckResult:
176
+ """Run a single checker with proper error handling."""
177
+ try:
178
+ return await checker.check()
179
+ except httpx.HTTPStatusError as e:
180
+ return CheckResult(
181
+ check_type=checker.check_type,
182
+ success=False,
183
+ errors=[f"HTTP {e.response.status_code}: {e.response.text[:200]}"],
184
+ )
185
+ except httpx.RequestError as e:
186
+ return CheckResult(
187
+ check_type=checker.check_type,
188
+ success=False,
189
+ errors=[f"Network error: {str(e)}"],
190
+ )
191
+ except TimeoutError:
192
+ return CheckResult(
193
+ check_type=checker.check_type,
194
+ success=False,
195
+ errors=["Check timed out"],
196
+ )
197
+ except Exception as e:
198
+ # Log unexpected errors for debugging
199
+ import logging
200
+
201
+ logger = logging.getLogger(__name__)
202
+ logger.error(
203
+ f"Unexpected error in {checker.check_type} checker: {e}", exc_info=True
204
+ )
205
+ return CheckResult(
206
+ check_type=checker.check_type,
207
+ success=False,
208
+ errors=[f"Unexpected error: {str(e)}"],
209
+ )
210
+
211
+ # Run checkers in parallel (except red team which needs deployment results)
212
+ checker_tasks = []
213
+ for checker in checkers_to_run:
214
+ if isinstance(checker, RedTeamChecker):
215
+ redteam_checker = checker
216
+ continue # Skip red team for now
217
+
218
+ checker_tasks.append(run_checker(checker))
219
+
220
+ # Run all checkers in parallel
221
+ if checker_tasks:
222
+ results = await asyncio.gather(*checker_tasks, return_exceptions=True)
223
+ for result in results:
224
+ if isinstance(result, Exception):
225
+ # This shouldn't happen due to error handling in run_checker, but handle it
226
+ import logging
227
+
228
+ logger = logging.getLogger(__name__)
229
+ logger.error(f"Checker task raised exception: {result}", exc_info=True)
230
+ else:
231
+ checks.append(result)
232
+
233
+ # Second pass: run red team checker with access to deployment results
234
+ if redteam_checker and (not checker_types or "redteam" in checker_types):
235
+ try:
236
+ # Pass deployment results directly to checker
237
+ deployment_results = [c for c in checks if c.check_type in ("vercel", "fly")]
238
+ result = await redteam_checker.check(deployment_results=deployment_results)
239
+ checks.append(result)
240
+ except httpx.HTTPStatusError as e:
241
+ checks.append(
242
+ CheckResult(
243
+ check_type="redteam",
244
+ success=False,
245
+ errors=[f"HTTP {e.response.status_code}: {e.response.text[:200]}"],
246
+ )
247
+ )
248
+ except httpx.RequestError as e:
249
+ checks.append(
250
+ CheckResult(
251
+ check_type="redteam",
252
+ success=False,
253
+ errors=[f"Network error: {str(e)}"],
254
+ )
255
+ )
256
+ except Exception as e:
257
+ import logging
258
+
259
+ logger = logging.getLogger(__name__)
260
+ logger.error(f"Red team check failed: {e}", exc_info=True)
261
+ checks.append(
262
+ CheckResult(
263
+ check_type="redteam",
264
+ success=False,
265
+ errors=[f"Red team check failed: {str(e)}"],
266
+ )
267
+ )
268
+
269
+ # Generate summary
270
+ report = GuardianReport(checks=checks)
271
+ report.summary = {
272
+ "total_checks": len(checks),
273
+ "successful_checks": sum(1 for c in checks if c.success),
274
+ "failed_checks": sum(1 for c in checks if not c.success),
275
+ "total_vulnerabilities": report.get_total_vulnerabilities(),
276
+ "critical_vulnerabilities": len(report.get_critical_vulnerabilities()),
277
+ "total_findings": report.get_total_findings(),
278
+ "critical_findings": len(report.get_critical_findings()),
279
+ "unhealthy_deployments": len(report.get_unhealthy_deployments()), # Count, not list
280
+ "open_repository_alerts": len(report.get_open_repository_alerts()),
281
+ "total_cost_usd": report.get_total_cost(),
282
+ }
283
+
284
+ # Update Prometheus metrics
285
+ try:
286
+ from devguard.metrics import update_metrics_from_report
287
+
288
+ update_metrics_from_report(report)
289
+ except ImportError:
290
+ # Metrics not available, skip
291
+ pass
292
+
293
+ return report