| | """Environment variable validation and suggestions.""" |
| |
|
| | from __future__ import annotations |
| |
|
| | import os |
| | import re |
| | from pathlib import Path |
| | from typing import Any, Dict, List, Optional |
| |
|
| |
|
| | class EnvironmentValidator: |
| | """Validates and suggests environment variables.""" |
| |
|
| | def __init__(self): |
| | self.common_vars = { |
| | "next.js": ["NEXT_PUBLIC_API_URL", "DATABASE_URL", "NEXTAUTH_SECRET", "NEXTAUTH_URL"], |
| | "django": ["SECRET_KEY", "DEBUG", "DATABASE_URL", "ALLOWED_HOSTS"], |
| | "fastapi": ["DATABASE_URL", "SECRET_KEY", "CORS_ORIGINS", "ENVIRONMENT"], |
| | "react": ["REACT_APP_API_URL", "REACT_APP_ENV"], |
| | "express": ["PORT", "NODE_ENV", "DATABASE_URL", "JWT_SECRET"], |
| | "nestjs": ["PORT", "DATABASE_URL", "JWT_SECRET", "NODE_ENV"], |
| | } |
| |
|
| | def validate_env_file(self, folder_path: str, framework: Optional[str] = None) -> Dict[str, Any]: |
| | """Validate .env files in codebase.""" |
| | path = Path(folder_path) |
| | env_files = list(path.rglob(".env*")) |
| | |
| | found_vars = [] |
| | missing_vars = [] |
| | issues = [] |
| | |
| | |
| | for env_file in env_files: |
| | if env_file.is_file() and ".git" not in str(env_file): |
| | try: |
| | content = env_file.read_text() |
| | |
| | var_pattern = r'^([A-Z_][A-Z0-9_]*)\s*=' |
| | vars_in_file = re.findall(var_pattern, content, re.MULTILINE) |
| | found_vars.extend(vars_in_file) |
| | except Exception: |
| | pass |
| | |
| | |
| | if framework: |
| | required_vars = self.common_vars.get(framework.lower(), []) |
| | for var in required_vars: |
| | if var not in found_vars: |
| | missing_vars.append({ |
| | "variable": var, |
| | "required": True, |
| | "description": self._get_var_description(var, framework) |
| | }) |
| | |
| | |
| | for env_file in env_files: |
| | try: |
| | content = env_file.read_text() |
| | |
| | if re.search(r'(password|secret|key|token)\s*=\s*["\'][^"\']+["\']', content, re.IGNORECASE): |
| | issues.append({ |
| | "file": str(env_file.relative_to(path)), |
| | "severity": "high", |
| | "issue": "Hardcoded secrets detected - use environment variables or secrets manager" |
| | }) |
| | |
| | |
| | gitignore = path / ".gitignore" |
| | if gitignore.exists(): |
| | gitignore_content = gitignore.read_text() |
| | if ".env" not in gitignore_content: |
| | issues.append({ |
| | "file": ".gitignore", |
| | "severity": "medium", |
| | "issue": ".env files should be in .gitignore" |
| | }) |
| | except Exception: |
| | pass |
| | |
| | return { |
| | "env_files_found": len(env_files), |
| | "variables_found": len(set(found_vars)), |
| | "missing_required": missing_vars, |
| | "issues": issues, |
| | "status": "valid" if not missing_vars and not issues else "needs_attention", |
| | "recommendations": self._get_recommendations(framework, missing_vars, issues) |
| | } |
| |
|
| | def suggest_env_vars(self, framework: str, platform: str) -> List[Dict[str, str]]: |
| | """Suggest environment variables based on framework and platform.""" |
| | suggestions = [] |
| | |
| | |
| | framework_vars = self.common_vars.get(framework.lower(), []) |
| | for var in framework_vars: |
| | suggestions.append({ |
| | "variable": var, |
| | "required": True, |
| | "description": self._get_var_description(var, framework), |
| | "category": "framework" |
| | }) |
| | |
| | |
| | platform_vars = { |
| | "vercel": ["VERCEL_URL", "VERCEL_ENV"], |
| | "netlify": ["NETLIFY", "CONTEXT"], |
| | "aws": ["AWS_REGION", "AWS_ACCESS_KEY_ID"], |
| | "gcp": ["GOOGLE_CLOUD_PROJECT", "GCP_REGION"], |
| | "azure": ["AZURE_REGION", "AZURE_SUBSCRIPTION_ID"], |
| | } |
| | |
| | platform_vars_list = platform_vars.get(platform.lower(), []) |
| | for var in platform_vars_list: |
| | suggestions.append({ |
| | "variable": var, |
| | "required": False, |
| | "description": f"Platform-specific variable for {platform}", |
| | "category": "platform" |
| | }) |
| | |
| | return suggestions |
| |
|
| | def _get_var_description(self, var: str, framework: str) -> str: |
| | """Get description for a variable.""" |
| | descriptions = { |
| | "DATABASE_URL": "Database connection string", |
| | "SECRET_KEY": "Secret key for encryption/signing", |
| | "API_URL": "API endpoint URL", |
| | "NODE_ENV": "Node.js environment (development/production)", |
| | "PORT": "Application port number", |
| | "DEBUG": "Debug mode flag", |
| | } |
| | return descriptions.get(var, f"Required for {framework}") |
| |
|
| | def _get_recommendations( |
| | self, |
| | framework: Optional[str], |
| | missing_vars: List[Dict], |
| | issues: List[Dict] |
| | ) -> List[str]: |
| | """Get recommendations based on validation results.""" |
| | recommendations = [] |
| | |
| | if missing_vars: |
| | recommendations.append(f"Add {len(missing_vars)} missing required environment variables") |
| | |
| | if issues: |
| | recommendations.append("Review security issues in environment files") |
| | |
| | recommendations.append("Use a secrets manager (AWS Secrets Manager, HashiCorp Vault) for production") |
| | recommendations.append("Never commit .env files to version control") |
| | |
| | if framework: |
| | recommendations.append(f"Follow {framework} best practices for environment configuration") |
| | |
| | return recommendations |
| |
|
| |
|