Skip to content

Commit 8b170a6

Browse files
committed
fix: use low-memory native HTTPS client
1 parent 329db44 commit 8b170a6

1 file changed

Lines changed: 44 additions & 15 deletions

File tree

skill/dependency-cve-audit/audit.mjs

Lines changed: 44 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { createHash } from "node:crypto"
22
import { readFile, mkdir, writeFile } from "node:fs/promises"
3+
import { request } from "node:https"
34
import path from "node:path"
45
import { fileURLToPath } from "node:url"
56

@@ -194,24 +195,11 @@ function markdownReport(report) {
194195

195196
async function queryOsv(queries) {
196197
const requestBody = { queries }
197-
const response = await fetch(OSV_BATCH_URL, {
198-
method: "POST",
199-
redirect: "error",
200-
headers: { "content-type": "application/json", "user-agent": `dependency-cve-audit/${SCANNER_VERSION}` },
201-
body: JSON.stringify(requestBody),
202-
})
203-
if (!response.ok) throw new Error(`OSV batch query failed with HTTP ${response.status}`)
204-
const batchResponse = await response.json()
198+
const batchResponse = await requestJson(OSV_BATCH_URL, { method: "POST", body: requestBody })
205199
const ids = [...new Set((batchResponse.results ?? []).flatMap((result) => (result.vulns ?? []).map((vuln) => vuln.id)).filter(Boolean))]
206200
const details = new Map()
207201
for (const id of ids) {
208-
const detailResponse = await fetch(`https://api.osv.dev/v1/vulns/${encodeURIComponent(id)}`, {
209-
method: "GET",
210-
redirect: "error",
211-
headers: { "user-agent": `dependency-cve-audit/${SCANNER_VERSION}` },
212-
})
213-
if (!detailResponse.ok) throw new Error(`OSV advisory ${id} failed with HTTP ${detailResponse.status}`)
214-
details.set(id, await detailResponse.json())
202+
details.set(id, await requestJson(`https://api.osv.dev/v1/vulns/${encodeURIComponent(id)}`, { method: "GET" }))
215203
}
216204
const responseBody = {
217205
results: (batchResponse.results ?? []).map((result) => ({
@@ -221,6 +209,47 @@ async function queryOsv(queries) {
221209
return { requestBody, batchResponse, detailResponses: [...details.values()], responseBody }
222210
}
223211

212+
function requestJson(url, { method, body }) {
213+
const encoded = body === undefined ? null : JSON.stringify(body)
214+
return new Promise((resolve, reject) => {
215+
const req = request(url, {
216+
method,
217+
headers: {
218+
accept: "application/json",
219+
"user-agent": `dependency-cve-audit/${SCANNER_VERSION}`,
220+
...(encoded ? { "content-type": "application/json", "content-length": Buffer.byteLength(encoded) } : {}),
221+
},
222+
}, (response) => {
223+
const chunks = []
224+
let bytes = 0
225+
response.on("data", (chunk) => {
226+
bytes += chunk.length
227+
if (bytes > 10 * 1024 * 1024) {
228+
req.destroy(new Error("OSV response exceeded 10 MiB"))
229+
return
230+
}
231+
chunks.push(chunk)
232+
})
233+
response.on("end", () => {
234+
const text = Buffer.concat(chunks).toString("utf8")
235+
if ((response.statusCode ?? 0) < 200 || (response.statusCode ?? 0) >= 300) {
236+
reject(new Error(`OSV request failed with HTTP ${response.statusCode}: ${text.slice(0, 200)}`))
237+
return
238+
}
239+
try {
240+
resolve(JSON.parse(text))
241+
} catch (error) {
242+
reject(new Error("OSV returned invalid JSON", { cause: error }))
243+
}
244+
})
245+
})
246+
req.setTimeout(30_000, () => req.destroy(new Error("OSV request timed out")))
247+
req.on("error", reject)
248+
if (encoded) req.write(encoded)
249+
req.end()
250+
})
251+
}
252+
224253
export async function runAudit(inputs) {
225254
const targetDir = path.resolve(String(inputs.target_dir ?? ""))
226255
const outputDir = path.resolve(String(inputs.output_dir ?? ""))

0 commit comments

Comments
 (0)