Spaces:
Running
Running
| """ | |
| Dependency vulnerability scanner using pip-audit. | |
| Scans requirements.txt / pyproject.toml for known CVEs. | |
| Best-effort: returns empty list if pip-audit is not installed or fails. | |
| """ | |
| import json | |
| import subprocess # nosec B404 | |
| import os | |
| from app.core.logger import get_logger | |
| logger = get_logger(__name__) | |
| def scan_dependencies(local_path: str) -> list[dict]: | |
| """ | |
| Run pip-audit on the repo and return a list of vulnerability dicts. | |
| Each dict has: package, version, id, description, fix_versions. | |
| Returns empty list if pip-audit unavailable or no manifest found. | |
| """ | |
| # Find dependency manifest | |
| manifest = None | |
| for candidate in ["requirements.txt", "pyproject.toml", "setup.cfg"]: | |
| full = os.path.join(local_path, candidate) | |
| if os.path.exists(full): | |
| manifest = full | |
| break | |
| if manifest is None: | |
| logger.info("No dependency manifest found β skipping vulnerability scan") | |
| return [] | |
| try: | |
| result = subprocess.run( # nosec B603 B607 | |
| ["pip-audit", "--requirement", manifest, "--format", "json", "--no-deps"], | |
| capture_output=True, | |
| text=True, | |
| timeout=120, | |
| ) | |
| except FileNotFoundError: | |
| logger.warning("pip-audit not installed β skipping dependency scan") | |
| return [] | |
| except subprocess.TimeoutExpired: | |
| logger.warning("pip-audit timed out") | |
| return [] | |
| except Exception as exc: | |
| logger.warning("pip-audit failed", extra={"error": str(exc)}) | |
| return [] | |
| if result.returncode not in (0, 1): # 0=clean, 1=vulns found, others=error | |
| logger.warning("pip-audit error", extra={"stderr": result.stderr[:200]}) | |
| return [] | |
| try: | |
| data = json.loads(result.stdout) | |
| except json.JSONDecodeError: | |
| return [] | |
| findings = [] | |
| for dep in data.get("dependencies", []): | |
| for vuln in dep.get("vulns", []): | |
| findings.append({ | |
| "package": dep.get("name", "unknown"), | |
| "version": dep.get("version", "unknown"), | |
| "id": vuln.get("id", ""), | |
| "description": vuln.get("description", ""), | |
| "fix_versions": vuln.get("fix_versions", []), | |
| }) | |
| logger.info("Dependency scan complete", extra={"vulnerabilities": len(findings)}) | |
| return findings |