55import copy
66import logging
77import os
8+ import re
89from typing import TYPE_CHECKING , Any
910from urllib .parse import urlparse
1011
1314
1415from vunnel .tool import fixdate
1516from vunnel .utils import http_wrapper as http
16- from vunnel .utils import vulnerability
17+ from vunnel .utils import vulnerability , osv
1718
1819if TYPE_CHECKING :
1920 from collections .abc import Generator
@@ -35,8 +36,8 @@ def __init__( # noqa: PLR0913
3536 download_timeout : int = 125 ,
3637 logger : logging .Logger | None = None ,
3738 security_reference_url : str | None = None ,
38- skip_redownload : bool = False ,
39- max_workers : int = 8 ,
39+ skip_download : bool = False ,
40+ max_workers : int = 64 ,
4041 ):
4142 if not fixdater :
4243 fixdater = fixdate .default_finder (workspace )
@@ -49,7 +50,7 @@ def __init__( # noqa: PLR0913
4950 self .security_reference_url = (
5051 security_reference_url .strip ("/" ) if security_reference_url else self ._security_reference_url_
5152 )
52- self .skip_redownload = skip_redownload
53+ self .skip_download = skip_download
5354 self .max_workers = max_workers
5455
5556 if not logger :
@@ -110,7 +111,7 @@ def __init__(# noqa: PLR0913
110111 download_timeout : int = 125 ,
111112 logger : logging .Logger | None = None ,
112113 security_reference_url : str | None = None ,
113- skip_redownload : bool = False ,
114+ skip_download : bool = False ,
114115 max_workers : int = 8 ,
115116 ):
116117 self ._db_filename = self ._extract_filename_from_url (url )
@@ -122,11 +123,15 @@ def __init__(# noqa: PLR0913
122123 download_timeout ,
123124 logger ,
124125 security_reference_url ,
125- skip_redownload = skip_redownload ,
126+ skip_download = skip_download ,
126127 max_workers = max_workers ,
127128 )
128129
129130 def _download (self ) -> None :
131+ if self .skip_download :
132+ self .logger .info (f"skip_download is enabled for { self .namespace } secdb feed" )
133+ return
134+
130135 if not os .path .exists (self .input_dir_path ):
131136 os .makedirs (self .input_dir_path , exist_ok = True )
132137
@@ -136,11 +141,6 @@ def _download(self) -> None:
136141 self .logger .info (f"downloading { self .namespace } secdb { self .url } " )
137142 r = http .get (self .url , self .logger , stream = True , timeout = self .download_timeout )
138143 file_path = os .path .join (self .input_dir_path , self ._db_filename )
139- # if the file already exists and skip_redownload is True, skip writing the file again. This is to avoid
140- # unnecessary redownloading and rewriting of the same file, which can save time on subsequent runs.
141- if self .skip_redownload and os .path .exists (file_path ):
142- self .logger .info (f"skipping download of { self .namespace } secdb since file already exists at { file_path } " )
143- return
144144 with open (file_path , "wb" ) as fp :
145145 for chunk in r .iter_content ():
146146 fp .write (chunk )
@@ -256,18 +256,23 @@ def _normalize(self, release: str, data: dict[str, Any]) -> dict[str, Any]: # n
256256
257257class OSVParser (Parser ):
258258 _input_dir_ = "osv"
259+ _cga_id_re = re .compile (r"^CGA(-[23456789cfghjmpqrvwx]{4}){3}$" )
259260
260261 def _download (self ) -> None :
261262 '''
262263 Download all OSV entry files based on the index file at self.url, which should point to the
263264 top level all.json file. For each entry in the index, we construct the URL for the individual
264265 entry file and download it to the input directory.
265266 '''
267+ self .fixdater .download ()
268+
269+ if self .skip_download :
270+ self .logger .info (f"skip_download is enabled for { self .namespace } osv feed" )
271+ return
272+
266273 if not os .path .exists (self .input_dir_path ):
267274 os .makedirs (self .input_dir_path , exist_ok = True )
268275
269- self .fixdater .download ()
270-
271276 try :
272277 self .logger .info (f"downloading { self .namespace } osv index { self .url } " )
273278 # self.url should point to the top level all.json file, e.g.
@@ -281,10 +286,15 @@ def _download(self) -> None:
281286 # We construct the URL for each entry by appending the entry ID and .json to the base URL
282287 # e.g. https://packages.cgr.dev/chainguard/v2/osv/CGA-2255-2h2p-73q2.json
283288 with concurrent .futures .ThreadPoolExecutor (max_workers = self .max_workers ) as executor :
284- futures = [
285- executor .submit (self ._download_single_file , f"{ base_url } /{ entry ['id' ]} .json" , f"{ entry ['id' ]} .json" )
286- for entry in index
287- ]
289+ futures = []
290+ for entry in index :
291+ entry_id = entry ["id" ]
292+ if not entry_id or not self ._cga_id_re .match (entry_id ):
293+ self .logger .warning (f"skipping osv entry with invalid id: { entry_id !r} " )
294+ continue
295+ futures .append (
296+ executor .submit (self ._download_single_file , f"{ base_url } /{ entry_id } .json" , f"{ entry_id } .json" ),
297+ )
288298 # surface the first exception (if any) — matches prior behavior where a single
289299 # failure aborted the batch via the outer try/except
290300 done , _not_done = concurrent .futures .wait (futures , return_when = concurrent .futures .FIRST_EXCEPTION )
@@ -298,12 +308,6 @@ def _download_single_file(self, url: str, filename: str) -> None:
298308 Download a single OSV entry file given its URL and the desired filename.
299309 '''
300310 file_path = os .path .join (self .input_dir_path , filename )
301- # if the file already exists and skip_redownload is True, skip writing the file again. This is to avoid
302- # unnecessary redownloading and rewriting of the same file, which can save time on subsequent
303- # runs.
304- if self .skip_redownload and os .path .exists (file_path ):
305- self .logger .info (f"skipping download of { self .namespace } osv entry { filename } since file already exists" )
306- return
307311 self .logger .info (f"downloading { self .namespace } osv entry { filename } " )
308312 r = http .get (url , self .logger , stream = True , timeout = self .download_timeout )
309313 with open (file_path , "wb" ) as fp :
@@ -339,4 +343,5 @@ def _normalize(self, release: str, data: dict[str, Any]) -> dict[str, Any]: # n
339343 # we map the osv id to the osv data to keep consistency in the secdb parser, which
340344 # does this for ease of identifying the associated vulnerability when writing records.
341345 # IE: {"CGA-1234-5678-9abc": {<full osv record>}}
346+ osv .patch_fix_date (data , self .fixdater )
342347 return {data ['id' ]: data }
0 commit comments