diff --git a/extensions/package-vulnerability-scanner/main.py b/extensions/package-vulnerability-scanner/main.py index 902dfa17..344bcc76 100644 --- a/extensions/package-vulnerability-scanner/main.py +++ b/extensions/package-vulnerability-scanner/main.py @@ -1,3 +1,7 @@ +import asyncio +import json + +import httpx from fastapi import FastAPI, HTTPException from fastapi.staticfiles import StaticFiles from posit import connect @@ -6,12 +10,14 @@ client = connect.Client() + @app.get("/api/content") async def search_content(show_all: bool = False): if show_all: return client.content.find() return client.me.content.find() + @app.get("/api/packages/{guid}") async def get_packages(guid: str): try: @@ -19,31 +25,56 @@ async def get_packages(guid: str): packages = list(content.packages) return packages except Exception as e: - raise HTTPException(status_code=404, detail=f"Content not found or error fetching packages: {str(e)}") - + raise HTTPException( + status_code=404, + detail=f"Content not found or error fetching packages: {str(e)}", + ) + + @app.get("/api/vulns") async def get_vulnerabilities(): - import httpx - import asyncio - repositories = ["pypi", "cran"] results = {} - + async def fetch_repo_vulns(repo): - async with httpx.AsyncClient() as client: - url = f"https://packagemanager.posit.co/__api__/repos/{repo}/vulns" - response = await client.get(url) + async with httpx.AsyncClient() as http: + # This fetches vulnerabilities from the public Posit Package Manager, + # which is always up to date. If customizing this to reference your + # own PPM instance, note that the has_vulns parameter requires + # PPM 2026.04 or later. Older versions may need to use the vulns + # parameter instead, or the legacy endpoint: + # + # url = f"https://your-ppm-instance/__api__/repos/{repo}/vulns" + # response = await http.get(url) + # return repo, response.json() + # + # tasks = [fetch_repo_vulns(repo) for repo in repositories] + # for repo, data in await asyncio.gather(*tasks): + # results[repo] = data + url = "https://packagemanager.posit.co/__api__/filter/packages" + payload = { + "repo": repo, + "has_vulns": True, + "omit_downloads": True, + "omit_dependencies": True, + } + response = await http.post(url, json=payload) response.raise_for_status() - return repo, response.json() - + packages = [ + json.loads(line) for line in response.text.strip().split("\n") if line + ] + return repo, packages + tasks = [fetch_repo_vulns(repo) for repo in repositories] - for repo, data in await asyncio.gather(*tasks): - results[repo] = data - + for repo, packages in await asyncio.gather(*tasks): + results[repo] = {pkg["name"]: pkg["vulns"] for pkg in packages} + return results + @app.get("/api/user") async def get_current_user(): return client.me + app.mount("/", StaticFiles(directory="dist", html=True), name="static") diff --git a/extensions/package-vulnerability-scanner/package-lock.json b/extensions/package-vulnerability-scanner/package-lock.json index facaf336..8e5b425d 100644 --- a/extensions/package-vulnerability-scanner/package-lock.json +++ b/extensions/package-vulnerability-scanner/package-lock.json @@ -1501,6 +1501,7 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -1719,6 +1720,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "devOptional": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -1745,6 +1747,7 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.8.tgz", "integrity": "sha512-dbU7/iLVa8KZALJyLOBOQ88nOXtNG8vxKuOT4I2mD+Ya70KPceF4IAmDsmU0h1Qsn5bPrvsY9HJstCRh3hG6Uw==", "license": "MIT", + "peer": true, "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", @@ -1829,6 +1832,7 @@ "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.27.tgz", "integrity": "sha512-aJ/UtoEyFySPBGarREmN4z6qNKpbEguYHMmXSiOGk69czc+zhs0NF6tEFrY8TZKAl8N/LYAkd4JHVd5E/AsSmw==", "license": "MIT", + "peer": true, "dependencies": { "@vue/compiler-dom": "3.5.27", "@vue/compiler-sfc": "3.5.27",