Repository: google/osv.dev

Issue:
Add "CVE" ecosystem for osv.dev filtering?
Currently, it's difficult to search for converted CVE records (per https://osv.dev/blog/posts/introducing-broad-c-c++-support/) via the osv.dev UI. 

Some solutions:
- We add a special "CVE" ecosystem to enable filtering by these. 
- We add a "Git" ecosystem to enable filtering by all entries that include Git ranges. This will include more than just CVEs though (e.g. OSS-Fuzz ones). 
- We detect and tag C/C++ relevant advisories and create a "C/C++" ecosystem. 

Adding an ecosystem has the additional benefit of surfacing an additional ecosystem bubble and filter on the home osv.dev UI.

Alternatively:
- We fix search results to show advisories with IDs matching the given search prefix first. 


Discussion:

Resolving patch:
diff --git a/docker/worker/worker.py b/docker/worker/worker.py
--- a/docker/worker/worker.py
+++ b/docker/worker/worker.py
@@ -517,7 +517,6 @@ def _do_update(self, source_repo, repo, vulnerability, relative_path,
     bug.update_from_vulnerability(vulnerability)
     bug.public = True
     bug.import_last_modified = orig_modified_date
-
     # OSS-Fuzz sourced bugs use a different format for source_id.
     if source_repo.name != 'oss-fuzz' or not bug.source_id:
       bug.source_id = f'{source_repo.name}:{relative_path}'
@@ -531,7 +530,6 @@ def _do_update(self, source_repo, repo, vulnerability, relative_path,
       logging.info('%s does not affect any packages. Marking as invalid.',
                    vulnerability.id)
       bug.status = osv.BugStatus.INVALID
-
     bug.put()
 
     osv.update_affected_commits(bug.key.id(), result.commits, bug.public)
diff --git a/gcp/appengine/frontend_handlers.py b/gcp/appengine/frontend_handlers.py
--- a/gcp/appengine/frontend_handlers.py
+++ b/gcp/appengine/frontend_handlers.py
@@ -575,6 +575,8 @@ def list_packages(vuln_affected: list[dict]):
   for affected in vuln_affected:
     for affected_range in affected.get('ranges', []):
       if affected_range['type'] in ['ECOSYSTEM', 'SEMVER']:
+        if 'package' not in affected:
+          continue
         package_entry = affected['package']['ecosystem'] + '/' + affected[
             'package']['name']
         if package_entry not in packages:
diff --git a/osv/models.py b/osv/models.py
--- a/osv/models.py
+++ b/osv/models.py
@@ -375,6 +375,14 @@ def _pre_put_hook(self):  # pylint: disable=arguments-differ
         if pkg.package.ecosystem
     }
 
+    for pkg in self.affected_packages:
+      for r in pkg.ranges:
+        if r.type == 'GIT':
+          ecosystems_set.add('GIT')
+          break
+      if 'GIT' in ecosystems_set:
+        break
+
     # For all ecosystems that specify a specific version with colon,
     # also add the base name
     ecosystems_set.update({ecosystems.normalize(x) for x in ecosystems_set})


Test patch:
diff --git a/docker/worker/worker_test.py b/docker/worker/worker_test.py
--- a/docker/worker/worker_test.py
+++ b/docker/worker/worker_test.py
@@ -612,19 +612,22 @@ def setUp(self):
         type=osv.SourceRepositoryType.REST_ENDPOINT,
         id='source',
         name='source',
-        rest_api_url=f'http://{SERVER_ADDRESS[0]}:{SERVER_ADDRESS[1]}',
+        rest_api_url=MOCK_ADDRESS_FORMAT,
+        link=MOCK_ADDRESS_FORMAT,
         editable=False,
         repo_username='',
         extension='.json',
+        ignore_git=True,
     )
     self.source_repo.put()
+    osv.ecosystems.config.work_dir = os.path.join(
+        os.path.dirname(os.path.abspath(__file__)), 'testdata/tmp/')
 
     mock_publish = mock.patch('google.cloud.pubsub_v1.PublisherClient.publish')
     self.mock_publish = mock_publish.start()
     self.addCleanup(mock_publish.stop)
     warnings.filterwarnings('ignore', 'unclosed', ResourceWarning)
     self.httpd = http.server.HTTPServer(SERVER_ADDRESS, MockDataHandler)
-    print(f'Serving mock data at {MOCK_ADDRESS_FORMAT}')
     thread = threading.Thread(target=self.httpd.serve_forever)
     thread.start()
 
@@ -635,15 +638,9 @@ def tearDown(self):
   def test_update(self):
     """Test updating rest."""
     solo_endpoint = MOCK_ADDRESS_FORMAT + 'CURL-CVE-2022-32221' + '.json'
-    self.source_repo.ignore_git = True
-    self.source_repo.type = osv.SourceRepositoryType.REST_ENDPOINT
-    self.source_repo.rest_api_url = MOCK_ADDRESS_FORMAT
-    self.source_repo.link = MOCK_ADDRESS_FORMAT
-    self.source_repo.put()
     sha = '6138604b5537caab2afc0ee3e2b11f1574fdd5d8f3c6173f64048341cf55aee4'
     task_runner = worker.TaskRunner(ndb_client, None, self.tmp_dir.name, None,
                                     None)
-
     message = mock.Mock()
     message.attributes = {
         'source': 'source',
@@ -654,6 +651,30 @@ def test_update(self):
     task_runner._source_update(message)
     self.mock_publish.assert_not_called()
 
+  def test_git_ranges(self):
+    """Test updating rest."""
+    solo_endpoint = MOCK_ADDRESS_FORMAT + 'CURL-CVE-2022-32221' + '.json'
+    sha = '6138604b5537caab2afc0ee3e2b11f1574fdd5d8f3c6173f64048341cf55aee4'
+    task_runner = worker.TaskRunner(ndb_client, None, self.tmp_dir.name, None,
+                                    None)
+    osv.Bug(
+        db_id='CURL-CVE-2022-32221',
+        ecosystem=[''],
+        source_id='source:CURL-CVE-2022-32221.json',
+        import_last_modified=datetime.datetime(2020, 1, 1, 0, 0),
+    ).put()
+    message = mock.Mock()
+    message.attributes = {
+        'source': 'source',
+        'path': solo_endpoint,
+        'original_sha256': sha,
+        'deleted': 'false',
+    }
+    task_runner._source_update(message)
+
+    self.expect_dict_equal('update_no_introduced',
+                           osv.Bug.get_by_id('CURL-CVE-2022-32221')._to_dict())
+
 
 class UpdateTest(unittest.TestCase, tests.ExpectationTest(TEST_DATA_DIR)):
   """Vulnerability update tests."""