Skip to content

Commit 359a930

Browse files
authored
fix: Update package vulnerability scanner to handle breaking PPM change (#335)
* update to new endpoint * information for legacy versions in code comment
1 parent a927c33 commit 359a930

2 files changed

Lines changed: 49 additions & 14 deletions

File tree

Lines changed: 45 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
import asyncio
2+
import json
3+
4+
import httpx
15
from fastapi import FastAPI, HTTPException
26
from fastapi.staticfiles import StaticFiles
37
from posit import connect
@@ -6,44 +10,71 @@
610

711
client = connect.Client()
812

13+
914
@app.get("/api/content")
1015
async def search_content(show_all: bool = False):
1116
if show_all:
1217
return client.content.find()
1318
return client.me.content.find()
1419

20+
1521
@app.get("/api/packages/{guid}")
1622
async def get_packages(guid: str):
1723
try:
1824
content = client.content.get(guid)
1925
packages = list(content.packages)
2026
return packages
2127
except Exception as e:
22-
raise HTTPException(status_code=404, detail=f"Content not found or error fetching packages: {str(e)}")
23-
28+
raise HTTPException(
29+
status_code=404,
30+
detail=f"Content not found or error fetching packages: {str(e)}",
31+
)
32+
33+
2434
@app.get("/api/vulns")
2535
async def get_vulnerabilities():
26-
import httpx
27-
import asyncio
28-
2936
repositories = ["pypi", "cran"]
3037
results = {}
31-
38+
3239
async def fetch_repo_vulns(repo):
33-
async with httpx.AsyncClient() as client:
34-
url = f"https://packagemanager.posit.co/__api__/repos/{repo}/vulns"
35-
response = await client.get(url)
40+
async with httpx.AsyncClient() as http:
41+
# This fetches vulnerabilities from the public Posit Package Manager,
42+
# which is always up to date. If customizing this to reference your
43+
# own PPM instance, note that the has_vulns parameter requires
44+
# PPM 2026.04 or later. Older versions may need to use the vulns
45+
# parameter instead, or the legacy endpoint:
46+
#
47+
# url = f"https://your-ppm-instance/__api__/repos/{repo}/vulns"
48+
# response = await http.get(url)
49+
# return repo, response.json()
50+
#
51+
# tasks = [fetch_repo_vulns(repo) for repo in repositories]
52+
# for repo, data in await asyncio.gather(*tasks):
53+
# results[repo] = data
54+
url = "https://packagemanager.posit.co/__api__/filter/packages"
55+
payload = {
56+
"repo": repo,
57+
"has_vulns": True,
58+
"omit_downloads": True,
59+
"omit_dependencies": True,
60+
}
61+
response = await http.post(url, json=payload)
3662
response.raise_for_status()
37-
return repo, response.json()
38-
63+
packages = [
64+
json.loads(line) for line in response.text.strip().split("\n") if line
65+
]
66+
return repo, packages
67+
3968
tasks = [fetch_repo_vulns(repo) for repo in repositories]
40-
for repo, data in await asyncio.gather(*tasks):
41-
results[repo] = data
42-
69+
for repo, packages in await asyncio.gather(*tasks):
70+
results[repo] = {pkg["name"]: pkg["vulns"] for pkg in packages}
71+
4372
return results
4473

74+
4575
@app.get("/api/user")
4676
async def get_current_user():
4777
return client.me
4878

79+
4980
app.mount("/", StaticFiles(directory="dist", html=True), name="static")

extensions/package-vulnerability-scanner/package-lock.json

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)