Spaces:
Sleeping
Sleeping
| """ | |
| π¦ Dependency Analyzer | |
| Check for outdated packages, CVEs, and unused dependencies | |
| """ | |
| from typing import Dict, List, Any, Optional | |
| import re | |
| import json | |
| import subprocess | |
| import logging | |
| from pathlib import Path | |
| logger = logging.getLogger(__name__) | |
| class DependencyAnalyzer: | |
| """Analyze project dependencies for security and optimization""" | |
| def parse_package_json(file_path: str) -> Optional[Dict[str, Any]]: | |
| """Parse package.json file""" | |
| try: | |
| with open(file_path, 'r', encoding='utf-8') as f: | |
| return json.load(f) | |
| except Exception as e: | |
| logger.error(f"Failed to parse package.json: {e}") | |
| return None | |
| def parse_requirements_txt(file_path: str) -> List[Dict[str, str]]: | |
| """Parse requirements.txt file""" | |
| dependencies = [] | |
| try: | |
| with open(file_path, 'r', encoding='utf-8') as f: | |
| for line in f: | |
| line = line.strip() | |
| if line and not line.startswith('#'): | |
| # Parse package==version or package>=version | |
| match = re.match(r'([a-zA-Z0-9_-]+)([>=<]+)?([\d.]+)?', line) | |
| if match: | |
| dependencies.append({ | |
| "name": match.group(1), | |
| "version": match.group(3) or "latest", | |
| "operator": match.group(2) or "==" | |
| }) | |
| except Exception as e: | |
| logger.error(f"Failed to parse requirements.txt: {e}") | |
| return dependencies | |
| def check_known_vulnerabilities(package_name: str, version: str) -> List[Dict[str, Any]]: | |
| """Check for known CVEs (simplified - would integrate with OSV/Snyk API)""" | |
| # Simplified vulnerability database | |
| known_issues = { | |
| "lodash": { | |
| "versions": ["<4.17.21"], | |
| "cves": ["CVE-2021-23337", "CVE-2020-28500"], | |
| "severity": "high", | |
| "description": "Prototype pollution vulnerability" | |
| }, | |
| "axios": { | |
| "versions": ["<0.21.1"], | |
| "cves": ["CVE-2020-28168"], | |
| "severity": "medium", | |
| "description": "SSRF vulnerability" | |
| }, | |
| "requests": { | |
| "versions": ["<2.31.0"], | |
| "cves": ["CVE-2023-32681"], | |
| "severity": "medium", | |
| "description": "Proxy-Authorization header leak" | |
| } | |
| } | |
| issues = [] | |
| if package_name in known_issues: | |
| vuln = known_issues[package_name] | |
| issues.append({ | |
| "package": package_name, | |
| "current_version": version, | |
| "vulnerable_versions": vuln["versions"], | |
| "cves": vuln["cves"], | |
| "severity": vuln["severity"], | |
| "description": vuln["description"], | |
| "recommendation": "Update to latest version" | |
| }) | |
| return issues | |
| def check_outdated_packages(dependencies: List[Dict[str, str]], | |
| ecosystem: str = "npm") -> List[Dict[str, Any]]: | |
| """Check which packages are outdated""" | |
| outdated = [] | |
| # Simplified version checking (would integrate with npm/PyPI API) | |
| for dep in dependencies: | |
| name = dep.get("name") | |
| version = dep.get("version", "0.0.0") | |
| # Mock outdated check | |
| if version.startswith("0.") or version.startswith("1."): | |
| outdated.append({ | |
| "package": name, | |
| "current": version, | |
| "latest": "2.0.0", # Mock latest version | |
| "age": "Very old", | |
| "recommendation": f"Update to 2.0.0" | |
| }) | |
| return outdated | |
| def analyze_dependencies(cls, project_path: str) -> Dict[str, Any]: | |
| """Full dependency analysis""" | |
| project_path = Path(project_path) | |
| results = { | |
| "dependencies": [], | |
| "vulnerabilities": [], | |
| "outdated": [], | |
| "unused": [], | |
| "stats": {} | |
| } | |
| # Check for package.json (Node.js) | |
| package_json_path = project_path / "package.json" | |
| if package_json_path.exists(): | |
| pkg_data = cls.parse_package_json(str(package_json_path)) | |
| if pkg_data: | |
| deps = pkg_data.get("dependencies", {}) | |
| dev_deps = pkg_data.get("devDependencies", {}) | |
| all_deps = [] | |
| for name, version in {**deps, **dev_deps}.items(): | |
| all_deps.append({"name": name, "version": version.lstrip("^~")}) | |
| results["dependencies"] = all_deps | |
| # Check vulnerabilities | |
| for dep in all_deps: | |
| vulns = cls.check_known_vulnerabilities(dep["name"], dep["version"]) | |
| results["vulnerabilities"].extend(vulns) | |
| # Check outdated | |
| results["outdated"] = cls.check_outdated_packages(all_deps, "npm") | |
| # Check for requirements.txt (Python) | |
| requirements_path = project_path / "requirements.txt" | |
| if requirements_path.exists(): | |
| deps = cls.parse_requirements_txt(str(requirements_path)) | |
| results["dependencies"].extend(deps) | |
| # Check vulnerabilities | |
| for dep in deps: | |
| vulns = cls.check_known_vulnerabilities(dep["name"], dep["version"]) | |
| results["vulnerabilities"].extend(vulns) | |
| # Check outdated | |
| outdated = cls.check_outdated_packages(deps, "pip") | |
| results["outdated"].extend(outdated) | |
| # Generate stats | |
| results["stats"] = { | |
| "total_dependencies": len(results["dependencies"]), | |
| "vulnerabilities_found": len(results["vulnerabilities"]), | |
| "outdated_count": len(results["outdated"]), | |
| "critical_vulns": sum(1 for v in results["vulnerabilities"] | |
| if v.get("severity") == "critical"), | |
| "high_vulns": sum(1 for v in results["vulnerabilities"] | |
| if v.get("severity") == "high") | |
| } | |
| return results | |
| def format_dependency_report(analysis: Dict[str, Any]) -> str: | |
| """Format dependency analysis into readable report""" | |
| stats = analysis.get("stats", {}) | |
| vulns = analysis.get("vulnerabilities", []) | |
| outdated = analysis.get("outdated", []) | |
| report = f""" | |
| # π¦ Dependency Analysis Report | |
| ## π Summary | |
| - **Total Dependencies**: {stats.get('total_dependencies', 0)} | |
| - **Vulnerabilities**: {stats.get('vulnerabilities_found', 0)} | |
| - π΄ Critical: {stats.get('critical_vulns', 0)} | |
| - π High: {stats.get('high_vulns', 0)} | |
| - **Outdated Packages**: {stats.get('outdated_count', 0)} | |
| """ | |
| if vulns: | |
| report += "## π‘οΈ Security Vulnerabilities\n\n" | |
| for vuln in vulns: | |
| severity = vuln.get("severity", "unknown").upper() | |
| emoji = "π΄" if severity == "CRITICAL" else "π " if severity == "HIGH" else "π‘" | |
| report += f"### {emoji} {vuln.get('package')} {vuln.get('current_version')}\n" | |
| report += f"**CVEs**: {', '.join(vuln.get('cves', []))}\n" | |
| report += f"**Description**: {vuln.get('description')}\n" | |
| report += f"**Fix**: {vuln.get('recommendation')}\n\n" | |
| if outdated: | |
| report += "## π Outdated Packages\n\n" | |
| for pkg in outdated[:10]: # Show top 10 | |
| report += f"- **{pkg.get('package')}**: {pkg.get('current')} β {pkg.get('latest')}\n" | |
| if not vulns and not outdated: | |
| report += "β **All dependencies are up-to-date and secure!**\n" | |
| return report | |