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.
- devguard/INTEGRATION_SUMMARY.md +121 -0
- devguard/__init__.py +3 -0
- devguard/__main__.py +6 -0
- devguard/checkers/__init__.py +41 -0
- devguard/checkers/api_usage.py +523 -0
- devguard/checkers/aws_cost.py +331 -0
- devguard/checkers/aws_iam.py +284 -0
- devguard/checkers/base.py +25 -0
- devguard/checkers/container.py +137 -0
- devguard/checkers/domain.py +189 -0
- devguard/checkers/firecrawl.py +117 -0
- devguard/checkers/fly.py +225 -0
- devguard/checkers/github.py +210 -0
- devguard/checkers/npm.py +327 -0
- devguard/checkers/npm_security.py +244 -0
- devguard/checkers/redteam.py +290 -0
- devguard/checkers/secret.py +279 -0
- devguard/checkers/swarm.py +376 -0
- devguard/checkers/tailscale.py +143 -0
- devguard/checkers/tailsnitch.py +303 -0
- devguard/checkers/tavily.py +179 -0
- devguard/checkers/vercel.py +192 -0
- devguard/cli.py +1510 -0
- devguard/cli_helpers.py +189 -0
- devguard/config.py +249 -0
- devguard/core.py +293 -0
- devguard/dashboard.py +715 -0
- devguard/discovery.py +363 -0
- devguard/http_client.py +142 -0
- devguard/llm_service.py +481 -0
- devguard/mcp_server.py +259 -0
- devguard/metrics.py +144 -0
- devguard/models.py +208 -0
- devguard/reporting.py +1571 -0
- devguard/sarif.py +295 -0
- devguard/scripts/ANALYSIS_SUMMARY.md +141 -0
- devguard/scripts/README.md +221 -0
- devguard/scripts/auto_fix_recommendations.py +145 -0
- devguard/scripts/generate_npmignore.py +175 -0
- devguard/scripts/generate_security_report.py +324 -0
- devguard/scripts/prepublish_check.sh +29 -0
- devguard/scripts/redteam_npm_packages.py +1262 -0
- devguard/scripts/review_all_repos.py +300 -0
- devguard/spec.py +617 -0
- devguard/sweeps/__init__.py +23 -0
- devguard/sweeps/ai_editor_config_audit.py +697 -0
- devguard/sweeps/cargo_publish_audit.py +655 -0
- devguard/sweeps/dependency_audit.py +419 -0
- devguard/sweeps/gitignore_audit.py +336 -0
- devguard/sweeps/local_dev.py +260 -0
- devguard/sweeps/local_dirty_worktree_secrets.py +521 -0
- devguard/sweeps/project_flaudit.py +636 -0
- devguard/sweeps/public_github_secrets.py +680 -0
- devguard/sweeps/publish_audit.py +478 -0
- devguard/sweeps/ssh_key_audit.py +327 -0
- devguard/utils.py +174 -0
- devguard-0.2.0.dist-info/METADATA +225 -0
- devguard-0.2.0.dist-info/RECORD +60 -0
- devguard-0.2.0.dist-info/WHEEL +4 -0
- 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
|