11import { createHash } from "node:crypto"
22import { readFile , mkdir , writeFile } from "node:fs/promises"
3+ import { request } from "node:https"
34import path from "node:path"
45import { fileURLToPath } from "node:url"
56
@@ -194,24 +195,11 @@ function markdownReport(report) {
194195
195196async 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+
224253export 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