import os from dataclasses import dataclass from typing import Any import httpx BASE_URL = "https://vulners.com/api/v3" @dataclass class VulnersClient: api_key: str | None = None timeout: float = 30.0 def __post_init__(self): if not self.api_key: self.api_key = os.environ.get("VULNERS_API_KEY") def _request(self, endpoint: str, payload: dict[str, Any]) -> dict[str, Any]: headers = {"Content-Type": "application/json"} if self.api_key: headers["X-Api-Key"] = self.api_key with httpx.Client(timeout=self.timeout) as client: resp = client.post(f"{BASE_URL}{endpoint}", headers=headers, json=payload) resp.raise_for_status() return resp.json() def search( self, query: str, size: int = 10, skip: int = 0, fields: list[str] | None = None, ) -> dict[str, Any]: payload = {"query": query, "size": size, "skip": skip} if fields: payload["fields"] = fields return self._request("/search/lucene/", payload) def get_cve(self, cve_id: str) -> dict[str, Any]: return self.search(f"id:{cve_id}", size=1) def search_cpe( self, vendor: str | None = None, product: str | None = None, version: str | None = None, size: int = 10, ) -> dict[str, Any]: parts = [] if vendor: parts.append(f"cpe.vendor:{vendor}") if product: parts.append(f"cpe.product:{product}") if version: parts.append(f"cpe.version:{version}") query = " AND ".join(parts) if parts else "*" return self.search(query, size=size) def software_vulns( self, software: str, version: str | None = None, max_vulns: int = 20, ) -> dict[str, Any]: payload = { "software": software, "type": "software", "maxVulnerabilities": max_vulns, } if version: payload["version"] = version return self._request("/burp/softwareapi/", payload)